adds more functions to to dirs and files (#25083)

ref https://forum.nim-lang.org/t/13272
This commit is contained in:
ringabout
2025-08-01 18:34:01 +08:00
committed by GitHub
parent 161b321796
commit e194c7cc87
4 changed files with 199 additions and 6 deletions

View File

@@ -33,6 +33,20 @@ errors.
- `strutils.multiReplace` overload for character set replacements in a single pass.
Useful for string sanitation. Follows existing multiReplace semantics.
- `std/files` adds:
- Exports `CopyFlag` enum and `FilePermission` type for fine-grained control of file operations
- New file operation procs with `Path` support:
- `getFilePermissions`, `setFilePermissions` for managing permissions
- `tryRemoveFile` for file deletion
- `copyFile` with configurable buffer size and symlink handling
- `copyFileWithPermissions` to preserve file attributes
- `copyFileToDir` for copying files into directories
- `std/dirs` adds:
- New directory operation procs with `Path` support:
- `copyDir` with special file handling options
- `copyDirWithPermissions` to recursively preserve attributes
- `system.setLenUninit` now supports refc, JS and VM backends.
[//]: # "Changes:"

View File

@@ -4,6 +4,7 @@ from std/paths import Path, ReadDirEffect, WriteDirEffect
from std/private/osdirs import dirExists, createDir, existsOrCreateDir, removeDir,
moveDir, walkDir, setCurrentDir,
copyDir, copyDirWithPermissions,
walkDirRec, PathComponent
export PathComponent
@@ -133,3 +134,59 @@ proc setCurrentDir*(newDir: Path) {.inline, tags: [].} =
## See also:
## * `getCurrentDir proc <paths.html#getCurrentDir>`_
osdirs.setCurrentDir(newDir.string)
proc copyDir*(source, dest: Path; skipSpecial = false) {.inline,
tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect].} =
## Copies a directory from `source` to `dest`.
##
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
## are skipped.
##
## If `skipSpecial` is true, then (besides all directories) only *regular*
## files (**without** special "file" objects like FIFOs, device files,
## etc) will be copied on Unix.
##
## If this fails, `OSError` is raised.
##
## On the Windows platform this proc will copy the attributes from
## `source` into `dest`.
##
## On other platforms created files and directories will inherit the
## default permissions of a newly created file/directory for the user.
## Use `copyDirWithPermissions proc`_
## to preserve attributes recursively on these platforms.
##
## See also:
## * `copyDirWithPermissions proc`_
copyDir(source.string, dest.string, skipSpecial)
proc copyDirWithPermissions*(source, dest: Path;
ignorePermissionErrors = true,
skipSpecial = false)
{.inline, tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect].} =
## Copies a directory from `source` to `dest` preserving file permissions.
##
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
## are skipped.
##
## If `skipSpecial` is true, then (besides all directories) only *regular*
## files (**without** special "file" objects like FIFOs, device files,
## etc) will be copied on Unix.
##
## If this fails, `OSError` is raised. This is a wrapper proc around
## `copyDir`_ and `copyFileWithPermissions`_ procs
## on non-Windows platforms.
##
## On Windows this proc is just a wrapper for `copyDir proc`_ since
## that proc already copies attributes.
##
## On non-Windows systems permissions are copied after the file or directory
## itself has been copied, which won't happen atomically and could lead to a
## race condition. If `ignorePermissionErrors` is true (default), errors while
## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
##
## See also:
## * `copyDir proc`_
copyDirWithPermissions(source.string, dest.string,
ignorePermissionErrors, skipSpecial)

View File

