Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab9a70e947 | ||
|
|
49ef073e34 | ||
|
|
889d825b3a | ||
|
|
f1cd06fdff | ||
|
|
c6e840b9b6 | ||
|
|
eb34ac4af8 | ||
|
|
66ae86c986 | ||
|
|
c432f710eb | ||
|
|
19c0c19bb6 | ||
|
|
ee1f46954c |
2
.github/codecov.yml
vendored
Normal file
2
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ignore:
|
||||||
|
- Tests
|
||||||
3
.github/ranger.yml
vendored
Normal file
3
.github/ranger.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
merges:
|
||||||
|
- action: delete_branch
|
||||||
|
- action: tag
|
||||||
@@ -19,9 +19,10 @@ jobs:
|
|||||||
name: macOS / Swift 4.2.1
|
name: macOS / Swift 4.2.1
|
||||||
|
|
||||||
- &xcodebuild
|
- &xcodebuild
|
||||||
before_install: swift package generate-xcodeproj
|
before_install: swift package generate-xcodeproj --enable-code-coverage
|
||||||
xcode_destination: platform=iOS Simulator,OS=latest,name=iPhone XS
|
xcode_destination: platform=iOS Simulator,OS=latest,name=iPhone XS
|
||||||
name: iOS / Swift 4.2.1
|
name: iOS / Swift 4.2.1
|
||||||
|
after_success: bash <(curl -s https://codecov.io/bash)
|
||||||
- <<: *xcodebuild
|
- <<: *xcodebuild
|
||||||
xcode_destination: platform=tvOS Simulator,OS=latest,name=Apple TV
|
xcode_destination: platform=tvOS Simulator,OS=latest,name=Apple TV
|
||||||
name: tvOS / Swift 4.2.1
|
name: tvOS / Swift 4.2.1
|
||||||
@@ -34,6 +35,7 @@ jobs:
|
|||||||
-scheme Path.swift-Package \
|
-scheme Path.swift-Package \
|
||||||
-destination 'platform=watchOS Simulator,OS=latest,name=Apple Watch Series 4 - 40mm' \
|
-destination 'platform=watchOS Simulator,OS=latest,name=Apple Watch Series 4 - 40mm' \
|
||||||
build | xcpretty
|
build | xcpretty
|
||||||
|
after_success: false
|
||||||
|
|
||||||
- &linux
|
- &linux
|
||||||
env: SWIFT_VERSION=4.2.1
|
env: SWIFT_VERSION=4.2.1
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -33,7 +33,7 @@ print(foo) // => /bar/foo
|
|||||||
print(foo.isFile) // => true
|
print(foo.isFile) // => true
|
||||||
|
|
||||||
// we support dynamic members (_use_sparingly_):
|
// we support dynamic members (_use_sparingly_):
|
||||||
let prefs = Path.home.Library.Preferences
|
let prefs = Path.home.Library.Preferences // => /Users/mxcl/Library/Preferences
|
||||||
|
|
||||||
// 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").mkdir(.p)).chmod(0o500)
|
try Bundle.resources.join("helper").copy(into: Path.home.join(".local/bin").mkdir(.p)).chmod(0o500)
|
||||||
@@ -107,8 +107,9 @@ let ls = Path.root.usr.bin.ls // => /usr/bin/ls
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is less commonly useful than you would think, hence our documentation
|
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.
|
does not use it. Usually you are joining variables or other `String` arguments
|
||||||
However when you need it, it’s *lovely*.
|
or trying to describe files (and files usually have extensions). However when
|
||||||
|
you need it, it’s *lovely*.
|
||||||
|
|
||||||
## Initializing from user-input
|
## Initializing from user-input
|
||||||
|
|
||||||
@@ -160,9 +161,9 @@ for entry in Path.home.ls() where entry.path.mtime > yesterday {
|
|||||||
//…
|
//…
|
||||||
}
|
}
|
||||||
|
|
||||||
let dirs = Path.home.ls().directories().filter {
|
let dirs = Path.home.ls().directories
|
||||||
//…
|
|
||||||
}
|
let files = Path.home.ls().files
|
||||||
|
|
||||||
let swiftFiles = Path.home.ls().files(withExtension: "swift")
|
let swiftFiles = Path.home.ls().files(withExtension: "swift")
|
||||||
```
|
```
|
||||||
@@ -220,7 +221,7 @@ package.append(.package(url: "https://github.com/mxcl/Path.swift", from: "0.5.0"
|
|||||||
CocoaPods:
|
CocoaPods:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
pod 'Path.swift' ~> '0.5.0'
|
pod 'Path.swift', '~> 0.5'
|
||||||
```
|
```
|
||||||
|
|
||||||
Carthage:
|
Carthage:
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public extension Path {
|
|||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func move(to: Path, overwrite: Bool = false) throws -> Path {
|
func move(to: Path, overwrite: Bool = false) throws -> Path {
|
||||||
if overwrite, to.exists {
|
if overwrite, to.isFile {
|
||||||
try FileManager.default.removeItem(at: to.url)
|
try FileManager.default.removeItem(at: to.url)
|
||||||
}
|
}
|
||||||
try FileManager.default.moveItem(at: url, to: to.url)
|
try FileManager.default.moveItem(at: url, to: to.url)
|
||||||
@@ -112,13 +112,16 @@ public extension Path {
|
|||||||
- SeeAlso: move(into:overwrite:)
|
- SeeAlso: move(into:overwrite:)
|
||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func move(into: Path) throws -> Path {
|
func move(into: Path, overwrite: Bool = false) throws -> Path {
|
||||||
if !into.exists {
|
if !into.exists {
|
||||||
try into.mkdir(.p)
|
try into.mkdir(.p)
|
||||||
} else if !into.isDirectory {
|
} else if !into.isDirectory {
|
||||||
throw CocoaError.error(.fileWriteFileExists)
|
throw CocoaError.error(.fileWriteFileExists)
|
||||||
}
|
}
|
||||||
let rv = into/basename()
|
let rv = into/basename()
|
||||||
|
if overwrite, rv.isFile {
|
||||||
|
try FileManager.default.removeItem(at: rv.url)
|
||||||
|
}
|
||||||
try FileManager.default.moveItem(at: url, to: rv.url)
|
try FileManager.default.moveItem(at: url, to: rv.url)
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
@@ -171,6 +174,21 @@ public extension Path {
|
|||||||
}
|
}
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Renames the file at path.
|
||||||
|
|
||||||
|
Path.root.foo.bar.rename(to: "baz") // => /foo/baz
|
||||||
|
|
||||||
|
- Parameter to: the new basename for the file
|
||||||
|
- Returns: The renamed path.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func rename(to newname: String) throws -> Path {
|
||||||
|
let newpath = parent/newname
|
||||||
|
try FileManager.default.moveItem(atPath: string, toPath: newpath.string)
|
||||||
|
return newpath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options for `Path.mkdir(_:)`
|
/// Options for `Path.mkdir(_:)`
|
||||||
|
|||||||
@@ -51,7 +51,14 @@ public extension Array where Element == Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters the list of entries to be a list of Paths that are files with the specified extension
|
/// Filters the list of entries to be a list of Paths that are files.
|
||||||
|
var files: [Path] {
|
||||||
|
return compactMap {
|
||||||
|
$0.kind == .file ? $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] {
|
func files(withExtension ext: String) -> [Path] {
|
||||||
return compactMap {
|
return compactMap {
|
||||||
$0.kind == .file && $0.path.extension == ext ? $0.path : nil
|
$0.kind == .file && $0.path.extension == ext ? $0.path : nil
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Foundation
|
|||||||
`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 paths will return the locale-aware sort order, which
|
||||||
will give you the same order as Finder.
|
will give you the same order as Finder.
|
||||||
|
|
||||||
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
|
||||||
@@ -20,13 +20,12 @@ import Foundation
|
|||||||
let p3 = Path.cwd/relativePathString
|
let p3 = Path.cwd/relativePathString
|
||||||
let p4 = Path(userInput) ?? Path.cwd/userInput
|
let p4 = Path(userInput) ?? Path.cwd/userInput
|
||||||
|
|
||||||
If you are constructing Paths from static-strings we provide support for
|
If you are constructing paths from static-strings we provide support for
|
||||||
dynamic members:
|
dynamic members:
|
||||||
|
|
||||||
let p1 = Path.root.usr.bin.ls // => /usr/bin/ls
|
let p1 = Path.root.usr.bin.ls // => /usr/bin/ls
|
||||||
|
|
||||||
- Note: There may not be an actual filesystem entry at the path. The underlying
|
- Note: A `Path` does not necessarily represent an actual filesystem entry.
|
||||||
representation for `Path` is `String`.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@dynamicMemberLookup
|
@dynamicMemberLookup
|
||||||
@@ -36,7 +35,7 @@ public struct Path: Equatable, Hashable, Comparable {
|
|||||||
self.string = string
|
self.string = string
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `nil` unless fed an absolute path
|
/// Returns `nil` unless fed an absolute path.
|
||||||
public init?(_ description: String) {
|
public init?(_ description: String) {
|
||||||
guard description.starts(with: "/") || description.starts(with: "~/") else { return nil }
|
guard description.starts(with: "/") || description.starts(with: "~/") else { return nil }
|
||||||
self.init(string: (description as NSString).standardizingPath)
|
self.init(string: (description as NSString).standardizingPath)
|
||||||
@@ -73,10 +72,15 @@ public struct Path: Equatable, Hashable, Comparable {
|
|||||||
/**
|
/**
|
||||||
Returns the filename extension of this path.
|
Returns the filename extension of this path.
|
||||||
- Remark: Implemented via `NSString.pathExtension`.
|
- Remark: Implemented via `NSString.pathExtension`.
|
||||||
|
- Note: We special case eg. `foo.tar.gz`.
|
||||||
*/
|
*/
|
||||||
@inlinable
|
@inlinable
|
||||||
public var `extension`: String {
|
public var `extension`: String {
|
||||||
return (string as NSString).pathExtension
|
if string.hasSuffix(".tar.gz") {
|
||||||
|
return "tar.gz"
|
||||||
|
} else {
|
||||||
|
return (string as NSString).pathExtension
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: Pathing
|
//MARK: Pathing
|
||||||
@@ -91,7 +95,7 @@ public struct Path: Equatable, Hashable, Comparable {
|
|||||||
|
|
||||||
- Parameter pathComponent: The string to join with this path.
|
- Parameter pathComponent: The string to join with this path.
|
||||||
- Returns: A new joined path.
|
- Returns: A new joined path.
|
||||||
- SeeAlso: `Path./(_:, _:)`
|
- SeeAlso: `Path./(_:_:)`
|
||||||
*/
|
*/
|
||||||
public func join<S>(_ pathComponent: S) -> Path where S: StringProtocol {
|
public func join<S>(_ pathComponent: S) -> Path where S: StringProtocol {
|
||||||
//TODO standardizingPath does more than we want really (eg tilde expansion)
|
//TODO standardizingPath does more than we want really (eg tilde expansion)
|
||||||
|
|||||||
@@ -15,20 +15,26 @@ class PathTests: XCTestCase {
|
|||||||
let tmpdir_ = try TemporaryDirectory()
|
let tmpdir_ = try TemporaryDirectory()
|
||||||
let tmpdir = tmpdir_.path
|
let tmpdir = tmpdir_.path
|
||||||
try tmpdir.a.mkdir().c.touch()
|
try tmpdir.a.mkdir().c.touch()
|
||||||
try tmpdir.b.touch()
|
try tmpdir.join("b.swift").touch()
|
||||||
try tmpdir.c.touch()
|
try tmpdir.c.touch()
|
||||||
try tmpdir.join(".d").mkdir().e.touch()
|
try tmpdir.join(".d").mkdir().e.touch()
|
||||||
|
|
||||||
var paths = Set<String>()
|
var paths = Set<String>()
|
||||||
|
let lsrv = try tmpdir.ls()
|
||||||
var dirs = 0
|
var dirs = 0
|
||||||
for entry in try tmpdir.ls() {
|
for entry in lsrv {
|
||||||
if entry.kind == .directory {
|
if entry.kind == .directory {
|
||||||
dirs += 1
|
dirs += 1
|
||||||
}
|
}
|
||||||
paths.insert(entry.path.basename())
|
paths.insert(entry.path.basename())
|
||||||
}
|
}
|
||||||
XCTAssertEqual(dirs, 2)
|
XCTAssertEqual(dirs, 2)
|
||||||
XCTAssertEqual(paths, ["a", "b", "c", ".d"])
|
XCTAssertEqual(dirs, lsrv.directories.count)
|
||||||
|
XCTAssertEqual(["a", ".d"], Set(lsrv.directories.map{ $0.relative(to: tmpdir) }))
|
||||||
|
XCTAssertEqual(["b.swift", "c"], Set(lsrv.files.map{ $0.relative(to: tmpdir) }))
|
||||||
|
XCTAssertEqual(["b.swift"], Set(lsrv.files(withExtension: "swift").map{ $0.relative(to: tmpdir) }))
|
||||||
|
XCTAssertEqual(["c"], Set(lsrv.files(withExtension: "").map{ $0.relative(to: tmpdir) }))
|
||||||
|
XCTAssertEqual(paths, ["a", "b.swift", "c", ".d"])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +75,18 @@ class PathTests: XCTestCase {
|
|||||||
XCTAssert((Path.root/"bin").isDirectory)
|
XCTAssert((Path.root/"bin").isDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testExtension() {
|
||||||
|
XCTAssertEqual(Path.root.join("a.swift").extension, "swift")
|
||||||
|
XCTAssertEqual(Path.root.join("a").extension, "")
|
||||||
|
XCTAssertEqual(Path.root.join("a.").extension, "")
|
||||||
|
XCTAssertEqual(Path.root.join("a..").extension, "")
|
||||||
|
XCTAssertEqual(Path.root.join("a..swift").extension, "swift")
|
||||||
|
XCTAssertEqual(Path.root.join("a..swift.").extension, "")
|
||||||
|
XCTAssertEqual(Path.root.join("a.tar.gz").extension, "tar.gz")
|
||||||
|
XCTAssertEqual(Path.root.join("a..tar.gz").extension, "tar.gz")
|
||||||
|
XCTAssertEqual(Path.root.join("a..tar..gz").extension, "gz")
|
||||||
|
}
|
||||||
|
|
||||||
func testMktemp() throws {
|
func testMktemp() throws {
|
||||||
var path: Path!
|
var path: Path!
|
||||||
try Path.mktemp {
|
try Path.mktemp {
|
||||||
@@ -147,12 +165,42 @@ class PathTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCopyInto() throws {
|
func testCopyInto() throws {
|
||||||
|
try Path.mktemp { root1 in
|
||||||
|
let bar1 = try root1.join("bar").touch()
|
||||||
|
try Path.mktemp { root2 in
|
||||||
|
let bar2 = try root2.join("bar").touch()
|
||||||
|
XCTAssertThrowsError(try bar1.copy(into: root2))
|
||||||
|
try bar1.copy(into: root2, overwrite: true)
|
||||||
|
XCTAssertTrue(bar1.exists)
|
||||||
|
XCTAssertTrue(bar2.exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMoveInto() throws {
|
||||||
|
try Path.mktemp { root1 in
|
||||||
|
let bar1 = try root1.join("bar").touch()
|
||||||
|
try Path.mktemp { root2 in
|
||||||
|
let bar2 = try root2.join("bar").touch()
|
||||||
|
XCTAssertThrowsError(try bar1.move(into: root2))
|
||||||
|
try bar1.move(into: root2, overwrite: true)
|
||||||
|
XCTAssertFalse(bar1.exists)
|
||||||
|
XCTAssertTrue(bar2.exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRename() throws {
|
||||||
try Path.mktemp { root in
|
try Path.mktemp { root in
|
||||||
let bar = try root.join("bar").touch()
|
do {
|
||||||
try Path.mktemp { root in
|
let file = try root.bar.touch()
|
||||||
try root.join("bar").touch()
|
let foo = try file.rename(to: "foo")
|
||||||
XCTAssertThrowsError(try bar.copy(into: root))
|
XCTAssertFalse(file.exists)
|
||||||
try bar.copy(into: root, overwrite: true)
|
XCTAssertTrue(foo.isFile)
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let file = try root.bar.touch()
|
||||||
|
XCTAssertThrowsError(try file.rename(to: "foo"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ extension PathTests {
|
|||||||
("testEnumeration", testEnumeration),
|
("testEnumeration", testEnumeration),
|
||||||
("testEnumerationSkippingHiddenFiles", testEnumerationSkippingHiddenFiles),
|
("testEnumerationSkippingHiddenFiles", testEnumerationSkippingHiddenFiles),
|
||||||
("testExists", testExists),
|
("testExists", testExists),
|
||||||
|
("testExtension", testExtension),
|
||||||
("testIsDirectory", testIsDirectory),
|
("testIsDirectory", testIsDirectory),
|
||||||
("testJoin", testJoin),
|
("testJoin", testJoin),
|
||||||
("testMkpathIfExists", testMkpathIfExists),
|
("testMkpathIfExists", testMkpathIfExists),
|
||||||
("testMktemp", testMktemp),
|
("testMktemp", testMktemp),
|
||||||
|
("testMoveInto", testMoveInto),
|
||||||
("testRelativePathCodable", testRelativePathCodable),
|
("testRelativePathCodable", testRelativePathCodable),
|
||||||
("testRelativeTo", testRelativeTo),
|
("testRelativeTo", testRelativeTo),
|
||||||
|
("testRename", testRename),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user