On windows, os.relativePath returns path as is when roots are different (#12329)

* On windows, os.relativePath returns path as is when roots are different
* Implement os.sameRoot without windows API
* Fix compile error when compiling lib/nimhcr.nim
* Fix compile error when compiling lib/nimhcr.nim on Windows
This commit is contained in:
Tomohiro
2019-10-08 02:57:16 +09:00
committed by Andreas Rumpf
parent f9d95fd6a7
commit 509f53b782
2 changed files with 106 additions and 28 deletions

View File

@@ -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.

View File

@@ -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"