Compare commits

..

10 Commits

Author SHA1 Message Date
repo-ranger[bot]
ab9a70e947 Merge pull request #25 from mxcl/rename
Refactor rename -> rename(to:)
2019-01-31 15:32:59 +00:00
Max Howell
49ef073e34 Refactor rename -> rename(to:) 2019-01-31 10:15:39 -05:00
Max Howell
889d825b3a Merge pull request #24 from mxcl/rename
Rename
2019-01-31 10:08:02 -05:00
Max Howell
f1cd06fdff Add CI post success hook yamls 2019-01-31 09:58:46 -05:00
Max Howell
c6e840b9b6 Add rename 2019-01-31 08:39:54 -05:00
Max Howell
eb34ac4af8 Add overwrite parameter to move(into:) 2019-01-31 08:37:32 -05:00
Max Howell
66ae86c986 Enable codecov.io 2019-01-31 08:37:14 -05:00
Max Howell
c432f710eb Merge pull request #22 from mxcl/files()
Entry.files defaults to all files
2019-01-28 12:17:08 -05:00
Max Howell
19c0c19bb6 Entry.files defaults to all files 2019-01-28 12:04:47 -05:00
Max Howell
ee1f46954c Fixes #20
[skip ci]
2019-01-28 11:05:51 -05:00
9 changed files with 114 additions and 26 deletions

2
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
ignore:
- Tests

3
.github/ranger.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
merges:
- action: delete_branch
- action: tag

View File

@@ -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

View File

@@ -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, its *lovely*. or trying to describe files (and files usually have extensions). However when
you need it, its *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:

View File

@@ -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(_:)`

View File

@@ -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

View File

@@ -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)

View File

@@ -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"))
} }
} }
} }

View File

@@ -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),
] ]
} }