[RFC] 'walkDir' now has a new 'checkDir' flag, to mimic behaviour of other languages (#13642)

Co-authored-by: narimiran
This commit is contained in:
Timothee Cour 2020-03-20 08:39:55 -07:00 • committed by GitHub
commit 1d665adecd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 31 deletions

View file

@ -7,7 +7,7 @@
on GCC's `__builtin_sadd_overflow` family of functions. (Clang also on GCC's `__builtin_sadd_overflow` family of functions. (Clang also
supports these). Some versions of GCC lack this feature and unfortunately supports these). Some versions of GCC lack this feature and unfortunately
we cannot detect this case reliably. So if you get compilation errors like we cannot detect this case reliably. So if you get compilation errors like
"undefined reference to '__builtin_saddll_overflow'" compile your programs "undefined reference to `__builtin_saddll_overflow`" compile your programs
with `-d:nimEmulateOverflowChecks`. with `-d:nimEmulateOverflowChecks`.
@ -34,8 +34,8 @@
- `options` now treats `proc` like other pointer types, meaning `nil` proc variables - `options` now treats `proc` like other pointer types, meaning `nil` proc variables
are converted to `None`. are converted to `None`.
- `relativePath("foo", "foo")` is now `"."`, not `""`, as `""` means invalid path - `relativePath("foo", "foo")` is now `"."`, not `""`, as `""` means invalid path
and shouldn't be conflated with `"."`; use -d:nimOldRelativePathBehavior to restore the old and shouldn't be conflated with `"."`; use -d:nimOldRelativePathBehavior to
behavior restore the old behavior
- `joinPath(a,b)` now honors trailing slashes in `b` (or `a` if `b` = "") - `joinPath(a,b)` now honors trailing slashes in `b` (or `a` if `b` = "")
- `times.parse` now only uses input to compute its result, and not `now`: - `times.parse` now only uses input to compute its result, and not `now`:
`parse("2020", "YYYY", utc())` is now `2020-01-01T00:00:00Z` instead of `parse("2020", "YYYY", utc())` is now `2020-01-01T00:00:00Z` instead of
@ -44,6 +44,12 @@
- `httpcore.==(string, HttpCode)` is now deprecated due to lack of practical - `httpcore.==(string, HttpCode)` is now deprecated due to lack of practical
usage. The `$` operator can be used to obtain the string form of `HttpCode` usage. The `$` operator can be used to obtain the string form of `HttpCode`
for comparison if desired. for comparison if desired.
- `os.walkDir` and `os.walkDirRec` now have new flag, `checkDir` (default: false).
If it is set to true, it will throw if input dir is invalid instead of a noop
(which is the default behaviour, as it was before this change),
`os.walkDirRec` only throws if top-level dir is invalid, but ignores errors for
subdirs, otherwise it would be impossible to resume iteration.
### Breaking changes in the compiler ### Breaking changes in the compiler

View file

@ -65,7 +65,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
if defined(nimsuggest) or graph.config.cmd == cmdCheck: if defined(nimsuggest) or graph.config.cmd == cmdCheck:
discard discard
else: else:
os.removeDir getString(a, 0) os.removeDir(getString(a, 0), getBool(a, 1))
cbos removeFile: cbos removeFile:
if defined(nimsuggest) or graph.config.cmd == cmdCheck: if defined(nimsuggest) or graph.config.cmd == cmdCheck:
discard discard

View file

