Improved documentation; Fixes #12

This commit is contained in:
Max Howell
2019-01-25 18:17:13 +00:00
parent b613449232
commit 4b16dac3bf
11 changed files with 181 additions and 127 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.DS_Store
/.build
/*.xcodeproj
/build
/docs

View File

@@ -69,6 +69,8 @@ jobs:
- UseModernBuildSystem=NO
output: output
github_url: https://github.com/mxcl/Path.swift
exclude:
- Sources/Path+StringConvertibles.swift
EOF
sed -i '' "s/TRAVIS_TAG/$TRAVIS_TAG/" .jazzy.yaml
# ^^ this weirdness because Travis multiline YAML is broken and inserts

View File

@@ -107,10 +107,8 @@ This is explicit, not hiding anything that code-review may miss and preventing
common bugs like accidentally creating `Path` objects from strings you did not
expect to be relative.
Our initializer is nameless because we conform to `LosslessStringConvertible`,
the same conformance as that `Int`, `Float` etc. conform. The protocol enforces
a nameless initialization and since it is appropriate for us to conform to it,
we do.
Our initializer is nameless to be consistent with the equivalent operation for
converting strings to `Int`, `Float` etc. in the standard library.
## Extensions

View File

@@ -1,6 +1,34 @@
import Foundation
public extension Path {
//MARK: Filesystem Attributes
/**
Returns the 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.
*/
var mtime: Date {
do {
let attrs = try FileManager.default.attributesOfItem(atPath: string)
return attrs[.modificationDate] as? Date ?? attrs[.creationDate] as? Date ?? Date(timeIntervalSince1970: 0)
} catch {
//TODO log error
return Date(timeIntervalSince1970: 0)
}
}
/**
Sets the files attributes using UNIX octal notation.
Path.home.join("foo").chmod(0o555)
*/
@discardableResult
func chmod(_ octal: Int) throws -> Path {
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
return self
}
/// - Note: If file is already locked, does nothing
/// - Note: If file doesnt exist, throws
@discardableResult
@@ -31,30 +59,4 @@ public extension Path {
}
return self
}
/**
Sets the files attributes using UNIX octal notation.
Path.home.join("foo").chmod(0o555)
*/
@discardableResult
func chmod(_ octal: Int) throws -> Path {
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
return self
}
/**
Returns the 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.
*/
var mtime: Date {
do {
let attrs = try FileManager.default.attributesOfItem(atPath: string)
return attrs[.modificationDate] as? Date ?? attrs[.creationDate] as? Date ?? Date(timeIntervalSince1970: 0)
} catch {
//TODO log error
return Date(timeIntervalSince1970: 0)
}
}
}

View File