@@ -6,8 +6,43 @@
from std/paths import Path, ReadDirEffect, WriteDirEffect
from std/private/osfiles import fileExists, removeFile,
moveFile
moveFile, copyFile, copyFileWithPermissions,
copyFileToDir, tryRemoveFile,
getFilePermissions, setFilePermissions,
CopyFlag, FilePermission
export CopyFlag, FilePermission
proc getFilePermissions*(filename: Path): set[FilePermission] {.inline, tags: [ReadDirEffect].} =
## Retrieves file permissions for `filename`.
##
## `OSError` is raised in case of an error.
## On Windows, only the ``readonly`` flag is checked, every other
## permission is available in any case.
##
## See also:
## * `setFilePermissions proc`_
result = getFilePermissions(filename.string)
proc setFilePermissions*(filename: Path, permissions: set[FilePermission],
followSymlinks = true)
{.inline, tags: [ReadDirEffect, WriteDirEffect].} =
## Sets the file permissions for `filename`.
##
## If `followSymlinks` set to true (default) and ``filename`` points to a
## symlink, permissions are set to the file symlink points to.
## `followSymlinks` set to false is a noop on Windows and some POSIX
## systems (including Linux) on which `lchmod` is either unavailable or always
## fails, given that symlinks permissions there are not observed.
##
## `OSError` is raised in case of an error.
## On Windows, only the ``readonly`` flag is changed, depending on
## ``fpUserWrite`` permission.
##
## See also:
## * `getFilePermissions proc`_
setFilePermissions(filename.string, permissions, followSymlinks)
proc fileExists*(filename: Path): bool {.inline, tags: [ReadDirEffect], sideEffect.} =
## Returns true if `filename` exists and is a regular file or symlink.
@@ -15,6 +50,18 @@ proc fileExists*(filename: Path): bool {.inline, tags: [ReadDirEffect], sideEffe
## Directories, device files, named pipes and sockets return false.
result = fileExists(filename.string)
proc tryRemoveFile*(file: Path): bool {.inline, tags: [WriteDirEffect].} =
## Removes the `file`.
##
## If this fails, returns `false`. This does not fail
## if the file never existed in the first place.
##
## On Windows, ignores the read-only attribute.
##
## See also:
## * `removeFile proc`_
result = tryRemoveFile(file.string)
proc removeFile*(file: Path) {.inline, tags: [WriteDirEffect].} =
## Removes the `file`.
##
@@ -26,6 +73,7 @@ proc removeFile*(file: Path) {.inline, tags: [WriteDirEffect].} =
## See also:
## * `removeDir proc <dirs.html#removeDir>`_
## * `moveFile proc`_
## * `tryRemoveFile proc`_
removeFile(file.string)
proc moveFile*(source, dest: Path) {.inline,
@@ -44,3 +92,73 @@ proc moveFile*(source, dest: Path) {.inline,
## * `moveDir proc <dirs.html#moveDir>`_
## * `removeFile proc`_
moveFile(source.string, dest.string)
proc copyFile*(source, dest: Path; options = cfSymlinkFollow; bufferSize = 16_384) {.inline, tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect].} =
## Copies a file from `source` to `dest`, where `dest.parentDir` must exist.
##
## On non-Windows OSes, `options` specify the way file is copied; by default,
## if `source` is a symlink, copies the file symlink points to. `options` is
## ignored on Windows: symlinks are skipped.
##
## If this fails, `OSError` is raised.
##
## On the Windows platform this proc will
## copy the source file's attributes into dest.
##
## On other platforms you need
## to use `getFilePermissions`_ and
## `setFilePermissions`_
## procs
## to copy them by hand (or use the convenience `copyFileWithPermissions
## proc`_),
## otherwise `dest` will inherit the default permissions of a newly
## created file for the user.
##
## If `dest` already exists, the file attributes
## will be preserved and the content overwritten.
##
## On OSX, `copyfile` C api will be used (available since OSX 10.5) unless
## `-d:nimLegacyCopyFile` is used.
##
## `copyFile` allows to specify `bufferSize` to improve I/O performance.
##
## See also:
## * `copyFileWithPermissions proc`_
copyFile(source.string, dest.string, {options}, bufferSize)
proc copyFileWithPermissions*(source, dest: Path;
ignorePermissionErrors = true,
options = cfSymlinkFollow) {.inline.} =
## Copies a file from `source` to `dest` preserving file permissions.
##
## On non-Windows OSes, `options` specify the way file is copied; by default,
## if `source` is a symlink, copies the file symlink points to. `options` is
## ignored on Windows: symlinks are skipped.
##
## This is a wrapper proc around `copyFile`_,
## `getFilePermissions`_ and `setFilePermissions`_
## procs on non-Windows platforms.
##
## On Windows this proc is just a wrapper for `copyFile proc`_ since
## that proc already copies attributes.
##
## On non-Windows systems permissions are copied after the file itself has
## been copied, which won't happen atomically and could lead to a race
## condition. If `ignorePermissionErrors` is true (default), errors while
## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
##
## See also:
## * `copyFile proc`_
copyFileWithPermissions(source.string, dest.string,
ignorePermissionErrors, {options})
proc copyFileToDir*(source, dir: Path, options = cfSymlinkFollow; bufferSize = 16_384) {.inline.} =
## Copies a file `source` into directory `dir`, which must exist.
##
## On non-Windows OSes, `options` specify the way file is copied; by default,
## if `source` is a symlink, copies the file symlink points to. `options` is
## ignored on Windows: symlinks are skipped.
##
## `copyFileToDir` allows to specify `bufferSize` to improve I/O performance.
copyFileToDir(source.string, dir.string, {options}, bufferSize)

View File

@@ -30,6 +30,9 @@ Raises
from stdtest/specialpaths import buildDir
import std/[syncio, assertions, osproc, os, strutils, pathnorm]
import std/paths except getCurrentDir
import std/[files, dirs]
block fileOperations:
let files = @["these.txt", "are.x", "testing.r", "files.q"]
let dirs = @["some", "created", "test", "dirs"]
@@ -52,11 +55,11 @@ block fileOperations:
doAssertRaises(OSError): copyFile(file, dname/sub/fname2)
doAssertRaises(OSError): copyFileToDir(file, dname/sub)
doAssertRaises(ValueError): copyFileToDir(file, "")
copyFile(file, file2)
copyFile(Path file, Path file2)
doAssert fileExists(file2)
doAssert readFile(file2) == str
createDir(dname/sub)
copyFileToDir(file, dname/sub)
copyFileToDir(Path file, Path dname/sub)
doAssert fileExists(dname/sub/fname)
removeDir(dname/sub)
doAssert not dirExists(dname/sub)
@@ -131,12 +134,13 @@ block fileOperations:
removeDir(dname)
# test copyDir:
createDir("a/b")
createDir(Path "a/b")
open("a/b/file.txt", fmWrite).close
createDir("a/b/c")
open("a/b/c/fileC.txt", fmWrite).close
copyDir("a", "../dest/a")
createDir(Path"a/b")
copyDir(Path "a", Path "../dest/a")
removeDir("a")
doAssert dirExists("../dest/a/b")
@@ -169,7 +173,7 @@ block fileOperations:
doAssert execCmd("mkfifo -m 600 a/fifoFile") == 0
copyDir("a/", "../dest/a/", skipSpecial = true)
copyDirWithPermissions("a/", "../dest2/a/", skipSpecial = true)
copyDirWithPermissions(Path "a/", Path "../dest2/a/", skipSpecial = true)
removeDir("a")
# Symlink handling in `copyFile`, `copyFileWithPermissions`, `copyFileToDir`,