-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add checkError parameter to os.walkDir #13011
Conversation
lib/pure/os.nim
Outdated
@@ -2021,6 +2026,8 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: | |||
let errCode = getLastError() | |||
if errCode == ERROR_NO_MORE_FILES: break | |||
else: raiseOSError(errCode.OSErrorCode) | |||
elif checkError: | |||
raiseOSError(osLastError()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raiseOSError(osLastError())
=>
raiseOSError(osLastError(), dir)
to show useful runtime info
ditto with other instance of raiseOSError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
I like it but what's so special about |
IMO we should default to |
Dunno, when I walk over a directory inaccessible dirs might as well not exist so I don't consider it "error prone". At least not more error prone than anything else related to path, file and directory handling (it's a dirty mess). |
but right now this doesn't even throw when the root dir doesn't exist / is empty etc import std/os
for a in walkDir("/hoome/"): # oups, mis-spellt /home/ but doesn't even throw
echo a
for a in walkDir(getEnv("nonexistant")): # oups, getEnv("nonexistant") is empty but doesn't even throw
echo a the only thing that would make sense to ignore is in-accessibility of NESTED dirs (for
|
Good points, I agree. |
Maybe I'm the idiot, but it's still not obvious to me what happens with It might be nice to have this thing throw two varieties of exception; one soft, one hard. |
no strong opinion on this but if you're gonna offer such control might as well offer the more flexible callback-way, using FilterDescend + FilterYield, see https://citycide.github.io/glob/latest/glob.html#FilterDescend ; it allows you full control on what you recurse into and what you yield. Perhaps it's good enough as a separate nimble package though. |
@disruptek
walkDirRec:
walkPattern:
I tested following import os
let input = if paramCount() > 0: paramStr(1) else: "."
for i in walkDir(input, checkError = true):
echo i testwalkpattern.nim import os
let input = if paramCount() > 0: paramStr(1) else: "."
for i in walkPattern(input, checkError = true):
echo i $ mkdir testdir
$ mkdir testdir/testsub
$ touch testdir/foo
$ touch testdir/testsub/foo
# remove executable permission
$ chmod 666 testdir $ nim c testwalkdir.nim
$ ./testwalkdir
(kind: pcFile, path: "./testwalkdir")
(kind: pcFile, path: "./testwalkdir.nim")
(kind: pcDir, path: "./testdir")
$ ./testwalkdir testdir
(kind: pcDir, path: "testdir/testsub")
(kind: pcFile, path: "testdir/foo")
$ ./testwalkdir testdir/testsub
/home/colab/Nim/lib/pure/os.nim(2082) testwalkdir
/home/colab/Nim/lib/pure/includes/oserr.nim(94) raiseOSError
Error: unhandled exception: Permission denied
Additional info: "testdir/testsub" [OSError] $ nim c testwalkpattern.nim
$ ./testwalkpattern \*
testdir
testwalkdir.nim
testwalkpattern
testwalkpattern.nim
$ ./testwalkpattern testdir/\*
testdir/foo
testdir/testsub
$ ./testwalkpattern testdir/testsub/\*
/home/colab/Nim/lib/pure/os.nim(1845) testwalkpattern
/home/colab/Nim/lib/pure/includes/oserr.nim(94) raiseOSError
Error: unhandled exception: Permission denied
Additional info: "testdir/testsub/*" [OSError]
$ ./testwalkpattern testdir/testsub
/home/colab/Nim/lib/pure/os.nim(1845) testwalkpattern
/home/colab/Nim/lib/pure/includes/oserr.nim(94) raiseOSError
Error: unhandled exception: Permission denied
Additional info: "testdir/testsub" [OSError] So I can use |
@Araq @timotheecour const defaultCheckErrorVal = not defined(nimWalkDirCheckErrorCmpat)
...
iterator walkDir*(dir: string;
relative=false;
checkError=defaultCheckErrorVal): tuple[kind: PathComponent, path: string] {. |
Yeah, I think so. Also enable this for the 1.0 emulation like so: when (NimMajor, NimMinor) <= (1, 0):
const defaultCheckErrorVal = false
else:
const defaultCheckErrorVal = not defined(nimWalkDirCheckErrorCmpat)
(Yes I know there are shorter ways to write the same, but this is most readable to me.) |
Er, if our very own code breaks with this switch turned on it's time to reconsider, sorry. Checking shouldn't be the default then. |
190402d
to
f62fdaf
Compare
One can distinguish between "fatal" errors, which are always raised, and "non-fatal" ones. I suggest to return the list of non-fatal errors via an additional parameter like The same logic can be applied to some procs like |
You can also use your own iteration scheme and error handling based on what os.nim gives to you. No need to burden the stdlib with every minor use case. |
f62fdaf
to
f8772ab
Compare
OK, you are right, we should avoid bloat. However the proposed exception-based walkDir is not completely correct in e.g. a subtle case of symbolic links. If a link points to file or directory inside an inaccessible directory, then it can not be determined that the link is to file or directory. Current implementation just shows it as I believe this problem can not be solved with exception-based implementations. I propose to add a new iterator |
@a-mr @Araq there's a subtle point we haven't discussed in this PR (also affects #13163): try:
for i in walkDir(...):
echo i
except:
echo "too late; can't resume" I see only 2 ways:
while true:
try:
process(myiter(arg))
except:
discard # caught but allows resuming
if finished(myiter(arg)): break but it's ugly; maybe this could be supported? for i in myiter(arg):
except: # applies to each iteration
# iteration continues
break # breaks iteration if user wants
finally:
# optional block |
Why not simply use the bare bones iterator and do the logic on your own? An iterator taking a callback means the iterator tries to do too much and fails at it... |
well even with a barbones iterator we need a wayt to let user know about unusual conditions such as inaccessible directory during a recursive traversal; simply throwing doesn't work as I just said (unrecoverable even with try/except); and simply ignoring isn't helpful either (no way to tell if something went wrong); the simplest is a callback |
Continuing iteration after raising inside an iterator is possible with closure iterator. type TestException = ref object of Exception
x: int
iterator mod3(): int {.closure.} =
var i = 0
while i != 12:
inc i
if i mod 3 == 0:
raise TestException(x: i)
yield i
when false:
#Doesn't work
#Keep raising exception after first exception.
iterator mod3(): int {.closure.} =
for i in 1..11:
if i mod 3 == 0:
raise TestException(x: i)
yield i
var myiter = mod3
while not finished(myiter):
try:
for i in myiter():
echo i
except TestException as e:
if e.x mod 9 == 0:
echo "Stop iteration"
break
else:
echo "Ignore Exception: ", e.x |
f8772ab
to
127963e
Compare
No, the simplest solution would be to yield the data that the user needs to evaluate the iteration. The user is gonna have to retrieve that anyway -- and you'd have to pass it to the callback. Why bother? We have a block in the form of the body of the loop and data in the form of loop vars... |
Exactly my thoughts. |
Closing in favor of #13163, sorry! |
Fix #10309 without breaking change.
Related to https://forum.nim-lang.org/t/4546
I think this is a better way than #12960.
Sample code:
Output on Linux:
Output on Windows 8.1:
"指定されたパスが見つかりません。" means "The specified path can not be found.".