@@ -1,13 +1,31 @@
import Foundation
/// Provided for relative-path coding. See the instructions in our `README`.
/**
Provided for relative-path coding. See the instructions in our
[README](https://github.com/mxcl/Path.swift/#codable).
*/
public extension CodingUserInfoKey {
/// If set paths are encoded as relative to this path.
/**
If set on an `Encoder`s `userInfo` all paths are encoded relative to this path.
For example:
let encoder = JSONEncoder()
encoder.userInfo[.relativePath] = Path.home
encoder.encode([Path.home, Path.home/"foo"])
- Remark: See the [README](https://github.com/mxcl/Path.swift/#codable) for more information.
*/
static let relativePath = CodingUserInfoKey(rawValue: "dev.mxcl.Path.relative")!
}
/// Provided for relative-path coding. See the instructions in our `README`.
/**
Provided for relative-path coding. See the instructions in our
[README](https://github.com/mxcl/Path.swift/#codable).
*/
extension Path: Codable {
/// - SeeAlso: `CodingUserInfoKey.relativePath`
// :nodoc:
public init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(String.self)
if value.hasPrefix("/") {
@@ -20,6 +38,8 @@ extension Path: Codable {
}
}
/// - SeeAlso: `CodingUserInfoKey.relativePath`
// :nodoc:
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let root = encoder.userInfo[.relativePath] as? Path {

View File

@@ -1,6 +1,8 @@
import Foundation
extension Path {
//MARK: Common Directories
/// Returns a `Path` containing ``FileManager.default.currentDirectoryPath`.
public static var cwd: Path {
return Path(string: FileManager.default.currentDirectoryPath)

View File

@@ -1,6 +1,8 @@
import Foundation
public extension Path {
//MARK: File Management
/**
Copies a file.
- Note: `throws` if `to` is a directory.

View File

@@ -1,13 +1,5 @@
import class Foundation.NSString
extension Path: LosslessStringConvertible {
/// Returns `nil` unless fed an absolute path
public init?(_ description: String) {
guard description.starts(with: "/") || description.starts(with: "~/") else { return nil }
self.init(string: (description as NSString).standardizingPath)
}
}
extension Path: CustomStringConvertible {
/// Returns `Path.string`
public var description: String {

View File

@@ -1,8 +1,10 @@
import Foundation
public extension Path {
//MARK: Directory Listings
/**
Same as the `ls -a` command is shallow
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`.
- Important: `includeHiddenFiles` does not work on Linux
*/
@@ -22,6 +24,7 @@ public extension Path {
}
}
/// Convenience functions for the array return value of `Path.ls()`
public extension Array where Element == Path.Entry {
/// Filters the list of entries to be a list of Paths that are directories.
var directories: [Path] {

View File

@@ -1,25 +1,11 @@
import Foundation
public extension Path {
/// Returns true if the path represents an actual file that is also writable by the current user.
var isWritable: Bool {
return FileManager.default.isWritableFile(atPath: string)
}
//MARK: Filesystem Properties
/// Returns true if the path represents an actual file that is also readable by the current user.
var isReadable: Bool {
return FileManager.default.isReadableFile(atPath: string)
}
/// Returns true if the path represents an actual file that is also deletable by the current user.
var isDeletable: Bool {
return FileManager.default.isDeletableFile(atPath: string)
}
/// Returns true if the path represents an actual directory.
var isDirectory: Bool {
var isDir: ObjCBool = false
return FileManager.default.fileExists(atPath: string, isDirectory: &isDir) && isDir.boolValue
/// Returns true if the path represents an actual filesystem entry.
var exists: Bool {
return FileManager.default.fileExists(atPath: string)
}
/// Returns true if the path represents an actual filesystem entry that is *not* a directory.
@@ -28,13 +14,29 @@ public extension Path {
return FileManager.default.fileExists(atPath: string, isDirectory: &isDir) && !isDir.boolValue
}
/// Returns true if the path represents an actual directory.
var isDirectory: Bool {
var isDir: ObjCBool = false
return FileManager.default.fileExists(atPath: string, isDirectory: &isDir) && isDir.boolValue
}
/// Returns true if the path represents an actual file that is also readable by the current user.
var isReadable: Bool {
return FileManager.default.isReadableFile(atPath: string)
}
/// Returns true if the path represents an actual file that is also writable by the current user.
var isWritable: Bool {
return FileManager.default.isWritableFile(atPath: string)
}
/// Returns true if the path represents an actual file that is also deletable by the current user.
var isDeletable: Bool {
return FileManager.default.isDeletableFile(atPath: string)
}
/// Returns true if the path represents an actual file that is also executable by the current user.
var isExecutable: Bool {
return FileManager.default.isExecutableFile(atPath: string)
}
/// Returns true if the path represents an actual filesystem entry.
var exists: Bool {
return FileManager.default.fileExists(atPath: string)
}
}

View File

@@ -3,26 +3,44 @@ import Foundation
/**
Represents a platform filesystem absolute path.
The recommended conversions from string are:
`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).
Converting from a `String` is a common first step, here are the recommended
ways to do that:
let p1 = Path.root/pathString
let p2 = Path.root/url.path
let p3 = Path.cwd/relativePathString
let p4 = Path(userInput) ?? Path.cwd/userInput
- Note: There may not be an actual filesystem entry at the path.
- Note: There may not be an actual filesystem entry at the path. The underlying
representation for `Path` is `String`.
*/
public struct Path: Equatable, Hashable, Comparable {
init(string: String) {
self.string = string
}
/// Returns `nil` unless fed an absolute path
public init?(_ description: String) {
guard description.starts(with: "/") || description.starts(with: "~/") else { return nil }
self.init(string: (description as NSString).standardizingPath)
}
//MARK: Properties
/// The underlying filesystem path
public let string: String
/**
Returns the filename extension of this path.
- Remark: Implemented via `NSString.pathExtension`.
*/
@inlinable
public var `extension`: String {
return (string as NSString).pathExtension
/// Returns a `URL` representing this file path.
public var url: URL {
return URL(fileURLWithPath: string)
}
/**
@@ -37,33 +55,51 @@ public struct Path: Equatable, Hashable, Comparable {
return Path(string: (string as NSString).deletingLastPathComponent)
}
/// Returns a `URL` representing this file path.
/**
Returns the filename extension of this path.
- Remark: Implemented via `NSString.pathExtension`.
*/
@inlinable
public var url: URL {
return URL(fileURLWithPath: string)
public var `extension`: String {
return (string as NSString).pathExtension
}
//MARK: Pathing
/**
Joins a path and a string to produce a new path.
Path.root.join("a") // => /a
Path.root.join("a/b") // => /a/b
Path.root.join("a").join("b") // => /a/b
Path.root.join("a").join("/b") // => /a/b
- Parameter pathComponent: The string to join with this path.
- Returns: A new joined path.
- SeeAlso: `Path./(_:, _:)`
*/
public func join<S>(_ pathComponent: S) -> Path where S: StringProtocol {
//TODO standardizingPath does more than we want really (eg tilde expansion)
let str = (string as NSString).appendingPathComponent(String(pathComponent))
return Path(string: (str as NSString).standardizingPath)
}
/**
The basename for the provided file, optionally dropping the file extension.
Joins a path and a string to produce a new path.
Path.root.join("foo.swift").basename() // => "foo.swift"
Path.root.join("foo.swift").basename(dropExtension: true) // => "foo"
Path.root/"a" // => /a
Path.root/"a/b" // => /a/b
Path.root/"a"/"b" // => /a/b
Path.root/"a"/"/b" // => /a/b
- Returns: A string that is the filenames basename.
- Parameter dropExtension: If `true` returns the basename without its file extension.
- Parameter lhs: The base path to join with `rhs`.
- Parameter rhs: The string to join with this `lhs`.
- Returns: A new joined path.
- SeeAlso: `join(_:)`
*/
public func basename(dropExtension: Bool = false) -> String {
let str = string as NSString
if !dropExtension {
return str.lastPathComponent
} else {
let ext = str.pathExtension
if !ext.isEmpty {
return String(str.lastPathComponent.dropLast(ext.count + 1))
} else {
return str.lastPathComponent
}
}
@inlinable
public static func /<S>(lhs: Path, rhs: S) -> Path where S: StringProtocol {
return lhs.join(rhs)
}
/**
@@ -104,48 +140,41 @@ public struct Path: Equatable, Hashable, Comparable {
}
/**
Joins a path and a string to produce a new path.
The basename for the provided file, optionally dropping the file extension.
Path.root.join("a") // => /a
Path.root.join("a/b") // => /a/b
Path.root.join("a").join("b") // => /a/b
Path.root.join("a").join("/b") // => /a/b
Path.root.join("foo.swift").basename() // => "foo.swift"
Path.root.join("foo.swift").basename(dropExtension: true) // => "foo"
- Parameter pathComponent: The string to join with this path.
- Returns: A new joined path.
- SeeAlso: `/(_:, _:)`
- Returns: A string that is the filenames basename.
- Parameter dropExtension: If `true` returns the basename without its file extension.
*/
public func join<S>(_ pathComponent: S) -> Path where S: StringProtocol {
//TODO standardizingPath does more than we want really (eg tilde expansion)
let str = (string as NSString).appendingPathComponent(String(pathComponent))
return Path(string: (str as NSString).standardizingPath)
public func basename(dropExtension: Bool = false) -> String {
let str = string as NSString
if !dropExtension {
return str.lastPathComponent
} else {
let ext = str.pathExtension
if !ext.isEmpty {
return String(str.lastPathComponent.dropLast(ext.count + 1))
} else {
return str.lastPathComponent
}
}
/**
Joins a path and a string to produce a new path.
Path.root/"a" // => /a
Path.root/"a/b" // => /a/b
Path.root/"a"/"b" // => /a/b
Path.root/"a"/"/b" // => /a/b
- Parameter lhs: The base path to join with `rhs`.
- Parameter rhs: The string to join with this `lhs`.
- Returns: A new joined path.
- SeeAlso: `Path.join(_:)`
*/
@inlinable
public static func /<S>(lhs: Path, rhs: S) -> Path where S: StringProtocol {
return lhs.join(rhs)
}
/// Returns the locale-aware sort order for the two paths.
/// :nodoc:
@inlinable
public static func <(lhs: Path, rhs: Path) -> Bool {
return lhs.string.compare(rhs.string, locale: .current) == .orderedAscending
}
/// A file entry from a directory listing.
//MARK: Entry
/**
A file entry from a directory listing.
- SeeAlso: `ls()`
*/
public struct Entry {
/// The kind of this directory entry.
public enum Kind {