summary refs log tree commit diff stats
path: root/lib/pure/pathnorm.nim
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2018-12-13 12:05:36 +0100
committerAraq <rumpf_a@web.de>2018-12-13 12:05:50 +0100
commit9cc4a57768156883acc4e8190c78a69ed2c3d526 (patch)
tree693e8b338654b3e9a00b726545fa99eab042466c /lib/pure/pathnorm.nim
parent467f53512fa77d6d7a706d8f946c2ec72594c8a5 (diff)
downloadNim-9cc4a57768156883acc4e8190c78a69ed2c3d526.tar.gz
os.nim: big refactoring, use the new pathnorm that was extracted by compiler/pathutils.nim; added os.relativePath
Diffstat (limited to 'lib/pure/pathnorm.nim')
-rw-r--r--lib/pure/pathnorm.nim90
1 files changed, 90 insertions, 0 deletions
diff --git a/lib/pure/pathnorm.nim b/lib/pure/pathnorm.nim
new file mode 100644
index 000000000..696f6b2ef
--- /dev/null
+++ b/lib/pure/pathnorm.nim
@@ -0,0 +1,90 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## OS-Path normalization. Used by ``os.nim`` but also
+## generally useful for dealing with paths. Note that this module
+## does not provide a stable API.
+
+# Yes, this uses import here, not include so that
+# we don't end up exporting these symbols from pathnorm and os:
+import "includes/osseps"
+
+type
+  PathIter* = object
+    i, prev: int
+    notFirst: bool
+
+proc hasNext*(it: PathIter; x: string): bool =
+  it.i < x.len
+
+proc next*(it: var PathIter; x: string): (int, int) =
+  it.prev = it.i
+  if not it.notFirst and x[it.i] in {DirSep, AltSep}:
+    # absolute path:
+    inc it.i
+  else:
+    while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i
+  if it.i > it.prev:
+    result = (it.prev, it.i-1)
+  elif hasNext(it, x):
+    result = next(it, x)
+
+  # skip all separators:
+  while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
+  it.notFirst = true
+
+iterator dirs(x: string): (int, int) =
+  var it: PathIter
+  while hasNext(it, x): yield next(it, x)
+
+proc isDot(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] and x[bounds[0]] == '.'
+
+proc isDotDot(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.'
+
+proc isSlash(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep}
+
+proc addNormalizePath*(x: string; result: var string; state: var int; dirSep = DirSep) =
+  ## Low level proc. Undocumented.
+
+  # state: 0th bit set if isAbsolute path. Other bits count
+  # the number of path components.
+  for b in dirs(x):
+    if (state shr 1 == 0) and isSlash(x, b):
+      result.add dirSep
+      state = state or 1
+    elif result.len > (state and 1) and isDotDot(x, b):
+      var d = result.len
+      # f/..
+      while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}:
+        dec d
+      if d > 0: setLen(result, d-1)
+    elif isDot(x, b):
+      discard "discard the dot"
+    elif b[1] >= b[0]:
+      if result.len > 0 and result[^1] notin {DirSep, AltSep}:
+        result.add dirSep
+      result.add substr(x, b[0], b[1])
+    inc state, 2
+
+proc normalizePath*(path: string; dirSep = DirSep): string =
+  ## Example:
+  ##
+  ## .. code-block:: nim
+  ##   assert normalizePath("./foo//bar/../baz") == "foo/baz"
+  ##
+  ##
+  ## - Turns multiple slashes into single slashes.
+  ## - Resolves '/foo/../bar' to '/bar'.
+  ## - Removes './' from the path.
+  result = newStringOfCap(path.len)
+  var state = 0
+  addNormalizePath(path, result, state, dirSep)