Skip to content

Commit

Permalink
Add normalizePath, absolutePath and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Federico Ceratto committed Nov 19, 2017
1 parent 035f0fb commit 14492f3
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 2 deletions.
63 changes: 61 additions & 2 deletions lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,11 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} =

proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns the full (`absolute`:idx:) path of the file `filename`,
## raises OSError in case of an error.
## Returns the full (`absolute`:idx:) path of an existing file `filename`,
## raises OSError in case of an error. Follows symlinks.
##
## To create absolute paths from any path (existing or not) see
## `<#absolutePath>`_.
when defined(windows):
var bufsize = MAX_PATH.int32
when useWinUnicode:
Expand Down Expand Up @@ -323,6 +326,62 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
result = $r
c_free(cast[pointer](r))

proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} =
## Normalize an UNIX path.
##
## Consecutive slashes (/) are collapsed, including an initial double slash.
##
## On relative paths, double dot (..) sequences are collapsed if possible.
## On absolute paths they are always collapsed.
##
## Warning: URL-encoded and Unicode attempts at directory traversal are not detected.
## Triple dot is not handled.
let is_abs = path[0] == DirSep
var stack: seq[string] = @[]
for p in split(path, {DirSep}):
case p
of "", ".":
continue
of "..":
if stack.len == 0:
if is_abs:
discard # collapse all double dots on absoluta paths
else:
stack.add(p)
elif stack[^1] == "..":
stack.add(p)
else:
discard stack.pop()
else:
stack.add(p)

if is_abs:
path = DirSep & join(stack, $DirSep)
elif stack.len > 0:
path = join(stack, $DirSep)
else:
path = "."

proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [].} =
## Returns a normalized UNIX path. See `<#normalizePath>`_
result = path
normalizePath result

proc makeAbsolutePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} =
## Generates the normalized, (`absolute`:idx:) version of `path`, based on
## the current directory. See `<#normalizePath>`_
if path.isAbsolute == false:
path = joinPath(getCurrentDir(), path)

if path.len > 1 and path[^1] == DirSep:
path = path[0..^2]

proc absolutePath*(path: string): string {.rtl, extern: "nos$1", tags: [].} =
## Returns the normalized, (`absolute`:idx:) version of `path`, based on
## the current directory. See `<#normalizePath>`_
result = path
makeAbsolutePath result

when defined(Windows):
proc openHandle(path: string, followSymlink=true): Handle =
var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
Expand Down
50 changes: 50 additions & 0 deletions tests/stdlib/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Raises
true
true
true
'''
"""
# test os path creation, iteration, and deletion
Expand Down Expand Up @@ -129,3 +130,52 @@ echo fileExists("../dest/a/b/file.txt")

echo fileExists("../dest/a/b/c/fileC.txt")
removeDir("../dest")

block normalizedPath:
block relative:
doAssert normalizedPath(".") == "."
doAssert normalizedPath("..") == ".."
doAssert normalizedPath("../") == ".."
doAssert normalizedPath("../..") == "../.."
doAssert normalizedPath("../a/..") == ".."
doAssert normalizedPath("../a/../") == ".."
doAssert normalizedPath("./") == "."

block absolute:
doAssert normalizedPath("/") == "/"
doAssert normalizedPath("/.") == "/"
doAssert normalizedPath("/..") == "/"
doAssert normalizedPath("/../") == "/"
doAssert normalizedPath("/../..") == "/"
doAssert normalizedPath("/../../") == "/"
doAssert normalizedPath("/../../../") == "/"
doAssert normalizedPath("/./") == "/"
doAssert normalizedPath("//") == "/"
doAssert normalizedPath("///") == "/"
doAssert normalizedPath("/a//b") == "/a/b"
doAssert normalizedPath("/a///b") == "/a/b"
doAssert normalizedPath("/a/b/c/..") == "/a/b"
doAssert normalizedPath("/a/b/c/../") == "/a/b"

when defined(Linux) or defined(osx):

block absolutePath:
block root:
let cwd = getCurrentDir()
setCurrentDir("/")
doAssert absolutePath("foo") == "/foo"
doAssert absolutePath("") == "/"
#doAssert absolutePath(".") == "/"
doAssert absolutePath("/") == "/"
setCurrentDir(cwd)

block tmp:
let cwd = getCurrentDir()
setCurrentDir("/tmp")
doAssert absolutePath("foo") == "/tmp/foo"
doAssert absolutePath("") == "/tmp"
#doAssert absolutePath(".") == "/tmp"
doAssert absolutePath("/") == "/"
#doAssert absolutePath("foo/..") == "/tmp/foo"
#doAssert absolutePath("../..") == "/tmp/foo"
setCurrentDir(cwd)

0 comments on commit 14492f3

Please sign in to comment.