Finder is a iterable Sequence; .type -> .kind
This commit is contained in:
2
.github/jazzy.yml
vendored
2
.github/jazzy.yml
vendored
@@ -3,7 +3,7 @@ custom_categories:
|
|||||||
- name: Path
|
- name: Path
|
||||||
children:
|
children:
|
||||||
- Path
|
- Path
|
||||||
- /(_:_:)
|
- Pathish
|
||||||
xcodebuild_arguments:
|
xcodebuild_arguments:
|
||||||
- UseModernBuildSystem=NO
|
- UseModernBuildSystem=NO
|
||||||
output:
|
output:
|
||||||
|
|||||||
@@ -30,6 +30,29 @@ public extension Pathish {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type of the entry.
|
||||||
|
/// - SeeAlso: `Path.EntryType`
|
||||||
|
@available(*, deprecated, message: "- SeeAlso: Path.type")
|
||||||
|
var kind: Path.EntryType? {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of the entry.
|
||||||
|
/// - SeeAlso: `Path.EntryType`
|
||||||
|
var type: Path.EntryType? {
|
||||||
|
var buf = stat()
|
||||||
|
guard lstat(string, &buf) == 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if buf.st_mode & S_IFMT == S_IFLNK {
|
||||||
|
return .symlink
|
||||||
|
} else if buf.st_mode & S_IFMT == S_IFDIR {
|
||||||
|
return .directory
|
||||||
|
} else {
|
||||||
|
return .file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sets the file’s attributes using UNIX octal notation.
|
Sets the file’s attributes using UNIX octal notation.
|
||||||
|
|
||||||
@@ -40,6 +63,8 @@ public extension Pathish {
|
|||||||
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
|
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
|
||||||
return Path(self)
|
return Path(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: Filesystem Locking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Applies the macOS filesystem “lock” attribute.
|
Applies the macOS filesystem “lock” attribute.
|
||||||
@@ -83,24 +108,17 @@ public extension Pathish {
|
|||||||
#endif
|
#endif
|
||||||
return Path(self)
|
return Path(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var kind: Path.Kind? {
|
|
||||||
var buf = stat()
|
|
||||||
guard lstat(string, &buf) == 0 else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if buf.st_mode & S_IFMT == S_IFLNK {
|
|
||||||
return .symlink
|
|
||||||
} else if buf.st_mode & S_IFMT == S_IFDIR {
|
|
||||||
return .directory
|
|
||||||
} else {
|
|
||||||
return .file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `extension` that provides `Kind`.
|
||||||
public extension Path {
|
public extension Path {
|
||||||
enum Kind {
|
/// A filesystem entry’s kind, file, directory, symlink etc.
|
||||||
case file, symlink, directory
|
enum EntryType: CaseIterable {
|
||||||
|
/// The entry is a file.
|
||||||
|
case file
|
||||||
|
/// The entry is a symlink.
|
||||||
|
case symlink
|
||||||
|
/// The entry is a directory.
|
||||||
|
case directory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// The `extension` that provides static properties that are common directories.
|
||||||
extension Path {
|
extension Path {
|
||||||
//MARK: Common Directories
|
//MARK: Common Directories
|
||||||
|
|
||||||
/// Returns a `Path` containing `FileManager.default.currentDirectoryPath`.
|
/// Returns a `Path` containing `FileManager.default.currentDirectoryPath`.
|
||||||
public static var cwd: DynamicPath {
|
public static var cwd: DynamicPath {
|
||||||
return .init(string: FileManager.default.currentDirectoryPath)
|
return .init(string: FileManager.default.currentDirectoryPath)
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import Glibc
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public extension Pathish {
|
public extension Pathish {
|
||||||
|
|
||||||
//MARK: File Management
|
//MARK: File Management
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Copies a file.
|
Copies a file.
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func copy<P: Pathish>(to: P, overwrite: Bool = false) throws -> Path {
|
func copy<P: Pathish>(to: P, overwrite: Bool = false) throws -> Path {
|
||||||
if overwrite, let tokind = to.kind, tokind != .directory, kind != .directory {
|
if overwrite, let tokind = to.type, tokind != .directory, type != .directory {
|
||||||
try FileManager.default.removeItem(at: to.url)
|
try FileManager.default.removeItem(at: to.url)
|
||||||
}
|
}
|
||||||
#if os(Linux) && !swift(>=5.2) // check if fixed
|
#if os(Linux) && !swift(>=5.2) // check if fixed
|
||||||
@@ -61,11 +62,11 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func copy<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
|
func copy<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
|
||||||
if into.kind == nil {
|
if into.type == nil {
|
||||||
try into.mkdir(.p)
|
try into.mkdir(.p)
|
||||||
}
|
}
|
||||||
let rv = into/basename()
|
let rv = into/basename()
|
||||||
if overwrite, let kind = rv.kind, kind != .directory {
|
if overwrite, let kind = rv.type, kind != .directory {
|
||||||
try FileManager.default.removeItem(at: rv.url)
|
try FileManager.default.removeItem(at: rv.url)
|
||||||
}
|
}
|
||||||
#if os(Linux) && !swift(>=5.2) // check if fixed
|
#if os(Linux) && !swift(>=5.2) // check if fixed
|
||||||
@@ -95,7 +96,7 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func move<P: Pathish>(to: P, overwrite: Bool = false) throws -> Path {
|
func move<P: Pathish>(to: P, overwrite: Bool = false) throws -> Path {
|
||||||
if overwrite, let kind = to.kind, kind != .directory {
|
if overwrite, let kind = to.type, kind != .directory {
|
||||||
try FileManager.default.removeItem(at: to.url)
|
try FileManager.default.removeItem(at: to.url)
|
||||||
}
|
}
|
||||||
try FileManager.default.moveItem(at: url, to: to.url)
|
try FileManager.default.moveItem(at: url, to: to.url)
|
||||||
@@ -119,13 +120,13 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func move<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
|
func move<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
|
||||||
switch into.kind {
|
switch into.type {
|
||||||
case nil:
|
case nil:
|
||||||
try into.mkdir(.p)
|
try into.mkdir(.p)
|
||||||
fallthrough
|
fallthrough
|
||||||
case .directory?:
|
case .directory?:
|
||||||
let rv = into/basename()
|
let rv = into/basename()
|
||||||
if overwrite, let rvkind = rv.kind, rvkind != .directory {
|
if overwrite, let rvkind = rv.type, rvkind != .directory {
|
||||||
try FileManager.default.removeItem(at: rv.url)
|
try FileManager.default.removeItem(at: rv.url)
|
||||||
}
|
}
|
||||||
try FileManager.default.moveItem(at: url, to: rv.url)
|
try FileManager.default.moveItem(at: url, to: rv.url)
|
||||||
@@ -147,7 +148,7 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@inlinable
|
@inlinable
|
||||||
func delete() throws {
|
func delete() throws {
|
||||||
if kind != nil {
|
if type != nil {
|
||||||
try FileManager.default.removeItem(at: url)
|
try FileManager.default.removeItem(at: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ public extension Pathish {
|
|||||||
@inlinable
|
@inlinable
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func touch() throws -> Path {
|
func touch() throws -> Path {
|
||||||
if kind == nil {
|
if type == nil {
|
||||||
guard FileManager.default.createFile(atPath: string, contents: nil) else {
|
guard FileManager.default.createFile(atPath: string, contents: nil) else {
|
||||||
throw CocoaError.error(.fileWriteUnknown)
|
throw CocoaError.error(.fileWriteUnknown)
|
||||||
}
|
}
|
||||||
@@ -233,7 +234,7 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func symlink<P: Pathish>(into dir: P) throws -> Path {
|
func symlink<P: Pathish>(into dir: P) throws -> Path {
|
||||||
switch dir.kind {
|
switch dir.type {
|
||||||
case nil, .symlink?:
|
case nil, .symlink?:
|
||||||
try dir.mkdir(.p)
|
try dir.mkdir(.p)
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|||||||
@@ -5,33 +5,104 @@ public extension Path {
|
|||||||
class Finder {
|
class Finder {
|
||||||
fileprivate init(path: Path) {
|
fileprivate init(path: Path) {
|
||||||
self.path = path
|
self.path = path
|
||||||
|
self.enumerator = FileManager.default.enumerator(atPath: path.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `path` find operations operate on.
|
/// The `path` find operations operate on.
|
||||||
public let path: Path
|
public let path: Path
|
||||||
/// The maximum directory depth find operations will dip. Zero means no subdirectories.
|
|
||||||
fileprivate(set) public var maxDepth: Int? = nil
|
private let enumerator: FileManager.DirectoryEnumerator!
|
||||||
|
|
||||||
|
/// The range of directory depths for which the find operation will return entries.b
|
||||||
|
private(set) public var depth: ClosedRange<Int> = 1...Int.max
|
||||||
|
|
||||||
/// The kinds of filesystem entries find operations will return.
|
/// The kinds of filesystem entries find operations will return.
|
||||||
fileprivate(set) public var kinds: Set<Path.Kind>?
|
public var types: Set<EntryType> {
|
||||||
|
return _types ?? Set(EntryType.allCases)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _types: Set<EntryType>?
|
||||||
|
|
||||||
/// The file extensions find operations will return. Files *and* directories unless you filter for `kinds`.
|
/// The file extensions find operations will return. Files *and* directories unless you filter for `kinds`.
|
||||||
fileprivate(set) public var extensions: Set<String>?
|
private(set) public var extensions: Set<String>?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Path.Finder: Sequence, IteratorProtocol {
|
||||||
|
public func next() -> Path? {
|
||||||
|
guard let enumerator = enumerator else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
while let relativePath = enumerator.nextObject() as? String {
|
||||||
|
let path = self.path/relativePath
|
||||||
|
|
||||||
|
#if !os(Linux) || swift(>=5.0)
|
||||||
|
if enumerator.level > depth.upperBound {
|
||||||
|
enumerator.skipDescendants()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if enumerator.level < depth.lowerBound {
|
||||||
|
if path == self.path, depth.lowerBound == 0 {
|
||||||
|
return path
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if let type = path.type, !types.contains(type) { continue }
|
||||||
|
if let exts = extensions, !exts.contains(path.extension) { continue }
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias Element = Path
|
||||||
|
}
|
||||||
|
|
||||||
public extension Path.Finder {
|
public extension Path.Finder {
|
||||||
/// Multiple calls will configure the Finder for the final depth call only.
|
/// A max depth of `0` returns only the path we are searching, `1` is that directory’s listing.
|
||||||
func maxDepth(_ maxDepth: Int) -> Path.Finder {
|
func depth(max maxDepth: Int) -> Path.Finder {
|
||||||
#if os(Linux) && !swift(>=5.0)
|
#if os(Linux) && !swift(>=5.0)
|
||||||
fputs("warning: maxDepth not implemented for Swift < 5\n", stderr)
|
fputs("warning: depth not implemented for Swift < 5\n", stderr)
|
||||||
#endif
|
#endif
|
||||||
self.maxDepth = maxDepth
|
depth = Swift.min(maxDepth, depth.lowerBound)...maxDepth
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A min depth of `0` also returns the path we are searching, `1` is that directory’s listing. Default is `1` thus not returning ourself.
|
||||||
|
func depth(min minDepth: Int) -> Path.Finder {
|
||||||
|
#if os(Linux) && !swift(>=5.0)
|
||||||
|
fputs("warning: depth not implemented for Swift < 5\n", stderr)
|
||||||
|
#endif
|
||||||
|
depth = minDepth...Swift.max(depth.upperBound, minDepth)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A max depth of `0` returns only the path we are searching, `1` is that directory’s listing.
|
||||||
|
/// A min depth of `0` also returns the path we are searching, `1` is that directory’s listing. Default is `1` thus not returning ourself.
|
||||||
|
func depth(_ rng: Range<Int>) -> Path.Finder {
|
||||||
|
#if os(Linux) && !swift(>=5.0)
|
||||||
|
fputs("warning: depth not implemented for Swift < 5\n", stderr)
|
||||||
|
#endif
|
||||||
|
depth = rng.lowerBound...(rng.upperBound - 1)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A max depth of `0` returns only the path we are searching, `1` is that directory’s listing.
|
||||||
|
/// A min depth of `0` also returns the path we are searching, `1` is that directory’s listing. Default is `1` thus not returning ourself.
|
||||||
|
func depth(_ rng: ClosedRange<Int>) -> Path.Finder {
|
||||||
|
#if os(Linux) && !swift(>=5.0)
|
||||||
|
fputs("warning: depth not implemented for Swift < 5\n", stderr)
|
||||||
|
#endif
|
||||||
|
depth = rng
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multiple calls will configure the Finder with multiple kinds.
|
/// Multiple calls will configure the Finder with multiple kinds.
|
||||||
func kind(_ kind: Path.Kind) -> Path.Finder {
|
func type(_ type: Path.EntryType) -> Path.Finder {
|
||||||
kinds = kinds ?? []
|
_types = _types ?? []
|
||||||
kinds!.insert(kind)
|
_types!.insert(type)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +113,6 @@ public extension Path.Finder {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate and return all results, note that this may take a while since we are recursive.
|
|
||||||
func execute() -> [Path] {
|
|
||||||
var rv: [Path] = []
|
|
||||||
execute{ rv.append($0); return .continue }
|
|
||||||
return rv
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The return type for `Path.Finder`
|
/// The return type for `Path.Finder`
|
||||||
enum ControlFlow {
|
enum ControlFlow {
|
||||||
/// Stop enumerating this directory, return to the parent.
|
/// Stop enumerating this directory, return to the parent.
|
||||||
@@ -61,34 +125,23 @@ public extension Path.Finder {
|
|||||||
|
|
||||||
/// Enumerate, one file at a time.
|
/// Enumerate, one file at a time.
|
||||||
func execute(_ closure: (Path) throws -> ControlFlow) rethrows {
|
func execute(_ closure: (Path) throws -> ControlFlow) rethrows {
|
||||||
guard let finder = FileManager.default.enumerator(atPath: path.string) else {
|
while let path = next() {
|
||||||
fputs("warning: could not enumerate: \(path)\n", stderr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
while let relativePath = finder.nextObject() as? String {
|
|
||||||
#if !os(Linux) || swift(>=5.0)
|
|
||||||
if let maxDepth = maxDepth, finder.level > maxDepth {
|
|
||||||
finder.skipDescendants()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
let path = self.path/relativePath
|
|
||||||
if path == self.path { continue }
|
|
||||||
if let kinds = kinds, let kind = path.kind, !kinds.contains(kind) { continue }
|
|
||||||
if let exts = extensions, !exts.contains(path.extension) { continue }
|
|
||||||
|
|
||||||
switch try closure(path) {
|
switch try closure(path) {
|
||||||
case .skip:
|
case .skip:
|
||||||
finder.skipDescendants()
|
enumerator.skipDescendants()
|
||||||
case .abort:
|
case .abort:
|
||||||
return
|
return
|
||||||
case .continue:
|
case .continue:
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Pathish {
|
public extension Pathish {
|
||||||
|
|
||||||
|
//MARK: Directory Listing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Same as the `ls` command ∴ output is ”shallow” and unsorted.
|
Same as the `ls` command ∴ output is ”shallow” and unsorted.
|
||||||
- Note: as per `ls`, by default we do *not* return hidden files. Specify `.a` for hidden files.
|
- Note: as per `ls`, by default we do *not* return hidden files. Specify `.a` for hidden files.
|
||||||
@@ -114,7 +167,7 @@ public extension Pathish {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience functions for the arraies of `Path`
|
/// Convenience functions for the arrays of `Path`
|
||||||
public extension Array where Element == Path {
|
public extension Array where Element == Path {
|
||||||
/// Filters the list of entries to be a list of Paths that are directories. Symlinks to directories are not returned.
|
/// Filters the list of entries to be a list of Paths that are directories. Symlinks to directories are not returned.
|
||||||
var directories: [Path] {
|
var directories: [Path] {
|
||||||
@@ -127,7 +180,12 @@ public extension Array where Element == Path {
|
|||||||
/// - Note: symlinks that point to files that do not exist are *not* returned.
|
/// - Note: symlinks that point to files that do not exist are *not* returned.
|
||||||
var files: [Path] {
|
var files: [Path] {
|
||||||
return filter {
|
return filter {
|
||||||
$0.exists && !$0.isDirectory
|
switch $0.type {
|
||||||
|
case .none, .directory?:
|
||||||
|
return false
|
||||||
|
case .file?, .symlink?:
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Darwin
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public extension Pathish {
|
public extension Pathish {
|
||||||
|
|
||||||
//MARK: Filesystem Properties
|
//MARK: Filesystem Properties
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ public struct Path: Pathish {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension Pathish {
|
public extension Pathish {
|
||||||
|
//MARK: Filesystem Representation
|
||||||
|
|
||||||
/// Returns a `URL` representing this file path.
|
/// Returns a `URL` representing this file path.
|
||||||
var url: URL {
|
var url: URL {
|
||||||
return URL(fileURLWithPath: string)
|
return URL(fileURLWithPath: string)
|
||||||
@@ -201,14 +203,14 @@ public extension Pathish {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Splits the string representation on the directory separator.
|
Splits the string representation on the directory separator.
|
||||||
- Important: The first element is always "/" to be consistent with `NSString.pathComponents`.
|
- Important: `NSString.pathComponents` will always return an initial `/` in its array for absolute paths to indicate that the path was absolute, we don’t do this because we are *always* absolute paths.
|
||||||
*/
|
*/
|
||||||
@inlinable
|
@inlinable
|
||||||
var components: [String] {
|
var components: [String] {
|
||||||
return ["/"] + string.split(separator: "/").map(String.init)
|
return string.split(separator: "/").map(String.init)
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: Pathing
|
//MARK:- Pathing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Joins a path and a string to produce a new path.
|
Joins a path and a string to produce a new path.
|
||||||
@@ -405,7 +407,7 @@ private func join_<S>(prefix: String, pathComponents: S) -> String where S: Sequ
|
|||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A path that supports arbituary dot notation, eg. Path.root.usr.bin
|
/// A path that supports arbituary dot notation, eg. `Path.root.usr.bin`
|
||||||
@dynamicMemberLookup
|
@dynamicMemberLookup
|
||||||
public struct DynamicPath: Pathish {
|
public struct DynamicPath: Pathish {
|
||||||
/// The normalized string representation of the underlying filesystem path
|
/// The normalized string representation of the underlying filesystem path
|
||||||
@@ -417,7 +419,7 @@ public struct DynamicPath: Pathish {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a `Path` to a `DynamicPath`
|
/// Converts a `Path` to a `DynamicPath`
|
||||||
public init(_ path: Path) {
|
public init<P: Pathish>(_ path: P) {
|
||||||
string = path.string
|
string = path.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
|
|
||||||
/// A type that represents a filesystem path, if you conform your type
|
/// A type that represents a filesystem path, if you conform your type
|
||||||
/// to `Pathish` it is your responsibility to ensure the string is correctly normalized
|
/// to `Pathish` it is your responsibility to ensure the string is correctly normalized
|
||||||
public protocol Pathish: Hashable, Comparable {
|
public protocol Pathish: Hashable, Comparable {
|
||||||
/// The normalized string representation of the underlying filesystem path
|
/// The normalized string representation of the underlying filesystem path
|
||||||
var string: String { get }
|
var string: String { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Pathish {
|
|
||||||
/// Two `Path`s are equal if their strings are identical. Strings are normalized upon construction, yet
|
|
||||||
/// if the files are different symlinks to the same file the equality check will not succeed. Use `realpath`
|
|
||||||
/// in such circumstances.
|
|
||||||
static func ==<P: Pathish> (lhs: Self, rhs: P) -> Bool {
|
|
||||||
return lhs.string == rhs.string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,39 +2,50 @@ import XCTest
|
|||||||
import Path
|
import Path
|
||||||
|
|
||||||
extension PathTests {
|
extension PathTests {
|
||||||
func testFindMaxDepth0() throws {
|
func testFindMaxDepth1() throws {
|
||||||
#if !os(Linux) || swift(>=5)
|
|
||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
try tmpdir.a.touch()
|
try tmpdir.a.touch()
|
||||||
try tmpdir.b.touch()
|
try tmpdir.b.touch()
|
||||||
try tmpdir.c.mkdir().join("e").touch()
|
try tmpdir.c.mkdir().join("e").touch()
|
||||||
|
|
||||||
XCTAssertEqual(
|
do {
|
||||||
Set(tmpdir.find().maxDepth(0).execute()),
|
let finder = tmpdir.find().depth(max: 1)
|
||||||
Set([tmpdir.a, tmpdir.b, tmpdir.c].map(Path.init)))
|
XCTAssertEqual(finder.depth, 1...1)
|
||||||
|
#if !os(Linux) || swift(>=5)
|
||||||
|
XCTAssertEqual(Set(finder), Set([tmpdir.a, tmpdir.b, tmpdir.c].map(Path.init)))
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let finder = tmpdir.find().depth(max: 0)
|
||||||
|
XCTAssertEqual(finder.depth, 0...0)
|
||||||
|
#if !os(Linux) || swift(>=5)
|
||||||
|
XCTAssertEqual(Set(finder), Set())
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFindMaxDepth1() throws {
|
func testFindMaxDepth2() throws {
|
||||||
#if !os(Linux) || swift(>=5)
|
|
||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
try tmpdir.a.touch()
|
try tmpdir.a.touch()
|
||||||
try tmpdir.b.mkdir().join("c").touch()
|
try tmpdir.b.mkdir().join("c").touch()
|
||||||
try tmpdir.b.d.mkdir().join("e").touch()
|
try tmpdir.b.d.mkdir().join("e").touch()
|
||||||
|
|
||||||
#if !os(Linux)
|
do {
|
||||||
XCTAssertEqual(
|
let finder = tmpdir.find().depth(max: 2)
|
||||||
Set(tmpdir.find().maxDepth(1).execute()),
|
XCTAssertEqual(finder.depth, 1...2)
|
||||||
Set([tmpdir.a, tmpdir.b, tmpdir.b.c].map(Path.init)))
|
XCTAssertEqual(
|
||||||
#else
|
Set(finder),
|
||||||
// Linux behavior is different :-/
|
Set([tmpdir.a, tmpdir.b, tmpdir.b.d, tmpdir.b.c].map(Path.init)))
|
||||||
XCTAssertEqual(
|
}
|
||||||
Set(tmpdir.find().maxDepth(1).execute()),
|
do {
|
||||||
Set([tmpdir.a, tmpdir.b, tmpdir.b.d, tmpdir.b.c].map(Path.init)))
|
let finder = tmpdir.find().depth(max: 3)
|
||||||
#endif
|
XCTAssertEqual(finder.depth, 1...3)
|
||||||
|
XCTAssertEqual(
|
||||||
|
Set(finder),
|
||||||
|
Set([tmpdir.a, tmpdir.b, tmpdir.b.d, tmpdir.b.c, tmpdir.b.d.e].map(Path.init)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFindExtension() throws {
|
func testFindExtension() throws {
|
||||||
@@ -43,10 +54,10 @@ extension PathTests {
|
|||||||
try tmpdir.join("bar.txt").touch()
|
try tmpdir.join("bar.txt").touch()
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
Set(tmpdir.find().extension("json").execute()),
|
Set(tmpdir.find().extension("json")),
|
||||||
[tmpdir.join("foo.json")])
|
[tmpdir.join("foo.json")])
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
Set(tmpdir.find().extension("txt").extension("json").execute()),
|
Set(tmpdir.find().extension("txt").extension("json")),
|
||||||
[tmpdir.join("foo.json"), tmpdir.join("bar.txt")])
|
[tmpdir.join("foo.json"), tmpdir.join("bar.txt")])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,13 +68,13 @@ extension PathTests {
|
|||||||
try tmpdir.bar.touch()
|
try tmpdir.bar.touch()
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
Set(tmpdir.find().kind(.file).execute()),
|
Set(tmpdir.find().type(.file)),
|
||||||
[tmpdir.join("bar")])
|
[tmpdir.join("bar")])
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
Set(tmpdir.find().kind(.directory).execute()),
|
Set(tmpdir.find().type(.directory)),
|
||||||
[tmpdir.join("foo")])
|
[tmpdir.join("foo")])
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
Set(tmpdir.find().kind(.file).kind(.directory).execute()),
|
Set(tmpdir.find().type(.file).type(.directory)),
|
||||||
Set(["foo", "bar"].map(tmpdir.join)))
|
Set(["foo", "bar"].map(tmpdir.join)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ class PathTests: XCTestCase {
|
|||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
XCTAssertTrue(tmpdir.exists)
|
XCTAssertTrue(tmpdir.exists)
|
||||||
XCTAssertFalse(try tmpdir.bar.symlink(as: tmpdir.foo).exists)
|
XCTAssertFalse(try tmpdir.bar.symlink(as: tmpdir.foo).exists)
|
||||||
XCTAssertTrue(tmpdir.foo.kind == .symlink)
|
XCTAssertTrue(tmpdir.foo.type == .symlink)
|
||||||
XCTAssertTrue(try tmpdir.bar.touch().symlink(as: tmpdir.baz).exists)
|
XCTAssertTrue(try tmpdir.bar.touch().symlink(as: tmpdir.baz).exists)
|
||||||
XCTAssertTrue(tmpdir.bar.kind == .file)
|
XCTAssertTrue(tmpdir.bar.type == .file)
|
||||||
XCTAssertTrue(tmpdir.kind == .directory)
|
XCTAssertTrue(tmpdir.type == .directory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,18 +393,18 @@ class PathTests: XCTestCase {
|
|||||||
|
|
||||||
// regression test: can delete a symlink that points to a non-existent file
|
// regression test: can delete a symlink that points to a non-existent file
|
||||||
let bar5 = try tmpdir.bar4.symlink(as: tmpdir.bar5)
|
let bar5 = try tmpdir.bar4.symlink(as: tmpdir.bar5)
|
||||||
XCTAssertEqual(bar5.kind, .symlink)
|
XCTAssertEqual(bar5.type, .symlink)
|
||||||
XCTAssertFalse(bar5.exists)
|
XCTAssertFalse(bar5.exists)
|
||||||
XCTAssertNoThrow(try bar5.delete())
|
XCTAssertNoThrow(try bar5.delete())
|
||||||
XCTAssertEqual(bar5.kind, nil)
|
XCTAssertEqual(bar5.type, nil)
|
||||||
|
|
||||||
// test that deleting a symlink *only* deletes the symlink
|
// test that deleting a symlink *only* deletes the symlink
|
||||||
let bar7 = try tmpdir.bar6.touch().symlink(as: tmpdir.bar7)
|
let bar7 = try tmpdir.bar6.touch().symlink(as: tmpdir.bar7)
|
||||||
XCTAssertEqual(bar7.kind, .symlink)
|
XCTAssertEqual(bar7.type, .symlink)
|
||||||
XCTAssertTrue(bar7.exists)
|
XCTAssertTrue(bar7.exists)
|
||||||
XCTAssertNoThrow(try bar7.delete())
|
XCTAssertNoThrow(try bar7.delete())
|
||||||
XCTAssertEqual(bar7.kind, nil)
|
XCTAssertEqual(bar7.type, nil)
|
||||||
XCTAssertEqual(tmpdir.bar6.kind, .file)
|
XCTAssertEqual(tmpdir.bar6.type, .file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,8 +619,8 @@ class PathTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPathComponents() throws {
|
func testPathComponents() throws {
|
||||||
XCTAssertEqual(Path.root.foo.bar.components, ["/", "foo", "bar"])
|
XCTAssertEqual(Path.root.foo.bar.components, ["foo", "bar"])
|
||||||
XCTAssertEqual(Path.root.components, ["/"])
|
XCTAssertEqual(Path.root.components, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFlatMap() throws {
|
func testFlatMap() throws {
|
||||||
@@ -637,9 +637,9 @@ class PathTests: XCTestCase {
|
|||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
let foo = try tmpdir.foo.touch()
|
let foo = try tmpdir.foo.touch()
|
||||||
let bar = try foo.symlink(as: tmpdir.bar)
|
let bar = try foo.symlink(as: tmpdir.bar)
|
||||||
XCTAssertEqual(tmpdir.kind, .directory)
|
XCTAssertEqual(tmpdir.type, .directory)
|
||||||
XCTAssertEqual(foo.kind, .file)
|
XCTAssertEqual(foo.type, .file)
|
||||||
XCTAssertEqual(bar.kind, .symlink)
|
XCTAssertEqual(bar.type, .symlink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user