1
0

FilePathComponents.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // MARK: - API
  8. @available(System 0.0.2, *)
  9. extension FilePath {
  10. /// Represents a root of a file path.
  11. ///
  12. /// On Unix, a root is simply the directory separator `/`.
  13. ///
  14. /// On Windows, a root contains the entire path prefix up to and including
  15. /// the final separator.
  16. ///
  17. /// Examples:
  18. /// * Unix:
  19. /// * `/`
  20. /// * Windows:
  21. /// * `C:\`
  22. /// * `C:`
  23. /// * `\`
  24. /// * `\\server\share\`
  25. /// * `\\?\UNC\server\share\`
  26. /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\`
  27. @available(System 0.0.2, *)
  28. public struct Root: Sendable {
  29. internal var _path: FilePath
  30. internal var _rootEnd: SystemString.Index
  31. internal init(_ path: FilePath, rootEnd: SystemString.Index) {
  32. self._path = path
  33. self._rootEnd = rootEnd
  34. _invariantCheck()
  35. }
  36. // TODO: Definitely want a small form for this on Windows,
  37. // and intern "/" for Unix.
  38. }
  39. /// Represents an individual, non-root component of a file path.
  40. ///
  41. /// Components can be one of the special directory components (`.` or `..`)
  42. /// or a file or directory name. Components are never empty and never
  43. /// contain the directory separator.
  44. ///
  45. /// Example:
  46. ///
  47. /// var path: FilePath = "/tmp"
  48. /// let file: FilePath.Component = "foo.txt"
  49. /// file.kind == .regular // true
  50. /// file.extension // "txt"
  51. /// path.append(file) // path is "/tmp/foo.txt"
  52. @available(System 0.0.2, *)
  53. public struct Component: Sendable {
  54. internal var _path: FilePath
  55. internal var _range: Range<SystemString.Index>
  56. // TODO: Make a small-component form to save on ARC overhead when
  57. // extracted from a path, and especially to save on allocation overhead
  58. // when constructing one from a String literal.
  59. internal init<RE: RangeExpression>(_ path: FilePath, _ range: RE)
  60. where RE.Bound == SystemString.Index {
  61. self._path = path
  62. self._range = range.relative(to: path._storage)
  63. precondition(!self._range.isEmpty, "FilePath components cannot be empty")
  64. self._invariantCheck()
  65. }
  66. }
  67. }
  68. @available(System 0.0.2, *)
  69. extension FilePath.Component {
  70. /// Whether a component is a regular file or directory name, or a special
  71. /// directory `.` or `..`
  72. @frozen
  73. @available(System 0.0.2, *)
  74. public enum Kind: Sendable {
  75. /// The special directory `.`, representing the current directory.
  76. case currentDirectory
  77. /// The special directory `..`, representing the parent directory.
  78. case parentDirectory
  79. /// A file or directory name
  80. case regular
  81. }
  82. /// The kind of this component
  83. public var kind: Kind {
  84. if _path._isCurrentDirectory(_range) { return .currentDirectory }
  85. if _path._isParentDirectory(_range) { return .parentDirectory }
  86. return .regular
  87. }
  88. }
  89. @available(System 0.0.2, *)
  90. extension FilePath.Root {
  91. // TODO: Windows analysis APIs
  92. }
  93. // MARK: - Internals
  94. extension SystemString {
  95. // TODO: take insertLeadingSlash: Bool
  96. // TODO: turn into an insert operation with slide
  97. internal mutating func appendComponents<C: Collection>(
  98. components: C
  99. ) where C.Element == FilePath.Component {
  100. // TODO(perf): Consider pre-pass to count capacity, slide
  101. defer {
  102. _removeTrailingSeparator()
  103. FilePath(self)._invariantCheck()
  104. }
  105. for idx in components.indices {
  106. let component = components[idx]
  107. component._withSystemChars { self.append(contentsOf: $0) }
  108. self.append(platformSeparator)
  109. }
  110. }
  111. }
  112. // Unifying protocol for common functionality between roots, components,
  113. // and views onto SystemString and FilePath.
  114. internal protocol _StrSlice: _PlatformStringable, Hashable, Codable {
  115. var _storage: SystemString { get }
  116. var _range: Range<SystemString.Index> { get }
  117. init?(_ str: SystemString)
  118. func _invariantCheck()
  119. }
  120. extension _StrSlice {
  121. internal var _slice: Slice<SystemString> {
  122. Slice(base: _storage, bounds: _range)
  123. }
  124. internal func _withSystemChars<T>(
  125. _ f: (UnsafeBufferPointer<SystemChar>) throws -> T
  126. ) rethrows -> T {
  127. try _storage.withNullTerminatedSystemChars {
  128. try f(UnsafeBufferPointer(rebasing: $0[_range]))
  129. }
  130. }
  131. internal func _withCodeUnits<T>(
  132. _ f: (UnsafeBufferPointer<CInterop.PlatformUnicodeEncoding.CodeUnit>) throws -> T
  133. ) rethrows -> T {
  134. try _slice.withCodeUnits(f)
  135. }
  136. internal init?(_platformString s: UnsafePointer<CInterop.PlatformChar>) {
  137. self.init(SystemString(platformString: s))
  138. }
  139. internal func _withPlatformString<Result>(
  140. _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
  141. ) rethrows -> Result {
  142. try _slice.withPlatformString(body)
  143. }
  144. internal var _systemString: SystemString { SystemString(_slice) }
  145. }
  146. extension _StrSlice {
  147. public static func == (lhs: Self, rhs: Self) -> Bool {
  148. lhs._slice.elementsEqual(rhs._slice)
  149. }
  150. public func hash(into hasher: inout Hasher) {
  151. hasher.combine(_slice.count) // discriminator
  152. for element in _slice {
  153. hasher.combine(element)
  154. }
  155. }
  156. }
  157. internal protocol _PathSlice: _StrSlice {
  158. var _path: FilePath { get }
  159. }
  160. extension _PathSlice {
  161. internal var _storage: SystemString { _path._storage }
  162. }
  163. @available(System 0.0.2, *)
  164. extension FilePath.Component: _PathSlice {
  165. }
  166. @available(System 0.0.2, *)
  167. extension FilePath.Root: _PathSlice {
  168. internal var _range: Range<SystemString.Index> {
  169. (..<_rootEnd).relative(to: _path._storage)
  170. }
  171. }
  172. @available(System 0.0.1, *)
  173. extension FilePath: _PlatformStringable {
  174. func _withPlatformString<Result>(_ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result) rethrows -> Result {
  175. try _storage.withPlatformString(body)
  176. }
  177. init(_platformString: UnsafePointer<CInterop.PlatformChar>) {
  178. self.init(SystemString(platformString: _platformString))
  179. }
  180. }
  181. @available(System 0.0.2, *)
  182. extension FilePath.Component {
  183. // The index of the `.` denoting an extension
  184. internal func _extensionIndex() -> SystemString.Index? {
  185. guard kind == .regular,
  186. let idx = _slice.lastIndex(of: .dot),
  187. idx != _slice.startIndex
  188. else { return nil }
  189. return idx
  190. }
  191. internal func _extensionRange() -> Range<SystemString.Index>? {
  192. guard let idx = _extensionIndex() else { return nil }
  193. return _slice.index(after: idx) ..< _slice.endIndex
  194. }
  195. internal func _stemRange() -> Range<SystemString.Index> {
  196. _slice.startIndex ..< (_extensionIndex() ?? _slice.endIndex)
  197. }
  198. }
  199. internal func _makeExtension(_ ext: String) -> SystemString {
  200. var result = SystemString()
  201. result.append(.dot)
  202. result.append(contentsOf: ext.unicodeScalars.lazy.map(SystemChar.init))
  203. return result
  204. }
  205. @available(System 0.0.2, *)
  206. extension FilePath.Component {
  207. internal init?(_ str: SystemString) {
  208. // FIXME: explicit null root? Or something else?
  209. let path = FilePath(str)
  210. guard path.root == nil, path.components.count == 1 else {
  211. return nil
  212. }
  213. self = path.components.first!
  214. self._invariantCheck()
  215. }
  216. }
  217. @available(System 0.0.2, *)
  218. extension FilePath.Root {
  219. internal init?(_ str: SystemString) {
  220. // FIXME: explicit null root? Or something else?
  221. let path = FilePath(str)
  222. guard path.root != nil, path.components.isEmpty else {
  223. return nil
  224. }
  225. self = path.root!
  226. self._invariantCheck()
  227. }
  228. }
  229. // MARK: - Invariants
  230. @available(System 0.0.2, *)
  231. extension FilePath.Component {
  232. // TODO: ensure this all gets easily optimized away in release...
  233. internal func _invariantCheck() {
  234. #if DEBUG
  235. precondition(!_slice.isEmpty)
  236. precondition(_slice.last != .null)
  237. precondition(_slice.allSatisfy { !isSeparator($0) } )
  238. precondition(_path._relativeStart <= _slice.startIndex)
  239. #endif // DEBUG
  240. }
  241. }
  242. @available(System 0.0.2, *)
  243. extension FilePath.Root {
  244. internal func _invariantCheck() {
  245. #if DEBUG
  246. precondition(self._rootEnd > _path._storage.startIndex)
  247. // TODO: Windows root invariants
  248. #endif
  249. }
  250. }