@ -2028,8 +2028,8 @@ proc staticWalkDir(dir: string; relative: bool): seq[
tuple[kind: PathComponent, path: string]] = tuple[kind: PathComponent, path: string]] =
discard discard
iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {. iterator walkDir*(dir: string; relative = false, checkDir = false):
tags: [ReadDirEffect].} = tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} =
## Walks over the directory `dir` and yields for each directory or file in ## Walks over the directory `dir` and yields for each directory or file in
## `dir`. The component type and full path for each item are returned. ## `dir`. The component type and full path for each item are returned.
## ##
@ -2038,7 +2038,6 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
## Example: This directory structure:: ## Example: This directory structure::
## dirA / dirB / fileB1.txt ## dirA / dirB / fileB1.txt
## / dirC ## / dirC
## / fileA1.txt
## / fileA2.txt ## / fileA2.txt
## ##
## and this code: ## and this code:
@ -2069,7 +2068,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
elif defined(windows): elif defined(windows):
var f: WIN32_FIND_DATA var f: WIN32_FIND_DATA
var h = findFirstFile(dir / "*", f) var h = findFirstFile(dir / "*", f)
if h != -1: if h == -1:
if checkDir:
raiseOSError(osLastError(), dir)
else:
defer: findClose(h) defer: findClose(h)
while true: while true:
var k = pcFile var k = pcFile
@ -2087,7 +2089,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
else: raiseOSError(errCode.OSErrorCode) else: raiseOSError(errCode.OSErrorCode)
else: else:
var d = opendir(dir) var d = opendir(dir)
if d != nil: if d == nil:
if checkDir:
raiseOSError(osLastError(), dir)
else:
defer: discard closedir(d) defer: discard closedir(d)
while true: while true:
var x = readdir(d) var x = readdir(d)
@ -2122,7 +2127,7 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
iterator walkDirRec*(dir: string, iterator walkDirRec*(dir: string,
yieldFilter = {pcFile}, followFilter = {pcDir}, yieldFilter = {pcFile}, followFilter = {pcDir},
relative = false): string {.tags: [ReadDirEffect].} = relative = false, checkDir = false): string {.tags: [ReadDirEffect].} =
## Recursively walks over the directory `dir` and yields for each file ## Recursively walks over the directory `dir` and yields for each file
## or directory in `dir`. ## or directory in `dir`.
## ##
@ -2159,14 +2164,20 @@ iterator walkDirRec*(dir: string,
## * `walkDir iterator <#walkDir.i,string>`_ ## * `walkDir iterator <#walkDir.i,string>`_
var stack = @[""] var stack = @[""]
var checkDir = checkDir
while stack.len > 0: while stack.len > 0:
let d = stack.pop() let d = stack.pop()
for k, p in walkDir(dir / d, relative = true): for k, p in walkDir(dir / d, relative = true, checkDir = checkDir):
let rel = d / p let rel = d / p
if k in {pcDir, pcLinkToDir} and k in followFilter: if k in {pcDir, pcLinkToDir} and k in followFilter:
stack.add rel stack.add rel
if k in yieldFilter: if k in yieldFilter:
yield if relative: rel else: dir / rel yield if relative: rel else: dir / rel
checkDir = false
# We only check top-level dir, otherwise if a subdir is invalid (eg. wrong
# permissions), it'll abort iteration and there would be no way to
# continue iteration.
# Future work can provide a way to customize this and do error reporting.
proc rawRemoveDir(dir: string) {.noNimScript.} = proc rawRemoveDir(dir: string) {.noNimScript.} =
when defined(windows): when defined(windows):
@ -2181,13 +2192,13 @@ proc rawRemoveDir(dir: string) {.noNimScript.} =
else: else:
if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir) if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir)
proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [
WriteDirEffect, ReadDirEffect], benign, noNimScript.} = WriteDirEffect, ReadDirEffect], benign, noNimScript.} =
## Removes the directory `dir` including all subdirectories and files ## Removes the directory `dir` including all subdirectories and files
## in `dir` (recursively). ## in `dir` (recursively).
## ##
## If this fails, `OSError` is raised. This does not fail if the directory never ## If this fails, `OSError` is raised. This does not fail if the directory never
## existed in the first place. ## existed in the first place, unless `checkDir` = true
## ##
## See also: ## See also:
## * `tryRemoveFile proc <#tryRemoveFile,string>`_ ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
@ -2197,10 +2208,13 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
## * `copyDir proc <#copyDir,string,string>`_ ## * `copyDir proc <#copyDir,string,string>`_
## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
## * `moveDir proc <#moveDir,string,string>`_ ## * `moveDir proc <#moveDir,string,string>`_
for kind, path in walkDir(dir): for kind, path in walkDir(dir, checkDir = checkDir):
case kind case kind
of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path) of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
of pcDir: removeDir(path) of pcDir: removeDir(path, true)
# for subdirectories there is no benefit in `checkDir = false`
# (unless perhaps for edge case of concurrent processes also deleting
# the same files)
rawRemoveDir(dir) rawRemoveDir(dir)
proc rawCreateDir(dir: string): bool {.noNimScript.} = proc rawCreateDir(dir: string): bool {.noNimScript.} =

