summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/ospaths.nim54
1 files changed, 50 insertions, 4 deletions
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index 1bcfd978b..bc6739dd3 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -449,6 +449,31 @@ 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.
@@ -530,12 +555,12 @@ proc getConfigDir*(): string {.rtl, extern: "nos$1",
   ## "~/.config/", otherwise.
   ##
   ## An OS-dependent trailing slash is always present at the end of the
-  ## returned string; `\\` on Windows and `/` on all other OSs.
+  ## returned string; `\` on Windows and `/` on all other OSs.
   when defined(windows):
-    result = string(getEnv("APPDATA")) & "\\"
+    result = getEnv("APPDATA").string
   else:
-    result = string(getEnv("XDG_CONFIG_HOME", "")) & "/"
-    if result == "/": result = string(getEnv("HOME")) & "/.config/"
+    result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string
+  result.normalizePathEnd(trailingSep = true)
 
 proc getTempDir*(): string {.rtl, extern: "nos$1",
   tags: [ReadEnvEffect, ReadIOEffect].} =
@@ -649,3 +674,24 @@ when isMainModule:
 
   when defined(posix):
     assert quoteShell("") == "''"
+
+  block normalizePathEndTest:
+    # handle edge cases correctly: shouldn't affect whether path is
+    # absolute/relative
+    doAssert "".normalizePathEnd(true) == ""
+    doAssert "".normalizePathEnd(false) == ""
+    doAssert "/".normalizePathEnd(true) == $DirSep
+    doAssert "/".normalizePathEnd(false) == $DirSep
+
+    when defined(posix):
+      doAssert "//".normalizePathEnd(false) == "/"
+      doAssert "foo.bar//".normalizePathEnd == "foo.bar"
+      doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/"
+    when defined(Windows):
+      doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo"
+      doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\"
+      # this one is controversial: we could argue for returning `D:\` instead,
+      # but this is simplest.
+      doAssert r"D:\".normalizePathEnd == r"D:"
+      doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\"
+      doAssert "/".normalizePathEnd == r"\"