1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
#
#
# 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.
##
## Unstable 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
when doslikeFileSystem: # UNC paths have leading `\\`
if hasNext(it, x) and x[it.i] == DirSep and
it.i+1 < x.len and x[it.i+1] != DirSep:
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.
var it: PathIter
it.notFirst = (state shr 1) > 0
if it.notFirst:
while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
while hasNext(it, x):
let b = next(it, x)
if (state shr 1 == 0) and isSlash(x, b):
if result.len == 0 or result[^1] notin {DirSep, AltSep}:
result.add dirSep
state = state or 1
elif isDotDot(x, b):
if (state shr 1) >= 1:
var d = result.len
# f/..
# We could handle stripping trailing sep here: foo// => foo like this:
# while (d-1) > (state and 1) and result[d-1] in {DirSep, AltSep}: dec d
# but right now we instead handle it inside os.joinPath
# strip path component: foo/bar => foo
while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}:
dec d
if d > 0:
setLen(result, d-1)
dec state, 2
else:
if result.len > 0 and result[^1] notin {DirSep, AltSep}:
result.add dirSep
result.add substr(x, b[0], b[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
if result == "" and x != "": result = "."
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 (but "foo/.." becomes ".")
result = newStringOfCap(path.len)
var state = 0
addNormalizePath(path, result, state, dirSep)
|