View file

@ -30,7 +30,7 @@ proc listDirsImpl(dir: string): seq[string] {.
tags: [ReadIOEffect], raises: [OSError].} = builtin tags: [ReadIOEffect], raises: [OSError].} = builtin
proc listFilesImpl(dir: string): seq[string] {. proc listFilesImpl(dir: string): seq[string] {.
tags: [ReadIOEffect], raises: [OSError].} = builtin tags: [ReadIOEffect], raises: [OSError].} = builtin
proc removeDir(dir: string) {. proc removeDir(dir: string, checkDir = true) {.
tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin
proc removeFile(dir: string) {. proc removeFile(dir: string) {.
tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin
@ -204,10 +204,10 @@ proc listFiles*(dir: string): seq[string] =
result = listFilesImpl(dir) result = listFilesImpl(dir)
checkOsError() checkOsError()
proc rmDir*(dir: string) {.raises: [OSError].} = proc rmDir*(dir: string, checkDir = false) {.raises: [OSError].} =
## Removes the directory `dir`. ## Removes the directory `dir`.
log "rmDir: " & dir: log "rmDir: " & dir:
removeDir dir removeDir(dir, checkDir = checkDir)
checkOsError() checkOsError()
proc rmFile*(file: string) {.raises: [OSError].} = proc rmFile*(file: string) {.raises: [OSError].} =

View file

@ -24,8 +24,10 @@ doAssert(existsEnv("dummy") == false)
# issue #7283 # issue #7283
putEnv("dummy", "myval") putEnv("dummy", "myval")
doAssert(existsEnv("dummy") == true) doAssert(existsEnv("dummy"))
doAssert(getEnv("dummy") == "myval") doAssert(getEnv("dummy") == "myval")
delEnv("dummy")
doAssert(existsEnv("dummy") == false)
# issue #7393 # issue #7393
let wd = getCurrentDir() let wd = getCurrentDir()
@ -64,6 +66,8 @@ else:
assert toDll("nim") == "libnim.so" assert toDll("nim") == "libnim.so"
rmDir("tempXYZ") rmDir("tempXYZ")
doAssertRaises(OSError):
rmDir("tempXYZ", checkDir = true)
assert dirExists("tempXYZ") == false assert dirExists("tempXYZ") == false
mkDir("tempXYZ") mkDir("tempXYZ")
assert dirExists("tempXYZ") == true assert dirExists("tempXYZ") == true
@ -81,8 +85,3 @@ when false:
rmDir("tempXYZ") rmDir("tempXYZ")
assert dirExists("tempXYZ") == false assert dirExists("tempXYZ") == false
putEnv("dummy", "myval")
doAssert(existsEnv("dummy") == true)
delEnv("dummy")
doAssert(existsEnv("dummy") == false)

View file

@ -163,13 +163,19 @@ block walkDirRec:
removeDir("walkdir_test") removeDir("walkdir_test")
when not defined(windows): block: # walkDir
block walkDirRelative: doAssertRaises(OSError):
createDir("walkdir_test") for a in walkDir("nonexistant", checkDir = true): discard
createSymlink(".", "walkdir_test/c") doAssertRaises(OSError):
for k, p in walkDir("walkdir_test", true): for p in walkDirRec("nonexistant", checkDir = true): discard
doAssert k == pcLinkToDir
removeDir("walkdir_test") when not defined(windows):
block walkDirRelative:
createDir("walkdir_test")
createSymlink(".", "walkdir_test/c")
for k, p in walkDir("walkdir_test", true):
doAssert k == pcLinkToDir
removeDir("walkdir_test")
block normalizedPath: block normalizedPath:
doAssert normalizedPath("") == "" doAssert normalizedPath("") == ""