Compare commits

..

5 Commits
0.6.0 ... 0.7.0

Author SHA1 Message Date
Max Howell
bbf1f24ef6 Fix Cocoapods deploy 2019-01-26 14:42:00 -05:00
Max Howell
c08ccdfb30 Merge pull request #15 from mxcl/dynamic-members
Dynamic members
2019-01-26 13:34:17 -05:00
Max Howell
859164e59f Dynamic Members 2019-01-26 13:23:25 -05:00
Max Howell
44be1c45a9 Add Path.ctime 2019-01-26 13:17:39 -05:00
Max Howell
99b948f9c1 Minor documentation fixes
[ci skip]
2019-01-26 13:17:39 -05:00
9 changed files with 99 additions and 43 deletions

View File

@@ -89,11 +89,28 @@ jobs:
- name: CocoaPods - name: CocoaPods
before_install: | before_install: |
export DESCRIPTION=$(swift - <<\ \ EOF
import Foundation
struct Response: Decodable { let description: String }
let token = ProcessInfo.processInfo.environment["GITHUB_TOKEN"]!
let url = URL(string: "https://api.github.com/repos/mxcl/Path.swift")!
var rq = URLRequest(url: url)
rq.setValue("token \(token)", forHTTPHeaderField: "Authorization")
let semaphore = DispatchSemaphore(value: 0)
var data: Data!
URLSession.shared.dataTask(with: rq) { d, _, _ in
data = d
semaphore.signal()
}.resume()
semaphore.wait()
let rsp = try JSONDecoder().decode(Response.self, from: data)
print(rsp.description, terminator: "")
EOF)
cat <<\ \ EOF> Path.swift.podspec cat <<\ \ EOF> Path.swift.podspec
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'Path.swift' s.name = 'Path.swift'
s.version = 'TRAVIS_TAG' s.version = ENV['TRAVIS_TAG']
s.summary = 'Delightful, robust file-pathing functions' s.summary = ENV['DESCRIPTION']
s.homepage = 'https://github.com/mxcl/Path.swift' s.homepage = 'https://github.com/mxcl/Path.swift'
s.license = { :type => 'Unlicense', :file => 'LICENSE.md' } s.license = { :type => 'Unlicense', :file => 'LICENSE.md' }
s.author = { 'mxcl' => 'mxcl@me.com' } s.author = { 'mxcl' => 'mxcl@me.com' }
@@ -107,7 +124,5 @@ jobs:
s.swift_version = '4.2' s.swift_version = '4.2'
end end
EOF EOF
sed -i '' "s/TRAVIS_TAG/$TRAVIS_TAG/" Path.swift.podspec
# ^^ see the Jazzy deployment for explanation
install: gem install cocoapods --pre install: gem install cocoapods --pre
script: pod trunk push script: pod trunk push

View File

@@ -28,7 +28,7 @@ print(bar) // => /bar
print(bar.isFile) // => true print(bar.isFile) // => true
// careful API considerations so as to avoid common bugs // 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) // => /bar/foo
print(foo.isFile) // => true print(foo.isFile) // => true
@@ -94,6 +94,18 @@ decoder.userInfo[.relativePath] = Path.home
decoder.decode(from: data) 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, its *lovely*.
## Initializing from user-input ## Initializing from user-input
The `Path` initializer returns `nil` unless fed an absolute path; thus to The `Path` initializer returns `nil` unless fed an absolute path; thus to

View File

