Skip to content

Commit

Permalink
fix lots of bugs with parentDir, refs #8734 (#13236)
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheecour authored and Araq committed Jan 23, 2020
1 parent b8a436a commit 3a5056d
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 19 deletions.
37 changes: 23 additions & 14 deletions lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,13 @@ 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`.
## not `AltSep`. Trailing `/.` are compressed, see examples.
if path.len == 0: return
var i = path.len
while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i)
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)
Expand All @@ -106,9 +109,11 @@ 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("/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)

Expand Down Expand Up @@ -429,8 +434,8 @@ proc parentDir*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
## Returns the parent directory of `path`.
##
## This is the same as ``splitPath(path).head`` when ``path`` doesn't end
## in a dir separator.
## This is similar to ``splitPath(path).head`` when ``path`` doesn't end
## in a dir separator, but also takes care of path normalizations.
## The remainder can be obtained with `lastPathPart(path) proc
## <#lastPathPart,string>`_.
##
Expand All @@ -443,19 +448,23 @@ proc parentDir*(path: string): string {.
assert parentDir("") == ""
when defined(posix):
assert parentDir("/usr/local/bin") == "/usr/local"
assert parentDir("foo/bar/") == "foo"
assert parentDir("foo/bar//") == "foo"
assert parentDir("//foo//bar//") == "//foo"
assert parentDir("//foo//bar//.") == "/foo"
assert parentDir("./foo") == "."
assert parentDir("/foo") == ""

result = normalizePathEnd(path)
assert parentDir("/./foo//./") == "/"
assert parentDir("a//./") == "."
assert parentDir("a/b/c/..") == "a"
result = pathnorm.normalizePath(path)
var sepPos = parentDirPos(result)
if sepPos >= 0:
while sepPos >= 0 and result[sepPos] in {DirSep, AltSep}: dec sepPos
result = substr(result, 0, sepPos)
else:
normalizePathEnd(result)
elif result == ".." or result == "." or result.len == 0 or result[^1] in {DirSep, AltSep}:
# `.` => `..` and .. => `../..`(etc) would be a sensible alternative
# `/` => `/` (as done with splitFile) would be a sensible alternative
result = ""
else:
result = "."

proc tailDir*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
Expand Down Expand Up @@ -598,7 +607,7 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {.
noSideEffect, rtl, extern: "nos$1".} =
## Splits a filename into `(dir, name, extension)` tuple.
##
## `dir` does not end in `DirSep <#DirSep>`_.
## `dir` does not end in `DirSep <#DirSep>`_ unless it's `/`.
## `extension` includes the leading dot.
##
## If `path` has no extension, `ext` is the empty string.
Expand Down
10 changes: 5 additions & 5 deletions tests/stdlib/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,11 @@ block isHidden:
when defined(posix):
doAssert ".foo.txt".isHidden
doAssert "bar/.foo.ext".isHidden
doAssert: not "bar".isHidden
doAssert: not "foo/".isHidden
# Corner cases: paths are not normalized when determining `isHidden`
doAssert: not ".foo/.".isHidden
doAssert: not ".foo/..".isHidden
doAssert not "bar".isHidden
doAssert not "foo/".isHidden
doAssert ".foo/.".isHidden
# Corner cases: `isHidden` is not yet `..` aware
doAssert not ".foo/..".isHidden

block absolutePath:
doAssertRaises(ValueError): discard absolutePath("a", "b")
Expand Down

0 comments on commit 3a5056d

Please sign in to comment.