Skip to content

Commit

Permalink
Finder is a iterable Sequence; .type -> .kind
Browse files Browse the repository at this point in the history
  • Loading branch information
mxcl committed Jul 24, 2019
1 parent dfad736 commit 0ef50df
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/jazzy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ custom_categories:
- name: Path
children:
- Path
- /(_:_:)
- Pathish
xcodebuild_arguments:
- UseModernBuildSystem=NO
output:
Expand Down
50 changes: 34 additions & 16 deletions Sources/Path+Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -40,6 +63,8 @@ public extension Pathish {
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
return Path(self)
}

//MARK: Filesystem Locking

/**
Applies the macOS filesystem “lock” attribute.
Expand Down Expand Up @@ -83,24 +108,17 @@ public extension Pathish {
#endif
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 {
enum Kind {
case file, symlink, directory
/// A filesystem entry’s kind, file, directory, symlink etc.
enum EntryType: CaseIterable {
/// The entry is a file.
case file
/// The entry is a symlink.
case symlink
/// The entry is a directory.
case directory
}
}
3 changes: 2 additions & 1 deletion Sources/Path+CommonDirectories.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Foundation

/// The `extension` that provides static properties that are common directories.
extension Path {
//MARK: Common Directories

/// Returns a `Path` containing `FileManager.default.currentDirectoryPath`.
public static var cwd: DynamicPath {
return .init(string: FileManager.default.currentDirectoryPath)
Expand Down
21 changes: 11 additions & 10 deletions Sources/Path+FileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import Glibc
#endif

public extension Pathish {

//MARK: File Management

/**
Copies a file.
Expand All @@ -25,7 +26,7 @@ public extension Pathish {
*/
@discardableResult
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)
}
#if os(Linux) && !swift(>=5.2) // check if fixed
Expand Down Expand Up @@ -61,11 +62,11 @@ public extension Pathish {
*/
@discardableResult
func copy<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
if into.kind == nil {
if into.type == nil {
try into.mkdir(.p)
}
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)
}
#if os(Linux) && !swift(>=5.2) // check if fixed
Expand Down Expand Up @@ -95,7 +96,7 @@ public extension Pathish {
*/
@discardableResult
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.moveItem(at: url, to: to.url)
Expand All @@ -119,13 +120,13 @@ public extension Pathish {
*/
@discardableResult
func move<P: Pathish>(into: P, overwrite: Bool = false) throws -> Path {
switch into.kind {
switch into.type {
case nil:
try into.mkdir(.p)
fallthrough
case .directory?:
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.moveItem(at: url, to: rv.url)
Expand All @@ -147,7 +148,7 @@ public extension Pathish {
*/
@inlinable
func delete() throws {
if kind != nil {
if type != nil {
try FileManager.default.removeItem(at: url)
}
}
Expand All @@ -159,7 +160,7 @@ public extension Pathish {
@inlinable
@discardableResult
func touch() throws -> Path {
if kind == nil {
if type == nil {
guard FileManager.default.createFile(atPath: string, contents: nil) else {
throw CocoaError.error(.fileWriteUnknown)
}
Expand Down Expand Up @@ -233,7 +234,7 @@ public extension Pathish {
*/
@discardableResult
func symlink<P: Pathish>(into dir: P) throws -> Path {
switch dir.kind {
switch dir.type {
case nil, .symlink?:
try dir.mkdir(.p)
fallthrough
Expand Down
132 changes: 95 additions & 37 deletions Sources/Path+ls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,104 @@ public extension Path {
class Finder {
fileprivate init(path: Path) {
self.path = path
self.enumerator = FileManager.default.enumerator(atPath: path.string)
}

/// The `path` find operations operate on.
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.
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`.
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 {
/// Multiple calls will configure the Finder for the final depth call only.
func maxDepth(_ maxDepth: Int) -> Path.Finder {
/// A max depth of `0` returns only the path we are searching, `1` is that directory’s listing.
func depth(max maxDepth: Int) -> Path.Finder {
#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
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
}

/// Multiple calls will configure the Finder with multiple kinds.
func kind(_ kind: Path.Kind) -> Path.Finder {
kinds = kinds ?? []
kinds!.insert(kind)
func type(_ type: Path.EntryType) -> Path.Finder {
_types = _types ?? []
_types!.insert(type)
return self
}

Expand All @@ -42,13 +113,6 @@ public extension Path.Finder {
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`
enum ControlFlow {
/// Stop enumerating this directory, return to the parent.
Expand All @@ -61,34 +125,23 @@ public extension Path.Finder {

/// Enumerate, one file at a time.
func execute(_ closure: (Path) throws -> ControlFlow) rethrows {
guard let finder = FileManager.default.enumerator(atPath: path.string) else {
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 }

while let path = next() {
switch try closure(path) {
case .skip:
finder.skipDescendants()
enumerator.skipDescendants()
case .abort:
return
case .continue:
break
continue
}
}
}
}

public extension Pathish {

//MARK: Directory Listing

/**
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.
Expand All @@ -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 {
/// Filters the list of entries to be a list of Paths that are directories. Symlinks to directories are not returned.
var directories: [Path] {
Expand All @@ -127,7 +180,12 @@ public extension Array where Element == Path {
/// - Note: symlinks that point to files that do not exist are *not* returned.
var files: [Path] {
return filter {
$0.exists && !$0.isDirectory
switch $0.type {
case .none, .directory?:
return false
case .file?, .symlink?:
return true
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Sources/Path->Bool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Darwin
#endif

public extension Pathish {

//MARK: Filesystem Properties

/**
Expand Down
Loading

0 comments on commit 0ef50df

Please sign in to comment.