Skip to content

Commit

Permalink
fix issue #8268 (joinPaths);add normalizePathEnd; add lots of tests
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheecour committed Jul 18, 2018
1 parent b45c9f6 commit 238df98
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 65 deletions.
135 changes: 83 additions & 52 deletions lib/pure/ospaths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -157,55 +157,111 @@ const
## The character which separates the base filename from the extension;
## for example, the '.' in ``os.nim``.

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:
doAssert(not "".isAbsolute)
doAssert(not ".".isAbsolute)
doAssert(not "foo".isAbsolute)
when defined(posix):
doAssert "/".isAbsolute
doAssert(not "a/".isAbsolute)
when defined(Windows):
doAssert "C:\\foo".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 normalizePathEnd*(path:string, trailingSep = false): string =
## Returns ``path`` with 0 or 1 trailing separator, depending on
## ``trailingSep``, and taking care of special case of root paths.
runnableExamples:
when defined(posix):
doAssert "foo.bar//".normalizePathEnd == "foo.bar"
doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/"
when defined(Windows):
doAssert """C:\foo\\""".normalizePathEnd == """C:\foo"""
doAssert """C:\foo""".normalizePathEnd(trailingSep = true) == """C:\foo\"""
doAssert """C:\""".normalizePathEnd == """C:\"""
if path.len == 0: return ""

result = strip(path, leading = false, trailing = true, {DirSep, AltSep})
if trailingSep:
result = result & DirSep
else:
# still need to end with `DirSep` for root
if result.len == 0:
result = "" & DirSep
else:
when defined(doslikeFileSystem):
if result[^-1] == ':':
# C:\
result = "" & DirSep

proc joinPath*(head, tail: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
## Joins two directory names to one.
## Concatenates paths ``head`` and ``tail``.
##
## If head is the empty string, tail is returned. If tail is the empty
## string, head is returned with a trailing path separator. If tail starts
## with a path separator it will be removed when concatenated to head. Other
## path separators not located on boundaries won't be modified.
## If ``tail`` is absolute or ``head`` is empty, returns ``tail``. If
## ``tail`` is empty returns ``head``. Else, returns the concatenation with
## normalized spearator between ``head`` and ``tail``.
runnableExamples:
when defined(posix):
doAssert joinPath("usr", "lib") == "usr/lib"
doAssert joinPath("usr", "") == "usr/"
doAssert joinPath("", "lib") == "lib"
doAssert joinPath("", "/lib") == "/lib"
doAssert joinPath("usr/", "/lib") == "usr/lib"
doAssert joinPath("usr/", "/lib") == "/lib" ## tail is absolute!
doAssert joinPath("usr///", "lib") == "usr/lib" ## `//` gets compressed
doAssert joinPath("//", "lib") == "/lib" ## ditto

if tail.isAbsolute:
return tail

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
result = normalizePathEnd(head, trailingSep = true) & tail

proc joinPath*(parts: varargs[string]): string {.noSideEffect,
rtl, extern: "nos$1OpenArray".} =
## The same as `joinPath(head, tail)`, but works with any number of
## directory parts. You need to pass at least one element or the proc
## will assert in debug builds and crash on release builds.
## directory parts.
runnableExamples:
doAssert joinPath() == ""
doAssert joinPath("foo") == "foo"
when defined(posix):
doAssert joinPath("foo", "bar") == "foo/bar"
doAssert joinPath("foo//", "bar/") == "foo/bar/"
doAssert joinPath("foo", "/bar") == "/bar"
doAssert joinPath("foo", "/bar", "/baz", "tail") == "/baz/tail"

if parts.len == 0:
return ""
result = parts[0]
for i in 1..high(parts):
result = joinPath(result, parts[i])

proc `/` * (head, tail: string): string {.noSideEffect.} =
## The same as ``joinPath(head, tail)``
##
## Here are some examples for Unix:
##
## .. code-block:: nim
## assert "usr" / "" == "usr/"
## assert "" / "lib" == "lib"
## assert "" / "/lib" == "/lib"
## assert "usr/" / "/lib" == "usr/lib"
## The same as ``joinPath(head, tail)``.
runnableExamples:
when defined(posix):
doAssert "usr" / "" == "usr/"
doAssert "" / "lib" == "lib"
doAssert "" / "/lib" == "/lib"
doAssert "usr/" / "/lib" == "/lib"
return joinPath(head, tail)

proc splitPath*(path: string): tuple[head, tail: string] {.
Expand Down Expand Up @@ -410,31 +466,6 @@ proc cmpPaths*(pathA, pathB: string): int {.
else:
result = cmpIgnoreCase(pathA, pathB)

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:
doAssert(not "".isAbsolute)
doAssert(not ".".isAbsolute)
when defined(posix):
doAssert "/".isAbsolute
doAssert(not "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.
Expand Down
44 changes: 31 additions & 13 deletions tests/stdlib/tospaths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,54 @@ discard """

