|
| 1 | +/* |
| 2 | + This source file is part of the Swift System open source project |
| 3 | + |
| 4 | + Copyright (c) 2020 Apple Inc. and the Swift System project authors |
| 5 | + Licensed under Apache License v2.0 with Runtime Library Exception |
| 6 | + |
| 7 | + See https://swift.org/LICENSE.txt for license information |
| 8 | +*/ |
| 9 | + |
| 10 | +// MARK: - API |
| 11 | + |
| 12 | +extension FilePath { |
| 13 | + /// A bidirectional, range replaceable collection of the non-root components |
| 14 | + /// that make up a file path. |
| 15 | + /// |
| 16 | + /// ComponentView provides access to standard `BidirectionalCollection` |
| 17 | + /// algorithms for accessing components from the front or back, as well as |
| 18 | + /// standard `RangeReplaceableCollection` algorithms for modifying the |
| 19 | + /// file path using component or range of components granularity. |
| 20 | + /// |
| 21 | + /// Example: |
| 22 | + /// |
| 23 | + /// var path: FilePath = "/./home/./username/scripts/./tree" |
| 24 | + /// let scriptIdx = path.components.lastIndex(of: "scripts")! |
| 25 | + /// path.components.insert("bin", at: scriptIdx) |
| 26 | + /// // path is "/./home/./username/bin/scripts/./tree" |
| 27 | + /// |
| 28 | + /// path.components.removeAll { $0.kind == .currentDirectory } |
| 29 | + /// // path is "/home/username/bin/scripts/tree" |
| 30 | + /// |
| 31 | + public struct ComponentView { |
| 32 | + internal var _path: FilePath |
| 33 | + internal var _start: SystemString.Index |
| 34 | + |
| 35 | + internal init(_ path: FilePath) { |
| 36 | + self._path = path |
| 37 | + self._start = path._relativeStart |
| 38 | + _invariantCheck() |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + /// View the non-root components that make up this path. |
| 43 | + public var components: ComponentView { |
| 44 | + __consuming get { ComponentView(self) } |
| 45 | + _modify { |
| 46 | + // RRC's empty init means that we cann't guarantee that the yielded |
| 47 | + // view will restore our root. So copy it out first. |
| 48 | + // |
| 49 | + // TODO(perf): Small-form root (especially on Unix). Have Root |
| 50 | + // always copy out (not worth ref counting). Make sure that we're |
| 51 | + // not needlessly sliding values around or triggering a COW |
| 52 | + let rootStr = self.root?._systemString ?? SystemString() |
| 53 | + var comp = ComponentView(self) |
| 54 | + self = FilePath() |
| 55 | + defer { |
| 56 | + self = comp._path |
| 57 | + |
| 58 | + if !rootStr.isEmpty { |
| 59 | + if let r = self.root { |
| 60 | + // Roots can be forgotten but never altered |
| 61 | + assert(r._slice.elementsEqual(rootStr)) |
| 62 | + } |
| 63 | + // TODO: conditional on it having changed? |
| 64 | + self.root = Root(rootStr) |
| 65 | + } |
| 66 | + } |
| 67 | + yield &comp |
| 68 | + } |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) |
| 73 | +extension FilePath.ComponentView: BidirectionalCollection { |
| 74 | + public typealias Element = FilePath.Component |
| 75 | + public struct Index: Comparable, Hashable { |
| 76 | + internal typealias Storage = SystemString.Index |
| 77 | + |
| 78 | + internal var _storage: Storage |
| 79 | + |
| 80 | + public static func < (lhs: Self, rhs: Self) -> Bool { |
| 81 | + lhs._storage < rhs._storage |
| 82 | + } |
| 83 | + |
| 84 | + fileprivate init(_ idx: Storage) { |
| 85 | + self._storage = idx |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + public var startIndex: Index { Index(_start) } |
| 90 | + public var endIndex: Index { Index(_path._storage.endIndex) } |
| 91 | + |
| 92 | + public func index(after i: Index) -> Index { |
| 93 | + return Index(_path._parseComponent(startingAt: i._storage).nextStart) |
| 94 | + } |
| 95 | + |
| 96 | + public func index(before i: Index) -> Index { |
| 97 | + Index(_path._parseComponent(priorTo: i._storage).lowerBound) |
| 98 | + } |
| 99 | + |
| 100 | + public subscript(position: Index) -> FilePath.Component { |
| 101 | + let end = _path._parseComponent(startingAt: position._storage).componentEnd |
| 102 | + return FilePath.Component(_path, position._storage ..< end) |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +extension FilePath.ComponentView: RangeReplaceableCollection { |
| 107 | + public init() { |
| 108 | + // FIXME: but what about the root? |
| 109 | + self.init(FilePath()) |
| 110 | + } |
| 111 | + |
| 112 | + // TODO(perf): We probably want to have concrete overrides or generic |
| 113 | + // specializations taking FP.ComponentView and |
| 114 | + // FP.ComponentView.SubSequence because we |
| 115 | + // can just memcpy in those cases. We |
| 116 | + // probably want to do that for all RRC operations. |
| 117 | + |
| 118 | + public mutating func replaceSubrange<C>( |
| 119 | + _ subrange: Range<Index>, with newElements: C |
| 120 | + ) where C : Collection, Self.Element == C.Element { |
| 121 | + defer { |
| 122 | + _path._invariantCheck() |
| 123 | + _invariantCheck() |
| 124 | + } |
| 125 | + if isEmpty { |
| 126 | + _path = FilePath(root: _path.root, newElements) |
| 127 | + return |
| 128 | + } |
| 129 | + let range = subrange.lowerBound._storage ..< subrange.upperBound._storage |
| 130 | + if newElements.isEmpty { |
| 131 | + let fromEnd = subrange.upperBound == endIndex |
| 132 | + _path._storage.removeSubrange(range) |
| 133 | + if fromEnd { |
| 134 | + _path._removeTrailingSeparator() |
| 135 | + } |
| 136 | + return |
| 137 | + } |
| 138 | + |
| 139 | + // TODO(perf): Avoid extra allocation by sliding elements down and |
| 140 | + // filling in the bytes ourselves. |
| 141 | + |
| 142 | + // If we're inserting at the end, we need a leading separator. |
| 143 | + var str = SystemString() |
| 144 | + let atEnd = subrange.lowerBound == endIndex |
| 145 | + if atEnd { |
| 146 | + str.append(platformSeparator) |
| 147 | + } |
| 148 | + str.appendComponents(components: newElements) |
| 149 | + if !atEnd { |
| 150 | + str.append(platformSeparator) |
| 151 | + } |
| 152 | + _path._storage.replaceSubrange(range, with: str) |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) |
| 157 | +extension FilePath { |
| 158 | + /// Create a file path from a root and a collection of components. |
| 159 | + public init<C: Collection>( |
| 160 | + root: Root?, _ components: C |
| 161 | + ) where C.Element == Component { |
| 162 | + var str = root?._systemString ?? SystemString() |
| 163 | + str.appendComponents(components: components) |
| 164 | + self.init(str) |
| 165 | + } |
| 166 | + |
| 167 | + /// Create a file path from a root and any number of components. |
| 168 | + public init(root: Root?, components: Component...) { |
| 169 | + self.init(root: root, components) |
| 170 | + } |
| 171 | + |
| 172 | + /// Create a file path from an optional root and a slice of another path's |
| 173 | + /// components. |
| 174 | + public init(root: Root?, _ components: ComponentView.SubSequence) { |
| 175 | + var str = root?._systemString ?? SystemString() |
| 176 | + let (start, end) = |
| 177 | + (components.startIndex._storage, components.endIndex._storage) |
| 178 | + str.append(contentsOf: components.base._slice[start..<end]) |
| 179 | + self.init(str) |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +// MARK: - Internals |
| 184 | + |
| 185 | +extension FilePath.ComponentView: _PathSlice { |
| 186 | + internal var _range: Range<SystemString.Index> { |
| 187 | + _start ..< _path._storage.endIndex |
| 188 | + } |
| 189 | + |
| 190 | + internal init(_ str: SystemString) { |
| 191 | + fatalError("TODO: consider dropping proto req") |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +// MARK: - Invariants |
| 196 | + |
| 197 | +extension FilePath.ComponentView { |
| 198 | + internal func _invariantCheck() { |
| 199 | + #if DEBUG |
| 200 | + if isEmpty { |
| 201 | + precondition(_path.isEmpty == (_path.root == nil)) |
| 202 | + return |
| 203 | + } |
| 204 | + |
| 205 | + // If path has a root, |
| 206 | + if _path.root != nil { |
| 207 | + precondition(first!._slice.startIndex > _path._storage.startIndex) |
| 208 | + precondition(first!._slice.startIndex == _path._relativeStart) |
| 209 | + } |
| 210 | + |
| 211 | + self.forEach { $0._invariantCheck() } |
| 212 | + |
| 213 | + if let base = last { |
| 214 | + precondition(base._slice.endIndex == _path._storage.endIndex) |
| 215 | + } |
| 216 | + |
| 217 | + precondition(FilePath(root: _path.root, self) == _path) |
| 218 | + #endif // DEBUG |
| 219 | + } |
| 220 | +} |
0 commit comments