Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e355c28e9 | ||
|
|
46b0cd883b | ||
|
|
9c6f807b0a | ||
|
|
dad3d84040 | ||
|
|
5377bceb5f | ||
|
|
f593437cf5 | ||
|
|
6e8a42f01d | ||
|
|
13d62c3068 | ||
|
|
99a0474b0f | ||
|
|
82640e629d | ||
|
|
f49e5c82c7 | ||
|
|
287afe3783 | ||
|
|
bb449ff412 | ||
|
|
14f03abaad | ||
|
|
ecbb3a60fe | ||
|
|
3af771f543 | ||
|
|
0b68e5c011 | ||
|
|
fec4ed25de | ||
|
|
6e78d9317e | ||
|
|
3035c45808 | ||
|
|
39f81ae258 | ||
|
|
670dc1163f | ||
|
|
eb33ff8906 | ||
|
|
f9cee2c75f | ||
|
|
7a974911d8 | ||
|
|
891d70ec7c |
24
.github/workflows/cd.yml
vendored
Normal file
24
.github/workflows/cd.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: CD
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: published
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: steven0351/publish-jazzy-docs@v1
|
||||||
|
with:
|
||||||
|
personal_access_token: ${{ secrets.PAT }}
|
||||||
|
config: .github/jazzy.yml
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
history: false
|
||||||
|
|
||||||
|
pods:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: pod trunk push --allow-warnings
|
||||||
|
env:
|
||||||
|
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||||
|
VERSION: ${{ github.event.release.tag_name }}
|
||||||
8
.github/workflows/checks.yml
vendored
8
.github/workflows/checks.yml
vendored
@@ -1,11 +1,13 @@
|
|||||||
name: Checks
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
paths:
|
||||||
|
- '**/*.swift'
|
||||||
|
- .github/workflows/checks.yml
|
||||||
jobs:
|
jobs:
|
||||||
macOS:
|
smoke:
|
||||||
runs-on: macos-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: swift --version
|
- run: swift --version
|
||||||
|
|||||||
140
.github/workflows/ci.yml
vendored
140
.github/workflows/ci.yml
vendored
@@ -1,99 +1,87 @@
|
|||||||
name: CI
|
name: CI
|
||||||
on: [pull_request]
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**/*.swift'
|
||||||
|
- .github/workflows/ci.yml
|
||||||
|
schedule:
|
||||||
|
- cron: '3 3 * * 5' # 3:03 AM, every Friday
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref || 'push' }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
smoke:
|
verify-linuxmain:
|
||||||
runs-on: macos-latest
|
runs-on: macos-10.15
|
||||||
steps:
|
steps:
|
||||||
- uses: technote-space/auto-cancel-redundant-job@v1
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: swift test --generate-linuxmain
|
- run: swift test --generate-linuxmain
|
||||||
- run: git diff --exit-code
|
- run: git diff --exit-code
|
||||||
macOS:
|
|
||||||
needs: smoke
|
apple:
|
||||||
runs-on: macos-latest
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode:
|
os:
|
||||||
# - 10.3 # Swift 5.0 (doesn’t work on GHA macOS image :-/)
|
- macos-10.15
|
||||||
- 11.3 # Swift 5.1
|
- macos-11
|
||||||
- ^11.4 # Swift 5.2
|
platform:
|
||||||
|
- iOS
|
||||||
|
- tvOS
|
||||||
|
- macOS
|
||||||
|
- watchOS
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: setup-xcode
|
- uses: mxcl/xcodebuild@v1
|
||||||
uses: maxim-lobanov/setup-xcode@1.0
|
|
||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.xcode }}
|
platform: ${{ matrix.platform }}
|
||||||
- run: swift --version
|
code-coverage: true
|
||||||
- run: swift test --parallel
|
warnings-as-errors: true
|
||||||
otherOS:
|
|
||||||
needs: smoke
|
|
||||||
runs-on: macos-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
destination:
|
|
||||||
- platform=iOS Simulator,OS=latest,name=iPhone 11
|
|
||||||
- platform=tvOS Simulator,OS=latest,name=Apple TV
|
|
||||||
- platform=macOS
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: maxim-lobanov/setup-xcode@1.0
|
|
||||||
with:
|
|
||||||
xcode-version: 12 # Swift 5.3
|
|
||||||
- run: swift --version
|
|
||||||
- run: swift package generate-xcodeproj --enable-code-coverage
|
|
||||||
- uses: sersoft-gmbh/xcodebuild-action@v1
|
|
||||||
with:
|
|
||||||
project: Path.swift.xcodeproj
|
|
||||||
scheme: Path.swift-Package
|
|
||||||
destination: ${{ matrix.destination }}
|
|
||||||
action: test
|
|
||||||
- uses: codecov/codecov-action@v1
|
- uses: codecov/codecov-action@v1
|
||||||
watchOS:
|
|
||||||
needs: smoke
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- run: swift package generate-xcodeproj
|
|
||||||
- uses: sersoft-gmbh/xcodebuild-action@v1
|
|
||||||
with:
|
|
||||||
project: Path.swift.xcodeproj
|
|
||||||
scheme: Path.swift-Package
|
|
||||||
destination: platform=watchOS Simulator,OS=latest,name=Apple Watch Series 5 - 40mm
|
|
||||||
action: build
|
|
||||||
linux-4-2:
|
|
||||||
needs: smoke
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: fwal/setup-swift@v1
|
|
||||||
with:
|
|
||||||
swift-version: 4.2
|
|
||||||
- run: swift test --parallel # doesn’t support code coverage
|
|
||||||
linux:
|
linux:
|
||||||
needs: smoke
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
swift:
|
swift:
|
||||||
- 5.0.3
|
- swift:4.2
|
||||||
- 5.1
|
- swift:5.0
|
||||||
- 5.2
|
- swift:5.1
|
||||||
# - 5.3 not yet available sigh
|
- swift:5.2
|
||||||
|
- swift:5.3
|
||||||
|
- swift:5.4
|
||||||
|
- swiftlang/swift:nightly-5.5
|
||||||
|
container:
|
||||||
|
image: ${{ matrix.swift }}
|
||||||
steps:
|
steps:
|
||||||
|
- uses: mxcl/get-swift-version@v1
|
||||||
|
id: swift
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: fwal/setup-swift@v1
|
|
||||||
with:
|
- run: useradd -ms /bin/bash mxcl
|
||||||
swift-version: ${{ matrix.swift }}
|
- run: chown -R mxcl .
|
||||||
- run: swift test --parallel --enable-code-coverage
|
# ^^ we need to be a normal user and not root for the tests to be valid
|
||||||
- name: Generate Coverage Report
|
|
||||||
|
- run: echo ARGS=--enable-code-coverage >> $GITHUB_ENV
|
||||||
|
if: ${{ steps.swift.outputs.marketing-version > 5 }}
|
||||||
|
|
||||||
|
- run: su mxcl -c "swift test --parallel $ARGS"
|
||||||
|
|
||||||
|
- name: Generate `.lcov`
|
||||||
|
if: ${{ steps.swift.outputs.marketing-version > 5 }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update && sudo apt-get -qq install llvm-10
|
apt-get -qq update && apt-get -qq install curl
|
||||||
export b=$(swift build --show-bin-path) && llvm-cov-10 \
|
b=$(swift build --show-bin-path)
|
||||||
export -format lcov \
|
llvm-cov export \
|
||||||
-instr-profile=$b/codecov/default.profdata \
|
-format lcov \
|
||||||
--ignore-filename-regex='\.build/' \
|
-instr-profile="$b"/codecov/default.profdata \
|
||||||
$b/Path.swiftPackageTests.xctest \
|
--ignore-filename-regex='\.build|Tests' \
|
||||||
|
"$b"/*.xctest \
|
||||||
> info.lcov
|
> info.lcov
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v1
|
- uses: codecov/codecov-action@v1
|
||||||
|
if: ${{ steps.swift.outputs.marketing-version > 5 }}
|
||||||
with:
|
with:
|
||||||
file: ./info.lcov
|
file: ./info.lcov
|
||||||
|
|||||||
16
Path.swift.podspec
Normal file
16
Path.swift.podspec
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Pod::Spec.new do |spec|
|
||||||
|
spec.name = "Path.swift"
|
||||||
|
spec.version = ENV['VERSION'] || "0.0.1"
|
||||||
|
spec.summary = "Delightful, robust, cross-platform and chainable file-pathing functions."
|
||||||
|
spec.homepage = "https://github.com/mxcl/Path.swift"
|
||||||
|
spec.license = "Unlicense"
|
||||||
|
spec.author = { "Max Howell" => "mxcl@me.com" }
|
||||||
|
spec.source = { :git => "https://github.com/mxcl/Path.swift.git", :tag => "#{spec.version}" }
|
||||||
|
spec.source_files = "Sources/*.swift"
|
||||||
|
spec.swift_versions = ['4.2', '5']
|
||||||
|
spec.module_name = 'Path'
|
||||||
|
spec.osx.deployment_target = '10.10'
|
||||||
|
spec.ios.deployment_target = '8.0'
|
||||||
|
spec.tvos.deployment_target = '9.0'
|
||||||
|
spec.watchos.deployment_target = '2.0'
|
||||||
|
end
|
||||||
34
README.md
34
README.md
@@ -45,16 +45,13 @@ 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
|
We emphasize safety and correctness, just like Swift, and also (again like
|
||||||
Swift), we provide a thoughtful and comprehensive (yet concise) API.
|
Swift), we provide a thoughtful and comprehensive (yet concise) API.
|
||||||
|
|
||||||
# Support mxcl
|
# Sponsor @mxcl
|
||||||
|
|
||||||
Hi, I’m Max Howell and I have written a lot of open source software—generally
|
Hi, I’m Max Howell and I have written a lot of open source software—generally
|
||||||
a good deal of my free time 👨🏻💻.
|
a good deal of my free time 👨🏻💻. Sponsorship helps me justify creating new open
|
||||||
|
source and maintaining it. Thank you.
|
||||||
|
|
||||||
<a href="https://www.patreon.com/mxcl">
|
[Sponsor @mxcl].
|
||||||
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
[Other donation/tipping options](http://mxcl.dev/#donate)
|
|
||||||
|
|
||||||
# Handbook
|
# Handbook
|
||||||
|
|
||||||
@@ -230,7 +227,7 @@ We provide `find()` for recursive listing:
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
for path in Path.home.find() {
|
for path in Path.home.find() {
|
||||||
// descends all directories, and includes hidden files
|
// descends all directories, and includes hidden files by default
|
||||||
// so it behaves the same as the terminal command `find`
|
// so it behaves the same as the terminal command `find`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -238,7 +235,7 @@ for path in Path.home.find() {
|
|||||||
It is configurable:
|
It is configurable:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
for path in Path.home.find().depth(max: 1).extension("swift").type(.file) {
|
for path in Path.home.find().depth(max: 1).extension("swift").type(.file).hidden(false) {
|
||||||
//…
|
//…
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -367,7 +364,7 @@ no filesystem entry there at all check if `type` is `nil`.
|
|||||||
|
|
||||||
Changing directory is dangerous, you should *always* try to avoid it and thus
|
Changing directory is dangerous, you should *always* try to avoid it and thus
|
||||||
we don’t even provide the method. If you are executing a sub-process then
|
we don’t even provide the method. If you are executing a sub-process then
|
||||||
use `Process.currentDirectoryURL` to change *its* working directory when it
|
use `Process.currentDirectoryURL` to change *its* working directory when it
|
||||||
executes.
|
executes.
|
||||||
|
|
||||||
If you must change directory then use `FileManager.changeCurrentDirectory` as
|
If you must change directory then use `FileManager.changeCurrentDirectory` as
|
||||||
@@ -408,7 +405,15 @@ developers) it is snappy and familiar.
|
|||||||
SwiftPM:
|
SwiftPM:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
package.append(.package(url: "https://github.com/mxcl/Path.swift.git", from: "1.0.0"))
|
package.append(
|
||||||
|
.package(url: "https://github.com/mxcl/Path.swift.git", from: "1.0.0")
|
||||||
|
)
|
||||||
|
|
||||||
|
package.targets.append(
|
||||||
|
.target(name: "Foo", dependencies: [
|
||||||
|
.product(name: "Path", package: "Path.swift")
|
||||||
|
])
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
CocoaPods:
|
CocoaPods:
|
||||||
@@ -421,6 +426,10 @@ Carthage:
|
|||||||
|
|
||||||
> Waiting on: [@Carthage#1945](https://github.com/Carthage/Carthage/pull/1945).
|
> Waiting on: [@Carthage#1945](https://github.com/Carthage/Carthage/pull/1945).
|
||||||
|
|
||||||
|
# Naming Conflicts with `SwiftUI.Path`, etc.
|
||||||
|
|
||||||
|
We have a typealias of `PathStruct` you can use instead.
|
||||||
|
|
||||||
# Alternatives
|
# Alternatives
|
||||||
|
|
||||||
* [Pathos](https://github.com/dduan/Pathos) by Daniel Duan
|
* [Pathos](https://github.com/dduan/Pathos) by Daniel Duan
|
||||||
@@ -430,7 +439,7 @@ Carthage:
|
|||||||
|
|
||||||
|
|
||||||
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20Linux%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg
|
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20Linux%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg
|
||||||
[badge-languages]: https://img.shields.io/badge/swift-4.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%205.3-orange.svg
|
[badge-languages]: https://img.shields.io/badge/swift-4.2%20%7C%205.x-orange.svg
|
||||||
[docs]: https://mxcl.dev/Path.swift/Structs/Path.html
|
[docs]: https://mxcl.dev/Path.swift/Structs/Path.html
|
||||||
[badge-jazzy]: https://raw.githubusercontent.com/mxcl/Path.swift/gh-pages/badge.svg?sanitize=true
|
[badge-jazzy]: https://raw.githubusercontent.com/mxcl/Path.swift/gh-pages/badge.svg?sanitize=true
|
||||||
[badge-codecov]: https://codecov.io/gh/mxcl/Path.swift/branch/master/graph/badge.svg
|
[badge-codecov]: https://codecov.io/gh/mxcl/Path.swift/branch/master/graph/badge.svg
|
||||||
@@ -439,3 +448,4 @@ Carthage:
|
|||||||
[codecov]: https://codecov.io/gh/mxcl/Path.swift
|
[codecov]: https://codecov.io/gh/mxcl/Path.swift
|
||||||
[badge-version]: https://img.shields.io/cocoapods/v/Path.swift.svg?label=version
|
[badge-version]: https://img.shields.io/cocoapods/v/Path.swift.svg?label=version
|
||||||
[cocoapods]: https://cocoapods.org/pods/Path.swift
|
[cocoapods]: https://cocoapods.org/pods/Path.swift
|
||||||
|
[Sponsor @mxcl]: https://github.com/sponsors/mxcl
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// The `extension` that provides static properties that are common directories.
|
/// The `extension` that provides static properties that are common directories.
|
||||||
extension Path {
|
private enum Foo {
|
||||||
//MARK: Common Directories
|
//MARK: Common Directories
|
||||||
|
|
||||||
/// Returns a `Path` containing `FileManager.default.currentDirectoryPath`.
|
/// Returns a `Path` containing `FileManager.default.currentDirectoryPath`.
|
||||||
public static var cwd: DynamicPath {
|
static var cwd: DynamicPath {
|
||||||
return .init(string: FileManager.default.currentDirectoryPath)
|
return .init(string: FileManager.default.currentDirectoryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `Path` representing the root path.
|
/// Returns a `Path` representing the root path.
|
||||||
public static var root: DynamicPath {
|
static var root: DynamicPath {
|
||||||
return .init(string: "/")
|
return .init(string: "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ extension Path {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Returns a `Path` representing the user’s home directory
|
/// Returns a `Path` representing the user’s home directory
|
||||||
public static var home: DynamicPath {
|
static var home: DynamicPath {
|
||||||
let string: String
|
let string: String
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if #available(OSX 10.12, *) {
|
if #available(OSX 10.12, *) {
|
||||||
@@ -70,7 +70,7 @@ extension Path {
|
|||||||
- Note: There is no standard location for documents on Linux, thus we return `~/Documents`.
|
- Note: There is no standard location for documents on Linux, thus we return `~/Documents`.
|
||||||
- Note: You should create a subdirectory before creating any files.
|
- Note: You should create a subdirectory before creating any files.
|
||||||
*/
|
*/
|
||||||
public static var documents: DynamicPath {
|
static var documents: DynamicPath {
|
||||||
return path(for: .documentDirectory)
|
return path(for: .documentDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ extension Path {
|
|||||||
- Note: On Linux this is `XDG_CACHE_HOME`.
|
- Note: On Linux this is `XDG_CACHE_HOME`.
|
||||||
- Note: You should create a subdirectory before creating any files.
|
- Note: You should create a subdirectory before creating any files.
|
||||||
*/
|
*/
|
||||||
public static var caches: DynamicPath {
|
static var caches: DynamicPath {
|
||||||
return path(for: .cachesDirectory)
|
return path(for: .cachesDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ extension Path {
|
|||||||
- Note: On Linux is `XDG_DATA_HOME`.
|
- Note: On Linux is `XDG_DATA_HOME`.
|
||||||
- Note: You should create a subdirectory before creating any files.
|
- Note: You should create a subdirectory before creating any files.
|
||||||
*/
|
*/
|
||||||
public static var applicationSupport: DynamicPath {
|
static var applicationSupport: DynamicPath {
|
||||||
return path(for: .applicationSupportDirectory)
|
return path(for: .applicationSupportDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,3 +108,35 @@ func defaultUrl(for searchPath: FileManager.SearchPathDirectory) -> DynamicPath
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// The `extension` that provides static properties that are common directories.
|
||||||
|
#if swift(>=5.5)
|
||||||
|
public extension Pathish where Self == Path {
|
||||||
|
static var home: DynamicPath { return Foo.home }
|
||||||
|
static var root: DynamicPath { return Foo.root }
|
||||||
|
static var cwd: DynamicPath { return Foo.cwd }
|
||||||
|
static var documents: DynamicPath { return Foo.documents }
|
||||||
|
static var caches: DynamicPath { return Foo.caches }
|
||||||
|
static var applicationSupport: DynamicPath { return Foo.applicationSupport }
|
||||||
|
static func source(for filePath: String = #filePath) -> (file: DynamicPath, directory: DynamicPath) {
|
||||||
|
return Foo.source(for: filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
public extension Path {
|
||||||
|
static var home: DynamicPath { return Foo.home }
|
||||||
|
static var root: DynamicPath { return Foo.root }
|
||||||
|
static var cwd: DynamicPath { return Foo.cwd }
|
||||||
|
static var documents: DynamicPath { return Foo.documents }
|
||||||
|
static var caches: DynamicPath { return Foo.caches }
|
||||||
|
static var applicationSupport: DynamicPath { return Foo.applicationSupport }
|
||||||
|
#if swift(>=5.3)
|
||||||
|
static func source(for filePath: String = #filePath) -> (file: DynamicPath, directory: DynamicPath) {
|
||||||
|
return Foo.source(for: filePath)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static func source(for file: String = #file) -> (file: DynamicPath, directory: DynamicPath) {
|
||||||
|
return Foo.source(for: file)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public extension Pathish {
|
|||||||
if overwrite, let tokind = to.type, tokind != .directory, type != .directory {
|
if overwrite, let tokind = to.type, tokind != .directory, type != .directory {
|
||||||
try FileManager.default.removeItem(at: to.url)
|
try FileManager.default.removeItem(at: to.url)
|
||||||
}
|
}
|
||||||
#if os(Linux) && !swift(>=5.3) // check if fixed
|
#if os(Linux)
|
||||||
|
//NOTE doing manually due to inconsistency in Linux Foundation behavior
|
||||||
if !overwrite, to.type != nil {
|
if !overwrite, to.type != nil {
|
||||||
throw CocoaError.error(.fileWriteFileExists)
|
throw CocoaError.error(.fileWriteFileExists)
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,8 @@ public extension Pathish {
|
|||||||
if overwrite, let kind = rv.type, kind != .directory {
|
if overwrite, let kind = rv.type, kind != .directory {
|
||||||
try FileManager.default.removeItem(at: rv.url)
|
try FileManager.default.removeItem(at: rv.url)
|
||||||
}
|
}
|
||||||
#if os(Linux) && !swift(>=5.3) // check if fixed
|
#if os(Linux)
|
||||||
|
//NOTE doing manually due to inconsistency in Linux Foundation behavior
|
||||||
if !overwrite, rv.type != nil {
|
if !overwrite, rv.type != nil {
|
||||||
throw CocoaError.error(.fileWriteFileExists)
|
throw CocoaError.error(.fileWriteFileExists)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public extension Path {
|
|||||||
|
|
||||||
/// The file extensions find operations will return. Files *and* directories unless you filter for `kinds`.
|
/// The file extensions find operations will return. Files *and* directories unless you filter for `kinds`.
|
||||||
private(set) public var extensions: Set<String>?
|
private(set) public var extensions: Set<String>?
|
||||||
|
|
||||||
|
/// Whether to return hidden files
|
||||||
|
public var hidden:Bool = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +53,12 @@ extension Path.Finder: Sequence, IteratorProtocol {
|
|||||||
if enumerator.level < depth.lowerBound {
|
if enumerator.level < depth.lowerBound {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !hidden, path.basename().hasPrefix(".") {
|
||||||
|
enumerator.skipDescendants()
|
||||||
|
continue
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if let type = path.type, !types.contains(type) { continue }
|
if let type = path.type, !types.contains(type) { continue }
|
||||||
if let exts = extensions, !exts.contains(path.extension) { continue }
|
if let exts = extensions, !exts.contains(path.extension) { continue }
|
||||||
return path
|
return path
|
||||||
@@ -114,6 +121,15 @@ public extension Path.Finder {
|
|||||||
extensions!.insert(ext)
|
extensions!.insert(ext)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to skip hidden files and folders.
|
||||||
|
func hidden(_ hidden: Bool) -> Path.Finder {
|
||||||
|
#if os(Linux) && !swift(>=5.0)
|
||||||
|
fputs("warning: hidden not implemented for Swift < 5\n", stderr)
|
||||||
|
#endif
|
||||||
|
self.hidden = hidden
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
/// The return type for `Path.Finder`
|
/// The return type for `Path.Finder`
|
||||||
enum ControlFlow {
|
enum ControlFlow {
|
||||||
@@ -198,6 +214,6 @@ public extension Array where Element == Path {
|
|||||||
|
|
||||||
/// Options for `Path.ls(_:)`
|
/// Options for `Path.ls(_:)`
|
||||||
public enum ListDirectoryOptions {
|
public enum ListDirectoryOptions {
|
||||||
/// Creates intermediary directories; works the same as `mkdir -p`.
|
/// Lists hidden files also
|
||||||
case a
|
case a
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import func Glibc.realpath
|
|||||||
let _realpath = Glibc.realpath
|
let _realpath = Glibc.realpath
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public typealias PathStruct = Path
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A `Path` represents an absolute path on a filesystem.
|
A `Path` represents an absolute path on a filesystem.
|
||||||
|
|
||||||
@@ -168,6 +170,7 @@ public extension Pathish {
|
|||||||
*/
|
*/
|
||||||
var parent: Path {
|
var parent: Path {
|
||||||
let index = string.lastIndex(of: "/")!
|
let index = string.lastIndex(of: "/")!
|
||||||
|
guard index != string.indices.startIndex else { return Path(string: "/") }
|
||||||
let substr = string[string.indices.startIndex..<index]
|
let substr = string[string.indices.startIndex..<index]
|
||||||
return Path(string: String(substr))
|
return Path(string: String(substr))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,28 @@ extension PathTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFindHidden() throws {
|
||||||
|
try Path.mktemp { tmpdir in
|
||||||
|
let dotFoo = try tmpdir.join(".foo.txt").touch()
|
||||||
|
let tmpDotA = try tmpdir.join(".a").mkdir()
|
||||||
|
let tmpDotAFoo = try tmpdir.join(".a").join("foo.txt").touch()
|
||||||
|
let tmpB = try tmpdir.b.mkdir()
|
||||||
|
let tmpBFoo = try tmpdir.b.join("foo.txt").touch()
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
Set(tmpdir.find().hidden(true)),
|
||||||
|
Set([dotFoo,tmpDotA,tmpDotAFoo,tmpB,tmpBFoo]),
|
||||||
|
relativeTo: tmpdir)
|
||||||
|
|
||||||
|
#if !os(Linux) || swift(>=5)
|
||||||
|
XCTAssertEqual(
|
||||||
|
Set(tmpdir.find().hidden(false)),
|
||||||
|
Set([tmpB,tmpBFoo]),
|
||||||
|
relativeTo: tmpdir)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testFindExtension() throws {
|
func testFindExtension() throws {
|
||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
try tmpdir.join("foo.json").touch()
|
try tmpdir.join("foo.json").touch()
|
||||||
|
|||||||
@@ -3,7 +3,20 @@ import func XCTest.XCTAssertEqual
|
|||||||
import Foundation
|
import Foundation
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
extension PathStruct {
|
||||||
|
var foo: Int { fatalError()}
|
||||||
|
}
|
||||||
|
|
||||||
class PathTests: XCTestCase {
|
class PathTests: XCTestCase {
|
||||||
|
func testNewStuff() {
|
||||||
|
#if swift(>=5.5)
|
||||||
|
func foo<P: Pathish>(_ path: P) {}
|
||||||
|
|
||||||
|
foo(.home)
|
||||||
|
foo(.root)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
func testConcatenation() {
|
func testConcatenation() {
|
||||||
XCTAssertEqual((Path.root/"bar").string, "/bar")
|
XCTAssertEqual((Path.root/"bar").string, "/bar")
|
||||||
XCTAssertEqual(Path.cwd.string, FileManager.default.currentDirectoryPath)
|
XCTAssertEqual(Path.cwd.string, FileManager.default.currentDirectoryPath)
|
||||||
@@ -192,6 +205,14 @@ class PathTests: XCTestCase {
|
|||||||
XCTAssertEqual(Path.root/"a/foo"/"../../../bar", Path.root/"bar")
|
XCTAssertEqual(Path.root/"a/foo"/"../../../bar", Path.root/"bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testParent() {
|
||||||
|
XCTAssertEqual(Path("/root/boot")!.parent.string, "/root")
|
||||||
|
XCTAssertEqual(Path("/root/boot")!.parent.parent.string, "/")
|
||||||
|
XCTAssertEqual(Path("/root/boot")!.parent.parent.parent.string, "/")
|
||||||
|
XCTAssertEqual(Path("/root")!.parent.string, "/")
|
||||||
|
XCTAssertEqual(Path("/root")!.parent.parent.string, "/")
|
||||||
|
}
|
||||||
|
|
||||||
func testDynamicMember() {
|
func testDynamicMember() {
|
||||||
XCTAssertEqual(Path.root.Documents, Path.root/"Documents")
|
XCTAssertEqual(Path.root.Documents, Path.root/"Documents")
|
||||||
|
|
||||||
@@ -199,7 +220,7 @@ class PathTests: XCTestCase {
|
|||||||
XCTAssertEqual(a.Documents, Path.home/"foo/Documents")
|
XCTAssertEqual(a.Documents, Path.home/"foo/Documents")
|
||||||
|
|
||||||
// verify use of the dynamic-member-subscript works according to our rules
|
// verify use of the dynamic-member-subscript works according to our rules
|
||||||
XCTAssertEqual(Path.home[dynamicMember: "../~foo"].string, "\(Path.home.parent.string)/~foo")
|
XCTAssertEqual(Path.home[dynamicMember: "../~foo"].string, Path(Path.home).parent.join("~foo").string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCopyTo() throws {
|
func testCopyTo() throws {
|
||||||
@@ -380,17 +401,13 @@ class PathTests: XCTestCase {
|
|||||||
|
|
||||||
func testTimes() throws {
|
func testTimes() throws {
|
||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
let foo = try tmpdir.foo.touch()
|
|
||||||
let now1 = Date().timeIntervalSince1970.rounded(.down)
|
let now1 = Date().timeIntervalSince1970.rounded(.down)
|
||||||
#if !os(Linux)
|
|
||||||
XCTAssertEqual(foo.ctime?.timeIntervalSince1970.rounded(.down), now1) //FIXME flakey
|
|
||||||
#endif
|
|
||||||
XCTAssertEqual(foo.mtime?.timeIntervalSince1970.rounded(.down), now1) //FIXME flakey
|
|
||||||
sleep(1)
|
sleep(1)
|
||||||
try foo.touch()
|
let foo = try tmpdir.foo.touch()
|
||||||
let now2 = Date().timeIntervalSince1970.rounded(.down)
|
#if !os(Linux)
|
||||||
XCTAssertNotEqual(now1, now2)
|
XCTAssertGreaterThan(foo.ctime?.timeIntervalSince1970.rounded(.down) ?? 0, now1) //FIXME flakey
|
||||||
XCTAssertEqual(foo.mtime?.timeIntervalSince1970.rounded(.down), now2) //FIXME flakey
|
#endif
|
||||||
|
XCTAssertGreaterThan(foo.mtime?.timeIntervalSince1970.rounded(.down) ?? 0, now1) //FIXME flakey
|
||||||
|
|
||||||
XCTAssertNil(tmpdir.void.mtime)
|
XCTAssertNil(tmpdir.void.mtime)
|
||||||
XCTAssertNil(tmpdir.void.ctime)
|
XCTAssertNil(tmpdir.void.ctime)
|
||||||
@@ -454,7 +471,7 @@ class PathTests: XCTestCase {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Contents.Frameworks)
|
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Contents.Frameworks)
|
||||||
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir.Contents.Resources)
|
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir.Contents.Resources)
|
||||||
#elseif os(tvOS) || os(iOS)
|
#elseif os(tvOS) || os(iOS) || os(watchOS)
|
||||||
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Frameworks)
|
XCTAssertEqual(bndl.defaultSharedFrameworksPath, tmpdir.Frameworks)
|
||||||
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir)
|
XCTAssertEqual(bndl.defaultResourcesPath, tmpdir)
|
||||||
#else
|
#else
|
||||||
@@ -506,8 +523,19 @@ class PathTests: XCTestCase {
|
|||||||
|
|
||||||
func testTouchThrowsIfCannotWrite() throws {
|
func testTouchThrowsIfCannotWrite() throws {
|
||||||
try Path.mktemp { tmpdir in
|
try Path.mktemp { tmpdir in
|
||||||
|
|
||||||
|
print(try FileManager.default.attributesOfItem(atPath: tmpdir.string)[.posixPermissions])
|
||||||
|
|
||||||
|
//FIXME fails in Docker image (only)
|
||||||
try tmpdir.chmod(0o000)
|
try tmpdir.chmod(0o000)
|
||||||
|
|
||||||
|
let attrs = try FileManager.default.attributesOfItem(atPath: tmpdir.string)
|
||||||
|
XCTAssertEqual(attrs[.posixPermissions] as? Int, 0)
|
||||||
|
|
||||||
|
print(attrs[.posixPermissions])
|
||||||
|
|
||||||
XCTAssertThrowsError(try tmpdir.bar.touch())
|
XCTAssertThrowsError(try tmpdir.bar.touch())
|
||||||
|
XCTAssertFalse(tmpdir.bar.exists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ extension PathTests {
|
|||||||
("testFindDepthRange", testFindDepthRange),
|
("testFindDepthRange", testFindDepthRange),
|
||||||
("testFindExecute", testFindExecute),
|
("testFindExecute", testFindExecute),
|
||||||
("testFindExtension", testFindExtension),
|
("testFindExtension", testFindExtension),
|
||||||
|
("testFindHidden", testFindHidden),
|
||||||
("testFindMaxDepth1", testFindMaxDepth1),
|
("testFindMaxDepth1", testFindMaxDepth1),
|
||||||
("testFindMaxDepth2", testFindMaxDepth2),
|
("testFindMaxDepth2", testFindMaxDepth2),
|
||||||
("testFindMinDepth", testFindMinDepth),
|
("testFindMinDepth", testFindMinDepth),
|
||||||
@@ -45,8 +46,10 @@ extension PathTests {
|
|||||||
("testMktemp", testMktemp),
|
("testMktemp", testMktemp),
|
||||||
("testMoveInto", testMoveInto),
|
("testMoveInto", testMoveInto),
|
||||||
("testMoveTo", testMoveTo),
|
("testMoveTo", testMoveTo),
|
||||||
|
("testNewStuff", testNewStuff),
|
||||||
("testNoUndesiredSymlinkResolution", testNoUndesiredSymlinkResolution),
|
("testNoUndesiredSymlinkResolution", testNoUndesiredSymlinkResolution),
|
||||||
("testOptionalInitializer", testOptionalInitializer),
|
("testOptionalInitializer", testOptionalInitializer),
|
||||||
|
("testParent", testParent),
|
||||||
("testPathComponents", testPathComponents),
|
("testPathComponents", testPathComponents),
|
||||||
("testReadlinkOnFileReturnsSelf", testReadlinkOnFileReturnsSelf),
|
("testReadlinkOnFileReturnsSelf", testReadlinkOnFileReturnsSelf),
|
||||||
("testReadlinkOnNonExistantFileThrows", testReadlinkOnNonExistantFileThrows),
|
("testReadlinkOnNonExistantFileThrows", testReadlinkOnNonExistantFileThrows),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ private func logic<P: Pathish>(_ set1: Set<Path>, _ set2: Set<Path>, relativeTo:
|
|||||||
if set1 != set2 {
|
if set1 != set2 {
|
||||||
let cvt: (Path) -> String = { $0.relative(to: relativeTo) }
|
let cvt: (Path) -> String = { $0.relative(to: relativeTo) }
|
||||||
let out1 = set1.map(cvt).sorted()
|
let out1 = set1.map(cvt).sorted()
|
||||||
let out2 = set1.map(cvt).sorted()
|
let out2 = set2.map(cvt).sorted()
|
||||||
fail("Set(\(out1)) is not equal to Set(\(out2))")
|
fail("Set(\(out1)) is not equal to Set(\(out2))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user