-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
# | ||
# Abstractions for operating system services | ||
# Copyright (c) 2023 Leorize | ||
# | ||
# Licensed under the terms of the MIT license which can be found in | ||
# the file "license.txt" included with this distribution. Alternatively, | ||
# the full text can be found at: https://spdx.org/licenses/MIT.html | ||
|
||
import ".."/strings | ||
|
||
import strsliceutils | ||
|
||
const | ||
SeparatorImpl = '\\' | ||
ValidSeparatorsImpl = {SeparatorImpl, '/'} | ||
|
||
template componentSlicesImpl() {.dirty.} = | ||
type | ||
State = enum | ||
Start | ||
MaybeRoot | ||
FoundPrefix | ||
DosDrivePrefix | ||
UncPrefix | ||
AtRoot | ||
PathElement | ||
|
||
var state = Start | ||
|
||
for slice in s.splitSlices(ValidSeparators): | ||
var | ||
stay = true | ||
slice = slice | ||
while stay: | ||
stay = false | ||
case state | ||
of Start: | ||
# Drive letter and maybe a path component | ||
if s.slice(slice).hasDosDrive: | ||
yield (Prefix, 0 .. 1) | ||
state = DosDrivePrefix | ||
if slice.len > 2: | ||
# This is a relative path (ie. C:abc) | ||
# Trim the drive letter and switch gear | ||
slice = slice.a + 2 .. slice.b | ||
state = PathElement | ||
stay = true | ||
|
||
# Single \ | ||
elif slice.len == 0: | ||
state = MaybeRoot | ||
|
||
else: | ||
state = PathElement | ||
stay = true | ||
|
||
of MaybeRoot: | ||
# Double \ | ||
if slice.len == 0: | ||
state = FoundPrefix | ||
else: | ||
yield (Root, 0 ..< 0) | ||
state = AtRoot | ||
stay = true | ||
|
||
of FoundPrefix: | ||
# Special prefix for NT and DOS paths | ||
if slice.len == 1 and s[slice.a] in {'.', '?'}: | ||
state = AtRoot | ||
yield (Prefix, 0 .. slice.b) | ||
yield (Root, 0 ..< 0) | ||
# UNC otherwise | ||
else: | ||
state = UncPrefix | ||
yield (Prefix, 0 .. slice.b) | ||
|
||
of DosDrivePrefix: | ||
# There is something after the DOS drive, this is a rooted path | ||
yield (Root, 0 ..< 0) | ||
state = AtRoot | ||
stay = true | ||
|
||
of UncPrefix: | ||
if slice.len > 0: | ||
state = AtRoot | ||
yield (Prefix, slice) | ||
yield (Root, 0 ..< 0) | ||
|
||
of AtRoot: | ||
if s.slice(slice) == "." or s.slice(slice) == "..": | ||
discard ". and .. at root is still root" | ||
elif slice.len > 0: | ||
state = PathElement | ||
yield (Element, slice) | ||
|
||
of PathElement: | ||
if slice.len > 0: | ||
if s.slice(slice) == "..": | ||
yield (PreviousDir, slice) | ||
elif s.slice(slice) != ".": | ||
yield (Element, slice) | ||
|
||
case state | ||
# Nothing after we found '\' or '\\' | ||
# Then it's just a root. | ||
of MaybeRoot, FoundPrefix: | ||
yield (Root, 0 ..< 0) | ||
# Incomplete UNC path | ||
# Cap it off with a root. | ||
of UncPrefix: | ||
yield (Root, 0 ..< 0) | ||
|
||
func isNotDos(p: Path | Nulless | openarray[char]): bool = | ||
## Returns whether `p` is not a DOS path. | ||
p.len > 1 and p.slice[0] == '\\' and p.slice[1] == '\\' | ||
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on i386 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
Check failure on line 115 in src/sys/private/paths_windows.nim GitHub Actions / windows on amd64 (Nim devel)
|
||
|
||
func hasDosDrive(p: Path | Nulless | openarray[char]): bool = | ||
## Returns whether `p` is prefixed with a DOS drive. | ||
p.len > 1 and p[1] == ":" | ||
|
||
func isIncompleteUnc(p: Path): bool = | ||
## Returns whether `p` is an UNC path without a share. | ||
if not p.isNotDos: | ||
return false | ||
|
||
for kind, slice in p.componentSlices: | ||
case kind | ||
of Prefix: | ||
if p.slice(slice) == r"\\." or p.slice(slice) == r"\\?": | ||
return false | ||
|
||
result = not result | ||
else: | ||
break | ||
|
||
template isAbsoluteImpl(): bool {.dirty.} = | ||
p.isNotDos() or (p.len > 2 and p.slice(1..2) == r":\") | ||
|
||
template joinImpl() {.dirty.} = | ||
# Temporary empty out the base if it's the current directory. | ||
# | ||
# It will be inserted for disambiguation as needed. | ||
if base == ".": | ||
base.string.setLen 0 | ||
|
||
var needTrailingSep = base.isIncompleteUnc | ||
for part in parts.items: | ||
for kind, slice in part.componentSlices: | ||
case kind | ||
of Root: | ||
discard "All should be relative to current base" | ||
of Prefix: | ||
if part.slice(slice) == r"\\." or part.slice(slice) == r"//.": | ||
discard "Skipped to avoid redundant current-directory symbols" | ||
elif part.slice(slice).isNotDos: | ||
# Treat these like a regular subfolder, for example: | ||
# | ||
# * `a` join `\\?\C:` => `a\?\C:` | ||
# * `.` join `\\?\C:` => `?\C:` | ||
# | ||
# At this point, only these can be found: | ||
# | ||
# * `\\?` | ||
# * `\\string without backslash` | ||
# | ||
# Skip the first two (back)slashes | ||
let slice = slice.a + 2 .. slice.b | ||
|
||
# If the result looks like it starts with a DOS drive and the path is empty | ||
if part.slice(slice).hasDosDrive and base.len == 0: | ||
# Add `.\` to disambiguates | ||
base.string.add r".\" | ||
|
||
base.string.add part.slice(slice) | ||
elif slice.len > 0: | ||
# The prefix is either a DOS drive or UNC share | ||
# | ||
# If the result looks like it starts with a DOS drive and the path is empty | ||
if part.slice(slice).hasDosDrive and base.len == 0: | ||
# Disambiguates with `.\` | ||
base.string.add r".\" | ||
|
||
base.string.add part.slice(slice) | ||
else: | ||
# If the result looks like it starts with a DOS drive and the path is empty | ||
if part.slice(slice).hasDosDrive and base.len == 0: | ||
# Add `.\` to disambiguate | ||
base.string.add r".\" | ||
|
||
# If the next position is not at the start of the path and there were | ||
# no separator at the end of the current path. | ||
elif base.len > 0 and base[^1] != Separator: | ||
# Insert a separator | ||
base.string.add Separator | ||
|
||
base.string.add part.slice(slice) | ||
|
||
if needTrailingSep and base[^1] != Separator: | ||
base.string.add Separator | ||
needTrailingSep = false | ||
|
||
# If the path is empty | ||
if base.len == 0: | ||
# Set it to "." | ||
base.string.add '.' | ||
|
||
template toPathImpl() {.dirty.} = | ||
result = Path: | ||
# Create a new buffer with the length of `s`. | ||
var path = newString(s.len) | ||
# Set the length to zero, which lets us keep the buffer. | ||
path.setLen 0 | ||
path | ||
|
||
var afterPrefix = false | ||
for kind, slice in s.componentSlices: | ||
case kind | ||
of Prefix: | ||
if s.slice(slice).isNotDos: | ||
# Skips the first two (back)slashes | ||
let slice = slice.a + 2 .. slice.b | ||
# Add our own | ||
path.add r"\\" | ||
# And the rest | ||
path.add s.slice(slice) | ||
elif s.slice(slice).hasDosDrive: | ||
# Normalize the drive by uppercasing it | ||
path.add: toAsciiUpper s[slice.a] | ||
path.add ':' | ||
else: | ||
# UNC share name | ||
path.add '\\' | ||
path.add s.slice(slice) | ||
|
||
afterPrefix = true | ||
of Root: | ||
path.add Separator | ||
|
||
afterPrefix = false | ||
else: | ||
# Add separator as needed | ||
if afterPrefix: | ||
discard "Don't add separator after a prefix to handle drive-relative paths" | ||
elif path.len > 0 and path[^1] != Separator: | ||
path.add Separator | ||
# Disambiguates an element that looked like a drive | ||
elif path.len == 0 and s.slice(slice).hasDosDrive: | ||
path.add r".\" | ||
|
||
path.add s.slice(slice) | ||
|
||
afterPrefix = false |