Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c84754ad8 | ||
|
|
8469565b06 | ||
|
|
ed45d10179 | ||
|
|
8033ae49b4 | ||
|
|
b290173486 | ||
|
|
8248354a80 | ||
|
|
14963e48f5 | ||
|
|
7f5340bc19 | ||
|
|
74074c634f | ||
|
|
74656bbfcd | ||
|
|
cced2af2cd | ||
|
|
b9abd07318 |
20
.travis.yml
20
.travis.yml
@@ -90,12 +90,15 @@ jobs:
|
||||
tags: true
|
||||
|
||||
- name: CocoaPods
|
||||
before_install: |
|
||||
before_install: export TRAVIS_REPO_NAME=${TRAVIS_REPO_SLUG#*/}
|
||||
install: gem install cocoapods
|
||||
before_script: |
|
||||
export DESCRIPTION=$(swift - <<\ \ EOF
|
||||
import Foundation
|
||||
struct Response: Decodable { let description: String }
|
||||
let token = ProcessInfo.processInfo.environment["GITHUB_TOKEN"]!
|
||||
let url = URL(string: "https://api.github.com/repos/mxcl/Path.swift")!
|
||||
let slug = ProcessInfo.processInfo.environment["TRAVIS_REPO_SLUG"]!
|
||||
let url = URL(string: "https://api.github.com/repos/\(slug)")!
|
||||
var rq = URLRequest(url: url)
|
||||
rq.setValue("token \(token)", forHTTPHeaderField: "Authorization")
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
@@ -108,15 +111,15 @@ jobs:
|
||||
let rsp = try JSONDecoder().decode(Response.self, from: data)
|
||||
print(rsp.description, terminator: "")
|
||||
EOF)
|
||||
cat <<\ \ EOF> Path.swift.podspec
|
||||
cat <<\ \ EOF> $TRAVIS_REPO_NAME.podspec
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'Path.swift'
|
||||
s.name = ENV['TRAVIS_REPO_NAME']
|
||||
s.version = ENV['TRAVIS_TAG']
|
||||
s.summary = ENV['DESCRIPTION']
|
||||
s.homepage = 'https://github.com/mxcl/Path.swift'
|
||||
s.license = { :type => 'Unlicense', :file => 'LICENSE.md' }
|
||||
s.author = { 'mxcl' => 'mxcl@me.com' }
|
||||
s.source = { :git => 'https://github.com/mxcl/Path.swift.git', :tag => s.version.to_s }
|
||||
s.homepage = "https://github.com/#{ENV['TRAVIS_REPO_SLUG']}"
|
||||
s.license = { type: 'Unlicense', file: 'LICENSE.md' }
|
||||
s.author = { mxcl: 'mxcl@me.com' }
|
||||
s.source = { git: "https://github.com/#{ENV['TRAVIS_REPO_SLUG']}.git", tag: s.version }
|
||||
s.social_media_url = 'https://twitter.com/mxcl'
|
||||
s.osx.deployment_target = '10.10'
|
||||
s.ios.deployment_target = '8.0'
|
||||
@@ -126,5 +129,4 @@ jobs:
|
||||
s.swift_version = '4.2'
|
||||
end
|
||||
EOF
|
||||
install: gem install cocoapods --pre
|
||||
script: pod trunk push
|
||||
|
||||
11
README.md
11
README.md
@@ -1,4 +1,4 @@
|
||||
# Path.swift ![badge-platforms][] ![badge-languages][] [![badge-ci][]][travis] [![badge-jazzy][]][docs] [![badge-codecov][]][codecov] ![badge-version][]
|
||||
# Path.swift ![badge-platforms][] ![badge-languages][] [![badge-ci][]][travis] [![badge-jazzy][]][docs] [![badge-codecov][]][codecov] [![badge-version][]][cocoapods]
|
||||
|
||||
A file-system pathing library focused on developer experience and robust end
|
||||
results.
|
||||
@@ -36,7 +36,7 @@ print(foo.isFile) // => true
|
||||
let prefs = Path.home.Library.Preferences // => /Users/mxcl/Library/Preferences
|
||||
|
||||
// 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.helper.copy(into: Path.root.usr.local.bin).chmod(0o500)
|
||||
```
|
||||
|
||||
We emphasize safety and correctness, just like Swift, and also (again like
|
||||
@@ -57,7 +57,8 @@ help me continue my work, I appreciate it x
|
||||
|
||||
# Handbook
|
||||
|
||||
Our [online API documentation][docs] is automatically updated for new releases.
|
||||
Our [online API documentation][docs] covers 100% of our public API and is
|
||||
automatically updated for new releases.
|
||||
|
||||
## Codable
|
||||
|
||||
@@ -229,7 +230,7 @@ for that as the check was deemed too expensive to be worthwhile.
|
||||
SwiftPM:
|
||||
|
||||
```swift
|
||||
package.append(.package(url: "https://github.com/mxcl/Path.swift", from: "0.5.0"))
|
||||
package.append(.package(url: "https://github.com/mxcl/Path.swift.git", from: "0.5.0"))
|
||||
```
|
||||
|
||||
CocoaPods:
|
||||
@@ -253,6 +254,7 @@ https://codebasesaga.com/canopy/
|
||||
|
||||
# Alternatives
|
||||
|
||||
* [Pathos](https://github.com/dduan/Pathos) by Daniel Duan
|
||||
* [PathKit](https://github.com/kylef/PathKit) by Kyle Fuller
|
||||
* [Files](https://github.com/JohnSundell/Files) by John Sundell
|
||||
* [Utility](https://github.com/apple/swift-package-manager) by Apple
|
||||
@@ -267,3 +269,4 @@ https://codebasesaga.com/canopy/
|
||||
[travis]: https://travis-ci.com/mxcl/Path.swift
|
||||
[codecov]: https://codecov.io/gh/mxcl/Path.swift
|
||||
[badge-version]: https://img.shields.io/cocoapods/v/Path.swift.svg?label=version
|
||||
[cocoapods]: https://cocoapods.org/pods/Path.swift
|
||||
|
||||
@@ -9,32 +9,25 @@ public extension Bundle {
|
||||
return str.flatMap(Path.init)
|
||||
}
|
||||
|
||||
/// Returns the path for the shared-frameworks directory in this bundle.
|
||||
/**
|
||||
Returns the path for the shared-frameworks directory in this bundle.
|
||||
- Note: This is typically `ShareFrameworks`
|
||||
*/
|
||||
var sharedFrameworks: Path {
|
||||
var `default`: Path {
|
||||
#if os(macOS)
|
||||
return path.join("Contents/Frameworks")
|
||||
#elseif os(Linux)
|
||||
return path.join("lib")
|
||||
#else
|
||||
return path.join("Frameworks")
|
||||
#endif
|
||||
return sharedFrameworksPath.flatMap(Path.init) ?? defaultSharedFrameworksPath
|
||||
}
|
||||
return sharedFrameworksPath.flatMap(Path.init) ?? `default`
|
||||
|
||||
/**
|
||||
Returns the path for the private-frameworks directory in this bundle.
|
||||
- Note: This is typically `Frameworks`
|
||||
*/
|
||||
var privateFrameworks: Path {
|
||||
return privateFrameworksPath.flatMap(Path.init) ?? defaultSharedFrameworksPath
|
||||
}
|
||||
|
||||
/// Returns the path for the resources directory in this bundle.
|
||||
var resources: Path {
|
||||
var `default`: Path {
|
||||
#if os(macOS)
|
||||
return path.join("Contents/Resources")
|
||||
#elseif os(Linux)
|
||||
return path.join("share")
|
||||
#else
|
||||
return path
|
||||
#endif
|
||||
}
|
||||
return resourcePath.flatMap(Path.init) ?? `default`
|
||||
return resourcePath.flatMap(Path.init) ?? defaultResourcesPath
|
||||
}
|
||||
|
||||
/// Returns the path for this bundle.
|
||||
@@ -107,3 +100,25 @@ public extension FileHandle {
|
||||
try self.init(forUpdating: path.url)
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Bundle {
|
||||
var defaultSharedFrameworksPath: Path {
|
||||
#if os(macOS)
|
||||
return path.join("Contents/Frameworks")
|
||||
#elseif os(Linux)
|
||||
return path.join("lib")
|
||||
#else
|
||||
return path.join("Frameworks")
|
||||
#endif
|
||||
}
|
||||
|
||||
var defaultResourcesPath: Path {
|
||||
#if os(macOS)
|
||||
return path.join("Contents/Resources")
|
||||
#elseif os(Linux)
|
||||
return path.join("share")
|
||||
#else
|
||||
return path
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import Foundation
|
||||
//#if os(Linux)
|
||||
//import func Glibc.chmod
|
||||
//#endif
|
||||
|
||||
public extension Path {
|
||||
//MARK: Filesystem Attributes
|
||||
@@ -40,15 +37,12 @@ public extension Path {
|
||||
*/
|
||||
@discardableResult
|
||||
func chmod(_ octal: Int) throws -> Path {
|
||||
// #if os(Linux)
|
||||
// Glibc.chmod(string, __mode_t(octal))
|
||||
// #else
|
||||
try FileManager.default.setAttributes([.posixPermissions: octal], ofItemAtPath: string)
|
||||
// #endif
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Applies the macOS filesystem “lock” attribute.
|
||||
- Note: If file is already locked, does nothing.
|
||||
- Note: If file doesn’t exist, throws.
|
||||
- Important: On Linux does nothing.
|
||||
@@ -70,6 +64,7 @@ public extension Path {
|
||||
- Note: If file isn‘t locked, does nothing.
|
||||
- Note: If file doesn’t exist, does nothing.
|
||||
- Important: On Linux does nothing.
|
||||
- SeeAlso: `lock()`
|
||||
*/
|
||||
@discardableResult
|
||||
func unlock() throws -> Path {
|
||||
|
||||
@@ -47,18 +47,7 @@ extension Path {
|
||||
fatalError()
|
||||
}
|
||||
#else
|
||||
guard let pathString = FileManager.default.urls(for: searchPath, in: .userDomainMask).first?.path else {
|
||||
switch searchPath {
|
||||
case .documentDirectory:
|
||||
return Path.home/"Documents"
|
||||
case .applicationSupportDirectory:
|
||||
return Path.home/"Library/Application Support"
|
||||
case .cachesDirectory:
|
||||
return Path.home/"Library/Caches"
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
guard let pathString = FileManager.default.urls(for: searchPath, in: .userDomainMask).first?.path else { return defaultUrl(for: searchPath) }
|
||||
return Path(string: pathString)
|
||||
#endif
|
||||
}
|
||||
@@ -90,3 +79,19 @@ extension Path {
|
||||
return path(for: .applicationSupportDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(Linux)
|
||||
func defaultUrl(for searchPath: FileManager.SearchPathDirectory) -> Path {
|
||||
switch searchPath {
|
||||
case .documentDirectory:
|
||||
return Path.home/"Documents"
|
||||
case .applicationSupportDirectory:
|
||||
return Path.home/"Library/Application Support"
|
||||
case .cachesDirectory:
|
||||
return Path.home/"Library/Caches"
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ public extension Path {
|
||||
// Create ~/.local/bin, copy `ls` there and make the new copy executable
|
||||
try Path.root.join("bin/ls").copy(into: Path.home.join(".local/bin").mkdir(.p)).chmod(0o500)
|
||||
|
||||
If the destination does not exist, this function creates the directory first.
|
||||
If the destination does not exist, this function creates the directory
|
||||
(including intermediary directories if necessary) first.
|
||||
|
||||
- Parameter into: Destination directory
|
||||
- Parameter overwrite: If true overwrites any file that already exists at `into`.
|
||||
@@ -56,12 +57,12 @@ public extension Path {
|
||||
`self` because even though *Path.swift’s* policy is to noop if the desired
|
||||
end result preexists, checking for this condition is too expensive a
|
||||
trade-off.
|
||||
- SeeAlso: `copy(into:overwrite:)`
|
||||
- SeeAlso: `copy(to:overwrite:)`
|
||||
*/
|
||||
@discardableResult
|
||||
func copy(into: Path, overwrite: Bool = false) throws -> Path {
|
||||
if !into.exists {
|
||||
try FileManager.default.createDirectory(at: into.url, withIntermediateDirectories: true)
|
||||
try into.mkdir(.p)
|
||||
}
|
||||
let rv = into/basename()
|
||||
if overwrite, rv.isFile {
|
||||
@@ -90,7 +91,7 @@ public extension Path {
|
||||
`self` because even though *Path.swift’s* policy is to noop if the desired
|
||||
end result preexists, checking for this condition is too expensive a
|
||||
trade-off.
|
||||
- SeeAlso: move(into:overwrite:)
|
||||
- SeeAlso: `move(into:overwrite:)`
|
||||
*/
|
||||
@discardableResult
|
||||
func move(to: Path, overwrite: Bool = false) throws -> Path {
|
||||
@@ -107,13 +108,14 @@ public extension Path {
|
||||
try Path.root.join("bar").move(into: .home)
|
||||
// => "/Users/mxcl/bar"
|
||||
|
||||
If the destination does not exist, this function creates the directory first.
|
||||
If the destination does not exist, this function creates the directory
|
||||
(including intermediary directories if necessary) first.
|
||||
|
||||
- Parameter into: Destination directory
|
||||
- Parameter overwrite: If true *overwrites* any file that already exists at `into`.
|
||||
- Note: `throws` if `into` is a file.
|
||||
- Returns: The `Path` of destination filename.
|
||||
- SeeAlso: move(into:overwrite:)
|
||||
- SeeAlso: `move(to:overwrite:)`
|
||||
*/
|
||||
@discardableResult
|
||||
func move(into: Path, overwrite: Bool = false) throws -> Path {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@testable import Path
|
||||
import XCTest
|
||||
import Path
|
||||
|
||||
class PathTests: XCTestCase {
|
||||
func testConcatenation() {
|
||||
@@ -253,6 +253,10 @@ class PathTests: XCTestCase {
|
||||
XCTAssertEqual(Path.caches.string, NSHomeDirectory() + "/Library/Caches")
|
||||
XCTAssertEqual(Path.cwd.string, FileManager.default.currentDirectoryPath)
|
||||
XCTAssertEqual(Path.applicationSupport.string, NSHomeDirectory() + "/Library/Application Support")
|
||||
|
||||
_ = defaultUrl(for: .documentDirectory)
|
||||
_ = defaultUrl(for: .cachesDirectory)
|
||||
_ = defaultUrl(for: .applicationSupportDirectory)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -352,13 +356,26 @@ class PathTests: XCTestCase {
|
||||
XCTAssertThrowsError(try JSONDecoder().decode([Path].self, from: data))
|
||||
}
|
||||
|
||||
func testBundleExtensions() {
|
||||
XCTAssertTrue(Bundle.main.path.isDirectory)
|
||||
XCTAssertTrue(Bundle.main.resources.isDirectory)
|
||||
func testBundleExtensions() throws {
|
||||
try Path.mktemp { tmpdir in
|
||||
let bndl = Bundle(path: tmpdir.string)!
|
||||
XCTAssertEqual(bndl.path, tmpdir)
|
||||
XCTAssertEqual(bndl.sharedFrameworks, tmpdir.SharedFrameworks)
|
||||
XCTAssertEqual(bndl.privateFrameworks, tmpdir.Frameworks)
|
||||
XCTAssertEqual(bndl.resources, tmpdir)
|
||||
XCTAssertNil(bndl.path(forResource: "foo", ofType: "bar"))
|
||||
|
||||
// don’t exist in tests
|
||||
_ = Bundle.main.path(forResource: "foo", ofType: "bar")
|
||||
_ = Bundle.main.sharedFrameworks
|
||||
#if os(macOS)
|
||||
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Contents.Frameworks)
|
||||
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir.Contents.Resources)
|
||||
#elseif os(tvOS) || os(iOS)
|
||||
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Frameworks)
|
||||
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir)
|
||||
#else
|
||||
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.lib)
|
||||
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir.share)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func testDataExtensions() throws {
|
||||
@@ -394,7 +411,17 @@ class PathTests: XCTestCase {
|
||||
XCTAssertThrowsError(try bar.touch())
|
||||
try bar.unlock()
|
||||
try bar.touch()
|
||||
|
||||
// a non existant file is already “unlocked”
|
||||
try tmpdir.nonExit.unlock()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func testTouchThrowsIfCannotWrite() throws {
|
||||
try Path.mktemp { tmpdir in
|
||||
try tmpdir.chmod(0o000)
|
||||
XCTAssertThrowsError(try tmpdir.bar.touch())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ extension PathTests {
|
||||
("testStringConvertibles", testStringConvertibles),
|
||||
("testStringExtensions", testStringExtensions),
|
||||
("testTimes", testTimes),
|
||||
("testTouchThrowsIfCannotWrite", testTouchThrowsIfCannotWrite),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user