Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbf1f24ef6 | ||
|
|
c08ccdfb30 | ||
|
|
859164e59f | ||
|
|
44be1c45a9 | ||
|
|
99b948f9c1 | ||
|
|
3beba13677 | ||
|
|
bafb05ff54 | ||
|
|
356a1b3ac2 | ||
|
|
6d8712f4d6 | ||
|
|
8744b68709 |
23
.travis.yml
23
.travis.yml
@@ -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
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -28,12 +28,12 @@ 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
|
||||||
|
|
||||||
// A practical example: installing a helper executable
|
// A practical example: installing a helper executable
|
||||||
try Bundle.resources.join("helper").copy(into: Path.home.join(".local/bin").mkpath()).chmod(0o500)
|
try Bundle.resources.join("helper").copy(into: Path.home.join(".local/bin").mkdir(.p)).chmod(0o500)
|
||||||
```
|
```
|
||||||
|
|
||||||
We emphasize safety and correctness, just like Swift, and also (again like
|
We emphasize safety and correctness, just like Swift, and also (again like
|
||||||
@@ -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, it’s *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
|
||||||
@@ -184,6 +196,16 @@ Path("~/foo")! // => /Users/mxcl/foo
|
|||||||
Path("~foo") // => nil
|
Path("~foo") // => nil
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*Path.swift* has the general policty that if the desired end result preexists,
|
||||||
|
then it’s a noop:
|
||||||
|
|
||||||
|
* If you try to delete a file, but the file doesn't exist, we do nothing.
|
||||||
|
* If you try to make a directory and it already exists, we do nothing.
|
||||||
|
|
||||||
|
However notably if you try to copy or move a file with specifying `overwrite`
|
||||||
|
and the file already exists at the destination and is identical, we don’t check
|
||||||
|
for that as the check was deemed too expensive to be worthwhile.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
SwiftPM:
|
SwiftPM:
|
||||||
|
|||||||
@@ -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 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 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 {
|
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)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public extension CodingUserInfoKey {
|
|||||||
*/
|
*/
|
||||||
extension Path: Codable {
|
extension Path: Codable {
|
||||||
/// - SeeAlso: `CodingUserInfoKey.relativePath`
|
/// - SeeAlso: `CodingUserInfoKey.relativePath`
|
||||||
// :nodoc:
|
/// :nodoc:
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let value = try decoder.singleValueContainer().decode(String.self)
|
let value = try decoder.singleValueContainer().decode(String.self)
|
||||||
if value.hasPrefix("/") {
|
if value.hasPrefix("/") {
|
||||||
@@ -39,7 +39,7 @@ extension Path: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// - SeeAlso: `CodingUserInfoKey.relativePath`
|
/// - SeeAlso: `CodingUserInfoKey.relativePath`
|
||||||
// :nodoc:
|
/// :nodoc:
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.singleValueContainer()
|
var container = encoder.singleValueContainer()
|
||||||
if let root = encoder.userInfo[.relativePath] as? Path {
|
if let root = encoder.userInfo[.relativePath] as? Path {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ public extension Path {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Copies a file.
|
Copies a file.
|
||||||
|
|
||||||
|
try Path.root.join("bar").copy(to: Path.home/"foo")
|
||||||
|
// => "/Users/mxcl/foo"
|
||||||
|
|
||||||
- Note: `throws` if `to` is a directory.
|
- Note: `throws` if `to` is a directory.
|
||||||
- Parameter to: Destination filename.
|
- Parameter to: Destination filename.
|
||||||
- Parameter overwrite: If `true` and both `self` and `to` are files, overwrites `to`.
|
- Parameter overwrite: If `true` and both `self` and `to` are files, overwrites `to`.
|
||||||
- Note: If either `self` or `to are directories, `overwrite` is ignored.
|
- Note: If either `self` or `to are directories, `overwrite` is ignored.
|
||||||
|
- Note: Throws if `overwrite` is `false` yet `to` is *already* identical to
|
||||||
|
`self` because even though *Path.swift’s* policy is to noop if the desired
|
||||||
|
end result preexists, checking for this condition is too expensive a
|
||||||
|
trade-off.
|
||||||
- Returns: `to` to allow chaining
|
- Returns: `to` to allow chaining
|
||||||
- SeeAlso: `copy(into:overwrite:)`
|
- SeeAlso: `copy(into:overwrite:)`
|
||||||
*/
|
*/
|
||||||
@@ -24,15 +32,22 @@ public extension Path {
|
|||||||
/**
|
/**
|
||||||
Copies a file into a directory
|
Copies a file into a directory
|
||||||
|
|
||||||
If the destination does not exist, this function creates the directory first.
|
try Path.root.join("bar").copy(into: .home)
|
||||||
|
// => "/Users/mxcl/bar"
|
||||||
// Create ~/.local/bin, copy `ls` there and make the new copy executable
|
|
||||||
try Path.root.join("bin/ls").copy(into: Path.home.join(".local/bin").mkpath()).chmod(0o500)
|
// Create ~/.local/bin, copy `ls` there and make the new copy executable
|
||||||
|
try Path.root.join("bin/ls").copy(into: Path.home.join(".local/bin").mkdir(.p)).chmod(0o500)
|
||||||
|
|
||||||
|
If the destination does not exist, this function creates the directory first.
|
||||||
|
|
||||||
- Note: `throws` if `into` is a file.
|
|
||||||
- Parameter into: Destination directory
|
- Parameter into: Destination directory
|
||||||
- Parameter overwrite: If true overwrites any file that already exists at `into`.
|
- Parameter overwrite: If true overwrites any file that already exists at `into`.
|
||||||
- Returns: The `Path` of the newly copied file.
|
- Returns: The `Path` of the newly copied file.
|
||||||
|
- Note: `throws` if `into` is a file.
|
||||||
|
- Note: Throws if `overwrite` is `false` yet `to` is *already* identical to
|
||||||
|
`self` because even though *Path.swift’s* policy is to noop if the desired
|
||||||
|
end result preexists, checking for this condition is too expensive a
|
||||||
|
trade-off.
|
||||||
- SeeAlso: `copy(into:overwrite:)`
|
- SeeAlso: `copy(into:overwrite:)`
|
||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -59,10 +74,18 @@ public extension Path {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Moves a file.
|
Moves a file.
|
||||||
- Note: `throws` if `to` is a directory.
|
|
||||||
|
try Path.root.join("bar").move(to: Path.home/"foo")
|
||||||
|
// => "/Users/mxcl/foo"
|
||||||
|
|
||||||
- Parameter to: Destination filename.
|
- Parameter to: Destination filename.
|
||||||
- Parameter overwrite: If true overwrites any file that already exists at `to`.
|
- Parameter overwrite: If true overwrites any file that already exists at `to`.
|
||||||
- Returns: `to` to allow chaining
|
- Returns: `to` to allow chaining
|
||||||
|
- Note: `throws` if `to` is a directory.
|
||||||
|
- Note: Throws if `overwrite` is `false` yet `to` is *already* identical to
|
||||||
|
`self` because even though *Path.swift’s* policy is to noop if the desired
|
||||||
|
end result preexists, checking for this condition is too expensive a
|
||||||
|
trade-off.
|
||||||
- SeeAlso: move(into:overwrite:)
|
- SeeAlso: move(into:overwrite:)
|
||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -77,18 +100,21 @@ public extension Path {
|
|||||||
/**
|
/**
|
||||||
Moves a file into a directory
|
Moves a file into a directory
|
||||||
|
|
||||||
|
try Path.root.join("bar").move(into: .home)
|
||||||
|
// => "/Users/mxcl/bar"
|
||||||
|
|
||||||
If the destination does not exist, this function creates the directory first.
|
If the destination does not exist, this function creates the directory first.
|
||||||
|
|
||||||
- Note: `throws` if `into` is a file.
|
|
||||||
- Parameter into: Destination directory
|
- Parameter into: Destination directory
|
||||||
- Parameter overwrite: If true overwrites any file that already exists at `into`.
|
- Parameter overwrite: If true *overwrites* any file that already exists at `into`.
|
||||||
|
- Note: `throws` if `into` is a file.
|
||||||
- Returns: The `Path` of destination filename.
|
- Returns: The `Path` of destination filename.
|
||||||
- SeeAlso: move(into:overwrite:)
|
- SeeAlso: move(into:overwrite:)
|
||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func move(into: Path) throws -> Path {
|
func move(into: Path) throws -> Path {
|
||||||
if !into.exists {
|
if !into.exists {
|
||||||
try into.mkpath()
|
try into.mkdir(.p)
|
||||||
} else if !into.isDirectory {
|
} else if !into.isDirectory {
|
||||||
throw CocoaError.error(.fileWriteFileExists)
|
throw CocoaError.error(.fileWriteFileExists)
|
||||||
}
|
}
|
||||||
@@ -97,10 +123,16 @@ public extension Path {
|
|||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the path, recursively if a directory.
|
/**
|
||||||
|
Deletes the path, recursively if a directory.
|
||||||
|
- Note: noop: if the path doesn’t exist
|
||||||
|
∵ *Path.swift* doesn’t error if desired end result preexists.
|
||||||
|
*/
|
||||||
@inlinable
|
@inlinable
|
||||||
func delete() throws {
|
func delete() throws {
|
||||||
try FileManager.default.removeItem(at: url)
|
if exists {
|
||||||
|
try FileManager.default.removeItem(at: url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,49 +145,36 @@ public extension Path {
|
|||||||
return try "".write(to: self)
|
return try "".write(to: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper due to Linux Swift being incomplete.
|
/**
|
||||||
private func _foo(go: () throws -> Void) throws {
|
Creates the directory at this path.
|
||||||
#if !os(Linux)
|
- 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.
|
||||||
|
- Returns: `self` to allow chaining.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func mkdir(_ options: MakeDirectoryOptions? = nil) throws -> Path {
|
||||||
do {
|
do {
|
||||||
try go()
|
let wid = options == .p
|
||||||
|
try FileManager.default.createDirectory(at: self.url, withIntermediateDirectories: wid, attributes: nil)
|
||||||
} catch CocoaError.Code.fileWriteFileExists {
|
} catch CocoaError.Code.fileWriteFileExists {
|
||||||
// noop
|
//noop (fails to trigger on Linux)
|
||||||
}
|
|
||||||
#else
|
|
||||||
do {
|
|
||||||
try go()
|
|
||||||
} catch {
|
} catch {
|
||||||
|
#if os(Linux)
|
||||||
let error = error as NSError
|
let error = error as NSError
|
||||||
guard error.domain == NSCocoaErrorDomain, error.code == CocoaError.Code.fileWriteFileExists.rawValue else {
|
guard error.domain == NSCocoaErrorDomain, error.code == CocoaError.Code.fileWriteFileExists.rawValue else {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
#else
|
||||||
#endif
|
throw error
|
||||||
}
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
Creates the directory at this path.
|
|
||||||
- Note: Does not create any intermediary directories.
|
|
||||||
- Returns: `self` to allow chaining.
|
|
||||||
*/
|
|
||||||
@discardableResult
|
|
||||||
func mkdir() throws -> Path {
|
|
||||||
try _foo {
|
|
||||||
try FileManager.default.createDirectory(at: self.url, withIntermediateDirectories: false, attributes: nil)
|
|
||||||
}
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates the directory at this path.
|
|
||||||
- Note: Creates any intermediary directories, if required.
|
|
||||||
- Returns: `self` to allow chaining.
|
|
||||||
*/
|
|
||||||
@discardableResult
|
|
||||||
func mkpath() throws -> Path {
|
|
||||||
try _foo {
|
|
||||||
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
}
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Options for `Path.mkdir(_:)`
|
||||||
|
public enum MakeDirectoryOptions {
|
||||||
|
/// Creates intermediary directories; works the same as `mkdir -p`.
|
||||||
|
case p
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -83,7 +83,7 @@ class PathTests: XCTestCase {
|
|||||||
try Path.mktemp {
|
try Path.mktemp {
|
||||||
for _ in 0...1 {
|
for _ in 0...1 {
|
||||||
try $0.join("a").mkdir()
|
try $0.join("a").mkdir()
|
||||||
try $0.join("b/c").mkpath()
|
try $0.join("b/c").mkdir(.p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user