| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- /*
- This source file is part of the Swift System open source project
- Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors
- Licensed under Apache License v2.0 with Runtime Library Exception
- See https://swift.org/LICENSE.txt for license information
- */
- // MARK: - API
- @available(System 0.0.2, *)
- extension FilePath {
- /// Represents a root of a file path.
- ///
- /// On Unix, a root is simply the directory separator `/`.
- ///
- /// On Windows, a root contains the entire path prefix up to and including
- /// the final separator.
- ///
- /// Examples:
- /// * Unix:
- /// * `/`
- /// * Windows:
- /// * `C:\`
- /// * `C:`
- /// * `\`
- /// * `\\server\share\`
- /// * `\\?\UNC\server\share\`
- /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\`
- @available(System 0.0.2, *)
- public struct Root: Sendable {
- internal var _path: FilePath
- internal var _rootEnd: SystemString.Index
- internal init(_ path: FilePath, rootEnd: SystemString.Index) {
- self._path = path
- self._rootEnd = rootEnd
- _invariantCheck()
- }
- // TODO: Definitely want a small form for this on Windows,
- // and intern "/" for Unix.
- }
- /// Represents an individual, non-root component of a file path.
- ///
- /// Components can be one of the special directory components (`.` or `..`)
- /// or a file or directory name. Components are never empty and never
- /// contain the directory separator.
- ///
- /// Example:
- ///
- /// var path: FilePath = "/tmp"
- /// let file: FilePath.Component = "foo.txt"
- /// file.kind == .regular // true
- /// file.extension // "txt"
- /// path.append(file) // path is "/tmp/foo.txt"
- @available(System 0.0.2, *)
- public struct Component: Sendable {
- internal var _path: FilePath
- internal var _range: Range<SystemString.Index>
- // TODO: Make a small-component form to save on ARC overhead when
- // extracted from a path, and especially to save on allocation overhead
- // when constructing one from a String literal.
- internal init<RE: RangeExpression>(_ path: FilePath, _ range: RE)
- where RE.Bound == SystemString.Index {
- self._path = path
- self._range = range.relative(to: path._storage)
- precondition(!self._range.isEmpty, "FilePath components cannot be empty")
- self._invariantCheck()
- }
- }
- }
- @available(System 0.0.2, *)
- extension FilePath.Component {
- /// Whether a component is a regular file or directory name, or a special
- /// directory `.` or `..`
- @frozen
- @available(System 0.0.2, *)
- public enum Kind: Sendable {
- /// The special directory `.`, representing the current directory.
- case currentDirectory
- /// The special directory `..`, representing the parent directory.
- case parentDirectory
- /// A file or directory name
- case regular
- }
- /// The kind of this component
- public var kind: Kind {
- if _path._isCurrentDirectory(_range) { return .currentDirectory }
- if _path._isParentDirectory(_range) { return .parentDirectory }
- return .regular
- }
- }
- @available(System 0.0.2, *)
- extension FilePath.Root {
- // TODO: Windows analysis APIs
- }
- // MARK: - Internals
- extension SystemString {
- // TODO: take insertLeadingSlash: Bool
- // TODO: turn into an insert operation with slide
- internal mutating func appendComponents<C: Collection>(
- components: C
- ) where C.Element == FilePath.Component {
- // TODO(perf): Consider pre-pass to count capacity, slide
- defer {
- _removeTrailingSeparator()
- FilePath(self)._invariantCheck()
- }
- for idx in components.indices {
- let component = components[idx]
- component._withSystemChars { self.append(contentsOf: $0) }
- self.append(platformSeparator)
- }
- }
- }
- // Unifying protocol for common functionality between roots, components,
- // and views onto SystemString and FilePath.
- internal protocol _StrSlice: _PlatformStringable, Hashable, Codable {
- var _storage: SystemString { get }
- var _range: Range<SystemString.Index> { get }
- init?(_ str: SystemString)
- func _invariantCheck()
- }
- extension _StrSlice {
- internal var _slice: Slice<SystemString> {
- Slice(base: _storage, bounds: _range)
- }
- internal func _withSystemChars<T>(
- _ f: (UnsafeBufferPointer<SystemChar>) throws -> T
- ) rethrows -> T {
- try _storage.withNullTerminatedSystemChars {
- try f(UnsafeBufferPointer(rebasing: $0[_range]))
- }
- }
- internal func _withCodeUnits<T>(
- _ f: (UnsafeBufferPointer<CInterop.PlatformUnicodeEncoding.CodeUnit>) throws -> T
- ) rethrows -> T {
- try _slice.withCodeUnits(f)
- }
- internal init?(_platformString s: UnsafePointer<CInterop.PlatformChar>) {
- self.init(SystemString(platformString: s))
- }
- internal func _withPlatformString<Result>(
- _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
- ) rethrows -> Result {
- try _slice.withPlatformString(body)
- }
- internal var _systemString: SystemString { SystemString(_slice) }
- }
- extension _StrSlice {
- public static func == (lhs: Self, rhs: Self) -> Bool {
- lhs._slice.elementsEqual(rhs._slice)
- }
- public func hash(into hasher: inout Hasher) {
- hasher.combine(_slice.count) // discriminator
- for element in _slice {
- hasher.combine(element)
- }
- }
- }
- internal protocol _PathSlice: _StrSlice {
- var _path: FilePath { get }
- }
- extension _PathSlice {
- internal var _storage: SystemString { _path._storage }
- }
- @available(System 0.0.2, *)
- extension FilePath.Component: _PathSlice {
- }
- @available(System 0.0.2, *)
- extension FilePath.Root: _PathSlice {
- internal var _range: Range<SystemString.Index> {
- (..<_rootEnd).relative(to: _path._storage)
- }
- }
- @available(System 0.0.1, *)
- extension FilePath: _PlatformStringable {
- func _withPlatformString<Result>(_ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result) rethrows -> Result {
- try _storage.withPlatformString(body)
- }
- init(_platformString: UnsafePointer<CInterop.PlatformChar>) {
- self.init(SystemString(platformString: _platformString))
- }
- }
- @available(System 0.0.2, *)
- extension FilePath.Component {
- // The index of the `.` denoting an extension
- internal func _extensionIndex() -> SystemString.Index? {
- guard kind == .regular,
- let idx = _slice.lastIndex(of: .dot),
- idx != _slice.startIndex
- else { return nil }
- return idx
- }
- internal func _extensionRange() -> Range<SystemString.Index>? {
- guard let idx = _extensionIndex() else { return nil }
- return _slice.index(after: idx) ..< _slice.endIndex
- }
- internal func _stemRange() -> Range<SystemString.Index> {
- _slice.startIndex ..< (_extensionIndex() ?? _slice.endIndex)
- }
- }
- internal func _makeExtension(_ ext: String) -> SystemString {
- var result = SystemString()
- result.append(.dot)
- result.append(contentsOf: ext.unicodeScalars.lazy.map(SystemChar.init))
- return result
- }
- @available(System 0.0.2, *)
- extension FilePath.Component {
- internal init?(_ str: SystemString) {
- // FIXME: explicit null root? Or something else?
- let path = FilePath(str)
- guard path.root == nil, path.components.count == 1 else {
- return nil
- }
- self = path.components.first!
- self._invariantCheck()
- }
- }
- @available(System 0.0.2, *)
- extension FilePath.Root {
- internal init?(_ str: SystemString) {
- // FIXME: explicit null root? Or something else?
- let path = FilePath(str)
- guard path.root != nil, path.components.isEmpty else {
- return nil
- }
- self = path.root!
- self._invariantCheck()
- }
- }
- // MARK: - Invariants
- @available(System 0.0.2, *)
- extension FilePath.Component {
- // TODO: ensure this all gets easily optimized away in release...
- internal func _invariantCheck() {
- #if DEBUG
- precondition(!_slice.isEmpty)
- precondition(_slice.last != .null)
- precondition(_slice.allSatisfy { !isSeparator($0) } )
- precondition(_path._relativeStart <= _slice.startIndex)
- #endif // DEBUG
- }
- }
- @available(System 0.0.2, *)
- extension FilePath.Root {
- internal func _invariantCheck() {
- #if DEBUG
- precondition(self._rootEnd > _path._storage.startIndex)
- // TODO: Windows root invariants
- #endif
- }
- }
|