Skip to content

Commit b874020

Browse files
authored
std/dirs: progress (#1502)
1 parent f1a5515 commit b874020

File tree

5 files changed

+214
-92
lines changed

5 files changed

+214
-92
lines changed

doc/stdlib.dgn.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,64 @@ Returns the absolute path of `path`, rooted at `root` (which must be absolute; d
11951195
Expands `~` or a path starting with `~/` to a full path, replacing `~` with getHomeDir() (otherwise returns `path` unmodified). Windows: this is still supported despite the Windows platform not having this convention.
11961196

11971197

1198+
### dirs
1199+
1200+
@../lib/std/dirs.nim
1201+
1202+
This module implements operations for creating, removing, and iterating over directories.
1203+
1204+
1205+
####tryCreateFinalDir
1206+
1207+
Tries to create the final directory in a path. In other words, it tries to create a single new directory, not a nested one. It returns the OS's error code making it easy to distinguish between "could not create" and "already exists".
1208+
1209+
####createDir
1210+
1211+
Creates a new directory `dir`. If the directory already exists, no error is raised. This can be used to create a nested directory structure directly.
1212+
1213+
####tryRemoveFinalDir
1214+
1215+
Tries to remove the final directory in a path. In other words, it tries to remove a single directory, not a nested one. It returns the OS's error code making it easy to distinguish between "could not remove" and "does not exist".
1216+
1217+
####removeDir
1218+
1219+
Removes the directory `dir`. If the directory does not exist, no error is raised.
1220+
1221+
1222+
####tryRemoveFile
1223+
1224+
Tries to remove the file. It returns the OS's error code making it easy to distinguish between "could not remove" and "does not exist".
1225+
1226+
####removeFile
1227+
1228+
Removes the file `file`. If the file did not exist, no error is raised.
1229+
1230+
1231+
####walkDir
1232+
1233+
Walks over all entries in the directory `dir`.
1234+
1235+
Yields tuples of `(kind, path)` where `kind` is one of:
1236+
- `pcFile` - regular file
1237+
- `pcDir` - directory
1238+
- `pcLinkToFile` - symbolic link to a file
1239+
- `pcLinkToDir` - symbolic link to a directory
1240+
1241+
If `relative` is true, yields relative paths (just the filename/dirname), otherwise yields full paths.
1242+
1243+
If `checkDir` is true, raises an error if `dir` doesn't exist or isn't a directory.
1244+
1245+
Special directories "." and ".." are skipped.
1246+
1247+
1248+
####getCurrentDir
1249+
Returns the current working directory as a `Path`. Raises an error if unable to retrieve the current directory.
1250+
1251+
####setCurrentDir
1252+
Sets the current working directory to `dir`. Raises an error if the directory does not exist or lacks permissions.
1253+
1254+
1255+
11981256
### appdirs
11991257

12001258
@../lib/std/appdirs.nim

lib/std/dirs.nim

Lines changed: 133 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## This module implements directory operations for creating, removing,
1+
## This module implements operations for creating, removing,
22
## and iterating over directories.
33
##
44
## **See also:**
@@ -14,7 +14,7 @@ export paths.Path, paths.initPath, paths.`/`, paths.`$`
1414

1515
when defined(windows):
1616
import windows/winlean
17-
from widestrs import newWideCString, toWideCString
17+
from widestrs import newWideCString, toWideCString, `$`
1818

1919
else:
2020
import posix/posix
@@ -100,10 +100,125 @@ proc removeFile*(file: Path) {.raises.} =
100100
else:
101101
raise res
102102

103-
#[
103+
type
104+
DirEntry* = object
105+
kind*: PathComponent
106+
path*: Path
107+
108+
DirWalker* = object
109+
when defined(windows):
110+
wimpl: WIN32_FIND_DATA
111+
handle: Handle
112+
else:
113+
pimpl: ptr DIR
114+
dir: Path
115+
status: ErrorCode
116+
117+
proc tryOpenDir*(dir: sink Path): DirWalker =
118+
## Tries to open the directory `dir` for iteration over its entries.
119+
result = DirWalker(dir: dir, status: EmptyError)
120+
when defined(windows):
121+
var dirStr = $result.dir & "\\*"
122+
result.handle = findFirstFileW(newWideCString(dirStr).rawData, result.wimpl)
123+
if result.handle == INVALID_HANDLE_VALUE:
124+
result.status = windowsToErrorCode getLastError()
125+
else:
126+
result.status = Success
127+
else:
128+
var dirStr = $result.dir
129+
result.pimpl = opendir(dirStr.toCString)
130+
if result.pimpl == nil:
131+
result.status = posixToErrorCode(errno)
132+
else:
133+
result.status = Success
134+
135+
proc fillDirEntry(w: var DirWalker; e: var DirEntry) =
136+
when defined(windows):
137+
let isDir = (w.wimpl.dwFileAttributes.uint32 and FILE_ATTRIBUTE_DIRECTORY) != 0'u32
138+
let isLink = (w.wimpl.dwFileAttributes.uint32 and FILE_ATTRIBUTE_REPARSE_POINT) != 0'u32
139+
140+
var kind: PathComponent
141+
if isLink:
142+
kind = if isDir: pcLinkToDir else: pcLinkToFile
143+
else:
144+
kind = if isDir: pcDir else: pcFile
145+
e.kind = kind
146+
let f = cast[ptr UncheckedArray[WinChar]](addr w.wimpl.cFileName)
147+
e.path = paths.initPath($f)
148+
149+
when defined(posix):
150+
proc pathComponentFromEntry(w: var DirWalker; name: string; dType: uint8): PathComponent =
151+
if dType == DT_UNKNOWN or dType == DT_LNK:
152+
var fullPath = concat($w.dir, "/", name)
153+
var s = default Stat
154+
if lstat(fullPath.toCString, s) == 0:
155+
if S_ISLNK(s.st_mode):
156+
# For symlinks, we need to check if they point to a directory
157+
var targetStat = default Stat
158+
if stat(fullPath.toCString, targetStat) == 0 and S_ISDIR(targetStat.st_mode):
159+
result = pcLinkToDir
160+
else:
161+
result = pcLinkToFile
162+
elif S_ISDIR(s.st_mode):
163+
result = pcDir
164+
else:
165+
result = pcFile
166+
elif dType == DT_DIR:
167+
result = pcDir
168+
else:
169+
result = pcFile
170+
171+
proc tryNextDir*(w: var DirWalker; e: var DirEntry): bool =
172+
when defined(windows):
173+
while w.status == Success:
174+
if findNextFileW(w.handle, w.wimpl) != 0'i32:
175+
if skipFindData(w.wimpl):
176+
discard "skip entry"
177+
else:
178+
break
179+
else:
180+
# do not overwrite first error
181+
if w.status == Success:
182+
w.status = windowsToErrorCode getLastError()
183+
break
184+
result = w.status == Success
185+
if result:
186+
fillDirEntry(w, e)
187+
else:
188+
while w.status == Success:
189+
let entry = readdir(w.pimpl)
190+
if entry == nil:
191+
if w.status == Success:
192+
w.status = posixToErrorCode(errno)
193+
result = false
194+
break
195+
else:
196+
result = true
197+
let cstr = cast[cstring](addr entry.d_name[0])
198+
let name = fromCString cstr
199+
# Skip "." and ".."
200+
if name == "." or name == "..":
201+
discard "skip"
202+
else:
203+
e.kind = pathComponentFromEntry(w, name, entry.d_type)
204+
e.path = initPath(name)
205+
break
206+
207+
proc tryCloseDir*(w: var DirWalker): ErrorCode =
208+
when defined(windows):
209+
if findClose(w.handle).isSuccess:
210+
result = Success
211+
else:
212+
result = windowsToErrorCode getLastError()
213+
else:
214+
if closedir(w.pimpl) == 0'i32:
215+
result = Success
216+
else:
217+
result = posixToErrorCode(errno)
218+
104219
iterator walkDir*(dir: Path,
105220
relative = false,
106-
checkDir = false): tuple[kind: PathComponent, path: Path] =
221+
checkDir = false): tuple[kind: PathComponent, path: Path] {.raises.} =
107222
## Walks over all entries in the directory `dir`.
108223
##
109224
## Yields tuples of `(kind, path)` where `kind` is one of:
@@ -118,86 +233,20 @@ iterator walkDir*(dir: Path,
118233
## If `checkDir` is true, raises an error if `dir` doesn't exist or isn't a directory.
119234
##
120235
## Special directories "." and ".." are skipped.
121-
##
122-
## See also:
123-
## * `walkDirRec iterator`_
124-
## * `dirExists proc <oscommons.html#dirExists,string>`_
125-
when defined(windows):
126-
var findData: WIN32_FIND_DATA
127-
var dirStr = $dir
128-
let searchPath = dirStr & (when defined(windows): '\\' else: '/') & "*"
129-
let handle = findFirstFile(searchPath, findData)
130-
if handle == INVALID_HANDLE_VALUE:
131-
if checkDir:
132-
raiseOSError(osLastError())
133-
else:
134-
defer:
135-
discard findClose(handle)
136-
137-
while true:
138-
if not skipFindData(findData):
139-
# Use same pattern as oscommons for getting filename
140-
var filename = ""
141-
var i = 0
142-
while findData.cFileName[i].int16 != 0'i16:
143-
filename.add char(findData.cFileName[i].int and 0xFF)
144-
inc i
145-
146-
let fullPath = if relative: initPath(filename) else: dir / initPath(filename)
147-
148-
let isDir = (findData.dwFileAttributes.uint32 and FILE_ATTRIBUTE_DIRECTORY) != 0'u32
149-
let isLink = (findData.dwFileAttributes.uint32 and FILE_ATTRIBUTE_REPARSE_POINT) != 0'u32
150-
151-
var kind: PathComponent
152-
if isLink:
153-
kind = if isDir: pcLinkToDir else: pcLinkToFile
154-
else:
155-
kind = if isDir: pcDir else: pcFile
156-
157-
yield (kind, fullPath)
158-
159-
if findNextFileW(handle, findData) == 0'i32:
160-
break
161-
162-
else: # POSIX
163-
var dirStr = $dir
164-
let d = opendir(dirStr.toCString)
165-
if d == nil:
166-
if checkDir:
167-
raiseOSError(osLastError())
168-
else:
169-
defer:
170-
discard closedir(d)
171-
172-
while true:
173-
let entry = readdir(d)
174-
if entry == nil:
175-
break
176-
177-
let name = $cast[cstring](addr entry.d_name[0])
178-
# Skip "." and ".."
179-
if name == "." or name == "..":
180-
continue
181-
182-
let fullPath = if relative:
183-
initPath(name)
184-
else:
185-
dir / initPath(name)
186-
187-
let fullPathStr = $fullPath
188-
# Determine the kind
189-
if symlinkExists(fullPathStr):
190-
let (pc, _) = getSymlinkFileKind(fullPathStr)
191-
yield (pc, fullPath)
192-
elif dirExists(fullPathStr):
193-
yield (pcDir, fullPath)
194-
elif fileExists(fullPathStr):
195-
yield (pcFile, fullPath)
196-
else:
197-
# Unknown type, treat as file
198-
yield (pcFile, fullPath)
199-
200-
]#
236+
var w = tryOpenDir(dir)
237+
if checkDir and w.status != Success:
238+
raise w.status
239+
try:
240+
var e = default DirEntry
241+
while w.status == Success:
242+
if tryNextDir(w, e):
243+
let rel = if relative: e.path else: dir / e.path
244+
yield (e.kind, rel)
245+
else:
246+
break
247+
finally:
248+
let res = tryCloseDir(w)
249+
if checkDir and res != Success: raise res
201250

202251
proc getCurrentDir*(): Path {.raises.} =
203252
## Returns the current working directory as a `Path`.

lib/std/posix/posix.nim

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ when defined(posix):
119119
type
120120
DIR* {.importc: "DIR", header: "<dirent.h>".} = object
121121
Dirent* {.importc: "struct dirent", header: "<dirent.h>".} = object
122+
d_type* {.importc: "d_type".}: uint8
122123
d_name* {.importc: "d_name".}: array[256, char]
123124

124125
proc opendir*(name: cstring): ptr DIR {.importc, header: "<dirent.h>", sideEffect.}
@@ -128,3 +129,15 @@ when defined(posix):
128129
proc mkdir*(path: cstring, mode: Mode): cint {.importc, header: "<sys/stat.h>", sideEffect.}
129130
proc rmdir*(path: cstring): cint {.importc, header: "<unistd.h>", sideEffect.}
130131
proc unlink*(path: cstring): cint {.importc, header: "<unistd.h>", sideEffect.}
132+
133+
# POSIX d_type constants
134+
const
135+
DT_UNKNOWN* = 0'u8 ## Unknown file type.
136+
DT_FIFO* = 1'u8 ## Named pipe, or FIFO.
137+
DT_CHR* = 2'u8 ## Character device.
138+
DT_DIR* = 4'u8 ## Directory.
139+
DT_BLK* = 6'u8 ## Block device.
140+
DT_REG* = 8'u8 ## Regular file.
141+
DT_LNK* = 10'u8 ## Symbolic link.
142+
DT_SOCK* = 12'u8 ## UNIX domain socket.
143+
DT_WHT* = 14'u8

src/hexer/destroyer.nim

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,17 +340,14 @@ proc trTry(c: var Context; n: var Cursor) =
340340
skip nn
341341
copyInto(c.dest, n):
342342
let fin = if nn.substructureKind == FinU: nn.firstSon else: default(Cursor)
343-
trNestedScope c, n, (if hasExcept: OtherPreventFinally else: Other), fin
343+
trNestedScope c, n, OtherPreventFinally, fin
344344
while n.substructureKind == ExceptU:
345345
copyInto(c.dest, n):
346346
takeTree c.dest, n # `E as e`
347347
trNestedScope c, n, Other, fin
348348
if n.substructureKind == FinU:
349-
if hasExcept:
350-
copyInto(c.dest, n):
351-
trNestedScope c, n
352-
else:
353-
skip n
349+
copyInto(c.dest, n):
350+
trNestedScope c, n
354351

355352
proc tr(c: var Context; n: var Cursor) =
356353
if isAtom(n) or isDeclarative(n):

src/nimony/sem.nim

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,8 +2558,13 @@ proc semFor(c: var SemContext; it: var Item) =
25582558
var n2 = it.n
25592559
skip n2
25602560
let hasMultiVars = n2.kind != ParRi
2561-
if hasMultiVars and iterCall.typ.skipModifier.typeKind == TupleT:
2562-
semForLoopTupleVar c, it, iterCall.typ
2561+
if hasMultiVars:
2562+
if iterCall.typ.skipModifier.typeKind == TupleT:
2563+
semForLoopTupleVar c, it, iterCall.typ
2564+
else:
2565+
# error handling
2566+
while it.n.kind != ParRi:
2567+
semForLoopVar c, it, c.types.autoType
25632568
else:
25642569
semForLoopVar c, it, iterCall.typ
25652570

0 commit comments

Comments
 (0)