diff options
-rw-r--r-- | changelog.md | 3 | ||||
-rw-r--r-- | lib/pure/os.nim | 32 | ||||
-rw-r--r-- | tests/stdlib/tos.nim | 29 |
3 files changed, 47 insertions, 17 deletions
diff --git a/changelog.md b/changelog.md index 5041f4d7e..1943df634 100644 --- a/changelog.md +++ b/changelog.md @@ -29,7 +29,8 @@ are converted to `None`. - `relativePath("foo", "foo")` is now `"."`, not `""`, as `""` means invalid path and shouldn't be conflated with `"."`; use -d:nimOldRelativePathBehavior to restore the old - behavioe + behavior +- `joinPath(a,b)` now honors trailing slashes in `b` (or `a` if `b` = "") ### Breaking changes in the compiler diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 5bdc5e11d..3db518273 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -122,14 +122,21 @@ proc normalizePathEnd(path: string, trailingSep = false): string = since((1, 1)): export normalizePathEnd +template endsWith(a: string, b: set[char]): bool = + a.len > 0 and a[^1] in b + +proc joinPathImpl(result: var string, state: var int, tail: string) = + let trailingSep = tail.endsWith({DirSep, AltSep}) or tail.len == 0 and result.endsWith({DirSep, AltSep}) + addNormalizePath(tail, result, state, DirSep) + normalizePathEnd(result, trailingSep=trailingSep) + proc joinPath*(head, tail: string): string {. noSideEffect, rtl, extern: "nos$1".} = ## Joins two directory names to one. ## - ## If `head` is the empty string, `tail` is returned. If `tail` is the empty - ## string, `head` is returned with a trailing path separator. If `tail` starts - ## with a path separator it will be removed when concatenated to `head`. - ## Path separators will be normalized. + ## returns normalized path concatenation of `head` and `tail`, preserving + ## whether or not `tail` has a trailing slash (or, if tail if empty, whether + ## head has one). ## ## See also: ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_ @@ -140,7 +147,9 @@ proc joinPath*(head, tail: string): string {. runnableExamples: when defined(posix): assert joinPath("usr", "lib") == "usr/lib" - assert joinPath("usr", "") == "usr/" + assert joinPath("usr", "lib/") == "usr/lib/" + assert joinPath("usr", "") == "usr" + assert joinPath("usr/", "") == "usr/" assert joinPath("", "") == "" assert joinPath("", "lib") == "lib" assert joinPath("", "/lib") == "/lib" @@ -149,11 +158,8 @@ proc joinPath*(head, tail: string): string {. result = newStringOfCap(head.len + tail.len) var state = 0 - addNormalizePath(head, result, state, DirSep) - if result.len != 0 and result[^1] notin {DirSep, AltSep} and tail.len == 0: - result.add DirSep - else: - addNormalizePath(tail, result, state, DirSep) + joinPathImpl(result, state, head) + joinPathImpl(result, state, tail) when false: if len(head) == 0: result = tail @@ -192,7 +198,7 @@ proc joinPath*(parts: varargs[string]): string {.noSideEffect, result = newStringOfCap(estimatedLen) var state = 0 for i in 0..high(parts): - addNormalizePath(parts[i], result, state, DirSep) + joinPathImpl(result, state, parts[i]) proc `/`*(head, tail: string): string {.noSideEffect.} = ## The same as `joinPath(head, tail) proc <#joinPath,string,string>`_. @@ -206,10 +212,10 @@ proc `/`*(head, tail: string): string {.noSideEffect.} = ## * `uri./ proc <uri.html#/,Uri,string>`_ runnableExamples: when defined(posix): - assert "usr" / "" == "usr/" + assert "usr" / "" == "usr" assert "" / "lib" == "lib" assert "" / "/lib" == "/lib" - assert "usr/" / "/lib" == "usr/lib" + assert "usr/" / "/lib/" == "usr/lib/" assert "usr" / "lib" / "../bin" == "usr/bin" return joinPath(head, tail) diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 15b82fadf..c649749e3 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -357,14 +357,37 @@ block ospaths: doAssert relativePath(r"\\foo\bar\baz.nim", r"\foo") == r"\\foo\bar\baz.nim" doAssert relativePath(r"c:\foo.nim", r"\foo") == r"c:\foo.nim" - doAssert joinPath("usr", "") == unixToNativePath"usr/" + doAssert joinPath("usr", "") == unixToNativePath"usr" doAssert joinPath("", "lib") == "lib" doAssert joinPath("", "/lib") == unixToNativePath"/lib" doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib" - doAssert joinPath("", "") == unixToNativePath"" - doAssert joinPath("/" / "") == unixToNativePath"/" + doAssert joinPath("", "") == unixToNativePath"" # issue #13455 + doAssert joinPath("", "/") == unixToNativePath"/" + doAssert joinPath("/", "/") == unixToNativePath"/" + doAssert joinPath("/", "") == unixToNativePath"/" + doAssert joinPath("/" / "") == unixToNativePath"/" # weird test case... doAssert joinPath("/", "/a/b/c") == unixToNativePath"/a/b/c" doAssert joinPath("foo/","") == unixToNativePath"foo/" + doAssert joinPath("foo/","abc") == unixToNativePath"foo/abc" + doAssert joinPath("foo//./","abc/.//") == unixToNativePath"foo/abc/" + doAssert joinPath("foo","abc") == unixToNativePath"foo/abc" + doAssert joinPath("","abc") == unixToNativePath"abc" + + doAssert joinPath("gook/.","abc") == unixToNativePath"gook/abc" + + # controversial: inconsistent with `joinPath("gook/.","abc")` + # on linux, `./foo` and `foo` are treated a bit differently for executables + # but not `./foo/bar` and `foo/bar` + doAssert joinPath(".", "/lib") == unixToNativePath"./lib" + doAssert joinPath(".","abc") == unixToNativePath"./abc" + + # cases related to issue #13455 + doAssert joinPath("foo", "", "") == "foo" + doAssert joinPath("foo", "") == "foo" + doAssert joinPath("foo/", "") == unixToNativePath"foo/" + doAssert joinPath("foo/", ".") == "foo" + doAssert joinPath("foo", "./") == unixToNativePath"foo/" + doAssert joinPath("foo", "", "bar/") == unixToNativePath"foo/bar/" block getTempDir: block TMPDIR: |