CommandLineToolkit
113 строк · 4.0 Кб
1import Foundation
2
3public final class AbsolutePath:
4Path,
5Codable,
6Hashable,
7Comparable,
8ExpressibleByStringLiteral,
9ExpressibleByArrayLiteral
10{
11// MARK: - Static interface
12
13public static let root = AbsolutePath(components: [String]())
14public static let home = AbsolutePath(NSHomeDirectory())
15public static let temp = AbsolutePath(NSTemporaryDirectory())
16
17/// Returns an `AbsolutePath` only if `string` has a value that looks like an absolute path - begins with `/`.
18/// - Parameter string: String representation of absolute path.
19/// - Throws: Error when `string` doesn't seem to be an absolute path.
20/// - Returns:`AbsolutePath` with path parsed from `string`.
21public static func validating(string: String) throws -> AbsolutePath {
22struct ValidationError: Error, CustomStringConvertible {
23let string: String
24var description: String { "String '\(string)' does not appear to be an absolute path" }
25}
26guard isAbsolute(path: string) else { throw ValidationError(string: string) }
27return AbsolutePath(string)
28}
29
30public static func isAbsolute(path: String) -> Bool {
31path.hasPrefix("/")
32}
33
34// MARK: - Instance members
35
36public let components: [String] // source value
37public let pathString: String // precomputed value
38
39// MARK: - Initializers members
40
41public init<S: StringProtocol>(components: [S]) {
42self.components = StringPathParsing.slashSeparatedComponents(paths: components)
43self.pathString = Self.pathString(components: self.components)
44}
45
46// MARK: - Instance interface
47
48public var isRoot: Bool {
49components.isEmpty
50}
51
52public var fileUrl: URL {
53return URL(fileURLWithPath: pathString)
54}
55
56public var standardized: AbsolutePath {
57AbsolutePath(fileUrl.standardized)
58}
59
60/// Returns true if current path is a child of given anchor path.
61/// Examples:
62/// `/path/to/something` is subpath of `/path/to`.
63/// `/path/to/something` is NOT subpath of `/path/to/something`.
64/// `/path/of/something` is NOT subpath of `/path/to/`.
65public func isSubpathOf(anchorPath: AbsolutePath) -> Bool {
66guard components.count > anchorPath.components.count else {
67return false
68}
69return zip(anchorPath.components, components.prefix(upTo: anchorPath.components.count)).allSatisfy { lhs, rhs in
70lhs == rhs
71}
72}
73
74/// Finds a `RelativePath` for this instance and a given anchor path.
75public func relativePath(anchorPath: AbsolutePath) -> RelativePath {
76let pathComponents = components
77let anchorComponents = anchorPath.components
78
79var componentsInCommon = 0
80for (pathComponent, anchorComponent) in zip(pathComponents, anchorComponents) {
81if pathComponent != anchorComponent {
82break
83}
84componentsInCommon += 1
85}
86
87let numberOfParentComponents = anchorComponents.count - componentsInCommon
88let numberOfPathComponents = pathComponents.count - componentsInCommon
89
90var relativeComponents = [String]()
91relativeComponents.reserveCapacity(numberOfParentComponents + numberOfPathComponents)
92for _ in 0..<numberOfParentComponents {
93relativeComponents.append("..")
94}
95relativeComponents.append(contentsOf: pathComponents[componentsInCommon..<pathComponents.count])
96
97return RelativePath(components: relativeComponents)
98}
99
100public static func +(lhs: AbsolutePath, rhs: RelativePath) -> AbsolutePath {
101return AbsolutePath(components: lhs.components + rhs.components)
102}
103
104// MARK: - Conformance to `ExpressibleByStringLiteral`
105
106public typealias StringLiteralType = String
107
108// MARK: - Private
109
110private static func pathString<S: StringProtocol>(components: [S]) -> String {
111"/" + components.joined(separator: "/")
112}
113}
114