Created
March 14, 2019 18:51
-
-
Save shawnthroop/bac93e38838cf8dfe0205094d4dca943 to your computer and use it in GitHub Desktop.
A value type representing components separated by "/"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// swift 4.2 | |
import Foundation | |
/// A value type representing a path comprized of components separated by "/" | |
public struct Path: Hashable { | |
public typealias Component = String | |
private(set) public var rawValue: String = "" | |
init<S: Sequence>(components: S) where S.Element == Component { | |
append(components) | |
} | |
public mutating func append(component: Component) { | |
if component.isEmpty { | |
return | |
} | |
switch (rawValue.suffix(1), component.prefix(1)) { | |
case ("/", "/"), ("~", "~"): | |
rawValue.removeLast() | |
case ("/", _), (_, "/"), (_, "~"): | |
break | |
case (_, _): | |
rawValue.append("/") | |
} | |
rawValue.append(component) | |
} | |
public mutating func append(_ components: Component...) { | |
append(components) | |
} | |
public mutating func append<S: Sequence>(_ components: S) where S.Element == Component { | |
for component in components { | |
append(component: component) | |
} | |
} | |
public func byAppending(component: Component) -> Path { | |
return copy(with: { $0.append(component: component) }) | |
} | |
public func byAppending(_ components: Component...) -> Path { | |
return byAppending(components) | |
} | |
public func byAppending<S: Sequence>(_ components: S) -> Path where S.Element == Component { | |
return copy(with: { $0.append(components) }) | |
} | |
public var lastComponent: Component? { | |
return lastComponentRange.map { Component(rawValue[$0]) } | |
} | |
@discardableResult | |
public mutating func removeLastComponent() -> Component { | |
guard let componentRange = lastComponentRange else { | |
return "" | |
} | |
var range = componentRange | |
if let fsCharRange = rawValue.rangeOfCharacter(before: range.lowerBound), rawValue[fsCharRange] == "/" { | |
range = fsCharRange.lowerBound..<range.upperBound | |
} | |
rawValue.removeSubrange(range) | |
return Component(rawValue[componentRange]) | |
} | |
} | |
extension Path { | |
public init(url: URL) { | |
self.init(rawValue: url.path) | |
} | |
public init(rawValue: String) { | |
append(component: rawValue) | |
} | |
} | |
extension Path: ExpressibleByStringLiteral { | |
public typealias StringLiteralType = String | |
public init(stringLiteral value: StringLiteralType) { | |
self.init(rawValue: value) | |
} | |
} | |
extension Path: ExpressibleByArrayLiteral { | |
public typealias ArrayLiteralElement = Component | |
public init(arrayLiteral elements: ArrayLiteralElement...) { | |
self.init(components: elements) | |
} | |
} | |
extension Path: CustomStringConvertible, CustomPlaygroundDisplayConvertible { | |
public var description: String { | |
return rawValue.description | |
} | |
public var playgroundDescription: Any { | |
return "Path(\"\(rawValue)\")" | |
} | |
} | |
extension Path: Codable { | |
public func encode(to encoder: Encoder) throws { | |
try rawValue.encode(to: encoder) | |
} | |
public init(from decoder: Decoder) throws { | |
self.init(rawValue: try String(from: decoder)) | |
} | |
} | |
extension Path { | |
static func + (lhs: Path, rhs: Path) -> Path { | |
return lhs + rhs.rawValue | |
} | |
static func + (lhs: Path, rhs: Path.Component) -> Path { | |
return lhs.byAppending(component: rhs) | |
} | |
static func += (lhs: inout Path, rhs: Path) { | |
lhs += rhs.rawValue | |
} | |
static func += (lhs: inout Path, rhs: Path.Component) { | |
lhs.append(component: rhs) | |
} | |
} | |
private extension Path { | |
var lastComponentRange: Range<String.Index>? { | |
return rawValue.reversed().split(separator: "/", maxSplits: 1).first.map { $0.endIndex.base..<$0.startIndex.base } | |
} | |
func copy(with transform: (inout Path) throws -> Void) rethrows -> Path { | |
var result = self | |
try transform(&result) | |
return result | |
} | |
} | |
private extension String { | |
func rangeOfCharacter(before i: Index) -> Range<Index>? { | |
return index(i, offsetBy: -1, limitedBy: startIndex).map { $0..<i } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment