Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix lots of bugs with parentDir, refs #8734 #13236

Merged
merged 3 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -417,8 +422,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 @@ -431,19 +436,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 @@ -586,7 +595,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