Skip to content

Commit e194c7c

Browse files
authored
adds more functions to to dirs and files (#25083)
ref https://forum.nim-lang.org/t/13272
1 parent 161b321 commit e194c7c

File tree

4 files changed

+199
-6
lines changed

4 files changed

+199
-6
lines changed

changelog.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ errors.
3333
- `strutils.multiReplace` overload for character set replacements in a single pass.
3434
Useful for string sanitation. Follows existing multiReplace semantics.
3535

36+
- `std/files` adds:
37+
- Exports `CopyFlag` enum and `FilePermission` type for fine-grained control of file operations
38+
- New file operation procs with `Path` support:
39+
- `getFilePermissions`, `setFilePermissions` for managing permissions
40+
- `tryRemoveFile` for file deletion
41+
- `copyFile` with configurable buffer size and symlink handling
42+
- `copyFileWithPermissions` to preserve file attributes
43+
- `copyFileToDir` for copying files into directories
44+
45+
- `std/dirs` adds:
46+
- New directory operation procs with `Path` support:
47+
- `copyDir` with special file handling options
48+
- `copyDirWithPermissions` to recursively preserve attributes
49+
3650
- `system.setLenUninit` now supports refc, JS and VM backends.
3751

3852
[//]: # "Changes:"

lib/std/dirs.nim

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from std/paths import Path, ReadDirEffect, WriteDirEffect
44

55
from std/private/osdirs import dirExists, createDir, existsOrCreateDir, removeDir,
66
moveDir, walkDir, setCurrentDir,
7+
copyDir, copyDirWithPermissions,
78
walkDirRec, PathComponent
89

910
export PathComponent
@@ -133,3 +134,59 @@ proc setCurrentDir*(newDir: Path) {.inline, tags: [].} =
133134
## See also:
134135
## * `getCurrentDir proc <paths.html#getCurrentDir>`_
135136
osdirs.setCurrentDir(newDir.string)
137+
138+
proc copyDir*(source, dest: Path; skipSpecial = false) {.inline,
139+
tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect].} =
140+
## Copies a directory from `source` to `dest`.
141+
##
142+
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
143+
## are skipped.
144+
##
145+
## If `skipSpecial` is true, then (besides all directories) only *regular*
146+
## files (**without** special "file" objects like FIFOs, device files,
147+
## etc) will be copied on Unix.
148+
##
149+
## If this fails, `OSError` is raised.
150+
##
151+
## On the Windows platform this proc will copy the attributes from
152+
## `source` into `dest`.
153+
##
154+
## On other platforms created files and directories will inherit the
155+
## default permissions of a newly created file/directory for the user.
156+
## Use `copyDirWithPermissions proc`_
157+
## to preserve attributes recursively on these platforms.
158+
##
159+
## See also:
160+
## * `copyDirWithPermissions proc`_
161+
copyDir(source.string, dest.string, skipSpecial)
162+
163+
proc copyDirWithPermissions*(source, dest: Path;
164+
ignorePermissionErrors = true,
165+
skipSpecial = false)
166+
{.inline, tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect].} =
167+
## Copies a directory from `source` to `dest` preserving file permissions.
168+
##
169+
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
170+
## are skipped.
171+
##
172+
## If `skipSpecial` is true, then (besides all directories) only *regular*
173+
## files (**without** special "file" objects like FIFOs, device files,
174+
## etc) will be copied on Unix.
175+
##
176+
## If this fails, `OSError` is raised. This is a wrapper proc around
177+
## `copyDir`_ and `copyFileWithPermissions`_ procs
178+
## on non-Windows platforms.
179+
##
180+
## On Windows this proc is just a wrapper for `copyDir proc`_ since
181+
## that proc already copies attributes.
182+
##
183+
## On non-Windows systems permissions are copied after the file or directory
184+
## itself has been copied, which won't happen atomically and could lead to a
185+
## race condition. If `ignorePermissionErrors` is true (default), errors while
186+
## reading/setting file attributes will be ignored, otherwise will raise
187+
## `OSError`.
188+
##
189+
## See also:
190+
## * `copyDir proc`_
191+
copyDirWithPermissions(source.string, dest.string,
192+
ignorePermissionErrors, skipSpecial)

lib/std/files.nim

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,62 @@
66
from std/paths import Path, ReadDirEffect, WriteDirEffect
77

