100% documentation please

This commit is contained in:
Max Howell
2019-01-19 14:36:27 -05:00
parent 29149da72b
commit 3eda9a9741
8 changed files with 146 additions and 9 deletions

View File

@@ -1,26 +1,31 @@
import Foundation
public extension Bundle {
/// Returns the path for requested resource in this bundle.
func path(forResource: String, ofType: String?) -> Path? {
let f: (String?, String?) -> String? = path(forResource:ofType:)
let str = f(forResource, ofType)
return str.flatMap(Path.init)
}
/// Returns the path for the shared-frameworks directory in this bundle.
public var sharedFrameworks: Path? {
return sharedFrameworksPath.flatMap(Path.init)
}
/// Returns the path for the resources directory in this bundle.
public var resources: Path? {
return resourcePath.flatMap(Path.init)
}
/// Returns the path for this bundle.
public var path: Path {
return Path(string: bundlePath)
}
}
public extension String {
/// Initializes this `String` with the contents of the provided path.
@inlinable
init(contentsOf path: Path) throws {
try self.init(contentsOfFile: path.string)
@@ -36,6 +41,7 @@ public extension String {
}
public extension Data {
/// Initializes this `Data` with the contents of the provided path.
@inlinable
init(contentsOf path: Path) throws {
try self.init(contentsOf: path.url)

View File

@@ -43,14 +43,18 @@ public extension Path {
return self
}
/// - Returns: modification-time or creation-time if none
/**
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.
*/
public var mtime: Date {
do {
let attrs = try FileManager.default.attributesOfItem(atPath: string)
return attrs[.modificationDate] as? Date ?? attrs[.creationDate] as? Date ?? Date()
} catch {
//TODO print(error)
return Date()
//TODO log error
return Date(timeIntervalSince1970: 0)
}
}
}

View File

@@ -1,6 +1,7 @@
import Foundation
public extension CodingUserInfoKey {
/// If set paths are encoded as relative to this path.
static let relativePath = CodingUserInfoKey(rawValue: "dev.mxcl.Path.relative")!
}

View File

@@ -41,6 +41,14 @@ public extension Path {
return rv
}
/**
Moves a file.
- Note: `throws` if `to` is a directory.
- Parameter to: Destination filename.
- Parameter overwrite: If true overwrites any file that already exists at `to`.
- Returns: `to` to allow chaining
- SeeAlso: move(into:overwrite:)
*/
@discardableResult
public func move(to: Path, overwrite: Bool = false) throws -> Path {
if overwrite, to.exists {
@@ -50,6 +58,17 @@ public extension Path {
return to
}
/**
Moves a file into a directory
If the destination does not exist, this function creates the directory first.
- Note: `throws` if `into` is a file.
- Parameter into: Destination directory
- Parameter overwrite: If true overwrites any file that already exists at `into`.
- Returns: The `Path` of destination filename.
- SeeAlso: move(into:overwrite:)
*/
@discardableResult
public func move(into: Path) throws -> Path {
if !into.exists {
@@ -62,17 +81,23 @@ public extension Path {
return rv
}
/// Deletes the path, recursively if a directory.
@inlinable
public func delete() throws {
try FileManager.default.removeItem(at: url)
}
/**
Creates an empty file at this path.
- Returns: `self` to allow chaining.
*/
@inlinable
@discardableResult
func touch() throws -> Path {
return try "".write(to: self)
}
/// Helper due to Linux Swift being incomplete.
private func _foo(go: () throws -> Void) throws {
#if !os(Linux)
do {
@@ -92,6 +117,11 @@ public extension Path {
#endif
}
/**
Creates the directory at this path.
- Note: Does not create any intermediary directories.
- Returns: `self` to allow chaining.
*/
@discardableResult
public func mkdir() throws -> Path {
try _foo {
@@ -100,6 +130,11 @@ public extension Path {
return self
}
/**
Creates the directory at this path.
- Note: Creates any intermediary directories, if required.
- Returns: `self` to allow chaining.
*/
@discardableResult
public func mkpath() throws -> Path {
try _foo {
@@ -108,8 +143,15 @@ public extension Path {
return self
}
/// - Note: If file doesnt exist, creates file
/// - Note: If file is not writable, makes writable first, resetting permissions after the write
/**
Replaces the contents of the file at this path with the provided string.
- Note: If file doesnt exist, creates file
- Note: If file is not writable, makes writable first, resetting permissions after the write
- Parameter contents: The string that will become the contents of this file.
- Parameter atomically: If `true` the operation will be performed atomically.
- Parameter encoding: The string encoding to use.
- Returns: `self` to allow chaining.
*/
@discardableResult
public func replaceContents(with contents: String, atomically: Bool = false, encoding: String.Encoding = .utf8) throws -> Path {
let resetPerms: Int?

View File

@@ -9,12 +9,14 @@ extension Path: LosslessStringConvertible {
}
extension Path: CustomStringConvertible {
/// Returns `Path.string`
public var description: String {
return string
}
}
extension Path: CustomDebugStringConvertible {
/// Returns eg. `Path(string: "/foo")`
public var debugDescription: String {
return "Path(string: \(string))"
}

View File

@@ -13,12 +13,14 @@ public extension Path {
}
public extension Array where Element == Path.Entry {
/// Filters the list of entries to be a list of Paths that are directories.
var directories: [Path] {
return compactMap {
$0.kind == .directory ? $0.path : nil
}
}
/// Filters the list of entries to be a list of Paths that are files with the specified extension
func files(withExtension ext: String) -> [Path] {
return compactMap {
$0.kind == .file && $0.path.extension == ext ? $0.path : nil

View File

@@ -1,24 +1,29 @@
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)
}
/// 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 that is *not* a directory.
var isFile: Bool {
var isDir: ObjCBool = true
return FileManager.default.fileExists(atPath: string, isDirectory: &isDir) && !isDir.boolValue
}
/// 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

@@ -1,16 +1,32 @@
import Foundation
/**
Represents a platform filesystem absolute path.
The recommended conversions from string are:
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 filename at the path.
*/
public struct Path: Equatable, Hashable, Comparable {
/// The underlying filesystem path
public let string: String
/// Returns a `Path` containing ``FileManager.default.currentDirectoryPath`.
public static var cwd: Path {
return Path(string: FileManager.default.currentDirectoryPath)
}
/// Returns a `Path` representing the root path.
public static var root: Path {
return Path(string: "/")
}
/// Returns a `Path` representing the users home directory
public static var home: Path {
let string: String
#if os(macOS)
@@ -25,21 +41,42 @@ public struct Path: Equatable, Hashable, Comparable {
return Path(string: string)
}
/**
Returns the filename extension of this path.
- Remark: Implemented via `NSString.pathExtension`.
*/
@inlinable
public var `extension`: String {
return (string as NSString).pathExtension
}
/// - Note: always returns a valid path, `Path.root.parent` *is* `Path.root`
/**
Returns the parent directory for this path.
Path is not aware of the nature of the underlying file, but this is
irrlevant since the operation is the same irrespective of this fact.
- Note: always returns a valid path, `Path.root.parent` *is* `Path.root`.
*/
public var parent: Path {
return Path(string: (string as NSString).deletingLastPathComponent)
}
/// Returns a `URL` representing this file path.
@inlinable
public var url: URL {
return URL(fileURLWithPath: string)
}
/**
The basename for the provided file, optionally dropping the file extension.
Path.root.join("foo.swift").basename() // => "foo.swift"
Path.root.join("foo.swift").basename(dropExtension: true) // => "foo"
- Returns: A string that is the filenames basename.
- Parameter dropExtension: If `true` returns the basename without its file extension.
*/
public func basename(dropExtension: Bool = false) -> String {
let str = string as NSString
if !dropExtension {
@@ -54,7 +91,13 @@ public struct Path: Equatable, Hashable, Comparable {
}
}
//TODO another variant that returns `nil` if result would start with `..`
/**
Returns a string representing the relative path to `base`.
- Note: If `base` is not a logical prefix for `self` your result will be prefixed some number of `../` components.
- Parameter base: The base to which we calculate the relative path.
- ToDo: Another variant that returns `nil` if result would start with `..`
*/
public func relative(to base: Path) -> String {
// Split the two paths into their components.
// FIXME: The is needs to be optimized to avoid unncessary copying.
@@ -85,27 +128,59 @@ public struct Path: Equatable, Hashable, Comparable {
}
}
public func join<S>(_ part: S) -> Path where S: StringProtocol {
/**
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,:String)
*/
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(part))
let str = (string as NSString).appendingPathComponent(String(pathComponent))
return Path(string: (str as NSString).standardizingPath)
}
/// Returns the locale-aware sort order for the two paths.
@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.
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
}
}
/**
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 func /<S>(lhs: Path, rhs: S) -> Path where S: StringProtocol {
return lhs.join(rhs)