diff options
-rw-r--r-- | lib/pure/os.nim | 119 | ||||
-rw-r--r-- | tests/stdlib/tos.nim | 15 |
2 files changed, 106 insertions, 28 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 03173708b..4e7e37698 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -232,11 +232,92 @@ proc splitPath*(path: string): tuple[head, tail: string] {. result.head = "" result.tail = path +proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1", 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): + result = path[0] == '/' + when FileSystemCaseSensitive: template `!=?`(a, b: char): bool = a != b else: template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b) +when doslikeFileSystem: + proc isAbsFromCurrentDrive(path: string): bool {.noSideEffect, raises: []} = + ## An absolute path from the root of the current drive (e.g. "\foo") + path.len > 0 and + (path[0] == AltSep or + (path[0] == DirSep and + (path.len == 1 or path[1] notin {DirSep, AltSep, ':'}))) + + proc isUNCPrefix(path: string): bool {.noSideEffect, raises: []} = + path[0] == DirSep and path[1] == DirSep + + proc sameRoot(path1, path2: string): bool {.noSideEffect, raises: []} = + ## Return true if path1 and path2 have a same root. + ## + ## Detail of windows path formats: + ## https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats + + assert(isAbsolute(path1)) + assert(isAbsolute(path2)) + + let + len1 = path1.len + len2 = path2.len + assert(len1 != 0 and len2 != 0) + + if isAbsFromCurrentDrive(path1) and isAbsFromCurrentDrive(path2): + return true + elif len1 == 1 or len2 == 1: + return false + else: + if path1[1] == ':' and path2[1] == ':': + return path1[0].toLowerAscii() == path2[0].toLowerAscii() + else: + var + p1, p2: PathIter + pp1 = next(p1, path1) + pp2 = next(p2, path2) + if pp1[1] - pp1[0] == 1 and pp2[1] - pp2[0] == 1 and + isUNCPrefix(path1) and isUNCPrefix(path2): + #UNC + var h = 0 + while p1.hasNext(path1) and p2.hasNext(path2) and h < 2: + pp1 = next(p1, path1) + pp2 = next(p2, path2) + let diff = pp1[1] - pp1[0] + if diff != pp2[1] - pp2[0]: + return false + for i in 0..diff: + if path1[i + pp1[0]] !=? path2[i + pp2[0]]: + return false + inc h + return h == 2 + else: + return false + proc relativePath*(path, base: string; sep = DirSep): string {. noSideEffect, rtl, extern: "nos$1", raises: [].} = ## Converts `path` to a path relative to `base`. @@ -245,6 +326,10 @@ proc relativePath*(path, base: string; sep = DirSep): string {. ## this can be useful to ensure the relative path only contains `'/'` ## so that it can be used for URL constructions. ## + ## On windows, if a root of `path` and a root of `base` are different, + ## returns `path` as is because it is impossible to make a relative path. + ## That means an absolute path can be returned. + ## ## See also: ## * `splitPath proc <#splitPath,string>`_ ## * `parentDir proc <#parentDir,string>`_ @@ -256,9 +341,13 @@ proc relativePath*(path, base: string; sep = DirSep): string {. assert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim" assert relativePath("", "/users/moo", '/') == "" - # Todo: If on Windows, path and base do not agree on the drive letter, - # return `path` as is. if path.len == 0: return "" + + when doslikeFileSystem: + if isAbsolute(path) and isAbsolute(base): + if not sameRoot(path, base): + return path + var f, b: PathIter var ff = (0, -1) var bb = (0, -1) # (int, int) @@ -645,32 +734,6 @@ proc cmpPaths*(pathA, pathB: string): int {. else: result = cmpIgnoreCase(a, b) -proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = - ## 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): - result = path[0] == '/' - proc unixToNativePath*(path: string, drive=""): string {. noSideEffect, rtl, extern: "nos$1".} = ## Converts an UNIX-like path to a native one. diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index ab0cce8de..35ea34841 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -334,6 +334,21 @@ block ospaths: doAssert relativePath("/foo", "/fOO", '/') == (when FileSystemCaseSensitive: "../foo" else: "") doAssert relativePath("/foO", "/foo", '/') == (when FileSystemCaseSensitive: "../foO" else: "") + when doslikeFileSystem: + doAssert relativePath(r"c:\foo.nim", r"C:\") == r"foo.nim" + doAssert relativePath(r"c:\foo\bar\baz.nim", r"c:\foo") == r"bar\baz.nim" + doAssert relativePath(r"c:\foo\bar\baz.nim", r"d:\foo") == r"c:\foo\bar\baz.nim" + doAssert relativePath(r"\foo\baz.nim", r"\foo") == r"baz.nim" + doAssert relativePath(r"\foo\bar\baz.nim", r"\bar") == r"..\foo\bar\baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foo\bar") == r"baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foO\bar") == r"baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\\bar\bar") == r"\\foo\bar\baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foo\car") == r"\\foo\bar\baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\\goo\bar") == r"\\foo\bar\baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"c:\") == r"\\foo\bar\baz.nim" + doAssert relativePath(r"\\foo\bar\baz.nim", r"\foo") == r"\\foo\bar\baz.nim" + doAssert relativePath(r"c:\foo.nim", r"\foo") == r"c:\foo.nim" + doAssert joinPath("usr", "") == unixToNativePath"usr/" doAssert joinPath("", "lib") == "lib" doAssert joinPath("", "/lib") == unixToNativePath"/lib" |