forked from yonaskolb/XcodeGen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPathExtensions.swift
More file actions
84 lines (75 loc) · 3.65 KB
/
PathExtensions.swift
File metadata and controls
84 lines (75 loc) · 3.65 KB
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
import Foundation
import PathKit
extension Path {
/// Returns a Path without any inner parent directory references.
///
/// Similar to `NSString.standardizingPath`, but works with relative paths.
///
/// ### Examples
/// - `a/b/../c` simplifies to `a/c`
/// - `../a/b` simplifies to `../a/b`
/// - `a/../../c` simplifies to `../c`
public func simplifyingParentDirectoryReferences() -> Path {
if !string.contains("..") { // Skip simplifying if its already simple
var string = self.string
while string.hasSuffix(Path.separator) { // Remove all trailing path separators
string.removeLast()
}
return Path(String(string))
}
return normalize().components.reduce(Path(), +)
}
/// Returns the relative path necessary to go from `base` to `self`.
///
/// Both paths must be absolute or relative paths.
/// - throws: Throws an error when the path types do not match, or when `base` has so many parent path components
/// that it refers to an unknown parent directory.
public func relativePath(from base: Path) throws -> Path {
enum PathArgumentError: Error {
/// Can't back out of an unknown parent directory
case unknownParentDirectory
/// It's impossible to determine the path between an absolute and a relative path
case unmatchedAbsolutePath
}
func pathComponents(for path: ArraySlice<String>, relativeTo base: ArraySlice<String>, memo: [String]) throws -> [String] {
switch (base.first, path.first) {
// Base case: Paths are equivalent
case (.none, .none):
return memo
// No path to backtrack from
case (.none, .some(let rhs)):
guard rhs != "." else {
// Skip . instead of appending it
return try pathComponents(for: path.dropFirst(), relativeTo: base, memo: memo)
}
return try pathComponents(for: path.dropFirst(), relativeTo: base, memo: memo + [rhs])
// Both sides have a common parent
case (.some(let lhs), .some(let rhs)) where memo.isEmpty && lhs == rhs:
return try pathComponents(for: path.dropFirst(), relativeTo: base.dropFirst(), memo: memo)
// `base` has a path to back out of
case (.some(let lhs), _):
guard lhs != ".." else {
throw PathArgumentError.unknownParentDirectory
}
guard lhs != "." else {
// Skip . instead of resolving it to ..
return try pathComponents(for: path, relativeTo: base.dropFirst(), memo: memo)
}
return try pathComponents(for: path, relativeTo: base.dropFirst(), memo: memo + [".."])
}
}
guard isAbsolute && base.isAbsolute || !isAbsolute && !base.isAbsolute else {
throw PathArgumentError.unmatchedAbsolutePath
}
return Path(components: try pathComponents(for: ArraySlice(simplifyingParentDirectoryReferences().components),
relativeTo: ArraySlice(base.simplifyingParentDirectoryReferences().components),
memo: []))
}
/// Returns whether `self` is a strict parent of `child`.
///
/// Both paths must be asbolute or relative paths.
public func isParent(of child: Path) throws -> Bool {
let relativePath = try child.relativePath(from: self)
return relativePath.components.allSatisfy { $0 != ".." }
}
}