@@ -4,14 +4,28 @@ public extension Path {
//MARK: Filesystem Attributes //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 doesnt 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 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 doesnt exist.
*/ */
var mtime: Date { var mtime: Date {
do { do {
let attrs = try FileManager.default.attributesOfItem(atPath: string) 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 { } catch {
//TODO log error //TODO log error
return Date(timeIntervalSince1970: 0) return Date(timeIntervalSince1970: 0)

View File

@@ -3,7 +3,7 @@ import Foundation
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: Path { public static var cwd: Path {
return Path(string: FileManager.default.currentDirectoryPath) return Path(string: FileManager.default.currentDirectoryPath)
} }
@@ -74,7 +74,7 @@ extension Path {
/** /**
The root for cache files. 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. - Note: You should create a subdirectory before creating any files.
*/ */
public static var caches: Path { public static var caches: Path {

View File

@@ -147,7 +147,6 @@ public extension Path {
/** /**
Creates the directory at this path. Creates the directory at this path.
- Note: Does not create any intermediary directories.
- Parameter options: Specify `mkdir(.p)` to create intermediary directories. - Parameter options: Specify `mkdir(.p)` to create intermediary directories.
- Note: We do not error if the directory already exists (even without `.p`) - Note: We do not error if the directory already exists (even without `.p`)
because *Path.swift* noops if the desired end result preexists. 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 { public enum MakeDirectoryOptions {
/// Creates intermediary directories. Works the same as mkdir -p. /// Creates intermediary directories; works the same as `mkdir -p`.
case p case p
} }

View File

@@ -1,8 +1,26 @@
import Foundation 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 //MARK: Directory Listings
/** /**
Same as the `ls -a` command output is shallow and unsorted. 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`. - 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()` /// 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. /// Filters the list of entries to be a list of Paths that are directories.
var directories: [Path] { var directories: [Path] {
return compactMap { return compactMap {

View File

@@ -1,14 +1,16 @@
import Foundation import Foundation
/** /**
Represents a platform filesystem absolute path. Represents a filesystem absolute path.
`Path` supports `Codable`, and can be configured to `Path` supports `Codable`, and can be configured to
[encode paths *relatively*](https://github.com/mxcl/Path.swift/#codable). [encode paths *relatively*](https://github.com/mxcl/Path.swift/#codable).
Sorting a `Sequence` of `Path`s will return the locale-aware sort order, which 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 will give you the same order as Finder.
first).
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 Converting from a `String` is a common first step, here are the recommended
ways to do that: ways to do that:
@@ -21,6 +23,8 @@ import Foundation
- Note: There may not be an actual filesystem entry at the path. The underlying - Note: There may not be an actual filesystem entry at the path. The underlying
representation for `Path` is `String`. representation for `Path` is `String`.
*/ */
@dynamicMemberLookup
public struct Path: Equatable, Hashable, Comparable { public struct Path: Equatable, Hashable, Comparable {
init(string: String) { init(string: String) {
@@ -43,6 +47,12 @@ public struct Path: Equatable, Hashable, Comparable {
return URL(fileURLWithPath: string) 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. Returns the parent directory for this path.
@@ -168,24 +178,4 @@ public struct Path: Equatable, Hashable, Comparable {
public static func <(lhs: Path, rhs: Path) -> Bool { public static func <(lhs: Path, rhs: Path) -> Bool {
return lhs.string.compare(rhs.string, locale: .current) == .orderedAscending 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
}
} }

View File

@@ -14,10 +14,10 @@ class PathTests: XCTestCase {
func testEnumeration() throws { func testEnumeration() throws {
let tmpdir_ = try TemporaryDirectory() let tmpdir_ = try TemporaryDirectory()
let tmpdir = tmpdir_.path let tmpdir = tmpdir_.path
try tmpdir.join("a").mkdir().join("c").touch() try tmpdir.a.mkdir().c.touch()
try tmpdir.join("b").touch() try tmpdir.b.touch()
try tmpdir.join("c").touch() try tmpdir.c.touch()
try tmpdir.join(".d").mkdir().join("e").touch() try tmpdir.join(".d").mkdir().e.touch()
var paths = Set<String>() var paths = Set<String>()
var dirs = 0 var dirs = 0
@@ -139,6 +139,13 @@ class PathTests: XCTestCase {
XCTAssertEqual(Path.root/"a/foo"/"../../../bar", Path.root/"bar") 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 { func testCopyInto() throws {
try Path.mktemp { root in try Path.mktemp { root in
let bar = try root.join("bar").touch() let bar = try root.join("bar").touch()

View File

@@ -6,6 +6,7 @@ extension PathTests {
("testCodable", testCodable), ("testCodable", testCodable),
("testConcatenation", testConcatenation), ("testConcatenation", testConcatenation),
("testCopyInto", testCopyInto), ("testCopyInto", testCopyInto),
("testDynamicMember", testDynamicMember),
("testEnumeration", testEnumeration), ("testEnumeration", testEnumeration),
("testEnumerationSkippingHiddenFiles", testEnumerationSkippingHiddenFiles), ("testEnumerationSkippingHiddenFiles", testEnumerationSkippingHiddenFiles),
("testExists", testExists), ("testExists", testExists),