From de5dce4bebd101454fc072a2b39877a232f0e245 Mon Sep 17 00:00:00 2001 From: xflywind <43030857+ringabout@users.noreply.github.com> Date: Thu, 20 Oct 2022 22:23:14 +0800 Subject: [PATCH] add skelton --- lib/std/dirs.nim | 422 ++----------------------------------------- lib/std/files.nim | 136 ++------------ lib/std/paths.nim | 219 +--------------------- lib/std/symlinks.nim | 13 ++ 4 files changed, 43 insertions(+), 747 deletions(-) create mode 100644 lib/std/symlinks.nim diff --git a/lib/std/dirs.nim b/lib/std/dirs.nim index 9699dfd76c2d4..1fbe6253a8b08 100644 --- a/lib/std/dirs.nim +++ b/lib/std/dirs.nim @@ -1,416 +1,20 @@ -import paths, files +import paths -import std/oserrors +from std/private/osdirs import dirExists, createDir, existsOrCreateDir, removeDir, moveDir -const weirdTarget = defined(nimscript) or defined(js) -when weirdTarget: - {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} -else: - {.pragma: noWeirdTarget.} +proc dirExists*(dir: Path): bool {.tags: [ReadDirEffect].} = + result = dirExists(dir.string) +proc createDir*(dir: Path) {.tags: [WriteDirEffect, ReadDirEffect].} = + createDir(dir.string) -when defined(nimscript): - # for procs already defined in scriptconfig.nim - template noNimJs(body): untyped = discard -elif defined(js): - {.pragma: noNimJs, error: "this proc is not available on the js target".} -else: - {.pragma: noNimJs.} +proc existsOrCreateDir*(dir: Path): bool {.tags: [WriteDirEffect, ReadDirEffect].} = + result = existsOrCreateDir(dir.string) -when weirdTarget: - discard -elif defined(windows): - import winlean, times -elif defined(posix): - import posix, times +proc removeDir*(dir: Path, checkDir = false + ) {.tags: [WriteDirEffect, ReadDirEffect].} = + removeDir(dir.string, checkDir) - proc toTime(ts: Timespec): times.Time {.inline.} = - result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) -else: - {.error: "OS module not ported to your operating system!".} - - -when defined(windows) and not weirdTarget: - template wrapUnary(varname, winApiProc, arg: untyped) = - var varname = winApiProc(newWideCString(arg)) - - template wrapBinary(varname, winApiProc, arg, arg2: untyped) = - var varname = winApiProc(newWideCString(arg), arg2) - proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle = - result = findFirstFileW(newWideCString(a), b) - template findNextFile(a, b: untyped): untyped = findNextFileW(a, b) - template getCommandLine(): untyped = getCommandLineW() - - template getFilename(f: untyped): untyped = - $cast[WideCString](addr(f.cFileName[0])) - - proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} = - # Note - takes advantage of null delimiter in the cstring - const dot = ord('.') - result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or - f.cFileName[1].int == dot and f.cFileName[2].int == 0) - - - -type - PathComponent* = enum ## Enumeration specifying a path component. - ## - ## See also: - ## * `walkDirRec iterator`_ - ## * `FileInfo object`_ - pcFile, ## path refers to a file - pcLinkToFile, ## path refers to a symbolic link to a file - pcDir, ## path refers to a directory - pcLinkToDir ## path refers to a symbolic link to a directory - -proc staticWalkDir(dir: string; relative: bool): seq[ - tuple[kind: PathComponent, path: string]] = - discard - -iterator walkDir*(dir: Path; relative = false, checkDir = false): - tuple[kind: PathComponent, path: Path] {.tags: [ReadDirEffect].} = - ## 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. - ## - ## Walking is not recursive. If ``relative`` is true (default: false) - ## the resulting path is shortened to be relative to ``dir``. - ## - ## If `checkDir` is true, `OSError` is raised when `dir` - ## doesn't exist. - ## - ## **Example:** - ## - ## This directory structure: - ## - ## dirA / dirB / fileB1.txt - ## / dirC - ## / fileA1.txt - ## / fileA2.txt - ## - ## and this code: - runnableExamples("-r:off"): - import std/[strutils, sugar] - # note: order is not guaranteed - # this also works at compile time - assert collect(for k in walkDir("dirA"): k.path).join(" ") == - "dirA/dirB dirA/dirC dirA/fileA2.txt dirA/fileA1.txt" - ## See also: - ## * `walkPattern iterator`_ - ## * `walkFiles iterator`_ - ## * `walkDirs iterator`_ - ## * `walkDirRec iterator`_ - - when nimvm: - for k, v in items(staticWalkDir(dir.string, relative)): - yield (k, Path(v)) - else: - when weirdTarget: - for k, v in items(staticWalkDir(dir, relative)): - yield (k, v) - elif defined(windows): - var f: WIN32_FIND_DATA - var h = findFirstFile(string(dir / Path("*")), f) - if h == -1: - if checkDir: - raiseOSError(osLastError(), dir.string) - else: - defer: findClose(h) - while true: - var k = pcFile - if not skipFindData(f): - if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: - k = pcDir - if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: - k = succ(k) - let xx = if relative: extractFilename(Path(getFilename(f))) - else: dir / extractFilename(Path(getFilename(f))) - yield (k, xx) - if findNextFile(h, f) == 0'i32: - let errCode = getLastError() - if errCode == ERROR_NO_MORE_FILES: break - else: raiseOSError(errCode.OSErrorCode) - else: - var d = opendir(dir) - if d == nil: - if checkDir: - raiseOSError(osLastError(), dir) - else: - defer: discard closedir(d) - while true: - var x = readdir(d) - if x == nil: break - var y = $cstring(addr x.d_name) - if y != "." and y != "..": - var s: Stat - let path = dir / y - if not relative: - y = path - var k = pcFile - - template kSetGeneric() = # pure Posix component `k` resolution - if lstat(path.cstring, s) < 0'i32: continue # don't yield - elif S_ISDIR(s.st_mode): - k = pcDir - elif S_ISLNK(s.st_mode): - k = getSymlinkFileKind(path) - - when defined(linux) or defined(macosx) or - defined(bsd) or defined(genode) or defined(nintendoswitch): - case x.d_type - of DT_DIR: k = pcDir - of DT_LNK: - if dirExists(path): k = pcLinkToDir - else: k = pcLinkToFile - of DT_UNKNOWN: - kSetGeneric() - else: # e.g. DT_REG etc - discard # leave it as pcFile - else: # assuming that field `d_type` is not present - kSetGeneric() - - yield (k, y) - -iterator walkDirRec*(dir: Path, - yieldFilter = {pcFile}, followFilter = {pcDir}, - relative = false, checkDir = false): Path {.tags: [ReadDirEffect].} = - ## Recursively walks over the directory `dir` and yields for each file - ## or directory in `dir`. - ## - ## If ``relative`` is true (default: false) the resulting path is - ## shortened to be relative to ``dir``, otherwise the full path is returned. - ## - ## If `checkDir` is true, `OSError` is raised when `dir` - ## doesn't exist. - ## - ## .. warning:: Modifying the directory structure while the iterator - ## is traversing may result in undefined behavior! - ## - ## Walking is recursive. `followFilter` controls the behaviour of the iterator: - ## - ## ===================== ============================================= - ## yieldFilter meaning - ## ===================== ============================================= - ## ``pcFile`` yield real files (default) - ## ``pcLinkToFile`` yield symbolic links to files - ## ``pcDir`` yield real directories - ## ``pcLinkToDir`` yield symbolic links to directories - ## ===================== ============================================= - ## - ## ===================== ============================================= - ## followFilter meaning - ## ===================== ============================================= - ## ``pcDir`` follow real directories (default) - ## ``pcLinkToDir`` follow symbolic links to directories - ## ===================== ============================================= - ## - ## - ## See also: - ## * `walkPattern iterator`_ - ## * `walkFiles iterator`_ - ## * `walkDirs iterator`_ - ## * `walkDir iterator`_ - - var stack = newseq[Path]() - var checkDir = checkDir - while stack.len > 0: - let d = stack.pop() - for k, p in walkDir(dir / d, relative = true, checkDir = checkDir): - let rel = d / p - if k in {pcDir, pcLinkToDir} and k in followFilter: - stack.add rel - if k in yieldFilter: - 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) {.noWeirdTarget.} = - when defined(windows): - wrapUnary(res, removeDirectoryW, dir) - let lastError = osLastError() - if res == 0'i32 and lastError.int32 != 3'i32 and - lastError.int32 != 18'i32 and lastError.int32 != 2'i32: - raiseOSError(lastError, dir) - else: - if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir) - -proc removeDir*(dir: Path, checkDir = false) {.tags: [ - WriteDirEffect, ReadDirEffect], gcsafe.} = - ## Removes the directory `dir` including all subdirectories and files - ## in `dir` (recursively). - ## - ## If this fails, `OSError` is raised. This does not fail if the directory never - ## existed in the first place, unless `checkDir` = true. - ## - ## See also: - ## * `tryRemoveFile proc`_ - ## * `removeFile proc`_ - ## * `existsOrCreateDir proc`_ - ## * `createDir proc`_ - ## * `copyDir proc`_ - ## * `copyDirWithPermissions proc`_ - ## * `moveDir proc`_ - for kind, path in walkDir(dir, checkDir = checkDir): - case kind - of pcFile, pcLinkToFile, pcLinkToDir: removeFile(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.string) - -proc dirExists*(dir: Path): bool {.tags: [ReadDirEffect], - noNimJs.} = - ## Returns true if the directory `dir` exists. If `dir` is a file, false - ## is returned. Follows symlinks. - ## - ## See also: - ## * `fileExists proc`_ - ## * `symlinkExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, dir.string) - if a != -1'i32: - result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 - else: - var res: Stat - result = stat(dir.string, res) >= 0'i32 and S_ISDIR(res.st_mode) - -proc rawCreateDir(dir: string): bool {.noWeirdTarget.} = - # Try to create one directory (not the whole path). - # returns `true` for success, `false` if the path has previously existed - # - # This is a thin wrapper over mkDir (or alternatives on other systems), - # so in case of a pre-existing path we don't check that it is a directory. - when defined(solaris): - let res = mkdir(dir, 0o777) - if res == 0'i32: - result = true - elif errno in {EEXIST, ENOSYS}: - result = false - else: - raiseOSError(osLastError(), dir) - elif defined(haiku): - let res = mkdir(dir, 0o777) - if res == 0'i32: - result = true - elif errno == EEXIST or errno == EROFS: - result = false - else: - raiseOSError(osLastError(), dir) - elif defined(posix): - let res = mkdir(dir, 0o777) - if res == 0'i32: - result = true - elif errno == EEXIST: - result = false - else: - #echo res - raiseOSError(osLastError(), dir) - else: - wrapUnary(res, createDirectoryW, dir) - - if res != 0'i32: - result = true - elif getLastError() == 183'i32: - result = false - else: - raiseOSError(osLastError(), dir) - -proc existsOrCreateDir*(dir: Path): bool {. - tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} = - ## Checks if a `directory`:idx: `dir` exists, and creates it otherwise. - ## - ## Does not create parent directories (raises `OSError` if parent directories do not exist). - ## Returns `true` if the directory already exists, and `false` otherwise. - ## - ## See also: - ## * `removeDir proc`_ - ## * `createDir proc`_ - ## * `copyDir proc`_ - ## * `copyDirWithPermissions proc`_ - ## * `moveDir proc`_ - result = not rawCreateDir(dir.string) - if result: - # path already exists - need to check that it is indeed a directory - if not dirExists(dir): - raise newException(IOError, "Failed to create '" & dir.string & "'") - -# proc createDir*(dir: Path) {. -# tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} = -# ## Creates the `directory`:idx: `dir`. -# ## -# ## The directory may contain several subdirectories that do not exist yet. -# ## The full path is created. If this fails, `OSError` is raised. -# ## -# ## It does **not** fail if the directory already exists because for -# ## most usages this does not indicate an error. -# ## -# ## See also: -# ## * `removeDir proc`_ -# ## * `existsOrCreateDir proc`_ -# ## * `copyDir proc`_ -# ## * `copyDirWithPermissions proc`_ -# ## * `moveDir proc`_ -# if dir.len == 0: -# return -# var omitNext = isAbsolute(dir) -# for p in parentDirs(dir, fromRoot=true): -# if omitNext: -# omitNext = false -# else: -# discard existsOrCreateDir(p) - -# proc copyDir*(source, dest: string) {.tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], gcsafe, noWeirdTarget.} = -# ## Copies a directory from `source` to `dest`. -# ## -# ## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks -# ## are skipped. -# ## -# ## 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`_ -# ## * `copyFile proc`_ -# ## * `copyFileWithPermissions proc`_ -# ## * `removeDir proc`_ -# ## * `existsOrCreateDir proc`_ -# ## * `createDir proc`_ -# ## * `moveDir proc`_ -# createDir(dest) -# for kind, path in walkDir(source): -# var noSource = splitPath(path).tail -# if kind == pcDir: -# copyDir(path, dest / noSource) -# else: -# copyFile(path, dest / noSource, {cfSymlinkAsIs}) - -# proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noWeirdTarget.} = -# ## Moves a directory from `source` to `dest`. -# ## -# ## Symlinks are not followed: if `source` contains symlinks, they themself are -# ## moved, not their target. -# ## -# ## If this fails, `OSError` is raised. -# ## -# ## See also: -# ## * `moveFile proc`_ -# ## * `copyDir proc`_ -# ## * `copyDirWithPermissions proc`_ -# ## * `removeDir proc`_ -# ## * `existsOrCreateDir proc`_ -# ## * `createDir proc`_ -# if not tryMoveFSObject(source, dest, isDir = true): -# # Fallback to copy & del -# copyDir(source, dest) -# removeDir(source) \ No newline at end of file +proc moveDir*(source, dest: Path) {.tags: [ReadIOEffect, WriteIOEffect].} = + moveDir(source.string, dest.string) diff --git a/lib/std/files.nim b/lib/std/files.nim index 2eb051d00b1c7..dc03140643566 100644 --- a/lib/std/files.nim +++ b/lib/std/files.nim @@ -1,132 +1,18 @@ import paths -import std/oserrors +from std/private/osfiles import fileExists, tryRemoveFile, removeFile, + removeFile, moveFile +proc fileExists*(filename: Path): bool {.inline, tags: [ReadDirEffect].} = + result = fileExists(filename.string) -const weirdTarget = defined(nimscript) or defined(js) +proc tryRemoveFile*(file: Path): bool {.inline, tags: [WriteDirEffect].} = + result = tryRemoveFile(file.string) -when weirdTarget: - {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} -else: - {.pragma: noWeirdTarget.} +proc removeFile*(file: Path) {.inline, tags: [WriteDirEffect].} = + removeFile(file.string) - -when weirdTarget: - discard -elif defined(windows): - import winlean, times -elif defined(posix): - import posix, times - - proc toTime(ts: Timespec): times.Time {.inline.} = - result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) -else: - {.error: "OS module not ported to your operating system!".} - - -when defined(windows) and not weirdTarget: - template deleteFile(file: untyped): untyped = deleteFileW(file) - template setFileAttributes(file, attrs: untyped): untyped = - setFileAttributesW(file, attrs) - -proc tryRemoveFile*(file: Path): bool {.tags: [WriteDirEffect], noWeirdTarget.} = - ## 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: - ## * `copyFile proc`_ - ## * `copyFileWithPermissions proc`_ - ## * `removeFile proc`_ - ## * `moveFile proc`_ - result = true - when defined(windows): - let f = newWideCString(file.string) - if deleteFile(f) == 0: - result = false - let err = getLastError() - if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND: - result = true - elif err == ERROR_ACCESS_DENIED and - setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and - deleteFile(f) != 0: - result = true - else: - if unlink(file) != 0'i32 and errno != ENOENT: - result = false - -proc removeFile*(file: Path) {.tags: [WriteDirEffect], noWeirdTarget.} = - ## Removes the `file`. - ## - ## If this fails, `OSError` is raised. This does not fail - ## if the file never existed in the first place. - ## - ## On Windows, ignores the read-only attribute. - ## - ## See also: - ## * `removeDir proc`_ - ## * `copyFile proc`_ - ## * `copyFileWithPermissions proc`_ - ## * `tryRemoveFile proc`_ - ## * `moveFile proc`_ - if not tryRemoveFile(file): - raiseOSError(osLastError(), file.string) - -proc tryMoveFSObject(source, dest: string, isDir: bool): bool {.noWeirdTarget.} = - ## Moves a file (or directory if `isDir` is true) from `source` to `dest`. - ## - ## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true). - ## In case of other errors `OSError` is raised. - ## Returns true in case of success. - when defined(windows): - let s = newWideCString(source) - let d = newWideCString(dest) - result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 - else: - result = c_rename(source, dest) == 0'i32 - - if not result: - let err = osLastError() - let isAccessDeniedError = - when defined(windows): - const AccessDeniedError = OSErrorCode(5) - isDir and err == AccessDeniedError - else: - err == EXDEV.OSErrorCode - if not isAccessDeniedError: - raiseOSError(err, $(source, dest)) - -proc moveFile*(source, dest: Path) {. - tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect], noWeirdTarget.} = - ## Moves a file from `source` to `dest`. - ## - ## Symlinks are not followed: if `source` is a symlink, it is itself moved, - ## not its target. - ## - ## If this fails, `OSError` is raised. - ## If `dest` already exists, it will be overwritten. - ## - ## Can be used to `rename files`:idx:. - ## - ## See also: - ## * `moveDir proc`_ - ## * `copyFile proc`_ - ## * `copyFileWithPermissions proc`_ - ## * `removeFile proc`_ - ## * `tryRemoveFile proc`_ - - if not tryMoveFSObject(source.string, dest.string, isDir = false): - when defined(windows): - doAssert false - else: - # Fallback to copy & del - copyFile(source, dest, {cfSymlinkAsIs}) - try: - removeFile(source) - except: - discard tryRemoveFile(dest) - raise +proc moveFile*(source, dest: Path) {.inline, + tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect].} = + moveFile(source.string, dest.string) diff --git a/lib/std/paths.nim b/lib/std/paths.nim index 4fb30feee8663..b9c5953a01725 100644 --- a/lib/std/paths.nim +++ b/lib/std/paths.nim @@ -1,152 +1,10 @@ -import includes/osseps - -include system/inclrtl -import std/private/since - -import strutils, pathnorm - -when defined(nimPreviewSlimSystem): - import std/[syncio, assertions] - -type - ReadDirEffect* = object of ReadIOEffect ## Effect that denotes a read - ## operation from the directory - ## structure. - WriteDirEffect* = object of WriteIOEffect ## Effect that denotes a write - ## operation to - ## the directory structure. - -proc normalizePathEnd(path: var string, trailingSep = false) = - ## Ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on - ## ``trailingSep``, and taking care of edge cases: it preservers whether - ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, - ## not `AltSep`. Trailing `/.` are compressed, see examples. - if path.len == 0: return - var i = path.len - while i >= 1: - if path[i-1] in {DirSep, AltSep}: dec(i) - elif path[i-1] == '.' and i >= 2 and path[i-2] in {DirSep, AltSep}: dec(i) - else: break - if trailingSep: - # foo// => foo - path.setLen(i) - # foo => foo/ - path.add DirSep - elif i > 0: - # foo// => foo - path.setLen(i) - else: - # // => / (empty case was already taken care of) - path = $DirSep - -proc normalizePathEnd(path: string, trailingSep = false): string = - ## outplace overload - runnableExamples: - when defined(posix): - assert normalizePathEnd("/lib//.//", trailingSep = true) == "/lib/" - assert normalizePathEnd("lib/./.", trailingSep = false) == "lib" - assert normalizePathEnd(".//./.", trailingSep = false) == "." - assert normalizePathEnd("", trailingSep = true) == "" # not / ! - assert normalizePathEnd("/", trailingSep = false) == "/" # not "" ! - result = path - result.normalizePathEnd(trailingSep) - - -template endsWith(a: string, b: set[char]): bool = - a.len > 0 and a[^1] in b - -proc joinPathImpl(result: var string, state: var int, tail: string) = - let trailingSep = tail.endsWith({DirSep, AltSep}) or tail.len == 0 and result.endsWith({DirSep, AltSep}) - normalizePathEnd(result, trailingSep=false) - addNormalizePath(tail, result, state, DirSep) - normalizePathEnd(result, trailingSep=trailingSep) - - -func joinPath(head, tail: string): string = - ## Joins two directory names to one. - ## - ## returns normalized path concatenation of `head` and `tail`, preserving - ## whether or not `tail` has a trailing slash (or, if tail if empty, whether - ## head has one). - ## - ## See also: - ## * `joinPath(parts: varargs[string]) proc`_ - ## * `/ proc`_ - ## * `splitPath proc`_ - ## * `uri.combine proc `_ - ## * `uri./ proc `_ - runnableExamples: - when defined(posix): - assert joinPath("usr", "lib") == "usr/lib" - assert joinPath("usr", "lib/") == "usr/lib/" - assert joinPath("usr", "") == "usr" - assert joinPath("usr/", "") == "usr/" - assert joinPath("", "") == "" - assert joinPath("", "lib") == "lib" - assert joinPath("", "/lib") == "/lib" - assert joinPath("usr/", "/lib") == "usr/lib" - assert joinPath("usr/lib", "../bin") == "usr/bin" - - result = newStringOfCap(head.len + tail.len) - var state = 0 - joinPathImpl(result, state, head) - joinPathImpl(result, state, tail) - when false: - if len(head) == 0: - result = tail - elif head[len(head)-1] in {DirSep, AltSep}: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & substr(tail, 1) - else: - result = head & tail - else: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & tail - else: - result = head & DirSep & tail - -func isAbsoluteImpl(path: string): bool {.raises: [].} = - ## Checks whether a given `path` is absolute. - ## - ## On Windows, network paths are considered absolute too. - runnableExamples: - assert not "".isAbsolute - assert not ".".isAbsolute - when defined(posix): - assert "/".isAbsolute - assert not "a/".isAbsolute - assert "/a/".isAbsolute - - if len(path) == 0: return false - - when doslikeFileSystem: - var len = len(path) - result = (path[0] in {'/', '\\'}) or - (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') - elif defined(macos): - # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path - result = path[0] != ':' - elif defined(RISCOS): - result = path[0] == '$' - elif defined(posix) or defined(js): - # `or defined(js)` wouldn't be needed pending https://github.com/nim-lang/Nim/issues/13469 - # This works around the problem for posix, but Windows is still broken with nim js -d:nodejs - result = path[0] == '/' - else: - doAssert false # if ever hits here, adapt as needed - -when doslikeFileSystem: - import std/private/ntpath +from std/private/ospaths2 {.all.} import joinPathImpl, joinPath, splitPath, + ReadDirEffect, WriteDirEffect +export ReadDirEffect, WriteDirEffect type Path* = distinct string -func len*(x: Path): int = - len(string(x)) - - -func isAbsolute*(path: Path): bool {.inline, raises: [].} = - result = isAbsoluteImpl(path.string) func joinPath*(head, tail: Path): Path {.inline.} = result = Path(joinPath(head.string, tail.string)) @@ -161,73 +19,8 @@ func joinPath*(parts: varargs[Path]): Path = result = Path(res) func `/`*(head, tail: Path): Path {.inline.} = - result = joinPath(head, tail) - -func splitPathImpl(path: string): tuple[head, tail: Path] = - ## Splits a directory into `(head, tail)` tuple, so that - ## ``head / tail == path`` (except for edge cases like "/usr"). - ## - ## See also: - ## * `joinPath(head, tail) proc`_ - ## * `joinPath(parts: varargs[string]) proc`_ - ## * `/ proc`_ - ## * `/../ proc`_ - ## * `relativePath proc`_ - runnableExamples: - assert splitPath("usr/local/bin") == ("usr/local", "bin") - assert splitPath("usr/local/bin/") == ("usr/local/bin", "") - assert splitPath("/bin/") == ("/bin", "") - when (NimMajor, NimMinor) <= (1, 0): - assert splitPath("/bin") == ("", "bin") - else: - assert splitPath("/bin") == ("/", "bin") - assert splitPath("bin") == ("", "bin") - assert splitPath("") == ("", "") - - when doslikeFileSystem: - let (drive, splitpath) = splitDrive(path) - let stop = drive.len - else: - const stop = 0 - - var sepPos = -1 - for i in countdown(len(path)-1, stop): - if path[i] in {DirSep, AltSep}: - sepPos = i - break - if sepPos >= 0: - result.head = Path(substr(path, 0, - if likely(sepPos >= 1): sepPos-1 else: 0 - )) - result.tail = Path(substr(path, sepPos+1)) - else: - when doslikeFileSystem: - result.head = Path(drive) - result.tail = Path(splitpath) - else: - result.head = Path("") - result.tail = Path(path) + joinPath(head, tail) func splitPath*(path: Path): tuple[head, tail: Path] {.inline.} = - splitPathImpl(path.string) - -func extractFilename*(path: Path): Path = - ## Extracts the filename of a given `path`. - ## - ## This is the same as ``name & ext`` from `splitFile(path) proc`_. - ## - ## See also: - ## * `searchExtPos proc`_ - ## * `splitFile proc`_ - ## * `lastPathPart proc`_ - ## * `changeFileExt proc`_ - ## * `addFileExt proc`_ - runnableExamples: - assert extractFilename("foo/bar/") == "" - assert extractFilename("foo/bar") == "bar" - assert extractFilename("foo/bar.baz") == "bar.baz" - - if path.len == 0 or path.string[path.len-1] in {DirSep, AltSep}: - result = Path("") - else: - result = splitPath(path).tail + let res = splitPath(path.string) + result = (Path(res.head), Path(res.tail)) diff --git a/lib/std/symlinks.nim b/lib/std/symlinks.nim new file mode 100644 index 0000000000000..94a4b2050af1d --- /dev/null +++ b/lib/std/symlinks.nim @@ -0,0 +1,13 @@ +import paths + +from std/private/ossymlinks import symlinkExists, createSymlink, expandSymlink + + +proc symlinkExists*(link: Path): bool {.tags: [ReadDirEffect].} = + result = symlinkExists(link.string) + +proc createSymlink*(src, dest: Path) = + createSymlink(src.string, dest.string) + +proc expandSymlink*(symlinkPath: Path): Path = + result = expandSymlink(symlinkPath)