Skip to content

Commit 2167575

Browse files
committed
Add FilePath Syntactic Operations API
Add the proposed non-file-system-interacting FilePath operations, Component, Root, and ComponentView. Proposal: https://forums.swift.org/t/api-review-filepath-syntactic-apis-version-2/44197 This change includes API/ABI additions and deprecations.
1 parent 920ef30 commit 2167575

11 files changed

+3380
-11
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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

Comments
 (0)