Skip to content

Commit

Permalink
fix nim-lang#8341: add lastPathPart
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheecour committed Sep 29, 2018
1 parent c404163 commit 12b8d58
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 49 deletions.
76 changes: 46 additions & 30 deletions lib/pure/ospaths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,29 @@ const
## The character which separates the base filename from the extension;
## for example, the '.' in ``os.nim``.

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`.
if path.len == 0: return
var i = path.len
while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i)
if trailingSep:
# foo// => foo
path.setLen(i)
# foo => foo/
path.add DirSep
elif i>0:
# foo// => foo
path.setLen(i)
else:
# // => / (empty case was already taken care of)
path = $DirSep

proc normalizePathEnd(path: string, trailingSep = false): string =
result = path
result.normalizePathEnd(trailingSep)

proc joinPath*(head, tail: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
Expand Down Expand Up @@ -253,10 +276,15 @@ proc parentDir*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
## Returns the parent directory of `path`.
##
## This is often the same as the ``head`` result of ``splitPath``.
## If there is no parent, "" is returned.
## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``.
## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``.
## This is the same as ``splitPath(path).head`` when ``path`` doesn't end
## in a dir separator.
## The remainder can be obtained with ``lastPathPart(path)``
runnableExamples:
doAssert parentDir("") == ""
when defined(posix):
doAssert parentDir("/usr/local/bin") == "/usr/local"
doAssert parentDir("foo/bar/") == "foo"

let sepPos = parentDirPos(path)
if sepPos >= 0:
result = substr(path, 0, sepPos-1)
Expand Down Expand Up @@ -368,12 +396,25 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {.
proc extractFilename*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
## Extracts the filename of a given `path`. This is the same as
## ``name & ext`` from ``splitFile(path)``.
## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``.
runnableExamples:
when defined(posix):
doAssert extractFilename("foo/bar/") == ""
doAssert extractFilename("foo/bar") == "bar"
if path.len == 0 or path[path.len-1] in {DirSep, AltSep}:
result = ""
else:
result = splitPath(path).tail

proc lastPathPart*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
## like ``extractFilename``, but ignores trailing dir separator; aka: baseName
## in some other languages.
runnableExamples:
when defined(posix):
doAssert lastPathPart("foo/bar/") == "bar"
let path = path.normalizePathEnd(trailingSep = false)
result = extractFilename(path)

proc changeFileExt*(filename, ext: string): string {.
noSideEffect, rtl, extern: "nos$1".} =
Expand Down Expand Up @@ -449,31 +490,6 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
elif defined(posix):
result = path[0] == '/'


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`.
if path.len == 0: return
var i = path.len
while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i)
if trailingSep:
# foo// => foo
path.setLen(i)
# foo => foo/
path.add DirSep
elif i>0:
# foo// => foo
path.setLen(i)
else:
# // => / (empty case was already taken care of)
path = $DirSep

proc normalizePathEnd(path: string, trailingSep = false): string =
result = path
result.normalizePathEnd(trailingSep)

proc unixToNativePath*(path: string, drive=""): string {.
noSideEffect, rtl, extern: "nos$1".} =
## Converts an UNIX-like path to a native one.
Expand Down
64 changes: 45 additions & 19 deletions tests/stdlib/tospaths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,50 @@ doAssert isAbsolute(unixToNativePath("/a/b", "a"))
doAssert unixToNativePath("a/b") == joinPath("a", "b")

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"
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:
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"
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:
#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"
#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 extractFilenameTest:
doAssert extractFilename("") == ""
when defined(posix):
doAssert extractFilename("foo/bar") == "bar"
doAssert extractFilename("foo/bar.txt") == "bar.txt"
doAssert extractFilename("foo/") == ""
doAssert extractFilename("/") == ""
when doslikeFileSystem:
doAssert extractFilename(r"foo\bar") == "bar"
doAssert extractFilename(r"foo\bar.txt") == "bar.txt"
doAssert extractFilename(r"foo\") == ""
doAssert extractFilename(r"C:\") == ""
doAssert extractFilename(r"C:") == ""

block lastPathPartTest:
doAssert lastPathPart("") == ""
when defined(posix):
doAssert lastPathPart("foo/bar.txt") == "bar.txt"
doAssert lastPathPart("foo/") == "foo"
doAssert lastPathPart("/") == ""
when doslikeFileSystem:
doAssert lastPathPart(r"foo\bar.txt") == "bar.txt"
doAssert lastPathPart(r"foo\") == "foo"
doAssert lastPathPart(r"C:\") == ""
doAssert lastPathPart(r"C:") == ""

0 comments on commit 12b8d58

Please sign in to comment.