88
from std/private/osfiles import fileExists, removeFile,
9-
moveFile
9+
moveFile, copyFile, copyFileWithPermissions,
10+
copyFileToDir, tryRemoveFile,
11+
getFilePermissions, setFilePermissions,
12+
CopyFlag, FilePermission
1013

14+
export CopyFlag, FilePermission
15+
16+
17+
proc getFilePermissions*(filename: Path): set[FilePermission] {.inline, tags: [ReadDirEffect].} =
18+
## Retrieves file permissions for `filename`.
19+
##
20+
## `OSError` is raised in case of an error.
21+
## On Windows, only the ``readonly`` flag is checked, every other
22+
## permission is available in any case.
23+
##
24+
## See also:
25+
## * `setFilePermissions proc`_
26+
result = getFilePermissions(filename.string)
27+
28+
proc setFilePermissions*(filename: Path, permissions: set[FilePermission],
29+
followSymlinks = true)
30+
{.inline, tags: [ReadDirEffect, WriteDirEffect].} =
31+
## Sets the file permissions for `filename`.
32+
##
33+
## If `followSymlinks` set to true (default) and ``filename`` points to a
34+
## symlink, permissions are set to the file symlink points to.
35+
## `followSymlinks` set to false is a noop on Windows and some POSIX
36+
## systems (including Linux) on which `lchmod` is either unavailable or always
37+
## fails, given that symlinks permissions there are not observed.
38+
##
39+
## `OSError` is raised in case of an error.
40+
## On Windows, only the ``readonly`` flag is changed, depending on
41+
## ``fpUserWrite`` permission.
42+
##
43+
## See also:
44+
## * `getFilePermissions proc`_
45+
setFilePermissions(filename.string, permissions, followSymlinks)
1146

1247
proc fileExists*(filename: Path): bool {.inline, tags: [ReadDirEffect], sideEffect.} =
1348
## Returns true if `filename` exists and is a regular file or symlink.
1449
##
1550
## Directories, device files, named pipes and sockets return false.
1651
result = fileExists(filename.string)
1752

53+
proc tryRemoveFile*(file: Path): bool {.inline, tags: [WriteDirEffect].} =
54+
## Removes the `file`.
55+
##
56+
## If this fails, returns `false`. This does not fail
57+
## if the file never existed in the first place.
58+
##
59+
## On Windows, ignores the read-only attribute.
60+
##
61+
## See also:
62+
## * `removeFile proc`_
63+
result = tryRemoveFile(file.string)
64+
1865
proc removeFile*(file: Path) {.inline, tags: [WriteDirEffect].} =
1966
## Removes the `file`.
2067
##
@@ -26,6 +73,7 @@ proc removeFile*(file: Path) {.inline, tags: [WriteDirEffect].} =
2673
## See also:
2774
## * `removeDir proc <dirs.html#removeDir>`_
2875
## * `moveFile proc`_
76+
## * `tryRemoveFile proc`_
2977
removeFile(file.string)
3078

