From 99b948f9c18385990b100a7ca747a2b152a1eafc Mon Sep 17 00:00:00 2001 From: Max Howell Date: Sat, 26 Jan 2019 18:00:16 +0000 Subject: [PATCH 1/3] Minor documentation fixes [ci skip] --- README.md | 2 +- Sources/Path+CommonDirectories.swift | 4 ++-- Sources/Path+FileManager.swift | 5 ++--- Sources/Path+ls.swift | 24 +++++++++++++++++++++--- Sources/Path.swift | 28 +++++----------------------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index c23e01c..54e85bf 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ print(bar) // => /bar print(bar.isFile) // => true // careful API considerations so as to avoid common bugs -let foo = try Path.root.join("foo").copy(into: Path.root.mkdir("bar")) +let foo = try Path.root.join("foo").copy(into: Path.root.join("bar").mkdir()) print(foo) // => /bar/foo print(foo.isFile) // => true diff --git a/Sources/Path+CommonDirectories.swift b/Sources/Path+CommonDirectories.swift index 800cd8f..fb2ddca 100644 --- a/Sources/Path+CommonDirectories.swift +++ b/Sources/Path+CommonDirectories.swift @@ -3,7 +3,7 @@ import Foundation extension Path { //MARK: Common Directories - /// Returns a `Path` containing ``FileManager.default.currentDirectoryPath`. + /// Returns a `Path` containing `FileManager.default.currentDirectoryPath`. public static var cwd: Path { return Path(string: FileManager.default.currentDirectoryPath) } @@ -74,7 +74,7 @@ extension Path { /** The root for cache files. - - Note: On Linux this is 'XDG_CACHE_HOME'. + - Note: On Linux this is `XDG_CACHE_HOME`. - Note: You should create a subdirectory before creating any files. */ public static var caches: Path { diff --git a/Sources/Path+FileManager.swift b/Sources/Path+FileManager.swift index ba46eae..20e39be 100644 --- a/Sources/Path+FileManager.swift +++ b/Sources/Path+FileManager.swift @@ -147,7 +147,6 @@ public extension Path { /** Creates the directory at this path. - - Note: Does not create any intermediary directories. - Parameter options: Specify `mkdir(.p)` to create intermediary directories. - Note: We do not error if the directory already exists (even without `.p`) because *Path.swift* noops if the desired end result preexists. @@ -174,8 +173,8 @@ public extension Path { } } -/// Options for `Path.mkdir` +/// Options for `Path.mkdir(_:)` public enum MakeDirectoryOptions { - /// Creates intermediary directories. Works the same as mkdir -p. + /// Creates intermediary directories; works the same as `mkdir -p`. case p } diff --git a/Sources/Path+ls.swift b/Sources/Path+ls.swift index 8ff2fab..a65a990 100644 --- a/Sources/Path+ls.swift +++ b/Sources/Path+ls.swift @@ -1,8 +1,26 @@ import Foundation -public extension Path { +/** + A file entry from a directory listing. + - SeeAlso: `ls()` +*/ +public struct Entry { + /// The kind of this directory entry. + public enum Kind { + /// The path is a file. + case file + /// The path is a directory. + case directory + } + /// The kind of this entry. + public let kind: Kind + /// The path of this entry. + public let path: Path +} + +public extension Path { //MARK: Directory Listings - + /** Same as the `ls -a` command ∴ output is ”shallow” and unsorted. - Parameter includeHiddenFiles: If `true`, hidden files are included in the results. Defaults to `true`. @@ -25,7 +43,7 @@ public extension Path { } /// Convenience functions for the array return value of `Path.ls()` -public extension Array where Element == Path.Entry { +public extension Array where Element == Entry { /// Filters the list of entries to be a list of Paths that are directories. var directories: [Path] { return compactMap { diff --git a/Sources/Path.swift b/Sources/Path.swift index 9e28236..908858b 100644 --- a/Sources/Path.swift +++ b/Sources/Path.swift @@ -1,14 +1,16 @@ import Foundation /** - Represents a platform filesystem absolute path. + Represents a filesystem absolute path. `Path` supports `Codable`, and can be configured to [encode paths *relatively*](https://github.com/mxcl/Path.swift/#codable). Sorting a `Sequence` of `Path`s will return the locale-aware sort order, which - will give you the same order as Finder, (though folders will not be sorted - first). + will give you the same order as Finder. + + All functions on `Path` are chainable and short to facilitate doing sequences + of file operations in a concise manner. Converting from a `String` is a common first step, here are the recommended ways to do that: @@ -168,24 +170,4 @@ public struct Path: Equatable, Hashable, Comparable { public static func <(lhs: Path, rhs: Path) -> Bool { return lhs.string.compare(rhs.string, locale: .current) == .orderedAscending } - -//MARK: Entry - - /** - A file entry from a directory listing. - - SeeAlso: `ls()` - */ - public struct Entry { - /// The kind of this directory entry. - public enum Kind { - /// The path is a file. - case file - /// The path is a directory. - case directory - } - /// The kind of this entry. - public let kind: Kind - /// The path of this entry. - public let path: Path - } } From 44be1c45a9196af26cdef87f7b8165e7393521d7 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Sat, 26 Jan 2019 13:06:23 -0500 Subject: [PATCH 2/3] Add `Path.ctime` --- Sources/Path+Attributes.swift | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Sources/Path+Attributes.swift b/Sources/Path+Attributes.swift index 393d3f2..ffe21f0 100644 --- a/Sources/Path+Attributes.swift +++ b/Sources/Path+Attributes.swift @@ -4,14 +4,28 @@ public extension Path { //MARK: Filesystem Attributes /** - Returns the modification-time. + Returns the creation-time of the file. + - Note: Returns UNIX-time-zero if there is no creation-time, this should only happen if the file doesn’t exist. + */ + var ctime: Date { + do { + let attrs = try FileManager.default.attributesOfItem(atPath: string) + return attrs[.creationDate] as? Date ?? Date(timeIntervalSince1970: 0) + } catch { + //TODO log error + return Date(timeIntervalSince1970: 0) + } + } + + /** + Returns the modification-time of the file. - Note: Returns the creation time if there is no modification time. - - Note: Returns UNIX-time-zero if neither are available, though this *should* be impossible. + - Note: Returns UNIX-time-zero if neither are available, this should only happen if the file doesn’t exist. */ var mtime: Date { do { let attrs = try FileManager.default.attributesOfItem(atPath: string) - return attrs[.modificationDate] as? Date ?? attrs[.creationDate] as? Date ?? Date(timeIntervalSince1970: 0) + return attrs[.modificationDate] as? Date ?? ctime } catch { //TODO log error return Date(timeIntervalSince1970: 0) From 859164e59f4fcf533aa31b2beec4a0623fc4b79b Mon Sep 17 00:00:00 2001 From: Max Howell Date: Tue, 22 Jan 2019 14:05:33 -0500 Subject: [PATCH 3/3] Dynamic Members --- .travis.yml | 10 +++++++++- README.md | 12 ++++++++++++ Sources/Path.swift | 8 ++++++++ Tests/PathTests/PathTests.swift | 15 +++++++++++---- Tests/PathTests/XCTestManifests.swift | 1 + 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 674c3c6..916a292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,11 +89,18 @@ jobs: - name: CocoaPods before_install: | + DESCRIPTION=$(swift - < Path.swift.podspec Pod::Spec.new do |s| s.name = 'Path.swift' s.version = 'TRAVIS_TAG' - s.summary = 'Delightful, robust file-pathing functions' + s.summary = 'DESCRIPTION' s.homepage = 'https://github.com/mxcl/Path.swift' s.license = { :type => 'Unlicense', :file => 'LICENSE.md' } s.author = { 'mxcl' => 'mxcl@me.com' } @@ -108,6 +115,7 @@ jobs: end EOF sed -i '' "s/TRAVIS_TAG/$TRAVIS_TAG/" Path.swift.podspec + sed -i '' "s/DESCRIPTION/$DESCRIPTION/" Path.swift.podspec # ^^ see the Jazzy deployment for explanation install: gem install cocoapods --pre script: pod trunk push diff --git a/README.md b/README.md index 54e85bf..6be184e 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,18 @@ decoder.userInfo[.relativePath] = Path.home decoder.decode(from: data) ``` +## Dynamic members + +We support `@dynamicMemberLookup`: + +```swift +let ls = Path.root.usr.bin.ls // => /usr/bin/ls +``` + +This is less commonly useful than you would think, hence our documentation +does not use it. Usually you are joining variables or other `String` arguments. +However when you need it, it’s *lovely*. + ## Initializing from user-input The `Path` initializer returns `nil` unless fed an absolute path; thus to diff --git a/Sources/Path.swift b/Sources/Path.swift index 908858b..f930677 100644 --- a/Sources/Path.swift +++ b/Sources/Path.swift @@ -23,6 +23,8 @@ import Foundation - Note: There may not be an actual filesystem entry at the path. The underlying representation for `Path` is `String`. */ + +@dynamicMemberLookup public struct Path: Equatable, Hashable, Comparable { init(string: String) { @@ -45,6 +47,12 @@ public struct Path: Equatable, Hashable, Comparable { return URL(fileURLWithPath: string) } + /// Facilitates constructing paths for static strings + public subscript(dynamicMember pathComponent: String) -> Path { + let str = (string as NSString).appendingPathComponent(pathComponent) + return Path(string: str) + } + /** Returns the parent directory for this path. diff --git a/Tests/PathTests/PathTests.swift b/Tests/PathTests/PathTests.swift index aa88eba..d7ace5b 100644 --- a/Tests/PathTests/PathTests.swift +++ b/Tests/PathTests/PathTests.swift @@ -14,10 +14,10 @@ class PathTests: XCTestCase { func testEnumeration() throws { let tmpdir_ = try TemporaryDirectory() let tmpdir = tmpdir_.path - try tmpdir.join("a").mkdir().join("c").touch() - try tmpdir.join("b").touch() - try tmpdir.join("c").touch() - try tmpdir.join(".d").mkdir().join("e").touch() + try tmpdir.a.mkdir().c.touch() + try tmpdir.b.touch() + try tmpdir.c.touch() + try tmpdir.join(".d").mkdir().e.touch() var paths = Set() var dirs = 0 @@ -139,6 +139,13 @@ class PathTests: XCTestCase { XCTAssertEqual(Path.root/"a/foo"/"../../../bar", Path.root/"bar") } + func testDynamicMember() { + XCTAssertEqual(Path.root.Documents, Path.root/"Documents") + + let a = Path.home.foo + XCTAssertEqual(a.Documents, Path.home/"foo/Documents") + } + func testCopyInto() throws { try Path.mktemp { root in let bar = try root.join("bar").touch() diff --git a/Tests/PathTests/XCTestManifests.swift b/Tests/PathTests/XCTestManifests.swift index d92bebc..b39a483 100644 --- a/Tests/PathTests/XCTestManifests.swift +++ b/Tests/PathTests/XCTestManifests.swift @@ -6,6 +6,7 @@ extension PathTests { ("testCodable", testCodable), ("testConcatenation", testConcatenation), ("testCopyInto", testCopyInto), + ("testDynamicMember", testDynamicMember), ("testEnumeration", testEnumeration), ("testEnumerationSkippingHiddenFiles", testEnumerationSkippingHiddenFiles), ("testExists", testExists),