import os

doAssert unixToNativePath("") == ""
doAssert unixToNativePath(".") == $CurDir
doAssert unixToNativePath("..") == $ParDir
doAssert isAbsolute(unixToNativePath("/"))
doAssert isAbsolute(unixToNativePath("/", "a"))
doAssert isAbsolute(unixToNativePath("/a"))
doAssert isAbsolute(unixToNativePath("/a", "a"))
doAssert isAbsolute(unixToNativePath("/a/b"))
doAssert isAbsolute(unixToNativePath("/a/b", "a"))
doAssert unixToNativePath("a/b") == joinPath("a", "b")
block unixToNativePath:
doAssert unixToNativePath("") == ""
doAssert unixToNativePath(".") == $CurDir
doAssert unixToNativePath("..") == $ParDir
doAssert isAbsolute(unixToNativePath("/"))
doAssert isAbsolute(unixToNativePath("/", "a"))
doAssert isAbsolute(unixToNativePath("/a"))
doAssert isAbsolute(unixToNativePath("/a", "a"))
doAssert isAbsolute(unixToNativePath("/a/b"))
doAssert isAbsolute(unixToNativePath("/a/b", "a"))
doAssert unixToNativePath("a/b") == joinPath("a", "b")

when defined(macos):
when defined(macos):
doAssert unixToNativePath("./") == ":"
doAssert unixToNativePath("./abc") == ":abc"
doAssert unixToNativePath("../abc") == "::abc"
doAssert unixToNativePath("../../abc") == ":::abc"
doAssert unixToNativePath("/abc", "a") == "abc"
doAssert unixToNativePath("/abc/def", "a") == "abc:def"
elif doslikeFileSystem:
elif doslikeFileSystem:
doAssert unixToNativePath("./") == ".\\"
doAssert unixToNativePath("./abc") == ".\\abc"
doAssert unixToNativePath("../abc") == "..\\abc"
doAssert unixToNativePath("../../abc") == "..\\..\\abc"
doAssert unixToNativePath("/abc", "a") == "a:\\abc"
doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def"
else:
else:
#Tests for unix
doAssert unixToNativePath("./") == "./"
doAssert unixToNativePath("./abc") == "./abc"
doAssert unixToNativePath("../abc") == "../abc"
doAssert unixToNativePath("../../abc") == "../../abc"
doAssert unixToNativePath("/abc", "a") == "/abc"
doAssert unixToNativePath("/abc/def", "a") == "/abc/def"

block normalizePathEnd:
doAssert "".normalizePathEnd == ""
doAssert "".normalizePathEnd(trailingSep = true) == ""
when defined(posix):
doAssert "/".normalizePathEnd == "/"
doAssert "foo.bar".normalizePathEnd == "foo.bar"
doAssert "foo.bar".normalizePathEnd(trailingSep = true) == "foo.bar/"
when defined(Windows):
doAssert """C:\\""".normalizePathEnd == """C:\"""
doAssert """C:\""".normalizePathEnd(trailingSep = true) == """C:\"""
doAssert """C:\foo\\bar\""".normalizePathEnd == """C:\foo\\bar"""

block joinPath:
when defined(posix):
doAssert joinPath("", "/lib") == "/lib"

0 comments on commit 238df98

Please sign in to comment.