/* This source file is part of the Swift.org open source project Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information See http://swift.org/CONTRIBUTORS.txt for Swift project authors */ #if os(Windows) import Foundation import WinSDK #endif #if os(Windows) private typealias PathImpl = WindowsPath #else private typealias PathImpl = UNIXPath #endif import protocol Foundation.CustomNSError import var Foundation.NSLocalizedDescriptionKey /// Represents an absolute file system path, independently of what (or whether /// anything at all) exists at that path in the file system at any given time. /// An absolute path always starts with a `/` character, and holds a normalized /// string representation. This normalization is strictly syntactic, and does /// not access the file system in any way. /// /// The absolute path string is normalized by: /// - Collapsing `..` path components /// - Removing `.` path components /// - Removing any trailing path separator /// - Removing any redundant path separators /// /// This string manipulation may change the meaning of a path if any of the /// path components are symbolic links on disk. However, the file system is /// never accessed in any way when initializing an AbsolutePath. /// /// Note that `~` (home directory resolution) is *not* done as part of path /// normalization, because it is normally the responsibility of the shell and /// not the program being invoked (e.g. when invoking `cd ~`, it is the shell /// that evaluates the tilde; the `cd` command receives an absolute path). public struct AbsolutePath: Hashable, Sendable { /// Check if the given name is a valid individual path component. /// /// This only checks with regard to the semantics enforced by `AbsolutePath` /// and `RelativePath`; particular file systems may have their own /// additional requirements. static func isValidComponent(_ name: String) -> Bool { return PathImpl.isValidComponent(name) } /// Private implementation details, shared with the RelativePath struct. private let _impl: PathImpl /// Private initializer when the backing storage is known. private init(_ impl: PathImpl) { _impl = impl } /// Initializes an AbsolutePath from a string that may be either absolute /// or relative; if relative, `basePath` is used as the anchor; if absolute, /// it is used as is, and in this case `basePath` is ignored. public init(validating str: String, relativeTo basePath: AbsolutePath) throws { if PathImpl(string: str).isAbsolute { try self.init(validating: str) } else { #if os(Windows) assert(!basePath.pathString.isEmpty) guard !str.isEmpty else { self.init(basePath._impl) return } let base: UnsafePointer = basePath.pathString.fileSystemRepresentation defer { base.deallocate() } let path: UnsafePointer = str.fileSystemRepresentation defer { path.deallocate() } var pwszResult: PWSTR! _ = String(cString: base).withCString(encodedAs: UTF16.self) { pwszBase in String(cString: path).withCString(encodedAs: UTF16.self) { pwszPath in PathAllocCombine(pwszBase, pwszPath, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &pwszResult) } } defer { LocalFree(pwszResult) } self.init(String(decodingCString: pwszResult, as: UTF16.self)) #else try self.init(basePath, RelativePath(validating: str)) #endif } } /// Initializes the AbsolutePath by concatenating a relative path to an /// existing absolute path, and renormalizing if necessary. public init(_ absPath: AbsolutePath, _ relPath: RelativePath) { self.init(absPath._impl.appending(relativePath: relPath._impl)) } /// Convenience initializer that appends a string to a relative path. public init(_ absPath: AbsolutePath, validating relStr: String) throws { try self.init(absPath, RelativePath(validating: relStr)) } /// Initializes the AbsolutePath from `absStr`, which must be an absolute /// path (i.e. it must begin with a path separator; this initializer does /// not interpret leading `~` characters as home directory specifiers). /// The input string will be normalized if needed, as described in the /// documentation for AbsolutePath. public init(validating path: String) throws { try self.init(PathImpl(validatingAbsolutePath: path)) } /// Directory component. An absolute path always has a non-empty directory /// component (the directory component of the root path is the root itself). public var dirname: String { return _impl.dirname } /// Last path component (including the suffix, if any). it is never empty. public var basename: String { return _impl.basename } /// Returns the basename without the extension. public var basenameWithoutExt: String { if let ext = self.extension { return String(basename.dropLast(ext.count + 1)) } return basename } /// Suffix (including leading `.` character) if any. Note that a basename /// that starts with a `.` character is not considered a suffix, nor is a /// trailing `.` character. public var suffix: String? { return _impl.suffix } /// Extension of the give path's basename. This follow same rules as /// suffix except that it doesn't include leading `.` character. public var `extension`: String? { return _impl.extension } /// Absolute path of parent directory. This always returns a path, because /// every directory has a parent (the parent directory of the root directory /// is considered to be the root directory itself). public var parentDirectory: AbsolutePath { return AbsolutePath(_impl.parentDirectory) } /// True if the path is the root directory. public var isRoot: Bool { return _impl.isRoot } /// Returns the absolute path with the relative path applied. public func appending(_ subpath: RelativePath) -> AbsolutePath { return AbsolutePath(self, subpath) } /// Returns the absolute path with an additional literal component appended. /// /// This method accepts pseudo-path like '.' or '..', but should not contain "/". public func appending(component: String) -> AbsolutePath { return AbsolutePath(_impl.appending(component: component)) } /// Returns the absolute path with additional literal components appended. /// /// This method should only be used in cases where the input is guaranteed /// to be a valid path component (i.e., it cannot be empty, contain a path /// separator, or be a pseudo-path like '.' or '..'). public func appending(components names: [String]) -> AbsolutePath { // FIXME: This doesn't seem a particularly efficient way to do this. return names.reduce(self, { path, name in path.appending(component: name) }) } public func appending(components names: String...) -> AbsolutePath { appending(components: names) } /// NOTE: We will most likely want to add other `appending()` methods, such /// as `appending(suffix:)`, and also perhaps `replacing()` methods, /// such as `replacing(suffix:)` or `replacing(basename:)` for some /// of the more common path operations. /// NOTE: We may want to consider adding operators such as `+` for appending /// a path component. /// NOTE: We will want to add a method to return the lowest common ancestor /// path. /// Root directory (whose string representation is just a path separator). public static let root = AbsolutePath(PathImpl.root) /// Normalized string representation (the normalization rules are described /// in the documentation of the initializer). This string is never empty. public var pathString: String { return _impl.string } /// Returns an array of strings that make up the path components of the /// absolute path. This is the same sequence of strings as the basenames /// of each successive path component, starting from the root. Therefore /// the first path component of an absolute path is always `/`. public var components: [String] { return _impl.components } } /// Represents a relative file system path. A relative path never starts with /// a `/` character, and holds a normalized string representation. As with /// AbsolutePath, the normalization is strictly syntactic, and does not access /// the file system in any way. /// /// The relative path string is normalized by: /// - Collapsing `..` path components that aren't at the beginning /// - Removing extraneous `.` path components /// - Removing any trailing path separator /// - Removing any redundant path separators /// - Replacing a completely empty path with a `.` /// /// This string manipulation may change the meaning of a path if any of the /// path components are symbolic links on disk. However, the file system is /// never accessed in any way when initializing a RelativePath. public struct RelativePath: Hashable, Sendable { /// Private implementation details, shared with the AbsolutePath struct. fileprivate let _impl: PathImpl /// Private initializer when the backing storage is known. private init(_ impl: PathImpl) { _impl = impl } /// Convenience initializer that verifies that the path is relative. public init(validating path: String) throws { try self.init(PathImpl(validatingRelativePath: path)) } /// Directory component. For a relative path without any path separators, /// this is the `.` string instead of the empty string. public var dirname: String { return _impl.dirname } /// Last path component (including the suffix, if any). It is never empty. public var basename: String { return _impl.basename } /// Returns the basename without the extension. public var basenameWithoutExt: String { if let ext = self.extension { return String(basename.dropLast(ext.count + 1)) } return basename } /// Suffix (including leading `.` character) if any. Note that a basename /// that starts with a `.` character is not considered a suffix, nor is a /// trailing `.` character. public var suffix: String? { return _impl.suffix } /// Extension of the give path's basename. This follow same rules as /// suffix except that it doesn't include leading `.` character. public var `extension`: String? { return _impl.extension } /// Normalized string representation (the normalization rules are described /// in the documentation of the initializer). This string is never empty. public var pathString: String { return _impl.string } /// Returns an array of strings that make up the path components of the /// relative path. This is the same sequence of strings as the basenames /// of each successive path component. Therefore the returned array of /// path components is never empty; even an empty path has a single path /// component: the `.` string. public var components: [String] { return _impl.components } /// Returns the relative path with the given relative path applied. public func appending(_ subpath: RelativePath) -> RelativePath { return RelativePath(_impl.appending(relativePath: subpath._impl)) } /// Returns the relative path with an additional literal component appended. /// /// This method accepts pseudo-path like '.' or '..', but should not contain "/". public func appending(component: String) -> RelativePath { return RelativePath(_impl.appending(component: component)) } /// Returns the relative path with additional literal components appended. /// /// This method should only be used in cases where the input is guaranteed /// to be a valid path component (i.e., it cannot be empty, contain a path /// separator, or be a pseudo-path like '.' or '..'). public func appending(components names: [String]) -> RelativePath { // FIXME: This doesn't seem a particularly efficient way to do this. return names.reduce(self, { path, name in path.appending(component: name) }) } public func appending(components names: String...) -> RelativePath { appending(components: names) } } extension AbsolutePath: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(pathString) } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() try self.init(validating: container.decode(String.self)) } } extension RelativePath: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(pathString) } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() try self.init(validating: container.decode(String.self)) } } // Make absolute paths Comparable. extension AbsolutePath: Comparable { public static func < (lhs: AbsolutePath, rhs: AbsolutePath) -> Bool { return lhs.pathString < rhs.pathString } } /// Make absolute paths CustomStringConvertible and CustomDebugStringConvertible. extension AbsolutePath: CustomStringConvertible, CustomDebugStringConvertible { public var description: String { return pathString } public var debugDescription: String { // FIXME: We should really be escaping backslashes and quotes here. return "" } } /// Make relative paths CustomStringConvertible and CustomDebugStringConvertible. extension RelativePath: CustomStringConvertible { public var description: String { return _impl.string } public var debugDescription: String { // FIXME: We should really be escaping backslashes and quotes here. return "" } } /// Private implementation shared between AbsolutePath and RelativePath. protocol Path: Hashable { /// Root directory. static var root: Self { get } /// Checks if a string is a valid component. static func isValidComponent(_ name: String) -> Bool /// Normalized string of the (absolute or relative) path. Never empty. var string: String { get } /// Returns whether the path is the root path. var isRoot: Bool { get } /// Returns whether the path is an absolute path. var isAbsolute: Bool { get } /// Returns the directory part of the stored path (relying on the fact that it has been normalized). Returns a /// string consisting of just `.` if there is no directory part (which is the case if and only if there is no path /// separator). var dirname: String { get } /// Returns the last past component. var basename: String { get } /// Returns the components of the path between each path separator. var components: [String] { get } /// Path of parent directory. This always returns a path, because every directory has a parent (the parent /// directory of the root directory is considered to be the root directory itself). var parentDirectory: Self { get } /// Creates a path from its normalized string representation. init(string: String) /// Creates a path from a string representation, validates that it is a valid absolute path and normalizes it. init(validatingAbsolutePath: String) throws /// Creates a path from a string representation, validates that it is a valid relative path and normalizes it. init(validatingRelativePath: String) throws /// Returns suffix with leading `.` if withDot is true otherwise without it. func suffix(withDot: Bool) -> String? /// Returns a new Path by appending the path component. func appending(component: String) -> Self /// Returns a path by concatenating a relative path and renormalizing if necessary. func appending(relativePath: Self) -> Self } extension Path { var suffix: String? { return suffix(withDot: true) } var `extension`: String? { return suffix(withDot: false) } } #if os(Windows) private struct WindowsPath: Path, Sendable { let string: String // NOTE: this is *NOT* a root path. It is a drive-relative path that needs // to be specified due to assumptions in the APIs. Use the platform // specific path separator as we should be normalizing the path normally. // This is required to make the `InMemoryFileSystem` correctly iterate // paths. static let root = Self(string: "\\") static func isValidComponent(_ name: String) -> Bool { return name != "" && name != "." && name != ".." && !name.contains("/") } static func isAbsolutePath(_ path: String) -> Bool { return !path.withCString(encodedAs: UTF16.self, PathIsRelativeW) } var dirname: String { let fsr: UnsafePointer = self.string.fileSystemRepresentation defer { fsr.deallocate() } let path: String = String(cString: fsr) return path.withCString(encodedAs: UTF16.self) { let data = UnsafeMutablePointer(mutating: $0) PathCchRemoveFileSpec(data, path.count) return String(decodingCString: data, as: UTF16.self) } } var isAbsolute: Bool { return Self.isAbsolutePath(self.string) } public var isRoot: Bool { return self.string.withCString(encodedAs: UTF16.self, PathCchIsRoot) } var basename: String { let path: String = self.string return path.withCString(encodedAs: UTF16.self) { PathStripPathW(UnsafeMutablePointer(mutating: $0)) return String(decodingCString: $0, as: UTF16.self) } } // FIXME: We should investigate if it would be more efficient to instead // return a path component iterator that does all its work lazily, moving // from one path separator to the next on-demand. // var components: [String] { let normalized: UnsafePointer = string.fileSystemRepresentation defer { normalized.deallocate() } return String(cString: normalized).components(separatedBy: "\\").filter { !$0.isEmpty } } var parentDirectory: Self { return self == .root ? self : Self(string: dirname) } init(string: String) { if string.first?.isASCII ?? false, string.first?.isLetter ?? false, string.first?.isLowercase ?? false, string.count > 1, string[string.index(string.startIndex, offsetBy: 1)] == ":" { self.string = "\(string.first!.uppercased())\(string.dropFirst(1))" } else { self.string = string } } private static func repr(_ path: String) -> String { guard !path.isEmpty else { return "" } let representation: UnsafePointer = path.fileSystemRepresentation defer { representation.deallocate() } return String(cString: representation) } init(validatingAbsolutePath path: String) throws { let realpath = Self.repr(path) if !Self.isAbsolutePath(realpath) { throw PathValidationError.invalidAbsolutePath(path) } self.init(string: realpath) } init(validatingRelativePath path: String) throws { if path.isEmpty || path == "." { self.init(string: ".") } else { let realpath: String = Self.repr(path) // Treat a relative path as an invalid relative path... if Self.isAbsolutePath(realpath) || realpath.first == "\\" { throw PathValidationError.invalidRelativePath(path) } self.init(string: realpath) } } func suffix(withDot: Bool) -> String? { return self.string.withCString(encodedAs: UTF16.self) { if let pointer = PathFindExtensionW($0) { let substring = String(decodingCString: pointer, as: UTF16.self) guard substring.length > 0 else { return nil } return withDot ? substring : String(substring.dropFirst(1)) } return nil } } func appending(component name: String) -> Self { var result: PWSTR? _ = string.withCString(encodedAs: UTF16.self) { root in name.withCString(encodedAs: UTF16.self) { path in PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) } } defer { LocalFree(result) } return Self(string: String(decodingCString: result!, as: UTF16.self)) } func appending(relativePath: Self) -> Self { var result: PWSTR? _ = string.withCString(encodedAs: UTF16.self) { root in relativePath.string.withCString(encodedAs: UTF16.self) { path in PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) } } defer { LocalFree(result) } return Self(string: String(decodingCString: result!, as: UTF16.self)) } } #else private struct UNIXPath: Path, Sendable { let string: String static let root = Self(string: "/") static func isValidComponent(_ name: String) -> Bool { return name != "" && name != "." && name != ".." && !name.contains("/") } var dirname: String { // FIXME: This method seems too complicated; it should be simplified, // if possible, and certainly optimized (using UTF8View). // Find the last path separator. guard let idx = string.lastIndex(of: "/") else { // No path separators, so the directory name is `.`. return "." } // Check if it's the only one in the string. if idx == string.startIndex { // Just one path separator, so the directory name is `/`. return "/" } // Otherwise, it's the string up to (but not including) the last path // separator. return String(string.prefix(upTo: idx)) } var isAbsolute: Bool { return string.hasPrefix("/") } var isRoot: Bool { return self == Self.root } var basename: String { // Find the last path separator. guard let idx = string.lastIndex(of: "/") else { // No path separators, so the basename is the whole string. return string } // Otherwise, it's the string from (but not including) the last path // separator. return String(string.suffix(from: string.index(after: idx))) } // FIXME: We should investigate if it would be more efficient to instead // return a path component iterator that does all its work lazily, moving // from one path separator to the next on-demand. // var components: [String] { // FIXME: This isn't particularly efficient; needs optimization, and // in fact, it might well be best to return a custom iterator so we // don't have to allocate everything up-front. It would be backed by // the path string and just return a slice at a time. let components = string.components(separatedBy: "/").filter({ !$0.isEmpty }) if string.hasPrefix("/") { return ["/"] + components } else { return components } } var parentDirectory: Self { return self == .root ? self : Self(string: dirname) } init(string: String) { self.string = string } init(normalizingAbsolutePath path: String) { precondition(path.first == "/", "Failure normalizing \(path), absolute paths should start with '/'") // At this point we expect to have a path separator as first character. assert(path.first == "/") // Fast path. if !mayNeedNormalization(absolute: path) { self.init(string: path) } // Split the character array into parts, folding components as we go. // As we do so, we count the number of characters we'll end up with in // the normalized string representation. var parts: [String] = [] var capacity = 0 for part in path.split(separator: "/") { switch part.count { case 0: // Ignore empty path components. continue case 1 where part.first == ".": // Ignore `.` path components. continue case 2 where part.first == "." && part.last == ".": // If there's a previous part, drop it; otherwise, do nothing. if let prev = parts.last { parts.removeLast() capacity -= prev.count } default: // Any other component gets appended. parts.append(String(part)) capacity += part.count } } capacity += max(parts.count, 1) // Create an output buffer using the capacity we've calculated. // FIXME: Determine the most efficient way to reassemble a string. var result = "" result.reserveCapacity(capacity) // Put the normalized parts back together again. var iter = parts.makeIterator() result.append("/") if let first = iter.next() { result.append(contentsOf: first) while let next = iter.next() { result.append("/") result.append(contentsOf: next) } } // Sanity-check the result (including the capacity we reserved). assert(!result.isEmpty, "unexpected empty string") assert(result.count == capacity, "count: " + "\(result.count), cap: \(capacity)") // Use the result as our stored string. self.init(string: result) } init(normalizingRelativePath path: String) { precondition(path.first != "/") // FIXME: Here we should also keep track of whether anything actually has // to be changed in the string, and if not, just return the existing one. // Split the character array into parts, folding components as we go. // As we do so, we count the number of characters we'll end up with in // the normalized string representation. var parts: [String] = [] var capacity = 0 for part in path.split(separator: "/") { switch part.count { case 0: // Ignore empty path components. continue case 1 where part.first == ".": // Ignore `.` path components. continue case 2 where part.first == "." && part.last == ".": // If at beginning, fall through to treat the `..` literally. guard let prev = parts.last else { fallthrough } // If previous component is anything other than `..`, drop it. if !(prev.count == 2 && prev.first == "." && prev.last == ".") { parts.removeLast() capacity -= prev.count continue } // Otherwise, fall through to treat the `..` literally. fallthrough default: // Any other component gets appended. parts.append(String(part)) capacity += part.count } } capacity += max(parts.count - 1, 0) // Create an output buffer using the capacity we've calculated. // FIXME: Determine the most efficient way to reassemble a string. var result = "" result.reserveCapacity(capacity) // Put the normalized parts back together again. var iter = parts.makeIterator() if let first = iter.next() { result.append(contentsOf: first) while let next = iter.next() { result.append("/") result.append(contentsOf: next) } } // Sanity-check the result (including the capacity we reserved). assert(result.count == capacity, "count: " + "\(result.count), cap: \(capacity)") // If the result is empty, return `.`, otherwise we return it as a string. self.init(string: result.isEmpty ? "." : result) } init(validatingAbsolutePath path: String) throws { switch path.first { case "/": self.init(normalizingAbsolutePath: path) case "~": throw PathValidationError.startsWithTilde(path) default: throw PathValidationError.invalidAbsolutePath(path) } } init(validatingRelativePath path: String) throws { switch path.first { case "/": throw PathValidationError.invalidRelativePath(path) default: self.init(normalizingRelativePath: path) } } func suffix(withDot: Bool) -> String? { // FIXME: This method seems too complicated; it should be simplified, // if possible, and certainly optimized (using UTF8View). // Find the last path separator, if any. let sIdx = string.lastIndex(of: "/") // Find the start of the basename. let bIdx = (sIdx != nil) ? string.index(after: sIdx!) : string.startIndex // Find the last `.` (if any), starting from the second character of // the basename (a leading `.` does not make the whole path component // a suffix). let fIdx = string.index(bIdx, offsetBy: 1, limitedBy: string.endIndex) ?? string.startIndex if let idx = string[fIdx...].lastIndex(of: ".") { // Unless it's just a `.` at the end, we have found a suffix. if string.distance(from: idx, to: string.endIndex) > 1 { let fromIndex = withDot ? idx : string.index(idx, offsetBy: 1) return String(string.suffix(from: fromIndex)) } else { return nil } } // If we get this far, there is no suffix. return nil } func appending(component name: String) -> Self { assert(!name.contains("/"), "\(name) is invalid path component") // Handle pseudo paths. switch name { case "", ".": return self case "..": return self.parentDirectory default: break } if self == Self.root { return Self(string: "/" + name) } else { return Self(string: string + "/" + name) } } func appending(relativePath: Self) -> Self { // Both paths are already normalized. The only case in which we have // to renormalize their concatenation is if the relative path starts // with a `..` path component. var newPathString = string if self != .root { newPathString.append("/") } let relativePathString = relativePath.string newPathString.append(relativePathString) // If the relative string starts with `.` or `..`, we need to normalize // the resulting string. // FIXME: We can actually optimize that case, since we know that the // normalization of a relative path can leave `..` path components at // the beginning of the path only. if relativePathString.hasPrefix(".") { if newPathString.hasPrefix("/") { return Self(normalizingAbsolutePath: newPathString) } else { return Self(normalizingRelativePath: newPathString) } } else { return Self(string: newPathString) } } } #endif /// Describes the way in which a path is invalid. public enum PathValidationError: Error { case startsWithTilde(String) case invalidAbsolutePath(String) case invalidRelativePath(String) } extension PathValidationError: CustomStringConvertible { public var description: String { switch self { case .startsWithTilde(let path): return "invalid absolute path '\(path)'; absolute path must begin with '/'" case .invalidAbsolutePath(let path): return "invalid absolute path '\(path)'" case .invalidRelativePath(let path): return "invalid relative path '\(path)'; relative path should not begin with '\(AbsolutePath.root.pathString)'" } } } extension AbsolutePath { /// Returns a relative path that, when concatenated to `base`, yields the /// callee path itself. If `base` is not an ancestor of the callee, the /// returned path will begin with one or more `..` path components. /// /// Because both paths are absolute, they always have a common ancestor /// (the root path, if nothing else). Therefore, any path can be made /// relative to any other path by using a sufficient number of `..` path /// components. /// /// This method is strictly syntactic and does not access the file system /// in any way. Therefore, it does not take symbolic links into account. public func relative(to base: AbsolutePath) -> RelativePath { let result: RelativePath // Split the two paths into their components. // FIXME: The is needs to be optimized to avoid unncessary copying. let pathComps = self.components let baseComps = base.components // It's common for the base to be an ancestor, so try that first. if pathComps.starts(with: baseComps) { // Special case, which is a plain path without `..` components. It // might be an empty path (when self and the base are equal). let relComps = pathComps.dropFirst(baseComps.count) #if os(Windows) let pathString = relComps.joined(separator: "\\") #else let pathString = relComps.joined(separator: "/") #endif do { result = try RelativePath(validating: pathString) } catch { preconditionFailure("invalid relative path computed from \(pathString)") } } else { // General case, in which we might well need `..` components to go // "up" before we can go "down" the directory tree. var newPathComps = ArraySlice(pathComps) var newBaseComps = ArraySlice(baseComps) while newPathComps.prefix(1) == newBaseComps.prefix(1) { // First component matches, so drop it. newPathComps = newPathComps.dropFirst() newBaseComps = newBaseComps.dropFirst() } // Now construct a path consisting of as many `..`s as are in the // `newBaseComps` followed by what remains in `newPathComps`. var relComps = Array(repeating: "..", count: newBaseComps.count) relComps.append(contentsOf: newPathComps) #if os(Windows) let pathString = relComps.joined(separator: "\\") #else let pathString = relComps.joined(separator: "/") #endif do { result = try RelativePath(validating: pathString) } catch { preconditionFailure("invalid relative path computed from \(pathString)") } } assert(AbsolutePath(base, result) == self) return result } /// Returns true if the path contains the given path. /// /// This method is strictly syntactic and does not access the file system /// in any way. @available(*, deprecated, renamed: "isDescendantOfOrEqual(to:)") public func contains(_ other: AbsolutePath) -> Bool { return isDescendantOfOrEqual(to: other) } /// Returns true if the path is an ancestor of the given path. /// /// This method is strictly syntactic and does not access the file system /// in any way. public func isAncestor(of descendant: AbsolutePath) -> Bool { return descendant.components.dropLast().starts(with: self.components) } /// Returns true if the path is an ancestor of or equal to the given path. /// /// This method is strictly syntactic and does not access the file system /// in any way. public func isAncestorOfOrEqual(to descendant: AbsolutePath) -> Bool { return descendant.components.starts(with: self.components) } /// Returns true if the path is a descendant of the given path. /// /// This method is strictly syntactic and does not access the file system /// in any way. public func isDescendant(of ancestor: AbsolutePath) -> Bool { return self.components.dropLast().starts(with: ancestor.components) } /// Returns true if the path is a descendant of or equal to the given path. /// /// This method is strictly syntactic and does not access the file system /// in any way. public func isDescendantOfOrEqual(to ancestor: AbsolutePath) -> Bool { return self.components.starts(with: ancestor.components) } } extension PathValidationError: CustomNSError { public var errorUserInfo: [String : Any] { return [NSLocalizedDescriptionKey: self.description] } } // FIXME: We should consider whether to merge the two `normalize()` functions. // The argument for doing so is that some of the code is repeated; the argument // against doing so is that some of the details are different, and since any // given path is either absolute or relative, it's wasteful to keep checking // for whether it's relative or absolute. Possibly we can do both by clever // use of generics that abstract away the differences. /// Fast check for if a string might need normalization. /// /// This assumes that paths containing dotfiles are rare: private func mayNeedNormalization(absolute string: String) -> Bool { var last = UInt8(ascii: "0") for c in string.utf8 { switch c { case UInt8(ascii: "/") where last == UInt8(ascii: "/"): return true case UInt8(ascii: ".") where last == UInt8(ascii: "/"): return true default: break } last = c } if last == UInt8(ascii: "/") { return true } return false } // MARK: - `AbsolutePath` backwards compatibility, delete after deprecation period. extension AbsolutePath { @_disfavoredOverload @available(*, deprecated, message: "use throwing `init(validating:)` variant instead") public init(_ absStr: String) { try! self.init(validating: absStr) } @_disfavoredOverload @available(*, deprecated, message: "use throwing `init(validating:relativeTo:)` variant instead") public init(_ str: String, relativeTo basePath: AbsolutePath) { try! self.init(validating: str, relativeTo: basePath) } @_disfavoredOverload @available(*, deprecated, message: "use throwing variant instead") public init(_ absPath: AbsolutePath, _ relStr: String) { try! self.init(absPath, validating: relStr) } } // MARK: - `AbsolutePath` backwards compatibility, delete after deprecation period. extension RelativePath { @_disfavoredOverload @available(*, deprecated, message: "use throwing variant instead") public init(_ string: String) { try! self.init(validating: string) } }