3179
proc moveFile*(source, dest: Path) {.inline,
@@ -44,3 +92,73 @@ proc moveFile*(source, dest: Path) {.inline,
4492
## * `moveDir proc <dirs.html#moveDir>`_
4593
## * `removeFile proc`_
4694
moveFile(source.string, dest.string)
95+
96+
proc copyFile*(source, dest: Path; options = cfSymlinkFollow; bufferSize = 16_384) {.inline, tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect].} =
97+
## Copies a file from `source` to `dest`, where `dest.parentDir` must exist.
98+
##
99+
## On non-Windows OSes, `options` specify the way file is copied; by default,
100+
## if `source` is a symlink, copies the file symlink points to. `options` is
101+
## ignored on Windows: symlinks are skipped.
102+
##
103+
## If this fails, `OSError` is raised.
104+
##
105+
## On the Windows platform this proc will
106+
## copy the source file's attributes into dest.
107+
##
108+
## On other platforms you need
109+
## to use `getFilePermissions`_ and
110+
## `setFilePermissions`_
111+
## procs
112+
## to copy them by hand (or use the convenience `copyFileWithPermissions
113+
## proc`_),
114+
## otherwise `dest` will inherit the default permissions of a newly
115+
## created file for the user.
116+
##
117+
## If `dest` already exists, the file attributes
118+
## will be preserved and the content overwritten.
119+
##
120+
## On OSX, `copyfile` C api will be used (available since OSX 10.5) unless
121+
## `-d:nimLegacyCopyFile` is used.
122+
##
123+
## `copyFile` allows to specify `bufferSize` to improve I/O performance.
124+
##
125+
## See also:
126+
## * `copyFileWithPermissions proc`_
127+
copyFile(source.string, dest.string, {options}, bufferSize)
128+
129+
proc copyFileWithPermissions*(source, dest: Path;
130+
ignorePermissionErrors = true,
131+
options = cfSymlinkFollow) {.inline.} =
132+
## Copies a file from `source` to `dest` preserving file permissions.
133+
##
134+
## On non-Windows OSes, `options` specify the way file is copied; by default,
135+
## if `source` is a symlink, copies the file symlink points to. `options` is
136+
## ignored on Windows: symlinks are skipped.
137+
##
138+
## This is a wrapper proc around `copyFile`_,
139+
## `getFilePermissions`_ and `setFilePermissions`_
140+
## procs on non-Windows platforms.
141+
##
142+
## On Windows this proc is just a wrapper for `copyFile proc`_ since
143+
## that proc already copies attributes.
144+
##
145+
## On non-Windows systems permissions are copied after the file itself has
146+
## been copied, which won't happen atomically and could lead to a race
147+
## condition. If `ignorePermissionErrors` is true (default), errors while
148+
## reading/setting file attributes will be ignored, otherwise will raise
149+
## `OSError`.
150+
##
151+
## See also:
152+
## * `copyFile proc`_
153+
copyFileWithPermissions(source.string, dest.string,
154+
ignorePermissionErrors, {options})
155+
156+
proc copyFileToDir*(source, dir: Path, options = cfSymlinkFollow; bufferSize = 16_384) {.inline.} =
157+
## Copies a file `source` into directory `dir`, which must exist.
158+
##
159+
## On non-Windows OSes, `options` specify the way file is copied; by default,
160+
## if `source` is a symlink, copies the file symlink points to. `options` is
161+
## ignored on Windows: symlinks are skipped.
162+
##
163+
## `copyFileToDir` allows to specify `bufferSize` to improve I/O performance.
164+
copyFileToDir(source.string, dir.string, {options}, bufferSize)

tests/stdlib/tos.nim

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ Raises
3030
from stdtest/specialpaths import buildDir
3131
import std/[syncio, assertions, osproc, os, strutils, pathnorm]
3232

33+
import std/paths except getCurrentDir
34+
import std/[files, dirs]
35+
3336
block fileOperations:
3437
let files = @["these.txt", "are.x", "testing.r", "files.q"]
3538
let dirs = @["some", "created", "test", "dirs"]
@@ -52,11 +55,11 @@ block fileOperations:
5255
doAssertRaises(OSError): copyFile(file, dname/sub/fname2)
5356
doAssertRaises(OSError): copyFileToDir(file, dname/sub)
5457
doAssertRaises(ValueError): copyFileToDir(file, "")
55-
copyFile(file, file2)
58+
copyFile(Path file, Path file2)
5659
doAssert fileExists(file2)
5760
doAssert readFile(file2) == str
5861
createDir(dname/sub)
59-
copyFileToDir(file, dname/sub)
62+
copyFileToDir(Path file, Path dname/sub)
6063
doAssert fileExists(dname/sub/fname)
6164
removeDir(dname/sub)
6265
doAssert not dirExists(dname/sub)
@@ -131,12 +134,13 @@ block fileOperations:
131134
removeDir(dname)
132135

133136
# test copyDir:
134-
createDir("a/b")
137+
createDir(Path "a/b")
135138
open("a/b/file.txt", fmWrite).close
136139
createDir("a/b/c")
137140
open("a/b/c/fileC.txt", fmWrite).close
138141

139-
copyDir("a", "../dest/a")
142+
createDir(Path"a/b")
143+
copyDir(Path "a", Path "../dest/a")
140144
removeDir("a")
141145

142146
doAssert dirExists("../dest/a/b")
@@ -169,7 +173,7 @@ block fileOperations:
169173
doAssert execCmd("mkfifo -m 600 a/fifoFile") == 0
170174

171175
copyDir("a/", "../dest/a/", skipSpecial = true)
172-
copyDirWithPermissions("a/", "../dest2/a/", skipSpecial = true)
176+
copyDirWithPermissions(Path "a/", Path "../dest2/a/", skipSpecial = true)
173177
removeDir("a")
174178

175179
# Symlink handling in `copyFile`, `copyFileWithPermissions`, `copyFileToDir`,

0 commit comments

Comments
 (0)