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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
#
#
# The Nim Compiler
# (c) Copyright 2018 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Path handling utilities for Nim. Strictly typed code in order
## to avoid the never ending time sink in getting path handling right.
import std/[os, pathnorm, strutils]
when defined(nimPreviewSlimSystem):
import std/[syncio, assertions]
type
AbsoluteFile* = distinct string
AbsoluteDir* = distinct string
RelativeFile* = distinct string
RelativeDir* = distinct string
AnyPath* = AbsoluteFile|AbsoluteDir|RelativeFile|RelativeDir
proc isEmpty*(x: AnyPath): bool {.inline.} = x.string.len == 0
proc copyFile*(source, dest: AbsoluteFile) =
os.copyFile(source.string, dest.string)
proc removeFile*(x: AbsoluteFile) {.borrow.}
proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] =
let (a, b, c) = splitFile(x.string)
result = (dir: AbsoluteDir(a), name: b, ext: c)
proc extractFilename*(x: AbsoluteFile): string {.borrow.}
proc fileExists*(x: AbsoluteFile): bool {.borrow.}
proc dirExists*(x: AbsoluteDir): bool {.borrow.}
proc quoteShell*(x: AbsoluteFile): string {.borrow.}
proc quoteShell*(x: AbsoluteDir): string {.borrow.}
proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.}
proc createDir*(x: AbsoluteDir) {.borrow.}
proc toAbsoluteDir*(path: string): AbsoluteDir =
result = if path.isAbsolute: AbsoluteDir(path)
else: AbsoluteDir(getCurrentDir() / path)
proc `$`*(x: AnyPath): string = x.string
when true:
proc eqImpl(x, y: string): bool {.inline.} =
result = cmpPaths(x, y) == 0
proc `==`*[T: AnyPath](x, y: T): bool = eqImpl(x.string, y.string)
template postProcessBase(base: AbsoluteDir): untyped =
# xxx: as argued here https://github.com/nim-lang/Nim/pull/10018#issuecomment-448192956
# empty paths should not mean `cwd` so the correct behavior would be to throw
# here and make sure `outDir` is always correctly initialized; for now
# we simply preserve pre-existing external semantics and treat it as `cwd`
when false:
doAssert isAbsolute(base.string), base.string
base
else:
if base.isEmpty: getCurrentDir().AbsoluteDir else: base
proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile =
let base = postProcessBase(base)
assert(not isAbsolute(f.string), f.string)
result = AbsoluteFile newStringOfCap(base.string.len + f.string.len)
var state = 0
addNormalizePath(base.string, result.string, state)
addNormalizePath(f.string, result.string, state)
proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir =
let base = postProcessBase(base)
assert(not isAbsolute(f.string))
result = AbsoluteDir newStringOfCap(base.string.len + f.string.len)
var state = 0
addNormalizePath(base.string, result.string, state)
addNormalizePath(f.string, result.string, state)
proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir;
sep = DirSep): RelativeFile =
# this currently fails for `tests/compilerapi/tcompilerapi.nim`
# it's needed otherwise would returns an absolute path
# assert not baseFilename.isEmpty, $fullPath
result = RelativeFile(relativePath(fullPath.string, baseFilename.string, sep))
proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile =
if isAbsolute(file): result = AbsoluteFile(file)
else: result = base / RelativeFile file
proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
proc writeFile*(x: AbsoluteFile; content: string) {.borrow.}
proc skipHomeDir(x: string): int =
when defined(windows):
if x.continuesWith("Users/", len("C:/")):
result = 3
else:
result = 0
else:
if x.startsWith("/home/") or x.startsWith("/Users/"):
result = 3
elif x.startsWith("/mnt/") and x.continuesWith("/Users/", len("/mnt/c")):
result = 5
else:
result = 0
proc relevantPart(s: string; afterSlashX: int): string =
result = newStringOfCap(s.len - 8)
var slashes = afterSlashX
for i in 0..<s.len:
if slashes == 0:
result.add s[i]
elif s[i] == '/':
dec slashes
template canonSlashes(x: string): string =
when defined(windows):
x.replace('\\', '/')
else:
x
proc customPathImpl(x: string): string =
# Idea: Encode a "protocol" via "//protocol/path" which is not ambiguous
# as path canonicalization would have removed the double slashes.
# /mnt/X/Users/Y
# X:\\Users\Y
# /home/Y
# -->
# //user/
if not isAbsolute(x):
result = customPathImpl(canonSlashes(getCurrentDir() / x))
else:
let slashes = skipHomeDir(x)
if slashes > 0:
result = "//user/" & relevantPart(x, slashes)
else:
result = x
proc customPath*(x: string): string =
customPathImpl canonSlashes x
|