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 14, 2017
1 parent 22ceab0 commit 8b2c961
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 2 deletions.
51 changes: 50 additions & 1 deletion 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`,
## Returns the full (`absolute`:idx:) path of an existing file `filename`,
## raises OSError in case of an error.
##
## 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,52 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
result = $r
c_free(cast[pointer](r))

proc normalizePath*(path: string): string =
## 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:
result = DirSep & join(stack, $DirSep)
elif stack.len > 0:
result = join(stack, $DirSep)
else:
result = "."

proc absolutePath*(path: string): string =
## Returns the normalized, (`absolute`:idx:) version of `path`, based on
## the current directory. See `<#normalizePath>`_
if path.isAbsolute:
result = path
else:
result = joinPath(getCurrentDir(), path)
if result.len > 1 and result[^1] == DirSep:
result = result[0..^2]

when defined(Windows):
proc openHandle(path: string, followSymlink=true): Handle =
var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
Expand Down
58 changes: 57 additions & 1 deletion tests/stdlib/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ Raises
true
true
true
[Suite] normalizePath
[Suite] absolutePath
'''
"""
# test os path creation, iteration, and deletion

import os, strutils
import os, strutils, unittest

let files = @["these.txt", "are.x", "testing.r", "files.q"]
let dirs = @["some", "created", "test", "dirs"]
Expand Down Expand Up @@ -129,3 +134,54 @@ echo fileExists("../dest/a/b/file.txt")

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

suite "normalizePath":
test "normalizePath relative":
check normalizePath(".") == "."
check normalizePath("..") == ".."
check normalizePath("../") == ".."
check normalizePath("../..") == "../.."
check normalizePath("../a/..") == ".."
check normalizePath("../a/../") == ".."
check normalizePath("./") == "."

test "normalizePath absolute":
check normalizePath("/") == "/"
check normalizePath("/.") == "/"
check normalizePath("/..") == "/"
check normalizePath("/../") == "/"
check normalizePath("/../..") == "/"
check normalizePath("/../../") == "/"
check normalizePath("/../../../") == "/"
check normalizePath("/./") == "/"
check normalizePath("//") == "/"
check normalizePath("///") == "/"
check normalizePath("/a//b") == "/a/b"
check normalizePath("/a///b") == "/a/b"
check normalizePath("/a/b/c/..") == "/a/b"
check normalizePath("/a/b/c/../") == "/a/b"

when defined(Linux) or defined(osx):

suite "absolutePath":
setup:
let cwd = getCurrentDir()

test "absolutePath /":
setCurrentDir("/")
check absolutePath("foo") == "/foo"
check absolutePath("") == "/"
#check absolutePath(".") == "/"
check absolutePath("/") == "/"

test "absolutePath /tmp":
setCurrentDir("/tmp")
check absolutePath("foo") == "/tmp/foo"
check absolutePath("") == "/tmp"
#check absolutePath(".") == "/tmp"
check absolutePath("/") == "/"
#check absolutePath("foo/..") == "/tmp/foo"
#check absolutePath("../..") == "/tmp/foo"

teardown:
setCurrentDir(cwd)

0 comments on commit 8b2c961

Please sign in to comment.