[ci skip] Update README.md

This commit is contained in:
Max Howell
2020-01-25 10:48:39 -05:00
parent 240d699986
commit 0de9715b46

114
README.md
View File

@@ -73,30 +73,36 @@ try JSONEncoder().encode([Path.home, Path.home/"foo"])
] ]
``` ```
However, often you want to encode relative paths: Though we recommend encoding *relative* paths:
```swift ```swift
let encoder = JSONEncoder() let encoder = JSONEncoder()
encoder.userInfo[.relativePath] = Path.home encoder.userInfo[.relativePath] = Path.home
encoder.encode([Path.home, Path.home/"foo"]) encoder.encode([Path.home, Path.home/"foo", Path.home/"../baz"])
``` ```
```json ```json
[ [
"", "",
"foo", "foo",
"../baz"
] ]
``` ```
**Note** make sure you decode with this key set *also*, otherwise we `fatal` **Note** if you encode with this key set you *must* decode with the key
(unless the paths are absolute obv.) set also:
```swift ```swift
let decoder = JSONDecoder() let decoder = JSONDecoder()
decoder.userInfo[.relativePath] = Path.home decoder.userInfo[.relativePath] = Path.home
decoder.decode(from: data) try decoder.decode(from: data) // would throw if `.relativePath` not set
``` ```
> ‡ If you are saving files to a system provided location, eg. Documents then
> the directory could change at Apples choice, or if say the user changes their
> username. Using relative paths also provides you with the flexibility in
> future to change where you are storing your files without hassle.
## Dynamic members ## Dynamic members
We support `@dynamicMemberLookup`: We support `@dynamicMemberLookup`:
@@ -109,7 +115,7 @@ We only provide this for “starting” function, eg. `Path.home` or `Bundle.pat
This is because we found in practice it was easy to write incorrect code, since This is because we found in practice it was easy to write incorrect code, since
everything would compile if we allowed arbituary variables to take *any* named everything would compile if we allowed arbituary variables to take *any* named
property as valid syntax. What we have is what you want most of the time but property as valid syntax. What we have is what you want most of the time but
much less dangerous. much less (potentially) dangerous (at runtime).
## Initializing from user-input ## Initializing from user-input
@@ -127,6 +133,28 @@ expect to be relative.
Our initializer is nameless to be consistent with the equivalent operation for Our initializer is nameless to be consistent with the equivalent operation for
converting strings to `Int`, `Float` etc. in the standard library. converting strings to `Int`, `Float` etc. in the standard library.
## Initializing from known strings
Theres no need to use the optional initializer in general if you have known
strings that you need to be paths:
```swift
let absolutePath = "/known/path"
let path1 = Path.root/pathString
let pathWithoutInitialSlash = "known/path"
let path2 = Path.root/pathWithoutInitialSlash
assert(path1 == path2)
let path3 = Path(absolutePath)! // at your options
assert(path2 == path3)
// be cautious:
let path4 = Path(pathWithoutInitialSlash)! // CRASH!
```
## Extensions ## Extensions
We have some extensions to Apple APIs: We have some extensions to Apple APIs:
@@ -168,38 +196,46 @@ let files = Path.home.ls().files
// ^^ files that both *exist* and are *not* directories // ^^ files that both *exist* and are *not* directories
let swiftFiles = Path.home.ls().files.filter{ $0.extension == "swift" } let swiftFiles = Path.home.ls().files.filter{ $0.extension == "swift" }
let includingHiddenFiles = Path.home.ls(.a)
``` ```
**Note** `ls()` does not throw, instead outputing a warning to the console if it
fails to list the directory. The rationale for this is weak, please open a
ticket for discussion.
We provide `find()` for recursive listing: We provide `find()` for recursive listing:
```swift ```swift
Path.home.find().execute { path in for path in Path.home.find() {
// descends all directories, and includes hidden files
// so it behaves the same as the terminal command `find`
}
```
It is configurable:
```swift
for path in Path.home.find().depth(max: 1).extension("swift").type(.file) {
// //
} }
``` ```
Which is configurable: It can be controlled with a closure syntax:
```swift ```swift
Path.home.find().depth(max: 1).extension("swift").type(.file) { path in Path.home.find().depth(2...3).execute { path in
guard path.basename() != "foo.lock" else { return .abort }
if path.basename() == ".build", path.isDirectory { return .skip }
// //
}
```
And can be controlled:
```swift
Path.home.find().execute { path in
guard foo else { return .skip }
guard bar else { return .abort }
return .continue return .continue
} }
``` ```
Or just get all paths at once: Or get everything at once as an array:
```swift ```swift
let paths = Path.home.find().execute() let paths = Path.home.find().map(\.self)
``` ```
# `Path.swift` is robust # `Path.swift` is robust
@@ -208,8 +244,8 @@ Some parts of `FileManager` are not exactly idiomatic. For example
`isExecutableFile` returns `true` even if there is no file there, it is instead `isExecutableFile` returns `true` even if there is no file there, it is instead
telling you that *if* you made a file there it *could* be executable. Thus we telling you that *if* you made a file there it *could* be executable. Thus we
check the POSIX permissions of the file first, before returning the result of check the POSIX permissions of the file first, before returning the result of
`isExecutableFile`. `Path.swift` has done the leg-work for you so you can get on `isExecutableFile`. `Path.swift` has done the leg-work for you so you can just
with your work without worries. get on with it and not have to worry.
There is also some magic going on in Foundations filesystem APIs, which we look There is also some magic going on in Foundations filesystem APIs, which we look
for and ensure our API is deterministic, eg. [this test]. for and ensure our API is deterministic, eg. [this test].
@@ -223,7 +259,8 @@ round them where necessary.
# Rules & Caveats # Rules & Caveats
Paths are just string representations, there *might not* be a real file there. Paths are just (normalized) string representations, there *might not* be a real
file there.
```swift ```swift
Path.home/"b" // => /Users/mxcl/b Path.home/"b" // => /Users/mxcl/b
@@ -231,7 +268,7 @@ Path.home/"b" // => /Users/mxcl/b
// joining multiple strings works as youd expect // joining multiple strings works as youd expect
Path.home/"b"/"c" // => /Users/mxcl/b/c Path.home/"b"/"c" // => /Users/mxcl/b/c
// joining multiple parts at a time is fine // joining multiple parts simultaneously is fine
Path.home/"b/c" // => /Users/mxcl/b/c Path.home/"b/c" // => /Users/mxcl/b/c
// joining with absolute paths omits prefixed slash // joining with absolute paths omits prefixed slash
@@ -264,13 +301,13 @@ Path("/foo/bar/../baz") // => /foo/baz
// symlinks are not resolved // symlinks are not resolved
Path.root.bar.symlink(as: "foo") Path.root.bar.symlink(as: "foo")
Path("foo") // => /foo Path("/foo") // => /foo
Path.foo // => /foo Path.root.foo // => /foo
// unless you do it explicitly // unless you do it explicitly
try Path.foo.readlink() // => /bar try Path.root.foo.readlink() // => /bar
// `readlink` only resolves the *final* path component, // `readlink` only resolves the *final* path component,
// thus use `realpath` if there are multiple symlinks // thus use `realpath` if there are multiple symlinks
``` ```
*Path.swift* has the general policy that if the desired end result preexists, *Path.swift* has the general policy that if the desired end result preexists,
@@ -280,7 +317,7 @@ then its a noop:
* If you try to make a directory and it already exists, we do nothing. * If you try to make a directory and it already exists, we do nothing.
* If you call `readlink` on a non-symlink, we return `self` * If you call `readlink` on a non-symlink, we return `self`
However notably if you try to copy or move a file with specifying `overwrite` However notably if you try to copy or move a file without specifying `overwrite`
and the file already exists at the destination and is identical, we dont check and the file already exists at the destination and is identical, we dont check
for that as the check was deemed too expensive to be worthwhile. for that as the check was deemed too expensive to be worthwhile.
@@ -291,9 +328,9 @@ for that as the check was deemed too expensive to be worthwhile.
equality check is required. equality check is required.
* There are several symlink paths on Mac that are typically automatically * There are several symlink paths on Mac that are typically automatically
resolved by Foundation, eg. `/private`, we attempt to do the same for resolved by Foundation, eg. `/private`, we attempt to do the same for
functions that you would expect it (notably `realpath`), we *do* the same for functions that you would expect it (notably `realpath`), we *do* the same
`Path.init`, but *do not* if you are joining a path that ends up being one of for `Path.init`, but *do not* if you are joining a path that ends up being
these paths, (eg. `Path.root.join("var/private')`). one of these paths, (eg. `Path.root.join("var/private')`).
If a `Path` is a symlink but the destination of the link does not exist `exists` If a `Path` is a symlink but the destination of the link does not exist `exists`
returns `false`. This seems to be the correct thing to do since symlinks are returns `false`. This seems to be the correct thing to do since symlinks are
@@ -316,8 +353,8 @@ Apple recommend this because they provide a magic translation for
file:///.file/id=6571367.15106761 file:///.file/id=6571367.15106761
Therefore, if you are not using this feature you are fine. If you have URLs the correct Therefore, if you are not using this feature you are fine. If you have URLs the
way to get a `Path` is: correct way to get a `Path` is:
```swift ```swift
if let path = Path(url: url) { if let path = Path(url: url) {
@@ -330,6 +367,13 @@ actual filesystem path, however we also check the URL has a `file` scheme first.
[file-refs]: https://developer.apple.com/documentation/foundation/nsurl/1408631-filereferenceurl [file-refs]: https://developer.apple.com/documentation/foundation/nsurl/1408631-filereferenceurl
# In defense of our naming scheme
Chainable syntax demands short method names, thus we adopted the naming scheme
of the terminal, which is absolutely not very “Apple” when it comes to how they
design their APIs, however for users of the terminal (which *surely* is most
developers) it is snappy and familiar.
# Installation # Installation
SwiftPM: SwiftPM: