├── .cargo └── config.toml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── docs.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock.rust134 ├── Cargo.lock.rust156 ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── camino-examples ├── .gitignore ├── Cargo.toml ├── README.md ├── benches │ └── bench.rs └── src │ ├── bin │ ├── clap-derive.rs │ └── serde.rs │ └── lib.rs ├── clippy.toml ├── release.toml ├── rustfmt.toml ├── src ├── lib.rs ├── proptest_impls.rs ├── serde_impls.rs └── tests.rs └── tests └── integration_tests.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xfmt = "fmt --all -- --config imports_granularity=Crate --config group_imports=One" 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sunshowers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | allow: 11 | - dependency-type: all 12 | ignore: 13 | - dependency-name: "clap" 14 | update-types: ["version-update:semver-major"] 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | name: CI 10 | 11 | concurrency: 12 | # Cancel in-progress jobs for pull requests but not for main branch runs. 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | lint: 18 | name: Lint 19 | runs-on: ubuntu-latest 20 | env: 21 | RUSTFLAGS: -D warnings 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@stable 25 | with: 26 | components: rustfmt, clippy 27 | - name: Lint (clippy) 28 | run: cargo clippy --workspace --all-features --all-targets 29 | - name: Lint (clippy in camino-examples) 30 | run: | 31 | cd camino-examples && cargo clippy --all-features --all-targets 32 | - name: Lint (rustfmt) 33 | run: cargo xfmt --check 34 | - name: Lint (rustfmt in camino-examples) 35 | run: | 36 | cd camino-examples && cargo xfmt --check 37 | - name: Check for differences 38 | run: git diff --exit-code 39 | 40 | build: 41 | name: Build and test 42 | runs-on: ${{ matrix.os }} 43 | strategy: 44 | matrix: 45 | # macos-14 for M1 runners 46 | os: [ ubuntu-latest, macos-14, windows-latest ] 47 | # 1.34 is the MSRV, and the other versions add new features through build.rs. 48 | # The nightly versions are used to ensure that build.rs's version detection is compatible 49 | # with them: 50 | # nightly-2022-06-17 is the last toolchain before `try_reserve_2` was stabilized. 51 | # nightly-2022-12-14 is the last toolchain before `path_buf_deref_mut` was stabilized. 52 | rust-version: 53 | - 1.34 54 | - 1.44 55 | - 1.56 56 | - nightly-2022-06-17 57 | - 1.63 58 | - nightly-2022-12-14 59 | - 1.68 60 | - 1.74 61 | - 1.79 62 | - stable 63 | exclude: 64 | # These versions started failing with "archive member 'lib.rmeta' with length 26456 is not 65 | # mach-o or llvm bitcode file". 66 | - os: macos-14 67 | rust-version: 1.34 68 | - os: macos-14 69 | rust-version: 1.44 70 | fail-fast: false 71 | env: 72 | RUSTFLAGS: -D warnings 73 | # Use the Git CLI to do fetches -- apparently there's something wrong with the version of 74 | # libgit2 shipped with Rust 1.44. This fixes that. 75 | CARGO_NET_GIT_FETCH_WITH_CLI: true 76 | steps: 77 | - uses: actions/checkout@v3 78 | - uses: dtolnay/rust-toolchain@v1 79 | with: 80 | toolchain: ${{ matrix.rust-version }} 81 | - name: Disable sparse registries on nightly-2022-12-14 82 | # Sparse registries are experimental on this nightly, but are enabled by 83 | # dtolnay/rust-toolchain. 84 | if: matrix.rust-version == 'nightly-2022-12-14' 85 | shell: bash 86 | run: | 87 | echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git >> $GITHUB_ENV 88 | - name: Install cargo-hack 89 | uses: taiki-e/install-action@cargo-hack 90 | - name: Use pinned Cargo.lock for Rust 1.34 and 1.44 91 | if: ${{ matrix.rust-version == 1.34 || matrix.rust-version == 1.44 }} 92 | run: cp Cargo.lock.rust134 Cargo.lock 93 | - name: Use pinned Cargo.lock for Rust 1.56-1.63 94 | if: ${{ matrix.rust-version == 1.56 || matrix.rust-version == 'nightly-2022-06-17' || matrix.rust-version == 1.63 }} 95 | run: cp Cargo.lock.rust156 Cargo.lock 96 | - name: Build the library 97 | run: cargo build 98 | - name: Test 99 | run: cargo test 100 | - name: Build all targets with all features 101 | # Some optional features are not compatible with earlier versions 102 | if: ${{ matrix.rust-version == 'stable' }} 103 | run: cargo hack --feature-powerset build --workspace --all-targets 104 | - name: Test all targets with all features 105 | # Some optional features are not compatible with earlier versions 106 | if: ${{ matrix.rust-version == 'stable' }} 107 | run: cargo hack --feature-powerset test --workspace --all-targets 108 | - name: Build camino-examples 109 | if: ${{ matrix.rust-version == 'stable' }} 110 | run: | 111 | cd camino-examples && cargo build 112 | - name: Test camino-examples 113 | if: ${{ matrix.rust-version == 'stable' }} 114 | run: | 115 | cd camino-examples && cargo test 116 | 117 | miri: 118 | name: Check unsafe code against miri 119 | runs-on: ubuntu-latest 120 | env: 121 | RUSTFLAGS: -D warnings 122 | steps: 123 | - uses: actions/checkout@v3 124 | - uses: dtolnay/rust-toolchain@v1 125 | with: 126 | toolchain: nightly 127 | components: rust-src, miri 128 | - name: Test the library 129 | run: cargo miri test 130 | env: 131 | MIRIFLAGS: -Zmiri-disable-isolation 132 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Docs 7 | 8 | jobs: 9 | docs: 10 | name: Build and deploy documentation 11 | concurrency: ci-${{ github.ref }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: dtolnay/rust-toolchain@stable 16 | - uses: Swatinem/rust-cache@v2 17 | - name: Build rustdoc 18 | env: 19 | RUSTC_BOOTSTRAP: 1 20 | RUSTDOCFLAGS: -Dwarnings --cfg=doc_cfg 21 | run: cargo doc --all-features --workspace 22 | - name: Organize 23 | run: | 24 | rm -rf target/gh-pages 25 | mkdir target/gh-pages 26 | mv target/doc target/gh-pages/rustdoc 27 | - name: Deploy 28 | uses: JamesIves/github-pages-deploy-action@releases/v4 29 | with: 30 | branch: gh-pages 31 | folder: target/gh-pages 32 | single-commit: true 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # adapted from https://github.com/taiki-e/cargo-hack/blob/main/.github/workflows/release.yml 2 | 3 | name: Publish release 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | create-release: 11 | if: github.repository_owner == 'camino-rs' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | persist-credentials: false 17 | - name: Install Rust 18 | uses: dtolnay/rust-toolchain@stable 19 | - run: cargo publish 20 | env: 21 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 22 | - uses: taiki-e/create-gh-release-action@v1 23 | with: 24 | changelog: CHANGELOG.md 25 | title: camino $version 26 | branch: main 27 | prefix: camino 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [1.1.9] - 2024-08-17 7 | 8 | ### Added 9 | 10 | - Top-level function `absolute_utf8` wraps `std::path::absolute`, converting paths to UTF-8. 11 | Requires Rust 1.79 and above. 12 | 13 | ## [1.1.8] - 2024-08-15 14 | 15 | ### Changed 16 | 17 | - Use `OsStr::as_encoded_bytes` on Rust 1.74 and above, making conversions from `OsStr` to `str` virtually free ([#93](https://github.com/camino-rs/camino/pull/93)). Thanks [@h-a-n-a](https://github.com/h-a-n-a) for your first contribution! 18 | 19 | ## [1.1.7] - 2024-05-14 20 | 21 | ### Fixed 22 | 23 | - Resolve `unexpected_cfg` warnings. 24 | 25 | ## [1.1.6] - 2023-07-11 26 | 27 | ### Added 28 | 29 | - Implement `Deserialize` for `Box`. 30 | 31 | ## [1.1.5] - 2023-07-11 32 | 33 | (This release was not published due to an internal issue.) 34 | 35 | ## [1.1.4] - 2023-03-09 36 | 37 | ### Added 38 | 39 | - Implement `DerefMut` for `Utf8PathBuf` on Rust 1.68 and above. 40 | 41 | ## [1.1.3] - 2023-02-21 42 | 43 | ### Added 44 | 45 | - New method `Utf8DirEntry::into_path` to return an owned `Utf8PathBuf`. 46 | 47 | ## [1.1.2] - 2022-08-12 48 | 49 | ### Added 50 | 51 | - New convenience methods [`FromPathBufError::into_io_error`] and 52 | [`FromPathError::into_io_error`]. 53 | 54 | ## [1.1.1] - 2022-08-12 55 | 56 | ### Fixed 57 | 58 | - Fixed a build regression on older nightlies in the 1.63 series 59 | ([#22](https://github.com/camino-rs/camino/issues/22)). 60 | - Documentation fixes. 61 | 62 | ## [1.1.0] - 2022-08-11 63 | 64 | ### Added 65 | 66 | - New methods, mirroring those in recent versions of Rust: 67 | - `Utf8Path::try_exists` checks whether a path exists. Note that while `std::path::Path` only provides this method for Rust 1.58 and above, `camino` backfills the method for all Rust versions it supports. 68 | - `Utf8PathBuf::shrink_to` shrinks a `Utf8PathBuf` to a given size. This was added in, and is gated on, Rust 1.56+. 69 | - `Utf8PathBuf::try_reserve` and `Utf8PathBuf::try_reserve_exact` implement fallible allocations. These were added in, and are gated on, Rust 1.63+. 70 | - A number of `#[must_use]` annotations to APIs, mirroring those added to `Path` and `PathBuf` in recent versions of Rust. The minor version bump is due to this change. 71 | 72 | ## [1.0.9] - 2022-05-19 73 | 74 | ### Fixed 75 | 76 | - Documentation fixes. 77 | 78 | ## [1.0.8] - 2022-05-09 79 | 80 | ### Added 81 | 82 | - New methods `canonicalize_utf8`, `read_link_utf8` and `read_dir_utf8` return `Utf8PathBuf`s, erroring out if a resulting path is not valid UTF-8. 83 | - New feature `proptest1` introduces proptest `Arbitrary` impls for `Utf8PathBuf` and 84 | `Box` ([#18], thanks [mcronce](https://github.com/mcronce) for your first contribution!) 85 | 86 | [#18]: https://github.com/camino-rs/camino/pull/18 87 | 88 | ## [1.0.7] - 2022-01-16 89 | 90 | ### Added 91 | 92 | - `Utf8Path::is_symlink` checks whether a path is a symlink. Note that while `std::path::Path` only 93 | provides this method for version 1.58 and above, `camino` backfills the method for all Rust versions 94 | it supports. 95 | 96 | ### Changed 97 | 98 | - Update repository links to new location [camino-rs/camino](https://github.com/camino-rs/camino). 99 | - Update `structopt` example to clap 3's builtin derive feature. 100 | (camino continues to work with structopt as before.) 101 | 102 | ## [1.0.6] - 2022-01-16 103 | 104 | (This release was yanked due to a publishing issue.) 105 | 106 | ## [1.0.5] - 2021-07-27 107 | 108 | ### Added 109 | 110 | - `Utf8PathBuf::into_std_path_buf` converts a `Utf8PathBuf` to a `PathBuf`; equivalent to the 111 | `From for PathBuf` impl, but may aid in type inference. 112 | - `Utf8Path::as_std_path` converts a `Utf8Path` to a `Path`; equivalent to the 113 | `AsRef<&Path> for &Utf8Path` impl, but may aid in type inference. 114 | 115 | ## [1.0.4] - 2021-03-19 116 | 117 | ### Fixed 118 | 119 | - `Hash` impls for `Utf8PathBuf` and `Utf8Path` now match as required by the `Borrow` contract ([#9]). 120 | 121 | [#9]: https://github.com/camino-rs/camino/issues/9 122 | 123 | ## [1.0.3] - 2021-03-11 124 | 125 | ### Added 126 | 127 | - `TryFrom for Utf8PathBuf` and `TryFrom<&Path> for &Utf8Path`, both of which return new error types ([#6]). 128 | - `AsRef`, `AsRef`, `AsRef` and `AsRef` impls for `Utf8Components`, `Utf8Component` and 129 | `Iter`. 130 | 131 | [#6]: https://github.com/camino-rs/camino/issues/6 132 | 133 | ## [1.0.2] - 2021-03-02 134 | 135 | ### Added 136 | 137 | - `From` impls for converting a `&Utf8Path` or a `Utf8PathBuf` into `Box`, `Rc`, `Arc` and `Cow<'a, Path>`. 138 | - `PartialEq` and `PartialOrd` implementations comparing `Utf8Path` and `Utf8PathBuf` with `Path`, `PathBuf` and its 139 | variants, and comparing `OsStr`, `OsString` and its variants. 140 | 141 | ## [1.0.1] - 2021-02-25 142 | 143 | ### Added 144 | 145 | - More `PartialEq` and `PartialOrd` implementations. 146 | - MSRV lowered to 1.34. 147 | 148 | ## [1.0.0] - 2021-02-23 149 | 150 | Initial release. 151 | 152 | [1.1.9]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.9 153 | [1.1.8]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.8 154 | [1.1.7]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.7 155 | [1.1.6]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.6 156 | [1.1.5]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.5 157 | [1.1.4]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.4 158 | [1.1.3]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.3 159 | [1.1.2]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.2 160 | [1.1.1]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.1 161 | [1.1.0]: https://github.com/camino-rs/camino/releases/tag/camino-1.1.0 162 | [1.0.9]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.9 163 | [1.0.8]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.8 164 | [1.0.7]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.7 165 | [1.0.6]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.6 166 | [1.0.5]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.5 167 | [1.0.4]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.4 168 | [1.0.3]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.3 169 | [1.0.2]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.2 170 | [1.0.1]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.1 171 | [1.0.0]: https://github.com/camino-rs/camino/releases/tag/camino-1.0.0 172 | -------------------------------------------------------------------------------- /Cargo.lock.rust134: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.79" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "atty" 10 | version = "0.2.14" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 16 | ] 17 | 18 | [[package]] 19 | name = "autocfg" 20 | version = "1.1.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | 23 | [[package]] 24 | name = "bincode" 25 | version = "1.3.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 29 | ] 30 | 31 | [[package]] 32 | name = "bit-set" 33 | version = "0.5.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | dependencies = [ 36 | "bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "bit-vec" 41 | version = "0.6.3" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "1.3.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "byteorder" 51 | version = "1.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "camino" 56 | version = "1.1.6" 57 | dependencies = [ 58 | "bincode 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "proptest 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "serde_bytes 0.11.10 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "camino-examples" 66 | version = "0.1.0" 67 | dependencies = [ 68 | "anyhow 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "camino 1.1.6", 70 | "clap 3.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "serde_json 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "0.1.10" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "1.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | 85 | [[package]] 86 | name = "clap" 87 | version = "3.2.25" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | dependencies = [ 90 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "clap_derive 3.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "clap_lex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "indexmap 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "once_cell 1.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "termcolor 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "textwrap 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_derive" 103 | version = "3.2.25" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "heck 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "clap_lex" 115 | version = "0.2.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | dependencies = [ 118 | "os_str_bytes 6.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 119 | ] 120 | 121 | [[package]] 122 | name = "fastrand" 123 | version = "1.9.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 127 | ] 128 | 129 | [[package]] 130 | name = "fnv" 131 | version = "1.0.7" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | 134 | [[package]] 135 | name = "getrandom" 136 | version = "0.2.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "hashbrown" 146 | version = "0.12.3" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "heck" 151 | version = "0.4.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | 154 | [[package]] 155 | name = "hermit-abi" 156 | version = "0.1.19" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | dependencies = [ 159 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "indexmap" 164 | version = "1.9.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | dependencies = [ 167 | "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "hashbrown 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 169 | ] 170 | 171 | [[package]] 172 | name = "instant" 173 | version = "0.1.12" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 177 | ] 178 | 179 | [[package]] 180 | name = "itoa" 181 | version = "1.0.10" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | 184 | [[package]] 185 | name = "lazy_static" 186 | version = "1.4.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | 189 | [[package]] 190 | name = "libc" 191 | version = "0.2.152" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | 194 | [[package]] 195 | name = "num-traits" 196 | version = "0.2.17" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "once_cell" 204 | version = "1.19.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "os_str_bytes" 209 | version = "6.6.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "ppv-lite86" 214 | version = "0.2.17" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "proc-macro-error" 219 | version = "1.0.4" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | dependencies = [ 222 | "proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 223 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 224 | "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", 227 | ] 228 | 229 | [[package]] 230 | name = "proc-macro-error-attr" 231 | version = "1.0.4" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | dependencies = [ 234 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "proc-macro2" 241 | version = "1.0.46" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "unicode-ident 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "proptest" 249 | version = "1.0.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | dependencies = [ 252 | "bit-set 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 256 | "num-traits 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "quick-error 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "rand 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 259 | "rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "rand_xorshift 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "rusty-fork 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 263 | "tempfile 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 264 | ] 265 | 266 | [[package]] 267 | name = "quick-error" 268 | version = "1.2.3" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | 271 | [[package]] 272 | name = "quick-error" 273 | version = "2.0.1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | 276 | [[package]] 277 | name = "quote" 278 | version = "1.0.9" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | dependencies = [ 281 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "rand" 286 | version = "0.8.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "rand_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 292 | ] 293 | 294 | [[package]] 295 | name = "rand_chacha" 296 | version = "0.3.1" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | dependencies = [ 299 | "ppv-lite86 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "rand_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 301 | ] 302 | 303 | [[package]] 304 | name = "rand_core" 305 | version = "0.6.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | dependencies = [ 308 | "getrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 309 | ] 310 | 311 | [[package]] 312 | name = "rand_xorshift" 313 | version = "0.3.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | dependencies = [ 316 | "rand_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 317 | ] 318 | 319 | [[package]] 320 | name = "redox_syscall" 321 | version = "0.2.16" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | dependencies = [ 324 | "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 325 | ] 326 | 327 | [[package]] 328 | name = "regex-syntax" 329 | version = "0.6.7" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | dependencies = [ 332 | "ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 333 | ] 334 | 335 | [[package]] 336 | name = "remove_dir_all" 337 | version = "0.5.3" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | dependencies = [ 340 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 341 | ] 342 | 343 | [[package]] 344 | name = "rusty-fork" 345 | version = "0.3.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | dependencies = [ 348 | "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "tempfile 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "ryu" 356 | version = "1.0.16" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | 359 | [[package]] 360 | name = "serde" 361 | version = "1.0.100" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | dependencies = [ 364 | "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 365 | ] 366 | 367 | [[package]] 368 | name = "serde_bytes" 369 | version = "0.11.10" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | dependencies = [ 372 | "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 373 | ] 374 | 375 | [[package]] 376 | name = "serde_derive" 377 | version = "1.0.100" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | dependencies = [ 380 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 381 | "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 382 | "syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)", 383 | ] 384 | 385 | [[package]] 386 | name = "serde_json" 387 | version = "1.0.99" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | dependencies = [ 390 | "itoa 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "ryu 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", 393 | ] 394 | 395 | [[package]] 396 | name = "strsim" 397 | version = "0.10.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | 400 | [[package]] 401 | name = "syn" 402 | version = "1.0.109" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | dependencies = [ 405 | "proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 406 | "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 407 | "unicode-ident 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", 408 | ] 409 | 410 | [[package]] 411 | name = "tempfile" 412 | version = "3.3.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | dependencies = [ 415 | "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 416 | "fastrand 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 417 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 418 | "redox_syscall 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", 419 | "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 420 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 421 | ] 422 | 423 | [[package]] 424 | name = "termcolor" 425 | version = "1.4.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | dependencies = [ 428 | "winapi-util 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 429 | ] 430 | 431 | [[package]] 432 | name = "textwrap" 433 | version = "0.16.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | 436 | [[package]] 437 | name = "ucd-util" 438 | version = "0.1.10" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | 441 | [[package]] 442 | name = "unicode-ident" 443 | version = "1.0.12" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | 446 | [[package]] 447 | name = "version_check" 448 | version = "0.9.4" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | 451 | [[package]] 452 | name = "wait-timeout" 453 | version = "0.2.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | dependencies = [ 456 | "libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)", 457 | ] 458 | 459 | [[package]] 460 | name = "wasi" 461 | version = "0.9.0+wasi-snapshot-preview1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | 464 | [[package]] 465 | name = "winapi" 466 | version = "0.3.9" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | dependencies = [ 469 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 470 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 471 | ] 472 | 473 | [[package]] 474 | name = "winapi-i686-pc-windows-gnu" 475 | version = "0.4.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | 478 | [[package]] 479 | name = "winapi-util" 480 | version = "0.1.6" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | dependencies = [ 483 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 484 | ] 485 | 486 | [[package]] 487 | name = "winapi-x86_64-pc-windows-gnu" 488 | version = "0.4.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | 491 | [metadata] 492 | "checksum anyhow 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 493 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 494 | "checksum autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 495 | "checksum bincode 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 496 | "checksum bit-set 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 497 | "checksum bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 498 | "checksum bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 499 | "checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" 500 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 501 | "checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 502 | "checksum clap 3.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 503 | "checksum clap_derive 3.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" 504 | "checksum clap_lex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 505 | "checksum fastrand 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 506 | "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 507 | "checksum getrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" 508 | "checksum hashbrown 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 509 | "checksum heck 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 510 | "checksum hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 511 | "checksum indexmap 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 512 | "checksum instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 513 | "checksum itoa 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 514 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 515 | "checksum libc 0.2.152 (registry+https://github.com/rust-lang/crates.io-index)" = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" 516 | "checksum num-traits 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 517 | "checksum once_cell 1.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 518 | "checksum os_str_bytes 6.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 519 | "checksum ppv-lite86 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 520 | "checksum proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 521 | "checksum proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 522 | "checksum proc-macro2 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" 523 | "checksum proptest 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" 524 | "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 525 | "checksum quick-error 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 526 | "checksum quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 527 | "checksum rand 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" 528 | "checksum rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 529 | "checksum rand_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 530 | "checksum rand_xorshift 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" 531 | "checksum redox_syscall 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 532 | "checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" 533 | "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 534 | "checksum rusty-fork 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" 535 | "checksum ryu 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 536 | "checksum serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)" = "f4473e8506b213730ff2061073b48fa51dcc66349219e2e7c5608f0296a1d95a" 537 | "checksum serde_bytes 0.11.10 (registry+https://github.com/rust-lang/crates.io-index)" = "f3c5113243e4a3a1c96587342d067f3e6b0f50790b6cf40d2868eb647a3eef0e" 538 | "checksum serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)" = "11e410fde43e157d789fc290d26bc940778ad0fdd47836426fbac36573710dbb" 539 | "checksum serde_json 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" 540 | "checksum strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 541 | "checksum syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)" = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 542 | "checksum tempfile 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 543 | "checksum termcolor 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 544 | "checksum textwrap 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 545 | "checksum ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" 546 | "checksum unicode-ident 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 547 | "checksum version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 548 | "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 549 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 550 | "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 551 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 552 | "checksum winapi-util 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 553 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 554 | -------------------------------------------------------------------------------- /Cargo.lock.rust156: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.4.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "bincode" 10 | version = "1.3.3" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "serde 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "bit-set" 18 | version = "0.5.3" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "bit-vec" 26 | version = "0.6.3" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "2.9.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "byteorder" 41 | version = "1.5.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "camino" 46 | version = "1.1.9" 47 | dependencies = [ 48 | "bincode 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "proptest 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "serde 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "serde_bytes 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", 52 | ] 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "errno" 61 | version = "0.3.12" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "windows-sys 0.59.0 (registry+https://github.com/rust-lang/crates.io-index)", 66 | ] 67 | 68 | [[package]] 69 | name = "fastrand" 70 | version = "2.3.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "fnv" 75 | version = "1.0.7" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | 78 | [[package]] 79 | name = "getrandom" 80 | version = "0.2.16" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "lazy_static" 90 | version = "1.5.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | 93 | [[package]] 94 | name = "libc" 95 | version = "0.2.172" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | 98 | [[package]] 99 | name = "libm" 100 | version = "0.2.15" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | 103 | [[package]] 104 | name = "linux-raw-sys" 105 | version = "0.4.15" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "num-traits" 110 | version = "0.2.18" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "autocfg 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "libm 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "ppv-lite86" 119 | version = "0.2.21" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | dependencies = [ 122 | "zerocopy 0.8.25 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "proc-macro2" 127 | version = "1.0.95" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | dependencies = [ 130 | "unicode-ident 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "proptest" 135 | version = "1.2.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "bit-set 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "lazy_static 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "num-traits 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "rand 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "rand_xorshift 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "rusty-fork 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "tempfile 3.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "unarray 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "quick-error" 154 | version = "1.2.3" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | 157 | [[package]] 158 | name = "quote" 159 | version = "1.0.40" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "rand" 167 | version = "0.8.5" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "rand_chacha" 177 | version = "0.3.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "ppv-lite86 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 182 | ] 183 | 184 | [[package]] 185 | name = "rand_core" 186 | version = "0.6.4" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "getrandom 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "rand_xorshift" 194 | version = "0.3.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | dependencies = [ 197 | "rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 198 | ] 199 | 200 | [[package]] 201 | name = "redox_syscall" 202 | version = "0.3.5" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | dependencies = [ 205 | "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 206 | ] 207 | 208 | [[package]] 209 | name = "regex-syntax" 210 | version = "0.6.29" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | 213 | [[package]] 214 | name = "rustix" 215 | version = "0.38.9" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | dependencies = [ 218 | "bitflags 2.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "errno 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "linux-raw-sys 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "windows-sys 0.48.0 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "rusty-fork" 227 | version = "0.3.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "tempfile 3.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 233 | "wait-timeout 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 234 | ] 235 | 236 | [[package]] 237 | name = "serde" 238 | version = "1.0.150" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | dependencies = [ 241 | "serde_derive 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "serde_bytes" 246 | version = "0.11.8" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | dependencies = [ 249 | "serde 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "serde_derive" 254 | version = "1.0.150" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "quote 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", 259 | "syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)", 260 | ] 261 | 262 | [[package]] 263 | name = "syn" 264 | version = "1.0.109" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | dependencies = [ 267 | "proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "quote 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "unicode-ident 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 270 | ] 271 | 272 | [[package]] 273 | name = "syn" 274 | version = "2.0.56" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | dependencies = [ 277 | "proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "quote 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "unicode-ident 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 280 | ] 281 | 282 | [[package]] 283 | name = "tempfile" 284 | version = "3.8.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | dependencies = [ 287 | "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "fastrand 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "redox_syscall 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "rustix 0.38.9 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "windows-sys 0.48.0 (registry+https://github.com/rust-lang/crates.io-index)", 292 | ] 293 | 294 | [[package]] 295 | name = "unarray" 296 | version = "0.1.4" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | 299 | [[package]] 300 | name = "unicode-ident" 301 | version = "1.0.18" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | 304 | [[package]] 305 | name = "wait-timeout" 306 | version = "0.2.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | dependencies = [ 309 | "libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)", 310 | ] 311 | 312 | [[package]] 313 | name = "wasi" 314 | version = "0.11.0+wasi-snapshot-preview1" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | 317 | [[package]] 318 | name = "windows-sys" 319 | version = "0.48.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | dependencies = [ 322 | "windows-targets 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 323 | ] 324 | 325 | [[package]] 326 | name = "windows-sys" 327 | version = "0.59.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | dependencies = [ 330 | "windows-targets 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 331 | ] 332 | 333 | [[package]] 334 | name = "windows-targets" 335 | version = "0.48.5" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | dependencies = [ 338 | "windows_aarch64_gnullvm 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "windows_aarch64_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 340 | "windows_i686_gnu 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 341 | "windows_i686_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 342 | "windows_x86_64_gnu 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "windows_x86_64_gnullvm 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "windows_x86_64_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)", 345 | ] 346 | 347 | [[package]] 348 | name = "windows-targets" 349 | version = "0.52.6" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | dependencies = [ 352 | "windows_aarch64_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 353 | "windows_aarch64_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 354 | "windows_i686_gnu 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "windows_i686_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "windows_i686_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "windows_x86_64_gnu 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "windows_x86_64_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "windows_x86_64_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)", 360 | ] 361 | 362 | [[package]] 363 | name = "windows_aarch64_gnullvm" 364 | version = "0.48.5" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | 367 | [[package]] 368 | name = "windows_aarch64_gnullvm" 369 | version = "0.52.6" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | 372 | [[package]] 373 | name = "windows_aarch64_msvc" 374 | version = "0.48.5" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | 377 | [[package]] 378 | name = "windows_aarch64_msvc" 379 | version = "0.52.6" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | 382 | [[package]] 383 | name = "windows_i686_gnu" 384 | version = "0.48.5" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | 387 | [[package]] 388 | name = "windows_i686_gnu" 389 | version = "0.52.6" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | 392 | [[package]] 393 | name = "windows_i686_gnullvm" 394 | version = "0.52.6" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | 397 | [[package]] 398 | name = "windows_i686_msvc" 399 | version = "0.48.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | 402 | [[package]] 403 | name = "windows_i686_msvc" 404 | version = "0.52.6" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | 407 | [[package]] 408 | name = "windows_x86_64_gnu" 409 | version = "0.48.5" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | 412 | [[package]] 413 | name = "windows_x86_64_gnu" 414 | version = "0.52.6" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | 417 | [[package]] 418 | name = "windows_x86_64_gnullvm" 419 | version = "0.48.5" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | 422 | [[package]] 423 | name = "windows_x86_64_gnullvm" 424 | version = "0.52.6" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | 427 | [[package]] 428 | name = "windows_x86_64_msvc" 429 | version = "0.48.5" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | 432 | [[package]] 433 | name = "windows_x86_64_msvc" 434 | version = "0.52.6" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | 437 | [[package]] 438 | name = "zerocopy" 439 | version = "0.8.25" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | dependencies = [ 442 | "zerocopy-derive 0.8.25 (registry+https://github.com/rust-lang/crates.io-index)", 443 | ] 444 | 445 | [[package]] 446 | name = "zerocopy-derive" 447 | version = "0.8.25" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | dependencies = [ 450 | "proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "quote 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "syn 2.0.56 (registry+https://github.com/rust-lang/crates.io-index)", 453 | ] 454 | 455 | [metadata] 456 | "checksum autocfg 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 457 | "checksum bincode 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 458 | "checksum bit-set 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 459 | "checksum bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 460 | "checksum bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 461 | "checksum bitflags 2.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 462 | "checksum byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 463 | "checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 464 | "checksum errno 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 465 | "checksum fastrand 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 466 | "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 467 | "checksum getrandom 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 468 | "checksum lazy_static 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 469 | "checksum libc 0.2.172 (registry+https://github.com/rust-lang/crates.io-index)" = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 470 | "checksum libm 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 471 | "checksum linux-raw-sys 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 472 | "checksum num-traits 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 473 | "checksum ppv-lite86 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 474 | "checksum proc-macro2 1.0.95 (registry+https://github.com/rust-lang/crates.io-index)" = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 475 | "checksum proptest 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" 476 | "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 477 | "checksum quote 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 478 | "checksum rand 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 479 | "checksum rand_chacha 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 480 | "checksum rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 481 | "checksum rand_xorshift 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" 482 | "checksum redox_syscall 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 483 | "checksum regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)" = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 484 | "checksum rustix 0.38.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" 485 | "checksum rusty-fork 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" 486 | "checksum serde 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)" = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" 487 | "checksum serde_bytes 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)" = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" 488 | "checksum serde_derive 1.0.150 (registry+https://github.com/rust-lang/crates.io-index)" = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" 489 | "checksum syn 1.0.109 (registry+https://github.com/rust-lang/crates.io-index)" = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 490 | "checksum syn 2.0.56 (registry+https://github.com/rust-lang/crates.io-index)" = "6e2415488199887523e74fd9a5f7be804dfd42d868ae0eca382e3917094d210e" 491 | "checksum tempfile 3.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 492 | "checksum unarray 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" 493 | "checksum unicode-ident 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 494 | "checksum wait-timeout 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 495 | "checksum wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 496 | "checksum windows-sys 0.48.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 497 | "checksum windows-sys 0.59.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 498 | "checksum windows-targets 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 499 | "checksum windows-targets 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 500 | "checksum windows_aarch64_gnullvm 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 501 | "checksum windows_aarch64_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 502 | "checksum windows_aarch64_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 503 | "checksum windows_aarch64_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 504 | "checksum windows_i686_gnu 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 505 | "checksum windows_i686_gnu 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 506 | "checksum windows_i686_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 507 | "checksum windows_i686_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 508 | "checksum windows_i686_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 509 | "checksum windows_x86_64_gnu 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 510 | "checksum windows_x86_64_gnu 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 511 | "checksum windows_x86_64_gnullvm 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 512 | "checksum windows_x86_64_gnullvm 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 513 | "checksum windows_x86_64_msvc 0.48.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 514 | "checksum windows_x86_64_msvc 0.52.6 (registry+https://github.com/rust-lang/crates.io-index)" = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 515 | "checksum zerocopy 0.8.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 516 | "checksum zerocopy-derive 0.8.25 (registry+https://github.com/rust-lang/crates.io-index)" = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 517 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["."] 3 | 4 | [package] 5 | name = "camino" 6 | description = "UTF-8 paths" 7 | version = "1.1.9" 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | rust-version = "1.34.0" 11 | keywords = ["paths", "utf8", "unicode", "filesystem"] 12 | categories = ["development-tools", "filesystem", "os"] 13 | repository = "https://github.com/camino-rs/camino" 14 | documentation = "https://docs.rs/camino" 15 | authors = [ 16 | "Without Boats ", 17 | "Ashley Williams ", 18 | "Steve Klabnik ", 19 | "Rain ", 20 | ] 21 | edition = "2018" 22 | exclude = [".cargo/**/*", ".github/**/*"] 23 | 24 | [package.metadata.docs.rs] 25 | all-features = true 26 | rustdoc-args = ["--cfg=doc_cfg"] 27 | 28 | [dependencies] 29 | proptest = { version = "1.0.0", optional = true } 30 | serde = { version = "1", features = ["derive"], optional = true } 31 | 32 | [dev-dependencies] 33 | bincode = "1" 34 | serde_bytes = "0.11.8" 35 | 36 | [features] 37 | serde1 = ["serde"] 38 | proptest1 = ["proptest"] 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # camino - UTF-8 paths 2 | 3 | [![camino on crates.io](https://img.shields.io/crates/v/camino)](https://crates.io/crates/camino) 4 | [![crates.io download count](https://img.shields.io/crates/d/camino)](https://crates.io/crates/camino) 5 | [![Documentation (latest release)](https://img.shields.io/badge/docs-latest%20version-brightgreen.svg)](https://docs.rs/camino) 6 | [![Documentation (main)](https://img.shields.io/badge/docs-main-purple.svg)](https://camino-rs.github.io/camino/rustdoc/camino/) 7 | [![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE-APACHE) 8 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE-MIT) 9 | 10 | This repository contains the source code for `camino`, an extension of the `std::path` module that adds new 11 | [`Utf8PathBuf`] and [`Utf8Path`] types. 12 | 13 | ## What is camino? 14 | 15 | `camino`'s [`Utf8PathBuf`] and [`Utf8Path`] types are like the standard library's [`PathBuf`] and [`Path`] types, except 16 | they are guaranteed to only contain UTF-8 encoded data. Therefore, they expose the ability to get their 17 | contents as strings, they implement `Display`, etc. 18 | 19 | The `std::path` types are not guaranteed to be valid UTF-8. This is the right decision for the standard library, 20 | since it must be as general as possible. However, on all platforms, non-Unicode paths are vanishingly uncommon for a 21 | number of reasons: 22 | 23 | - Unicode won. There are still some legacy codebases that store paths in encodings like [Shift JIS], but most 24 | have been converted to Unicode at this point. 25 | - Unicode is the common subset of supported paths across Windows and Unix platforms. (On Windows, Rust stores paths 26 | as [an extension to UTF-8](https://simonsapin.github.io/wtf-8/), and converts them to UTF-16 at Win32 27 | API boundaries.) 28 | - There are already many systems, such as Cargo, that only support UTF-8 paths. If your own tool interacts with any such 29 | system, you can assume that paths are valid UTF-8 without creating any additional burdens on consumers. 30 | - The ["makefile problem"](https://www.mercurial-scm.org/wiki/EncodingStrategy#The_.22makefile_problem.22) asks: given a 31 | Makefile or other metadata file (such as `Cargo.toml`) that lists the names of other files, how should the names in 32 | the Makefile be matched with the ones on disk? This has _no general, cross-platform solution_ in systems that support 33 | non-UTF-8 paths. However, restricting paths to UTF-8 eliminates this problem. 34 | 35 | [Shift JIS]: https://en.wikipedia.org/wiki/Shift_JIS 36 | 37 | Therefore, many programs that want to manipulate paths _do_ assume they contain UTF-8 data, and convert them to `str`s 38 | as necessary. However, because this invariant is not encoded in the `Path` type, conversions such as 39 | `path.to_str().unwrap()` need to be repeated again and again, creating a frustrating experience. 40 | 41 | Instead, `camino` allows you to check that your paths are UTF-8 _once_, and then manipulate them 42 | as valid UTF-8 from there on, avoiding repeated lossy and confusing conversions. 43 | 44 | ## Examples 45 | 46 | The documentation for [`Utf8PathBuf`] and [`Utf8Path`] contains several examples. 47 | 48 | For examples of how to use `camino` with other libraries like `serde` and `clap`, see the [`camino-examples`] directory. 49 | 50 | ## API design 51 | 52 | `camino` is a very thin wrapper around `std::path`. [`Utf8Path`] and [`Utf8PathBuf`] are drop-in replacements 53 | for [`Path`] and [`PathBuf`]. 54 | 55 | Most APIs are the same, but those at the boundary with `str` are different. Some examples: 56 | 57 | - `Path::to_str() -> Option<&str>` has been renamed to `Utf8Path::as_str() -> &str`. 58 | - [`Utf8Path`] implements `Display`, and `Path::display()` has been removed. 59 | - Iterating over a [`Utf8Path`] returns `&str`, not `&OsStr`. 60 | 61 | Every [`Utf8Path`] is a valid [`Path`], so [`Utf8Path`] implements `AsRef`. Any APIs that accept `impl AsRef` 62 | will continue to work with [`Utf8Path`] instances. 63 | 64 | ## Should you use camino? 65 | 66 | `camino` trades off some utility for a great deal of simplicity. Whether `camino` is appropriate for a project or not 67 | is ultimately a case-by-case decision. Here are some general guidelines that may help. 68 | 69 | _You should consider using camino if..._ 70 | 71 | - **You're building portable, cross-platform software.** While both Unix and Windows platforms support different kinds 72 | of non-Unicode paths, Unicode is the common subset that's supported across them. 73 | - **Your system has files that contain the names of other files.** If you don't use UTF-8 paths, you will run into the 74 | makefile problem described above, which has no general, cross-platform solution. 75 | - **You're interacting with existing systems that already assume UTF-8 paths.** In that case you won't be adding any new 76 | burdens on downstream consumers. 77 | - **You're building something brand new and are willing to ask your users to rename their paths if necessary.** Projects 78 | that don't have to worry about legacy compatibility have more flexibility in choosing what paths they support. 79 | 80 | In general, using camino is the right choice for most projects. 81 | 82 | _You should **NOT** use camino, if..._ 83 | 84 | - **You're writing a core system utility.** If you're writing, say, an `mv` or `cat` replacement, you should 85 | **not** use camino. Instead, use [`std::path::Path`] and add extensive tests for non-UTF-8 paths. 86 | - **You have legacy compatibility constraints.** For example, Git supports non-UTF-8 paths. If your tool needs to handle 87 | arbitrary Git repositories, it should use its own path type that's a wrapper around `Vec`. 88 | - [`std::path::Path`] supports arbitrary bytestrings [on Unix] but not on Windows. 89 | - **There's some other reason you need to support non-UTF-8 paths.** Some tools like disk recovery utilities need to 90 | handle potentially corrupt filenames: only being able to handle UTF-8 paths would greatly diminish their utility. 91 | 92 | [on Unix]: https://doc.rust-lang.org/std/os/unix/ffi/index.html 93 | 94 | ## Optional features 95 | 96 | By default, `camino` has **no dependencies** other than `std`. There are some optional features that enable 97 | dependencies: 98 | 99 | - `serde1` adds serde [`Serialize`] and [`Deserialize`] impls for [`Utf8PathBuf`] and [`Utf8Path`] 100 | (zero-copy). 101 | - `proptest1` adds [proptest](https://altsysrq.github.io/proptest-book/) [`Arbitrary`] 102 | implementations for [`Utf8PathBuf`] and `Box`. 103 | 104 | > NOTE: Enabling the `serde` or `proptest` features will not do anything. You must enable the `serde1` and `proptest1` features, respectively. 105 | 106 | ## Rust version support 107 | 108 | The minimum supported Rust version (MSRV) for `camino` with default features is **1.34**. This project is tested in CI 109 | against the latest stable version of Rust and the MSRV. 110 | 111 | - _Stable APIs_ added in later Rust versions are supported either through conditional compilation in `build.rs`, or through backfills that also work on older versions. 112 | - _Deprecations_ are kept in sync with the version of Rust they're added in. 113 | - _Unstable APIs_ are currently not supported. Please 114 | [file an issue on GitHub](https://github.com/camino-rs/camino/issues/new) if you need an unstable API. 115 | 116 | `camino` is designed to be a core library and has a conservative MSRV policy. MSRV increases will only happen for 117 | a compelling enough reason, and will involve at least a minor version bump. 118 | 119 | Optional features may pull in dependencies that require a newer version of Rust. 120 | 121 | ## License 122 | 123 | This project is available under the terms of either the [Apache 2.0 license](LICENSE-APACHE) or the [MIT 124 | license](LICENSE-MIT). 125 | 126 | This project's documentation is adapted from [The Rust Programming Language](https://github.com/rust-lang/rust/), which is 127 | available under the terms of either the [Apache 2.0 license](https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE) 128 | or the [MIT license](https://github.com/rust-lang/rust/blob/master/LICENSE-MIT). 129 | 130 | [`Utf8PathBuf`]: https://docs.rs/camino/*/camino/struct.Utf8PathBuf.html 131 | [`Utf8Path`]: https://docs.rs/camino/*/camino/struct.Utf8Path.html 132 | [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html 133 | [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 134 | [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 135 | [`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html 136 | [`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html 137 | [`camino-examples`]: https://github.com/camino-rs/camino/tree/main/camino-examples 138 | [`Arbitrary`]: https://docs.rs/proptest/1/proptest/arbitrary/trait.Arbitrary.html 139 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! Adapted from 5 | //! https://github.com/dtolnay/syn/blob/a54fb0098c6679f1312113ae2eec0305c51c7390/build.rs. 6 | 7 | use std::{env, process::Command, str}; 8 | 9 | // The rustc-cfg strings below are *not* public API. Please let us know by 10 | // opening a GitHub issue if your build environment requires some way to enable 11 | // these cfgs other than by executing our build script. 12 | fn main() { 13 | println!("cargo:rerun-if-changed=build.rs"); 14 | 15 | // Required by Rust 1.79+. 16 | println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); 17 | println!("cargo:rustc-check-cfg=cfg(path_buf_deref_mut)"); 18 | println!("cargo:rustc-check-cfg=cfg(path_buf_capacity)"); 19 | println!("cargo:rustc-check-cfg=cfg(shrink_to)"); 20 | println!("cargo:rustc-check-cfg=cfg(try_reserve_2)"); 21 | println!("cargo:rustc-check-cfg=cfg(os_str_bytes)"); 22 | println!("cargo:rustc-check-cfg=cfg(absolute_path)"); 23 | 24 | let compiler = match rustc_version() { 25 | Some(compiler) => compiler, 26 | None => return, 27 | }; 28 | 29 | // NOTE: 30 | // Adding a new cfg gated by Rust version MUST be accompanied by an addition to the matrix in 31 | // .github/workflows/ci.yml. 32 | if compiler.minor >= 44 { 33 | println!("cargo:rustc-cfg=path_buf_capacity"); 34 | } 35 | if compiler.minor >= 56 { 36 | println!("cargo:rustc-cfg=shrink_to"); 37 | } 38 | // NOTE: the below checks use == rather than `matches!`. This is because `matches!` isn't stable 39 | // on Rust 1.34. 40 | // try_reserve_2 was added in a 1.63 nightly. 41 | if (compiler.minor >= 63 42 | && (compiler.channel == ReleaseChannel::Stable || compiler.channel == ReleaseChannel::Beta)) 43 | || compiler.minor >= 64 44 | { 45 | println!("cargo:rustc-cfg=try_reserve_2"); 46 | } 47 | // path_buf_deref_mut was added in a 1.68 nightly. 48 | if (compiler.minor >= 68 49 | && (compiler.channel == ReleaseChannel::Stable || compiler.channel == ReleaseChannel::Beta)) 50 | || compiler.minor >= 69 51 | { 52 | println!("cargo:rustc-cfg=path_buf_deref_mut"); 53 | } 54 | // os_str_bytes was added in 1.74. 55 | if (compiler.minor >= 74 && compiler.channel == ReleaseChannel::Stable) || compiler.minor >= 75 56 | { 57 | println!("cargo:rustc-cfg=os_str_bytes"); 58 | } 59 | // absolute_path was added in 1.79. 60 | if (compiler.minor >= 79 && compiler.channel == ReleaseChannel::Stable) || compiler.minor >= 80 61 | { 62 | println!("cargo:rustc-cfg=absolute_path"); 63 | } 64 | 65 | // Catch situations where the actual features aren't enabled. Currently, they're only shown with 66 | // `-vv` output, but maybe that will be noticed. 67 | #[cfg(all(feature = "proptest", not(feature = "proptest1")))] 68 | { 69 | println!( 70 | "cargo:warning=proptest feature is enabled, but proptest1 isn't -- this won't do anything" 71 | ); 72 | } 73 | #[cfg(all(feature = "serde", not(feature = "serde1")))] 74 | { 75 | println!( 76 | "cargo:warning=serde feature is enabled, but serde1 isn't -- this won't do anything" 77 | ); 78 | } 79 | } 80 | 81 | struct Compiler { 82 | minor: u32, 83 | channel: ReleaseChannel, 84 | } 85 | 86 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 87 | enum ReleaseChannel { 88 | Stable, 89 | Beta, 90 | Nightly, 91 | } 92 | 93 | fn rustc_version() -> Option { 94 | let rustc = env::var_os("RUSTC")?; 95 | let output = Command::new(rustc).arg("--version").output().ok()?; 96 | let version = str::from_utf8(&output.stdout).ok()?; 97 | let mut pieces = version.split('.'); 98 | if pieces.next() != Some("rustc 1") { 99 | return None; 100 | } 101 | let minor = pieces.next()?.parse().ok()?; 102 | let channel = if version.contains("nightly") { 103 | ReleaseChannel::Nightly 104 | } else if version.contains("beta") { 105 | ReleaseChannel::Beta 106 | } else { 107 | ReleaseChannel::Stable 108 | }; 109 | Some(Compiler { minor, channel }) 110 | } 111 | -------------------------------------------------------------------------------- /camino-examples/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /camino-examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | # camino-examples pulls in crates that aren't supported by old versions of Rust, so make it its own 3 | # workspace. 4 | members = ["."] 5 | 6 | [package] 7 | name = "camino-examples" 8 | description = "Examples for camino" 9 | version = "0.1.0" 10 | edition = "2018" 11 | publish = false 12 | 13 | [dependencies] 14 | anyhow = "1.0.38" 15 | camino = { path = "..", features = ["serde1"] } 16 | clap = { version = "3.0.7", features = ["derive"] } 17 | serde = { version = "1", features = ["derive"] } 18 | serde_json = { version = "1.0.62" } 19 | 20 | [dev-dependencies] 21 | criterion = "0.5.1" 22 | 23 | [[bin]] 24 | name = "serde" 25 | 26 | [[bench]] 27 | name = "bench" 28 | harness = false 29 | -------------------------------------------------------------------------------- /camino-examples/README.md: -------------------------------------------------------------------------------- 1 | # camino-examples 2 | 3 | This crate contains examples showing how to use camino. 4 | 5 | ## Examples 6 | 7 | * [`src/bin/clap-derive.rs`](src/bin/clap-derive.rs): Shows how to use clap's derive feature with camino. 8 | 9 | Run with `cargo run --package camino-examples --bin clap-derive`. 10 | 11 | * [`src/bin/serde.rs`](src/bin/serde.rs): Shows how to use [serde](https://serde.rs/) with camino. 12 | 13 | Run with `cargo run --package camino-examples --bin serde`. 14 | -------------------------------------------------------------------------------- /camino-examples/benches/bench.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | // This benchmark is here because criterion has a higher MSRV than camino -- camino-examples is only 5 | // tested on stable, which is good enough. 6 | 7 | use camino::Utf8PathBuf; 8 | use criterion::*; 9 | 10 | fn bench_path(c: &mut Criterion) { 11 | let mut group = c.benchmark_group("Path"); 12 | for i in [10, 100, 1000, 10000] { 13 | let p = "i".repeat(i); 14 | let buf = Utf8PathBuf::from(black_box(p)); 15 | group.bench_with_input(BenchmarkId::new("Utf8PathBuf::as_str", i), &buf, |b, i| { 16 | b.iter(|| { 17 | let _ = black_box(&i).as_str(); 18 | }) 19 | }); 20 | } 21 | } 22 | 23 | criterion_group!(benches, bench_path); 24 | criterion_main!(benches); 25 | -------------------------------------------------------------------------------- /camino-examples/src/bin/clap-derive.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use camino::Utf8PathBuf; 5 | use clap::Parser; 6 | 7 | /// This example shows how a `Utf8Path` can be used with `clap`'s derive-based argument parsing. 8 | /// 9 | /// Using a `Utf8Path` in argument parsing in this manner means that non-UTF-8 paths can be rejected 10 | /// at the boundaries of your program. 11 | /// 12 | /// To run this example, run `cargo run --package camino-examples --bin clap-derive`. 13 | #[derive(Parser)] 14 | #[clap(rename_all = "kebab-case")] 15 | struct Opt { 16 | /// Input file 17 | input: Utf8PathBuf, 18 | 19 | /// Output file 20 | output: Option, 21 | } 22 | 23 | pub fn main() { 24 | // Parse the arguments. 25 | let opt = Opt::parse(); 26 | 27 | // Print the input and output files. 28 | println!("input file: {}", opt.input); 29 | match &opt.output { 30 | Some(output) => println!("output file: {}", output), 31 | None => println!("no output file"), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /camino-examples/src/bin/serde.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use anyhow::Result; 5 | use camino::{Utf8Path, Utf8PathBuf}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::borrow::Cow; 8 | 9 | /// This example demonstrates how to use a `Utf8Path` in a `serde` struct. 10 | /// 11 | /// With the `serde1` feature, `camino` paths can be used as targets for `serde` serialization and 12 | /// deserialization. (Note that serde itself [does not support] parsing non-UTF-8 `PathBuf`s, so 13 | /// there is no loss of generality in switching to `Utf8PathBuf` instances.) 14 | /// 15 | /// To run this example, run `cargo run --package camino-examples --bin serde`. 16 | /// 17 | /// [does not support]: https://docs.rs/crate/serde/1.0.123/source/src/de/impls.rs 18 | #[derive(Serialize, Deserialize)] 19 | struct MyStruct { 20 | input: Utf8PathBuf, 21 | output: Utf8PathBuf, 22 | } 23 | 24 | /// A borrowed version of `MyStruct`, to demonstrate zero-copy deserialization to `Utf8Path`s. 25 | #[derive(Serialize, Deserialize)] 26 | struct MyStructBorrowed<'a> { 27 | #[serde(borrow)] 28 | input: &'a Utf8Path, 29 | // Note: This always deserializes to an owned string because of 30 | // https://github.com/serde-rs/serde/issues/1852. In the future we may add a `camino-utils` 31 | // library with a `CowUtf8Path<'a>` wrapper which can deserialize to the borrowed implementation 32 | // if possible. 33 | #[serde(borrow)] 34 | output: Cow<'a, Utf8Path>, 35 | } 36 | 37 | static JSON_STR: &str = "{ \"input\": \"/foo/bar\", \"output\": \"/baz\\\\/quux\" }"; 38 | pub fn main() -> Result<()> { 39 | println!("*** json string: {}", JSON_STR); 40 | 41 | println!("*** Trying deserialize..."); 42 | 43 | // Deserialize to MyStruct. 44 | let deserialized: MyStruct = serde_json::from_str(JSON_STR)?; 45 | assert_eq!(deserialized.input, "/foo/bar"); 46 | assert_eq!(deserialized.output, "/baz\\/quux"); 47 | 48 | println!("*** Trying serialize..."); 49 | let serialized = serde_json::to_string_pretty(&deserialized)?; 50 | println!("serialize output: {}", serialized); 51 | 52 | println!("*** Trying zero-copy deserialize..."); 53 | 54 | // Zero-copy deserialize to MyStructBorrowed. 55 | let zero_copy: MyStructBorrowed<'_> = serde_json::from_str(JSON_STR)?; 56 | assert_eq!(zero_copy.input, "/foo/bar"); 57 | assert_eq!(zero_copy.output.as_str(), "/baz\\/quux"); 58 | 59 | println!("*** Trying zero-copy serialize..."); 60 | let serialized = serde_json::to_string_pretty(&zero_copy)?; 61 | println!("serialize output: {}", serialized); 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /camino-examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.34.0" 2 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-tag = true 2 | # Required for templates below to work 3 | consolidate-commits = false 4 | pre-release-commit-message = "[{{crate_name}}] version {{version}}" 5 | tag-message = "[{{crate_name}}] version {{version}}" 6 | tag-name = "camino-{{version}}" 7 | publish = false 8 | dependent-version = "upgrade" 9 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | style_edition = "2024" 3 | use_field_init_shorthand = true 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | #![warn(missing_docs)] 5 | #![cfg_attr(doc_cfg, feature(doc_cfg, doc_auto_cfg))] 6 | 7 | //! UTF-8 encoded paths. 8 | //! 9 | //! `camino` is an extension of the `std::path` module that adds new [`Utf8PathBuf`] and [`Utf8Path`] 10 | //! types. These are like the standard library's [`PathBuf`] and [`Path`] types, except they are 11 | //! guaranteed to only contain UTF-8 encoded data. Therefore, they expose the ability to get their 12 | //! contents as strings, they implement `Display`, etc. 13 | //! 14 | //! The `std::path` types are not guaranteed to be valid UTF-8. This is the right decision for the standard library, 15 | //! since it must be as general as possible. However, on all platforms, non-Unicode paths are vanishingly uncommon for a 16 | //! number of reasons: 17 | //! * Unicode won. There are still some legacy codebases that store paths in encodings like Shift-JIS, but most 18 | //! have been converted to Unicode at this point. 19 | //! * Unicode is the common subset of supported paths across Windows and Unix platforms. (On Windows, Rust stores paths 20 | //! as [an extension to UTF-8](https://simonsapin.github.io/wtf-8/), and converts them to UTF-16 at Win32 21 | //! API boundaries.) 22 | //! * There are already many systems, such as Cargo, that only support UTF-8 paths. If your own tool interacts with any such 23 | //! system, you can assume that paths are valid UTF-8 without creating any additional burdens on consumers. 24 | //! * The ["makefile problem"](https://www.mercurial-scm.org/wiki/EncodingStrategy#The_.22makefile_problem.22) 25 | //! (which also applies to `Cargo.toml`, and any other metadata file that lists the names of other files) has *no general, 26 | //! cross-platform solution* in systems that support non-UTF-8 paths. However, restricting paths to UTF-8 eliminates 27 | //! this problem. 28 | //! 29 | //! Therefore, many programs that want to manipulate paths *do* assume they contain UTF-8 data, and convert them to `str`s 30 | //! as necessary. However, because this invariant is not encoded in the `Path` type, conversions such as 31 | //! `path.to_str().unwrap()` need to be repeated again and again, creating a frustrating experience. 32 | //! 33 | //! Instead, `camino` allows you to check that your paths are UTF-8 *once*, and then manipulate them 34 | //! as valid UTF-8 from there on, avoiding repeated lossy and confusing conversions. 35 | 36 | // General note: we use #[allow(clippy::incompatible_msrv)] for code that's already guarded by a 37 | // version-specific cfg conditional. 38 | 39 | use std::{ 40 | borrow::{Borrow, Cow}, 41 | cmp::Ordering, 42 | convert::{Infallible, TryFrom, TryInto}, 43 | error, 44 | ffi::{OsStr, OsString}, 45 | fmt, 46 | fs::{self, Metadata}, 47 | hash::{Hash, Hasher}, 48 | io, 49 | iter::FusedIterator, 50 | ops::Deref, 51 | path::*, 52 | rc::Rc, 53 | str::FromStr, 54 | sync::Arc, 55 | }; 56 | 57 | #[cfg(feature = "proptest1")] 58 | mod proptest_impls; 59 | #[cfg(feature = "serde1")] 60 | mod serde_impls; 61 | #[cfg(test)] 62 | mod tests; 63 | 64 | /// An owned, mutable UTF-8 path (akin to [`String`]). 65 | /// 66 | /// This type provides methods like [`push`] and [`set_extension`] that mutate 67 | /// the path in place. It also implements [`Deref`] to [`Utf8Path`], meaning that 68 | /// all methods on [`Utf8Path`] slices are available on `Utf8PathBuf` values as well. 69 | /// 70 | /// [`push`]: Utf8PathBuf::push 71 | /// [`set_extension`]: Utf8PathBuf::set_extension 72 | /// 73 | /// # Examples 74 | /// 75 | /// You can use [`push`] to build up a `Utf8PathBuf` from 76 | /// components: 77 | /// 78 | /// ``` 79 | /// use camino::Utf8PathBuf; 80 | /// 81 | /// let mut path = Utf8PathBuf::new(); 82 | /// 83 | /// path.push(r"C:\"); 84 | /// path.push("windows"); 85 | /// path.push("system32"); 86 | /// 87 | /// path.set_extension("dll"); 88 | /// ``` 89 | /// 90 | /// However, [`push`] is best used for dynamic situations. This is a better way 91 | /// to do this when you know all of the components ahead of time: 92 | /// 93 | /// ``` 94 | /// use camino::Utf8PathBuf; 95 | /// 96 | /// let path: Utf8PathBuf = [r"C:\", "windows", "system32.dll"].iter().collect(); 97 | /// ``` 98 | /// 99 | /// We can still do better than this! Since these are all strings, we can use 100 | /// `From::from`: 101 | /// 102 | /// ``` 103 | /// use camino::Utf8PathBuf; 104 | /// 105 | /// let path = Utf8PathBuf::from(r"C:\windows\system32.dll"); 106 | /// ``` 107 | /// 108 | /// Which method works best depends on what kind of situation you're in. 109 | // NB: Internal PathBuf must only contain utf8 data 110 | #[derive(Clone, Default)] 111 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] 112 | #[cfg_attr(feature = "serde1", serde(transparent))] 113 | #[repr(transparent)] 114 | pub struct Utf8PathBuf(PathBuf); 115 | 116 | impl Utf8PathBuf { 117 | /// Allocates an empty `Utf8PathBuf`. 118 | /// 119 | /// # Examples 120 | /// 121 | /// ``` 122 | /// use camino::Utf8PathBuf; 123 | /// 124 | /// let path = Utf8PathBuf::new(); 125 | /// ``` 126 | #[must_use] 127 | pub fn new() -> Utf8PathBuf { 128 | Utf8PathBuf(PathBuf::new()) 129 | } 130 | 131 | /// Creates a new `Utf8PathBuf` from a `PathBuf` containing valid UTF-8 characters. 132 | /// 133 | /// Errors with the original `PathBuf` if it is not valid UTF-8. 134 | /// 135 | /// For a version that returns a type that implements [`std::error::Error`], use the 136 | /// `TryFrom` impl. 137 | /// 138 | /// # Examples 139 | /// 140 | /// ``` 141 | /// use camino::Utf8PathBuf; 142 | /// use std::ffi::OsStr; 143 | /// # #[cfg(unix)] 144 | /// use std::os::unix::ffi::OsStrExt; 145 | /// use std::path::PathBuf; 146 | /// 147 | /// let unicode_path = PathBuf::from("/valid/unicode"); 148 | /// Utf8PathBuf::from_path_buf(unicode_path).expect("valid Unicode path succeeded"); 149 | /// 150 | /// // Paths on Unix can be non-UTF-8. 151 | /// # #[cfg(unix)] 152 | /// let non_unicode_str = OsStr::from_bytes(b"\xFF\xFF\xFF"); 153 | /// # #[cfg(unix)] 154 | /// let non_unicode_path = PathBuf::from(non_unicode_str); 155 | /// # #[cfg(unix)] 156 | /// Utf8PathBuf::from_path_buf(non_unicode_path).expect_err("non-Unicode path failed"); 157 | /// ``` 158 | pub fn from_path_buf(path: PathBuf) -> Result { 159 | match path.into_os_string().into_string() { 160 | Ok(string) => Ok(Utf8PathBuf::from(string)), 161 | Err(os_string) => Err(PathBuf::from(os_string)), 162 | } 163 | } 164 | 165 | /// Converts a `Utf8PathBuf` to a [`PathBuf`]. 166 | /// 167 | /// This is equivalent to the `From for PathBuf` impl, but may aid in type 168 | /// inference. 169 | /// 170 | /// # Examples 171 | /// 172 | /// ``` 173 | /// use camino::Utf8PathBuf; 174 | /// use std::path::PathBuf; 175 | /// 176 | /// let utf8_path_buf = Utf8PathBuf::from("foo.txt"); 177 | /// let std_path_buf = utf8_path_buf.into_std_path_buf(); 178 | /// assert_eq!(std_path_buf.to_str(), Some("foo.txt")); 179 | /// 180 | /// // Convert back to a Utf8PathBuf. 181 | /// let new_utf8_path_buf = Utf8PathBuf::from_path_buf(std_path_buf).unwrap(); 182 | /// assert_eq!(new_utf8_path_buf, "foo.txt"); 183 | /// ``` 184 | #[must_use = "`self` will be dropped if the result is not used"] 185 | pub fn into_std_path_buf(self) -> PathBuf { 186 | self.into() 187 | } 188 | 189 | /// Creates a new `Utf8PathBuf` with a given capacity used to create the internal [`PathBuf`]. 190 | /// See [`with_capacity`] defined on [`PathBuf`]. 191 | /// 192 | /// *Requires Rust 1.44 or newer.* 193 | /// 194 | /// # Examples 195 | /// 196 | /// ``` 197 | /// use camino::Utf8PathBuf; 198 | /// 199 | /// let mut path = Utf8PathBuf::with_capacity(10); 200 | /// let capacity = path.capacity(); 201 | /// 202 | /// // This push is done without reallocating 203 | /// path.push(r"C:\"); 204 | /// 205 | /// assert_eq!(capacity, path.capacity()); 206 | /// ``` 207 | /// 208 | /// [`with_capacity`]: PathBuf::with_capacity 209 | #[cfg(path_buf_capacity)] 210 | #[allow(clippy::incompatible_msrv)] 211 | #[must_use] 212 | pub fn with_capacity(capacity: usize) -> Utf8PathBuf { 213 | Utf8PathBuf(PathBuf::with_capacity(capacity)) 214 | } 215 | 216 | /// Coerces to a [`Utf8Path`] slice. 217 | /// 218 | /// # Examples 219 | /// 220 | /// ``` 221 | /// use camino::{Utf8Path, Utf8PathBuf}; 222 | /// 223 | /// let p = Utf8PathBuf::from("/test"); 224 | /// assert_eq!(Utf8Path::new("/test"), p.as_path()); 225 | /// ``` 226 | #[must_use] 227 | pub fn as_path(&self) -> &Utf8Path { 228 | // SAFETY: every Utf8PathBuf constructor ensures that self is valid UTF-8 229 | unsafe { Utf8Path::assume_utf8(&self.0) } 230 | } 231 | 232 | /// Extends `self` with `path`. 233 | /// 234 | /// If `path` is absolute, it replaces the current path. 235 | /// 236 | /// On Windows: 237 | /// 238 | /// * if `path` has a root but no prefix (e.g., `\windows`), it 239 | /// replaces everything except for the prefix (if any) of `self`. 240 | /// * if `path` has a prefix but no root, it replaces `self`. 241 | /// 242 | /// # Examples 243 | /// 244 | /// Pushing a relative path extends the existing path: 245 | /// 246 | /// ``` 247 | /// use camino::Utf8PathBuf; 248 | /// 249 | /// let mut path = Utf8PathBuf::from("/tmp"); 250 | /// path.push("file.bk"); 251 | /// assert_eq!(path, Utf8PathBuf::from("/tmp/file.bk")); 252 | /// ``` 253 | /// 254 | /// Pushing an absolute path replaces the existing path: 255 | /// 256 | /// ``` 257 | /// use camino::Utf8PathBuf; 258 | /// 259 | /// let mut path = Utf8PathBuf::from("/tmp"); 260 | /// path.push("/etc"); 261 | /// assert_eq!(path, Utf8PathBuf::from("/etc")); 262 | /// ``` 263 | pub fn push(&mut self, path: impl AsRef) { 264 | self.0.push(&path.as_ref().0) 265 | } 266 | 267 | /// Truncates `self` to [`self.parent`]. 268 | /// 269 | /// Returns `false` and does nothing if [`self.parent`] is [`None`]. 270 | /// Otherwise, returns `true`. 271 | /// 272 | /// [`self.parent`]: Utf8Path::parent 273 | /// 274 | /// # Examples 275 | /// 276 | /// ``` 277 | /// use camino::{Utf8Path, Utf8PathBuf}; 278 | /// 279 | /// let mut p = Utf8PathBuf::from("/spirited/away.rs"); 280 | /// 281 | /// p.pop(); 282 | /// assert_eq!(Utf8Path::new("/spirited"), p); 283 | /// p.pop(); 284 | /// assert_eq!(Utf8Path::new("/"), p); 285 | /// ``` 286 | pub fn pop(&mut self) -> bool { 287 | self.0.pop() 288 | } 289 | 290 | /// Updates [`self.file_name`] to `file_name`. 291 | /// 292 | /// If [`self.file_name`] was [`None`], this is equivalent to pushing 293 | /// `file_name`. 294 | /// 295 | /// Otherwise it is equivalent to calling [`pop`] and then pushing 296 | /// `file_name`. The new path will be a sibling of the original path. 297 | /// (That is, it will have the same parent.) 298 | /// 299 | /// [`self.file_name`]: Utf8Path::file_name 300 | /// [`pop`]: Utf8PathBuf::pop 301 | /// 302 | /// # Examples 303 | /// 304 | /// ``` 305 | /// use camino::Utf8PathBuf; 306 | /// 307 | /// let mut buf = Utf8PathBuf::from("/"); 308 | /// assert_eq!(buf.file_name(), None); 309 | /// buf.set_file_name("bar"); 310 | /// assert_eq!(buf, Utf8PathBuf::from("/bar")); 311 | /// assert!(buf.file_name().is_some()); 312 | /// buf.set_file_name("baz.txt"); 313 | /// assert_eq!(buf, Utf8PathBuf::from("/baz.txt")); 314 | /// ``` 315 | pub fn set_file_name(&mut self, file_name: impl AsRef) { 316 | self.0.set_file_name(file_name.as_ref()) 317 | } 318 | 319 | /// Updates [`self.extension`] to `extension`. 320 | /// 321 | /// Returns `false` and does nothing if [`self.file_name`] is [`None`], 322 | /// returns `true` and updates the extension otherwise. 323 | /// 324 | /// If [`self.extension`] is [`None`], the extension is added; otherwise 325 | /// it is replaced. 326 | /// 327 | /// [`self.file_name`]: Utf8Path::file_name 328 | /// [`self.extension`]: Utf8Path::extension 329 | /// 330 | /// # Examples 331 | /// 332 | /// ``` 333 | /// use camino::{Utf8Path, Utf8PathBuf}; 334 | /// 335 | /// let mut p = Utf8PathBuf::from("/feel/the"); 336 | /// 337 | /// p.set_extension("force"); 338 | /// assert_eq!(Utf8Path::new("/feel/the.force"), p.as_path()); 339 | /// 340 | /// p.set_extension("dark_side"); 341 | /// assert_eq!(Utf8Path::new("/feel/the.dark_side"), p.as_path()); 342 | /// ``` 343 | pub fn set_extension(&mut self, extension: impl AsRef) -> bool { 344 | self.0.set_extension(extension.as_ref()) 345 | } 346 | 347 | /// Consumes the `Utf8PathBuf`, yielding its internal [`String`] storage. 348 | /// 349 | /// # Examples 350 | /// 351 | /// ``` 352 | /// use camino::Utf8PathBuf; 353 | /// 354 | /// let p = Utf8PathBuf::from("/the/head"); 355 | /// let s = p.into_string(); 356 | /// assert_eq!(s, "/the/head"); 357 | /// ``` 358 | #[must_use = "`self` will be dropped if the result is not used"] 359 | pub fn into_string(self) -> String { 360 | self.into_os_string().into_string().unwrap() 361 | } 362 | 363 | /// Consumes the `Utf8PathBuf`, yielding its internal [`OsString`] storage. 364 | /// 365 | /// # Examples 366 | /// 367 | /// ``` 368 | /// use camino::Utf8PathBuf; 369 | /// use std::ffi::OsStr; 370 | /// 371 | /// let p = Utf8PathBuf::from("/the/head"); 372 | /// let s = p.into_os_string(); 373 | /// assert_eq!(s, OsStr::new("/the/head")); 374 | /// ``` 375 | #[must_use = "`self` will be dropped if the result is not used"] 376 | pub fn into_os_string(self) -> OsString { 377 | self.0.into_os_string() 378 | } 379 | 380 | /// Converts this `Utf8PathBuf` into a [boxed](Box) [`Utf8Path`]. 381 | #[must_use = "`self` will be dropped if the result is not used"] 382 | pub fn into_boxed_path(self) -> Box { 383 | let ptr = Box::into_raw(self.0.into_boxed_path()) as *mut Utf8Path; 384 | // SAFETY: 385 | // * self is valid UTF-8 386 | // * ptr was constructed by consuming self so it represents an owned path 387 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *mut Path to 388 | // *mut Utf8Path is valid 389 | unsafe { Box::from_raw(ptr) } 390 | } 391 | 392 | /// Invokes [`capacity`] on the underlying instance of [`PathBuf`]. 393 | /// 394 | /// *Requires Rust 1.44 or newer.* 395 | /// 396 | /// [`capacity`]: PathBuf::capacity 397 | #[cfg(path_buf_capacity)] 398 | #[allow(clippy::incompatible_msrv)] 399 | #[must_use] 400 | pub fn capacity(&self) -> usize { 401 | self.0.capacity() 402 | } 403 | 404 | /// Invokes [`clear`] on the underlying instance of [`PathBuf`]. 405 | /// 406 | /// *Requires Rust 1.44 or newer.* 407 | /// 408 | /// [`clear`]: PathBuf::clear 409 | #[cfg(path_buf_capacity)] 410 | #[allow(clippy::incompatible_msrv)] 411 | pub fn clear(&mut self) { 412 | self.0.clear() 413 | } 414 | 415 | /// Invokes [`reserve`] on the underlying instance of [`PathBuf`]. 416 | /// 417 | /// *Requires Rust 1.44 or newer.* 418 | /// 419 | /// [`reserve`]: PathBuf::reserve 420 | #[cfg(path_buf_capacity)] 421 | #[allow(clippy::incompatible_msrv)] 422 | pub fn reserve(&mut self, additional: usize) { 423 | self.0.reserve(additional) 424 | } 425 | 426 | /// Invokes [`try_reserve`] on the underlying instance of [`PathBuf`]. 427 | /// 428 | /// *Requires Rust 1.63 or newer.* 429 | /// 430 | /// [`try_reserve`]: PathBuf::try_reserve 431 | #[cfg(try_reserve_2)] 432 | #[allow(clippy::incompatible_msrv)] 433 | #[inline] 434 | pub fn try_reserve( 435 | &mut self, 436 | additional: usize, 437 | ) -> Result<(), std::collections::TryReserveError> { 438 | self.0.try_reserve(additional) 439 | } 440 | 441 | /// Invokes [`reserve_exact`] on the underlying instance of [`PathBuf`]. 442 | /// 443 | /// *Requires Rust 1.44 or newer.* 444 | /// 445 | /// [`reserve_exact`]: PathBuf::reserve_exact 446 | #[cfg(path_buf_capacity)] 447 | #[allow(clippy::incompatible_msrv)] 448 | pub fn reserve_exact(&mut self, additional: usize) { 449 | self.0.reserve_exact(additional) 450 | } 451 | 452 | /// Invokes [`try_reserve_exact`] on the underlying instance of [`PathBuf`]. 453 | /// 454 | /// *Requires Rust 1.63 or newer.* 455 | /// 456 | /// [`try_reserve_exact`]: PathBuf::try_reserve_exact 457 | #[cfg(try_reserve_2)] 458 | #[allow(clippy::incompatible_msrv)] 459 | #[inline] 460 | pub fn try_reserve_exact( 461 | &mut self, 462 | additional: usize, 463 | ) -> Result<(), std::collections::TryReserveError> { 464 | self.0.try_reserve_exact(additional) 465 | } 466 | 467 | /// Invokes [`shrink_to_fit`] on the underlying instance of [`PathBuf`]. 468 | /// 469 | /// *Requires Rust 1.44 or newer.* 470 | /// 471 | /// [`shrink_to_fit`]: PathBuf::shrink_to_fit 472 | #[cfg(path_buf_capacity)] 473 | #[allow(clippy::incompatible_msrv)] 474 | pub fn shrink_to_fit(&mut self) { 475 | self.0.shrink_to_fit() 476 | } 477 | 478 | /// Invokes [`shrink_to`] on the underlying instance of [`PathBuf`]. 479 | /// 480 | /// *Requires Rust 1.56 or newer.* 481 | /// 482 | /// [`shrink_to`]: PathBuf::shrink_to 483 | #[cfg(shrink_to)] 484 | #[allow(clippy::incompatible_msrv)] 485 | #[inline] 486 | pub fn shrink_to(&mut self, min_capacity: usize) { 487 | self.0.shrink_to(min_capacity) 488 | } 489 | } 490 | 491 | impl Deref for Utf8PathBuf { 492 | type Target = Utf8Path; 493 | 494 | fn deref(&self) -> &Utf8Path { 495 | self.as_path() 496 | } 497 | } 498 | 499 | /// *Requires Rust 1.68 or newer.* 500 | #[cfg(path_buf_deref_mut)] 501 | #[allow(clippy::incompatible_msrv)] 502 | impl std::ops::DerefMut for Utf8PathBuf { 503 | fn deref_mut(&mut self) -> &mut Self::Target { 504 | unsafe { Utf8Path::assume_utf8_mut(&mut self.0) } 505 | } 506 | } 507 | 508 | impl fmt::Debug for Utf8PathBuf { 509 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 510 | fmt::Debug::fmt(&**self, f) 511 | } 512 | } 513 | 514 | impl fmt::Display for Utf8PathBuf { 515 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 516 | fmt::Display::fmt(self.as_str(), f) 517 | } 518 | } 519 | 520 | impl> Extend

for Utf8PathBuf { 521 | fn extend>(&mut self, iter: I) { 522 | for path in iter { 523 | self.push(path); 524 | } 525 | } 526 | } 527 | 528 | /// A slice of a UTF-8 path (akin to [`str`]). 529 | /// 530 | /// This type supports a number of operations for inspecting a path, including 531 | /// breaking the path into its components (separated by `/` on Unix and by either 532 | /// `/` or `\` on Windows), extracting the file name, determining whether the path 533 | /// is absolute, and so on. 534 | /// 535 | /// This is an *unsized* type, meaning that it must always be used behind a 536 | /// pointer like `&` or [`Box`]. For an owned version of this type, 537 | /// see [`Utf8PathBuf`]. 538 | /// 539 | /// # Examples 540 | /// 541 | /// ``` 542 | /// use camino::Utf8Path; 543 | /// 544 | /// // Note: this example does work on Windows 545 | /// let path = Utf8Path::new("./foo/bar.txt"); 546 | /// 547 | /// let parent = path.parent(); 548 | /// assert_eq!(parent, Some(Utf8Path::new("./foo"))); 549 | /// 550 | /// let file_stem = path.file_stem(); 551 | /// assert_eq!(file_stem, Some("bar")); 552 | /// 553 | /// let extension = path.extension(); 554 | /// assert_eq!(extension, Some("txt")); 555 | /// ``` 556 | // NB: Internal Path must only contain utf8 data 557 | #[repr(transparent)] 558 | pub struct Utf8Path(Path); 559 | 560 | impl Utf8Path { 561 | /// Directly wraps a string slice as a `Utf8Path` slice. 562 | /// 563 | /// This is a cost-free conversion. 564 | /// 565 | /// # Examples 566 | /// 567 | /// ``` 568 | /// use camino::Utf8Path; 569 | /// 570 | /// Utf8Path::new("foo.txt"); 571 | /// ``` 572 | /// 573 | /// You can create `Utf8Path`s from `String`s, or even other `Utf8Path`s: 574 | /// 575 | /// ``` 576 | /// use camino::Utf8Path; 577 | /// 578 | /// let string = String::from("foo.txt"); 579 | /// let from_string = Utf8Path::new(&string); 580 | /// let from_path = Utf8Path::new(&from_string); 581 | /// assert_eq!(from_string, from_path); 582 | /// ``` 583 | pub fn new(s: &(impl AsRef + ?Sized)) -> &Utf8Path { 584 | let path = Path::new(s.as_ref()); 585 | // SAFETY: s is a str which means it is always valid UTF-8 586 | unsafe { Utf8Path::assume_utf8(path) } 587 | } 588 | 589 | /// Converts a [`Path`] to a `Utf8Path`. 590 | /// 591 | /// Returns `None` if the path is not valid UTF-8. 592 | /// 593 | /// For a version that returns a type that implements [`std::error::Error`], use the 594 | /// [`TryFrom<&Path>`][tryfrom] impl. 595 | /// 596 | /// [tryfrom]: #impl-TryFrom<%26'a+Path>-for-%26'a+Utf8Path 597 | /// 598 | /// # Examples 599 | /// 600 | /// ``` 601 | /// use camino::Utf8Path; 602 | /// use std::ffi::OsStr; 603 | /// # #[cfg(unix)] 604 | /// use std::os::unix::ffi::OsStrExt; 605 | /// use std::path::Path; 606 | /// 607 | /// let unicode_path = Path::new("/valid/unicode"); 608 | /// Utf8Path::from_path(unicode_path).expect("valid Unicode path succeeded"); 609 | /// 610 | /// // Paths on Unix can be non-UTF-8. 611 | /// # #[cfg(unix)] 612 | /// let non_unicode_str = OsStr::from_bytes(b"\xFF\xFF\xFF"); 613 | /// # #[cfg(unix)] 614 | /// let non_unicode_path = Path::new(non_unicode_str); 615 | /// # #[cfg(unix)] 616 | /// assert!(Utf8Path::from_path(non_unicode_path).is_none(), "non-Unicode path failed"); 617 | /// ``` 618 | pub fn from_path(path: &Path) -> Option<&Utf8Path> { 619 | path.as_os_str().to_str().map(Utf8Path::new) 620 | } 621 | 622 | /// Converts a `Utf8Path` to a [`Path`]. 623 | /// 624 | /// This is equivalent to the `AsRef<&Path> for &Utf8Path` impl, but may aid in type inference. 625 | /// 626 | /// # Examples 627 | /// 628 | /// ``` 629 | /// use camino::Utf8Path; 630 | /// use std::path::Path; 631 | /// 632 | /// let utf8_path = Utf8Path::new("foo.txt"); 633 | /// let std_path: &Path = utf8_path.as_std_path(); 634 | /// assert_eq!(std_path.to_str(), Some("foo.txt")); 635 | /// 636 | /// // Convert back to a Utf8Path. 637 | /// let new_utf8_path = Utf8Path::from_path(std_path).unwrap(); 638 | /// assert_eq!(new_utf8_path, "foo.txt"); 639 | /// ``` 640 | #[inline] 641 | pub fn as_std_path(&self) -> &Path { 642 | self.as_ref() 643 | } 644 | 645 | /// Yields the underlying [`str`] slice. 646 | /// 647 | /// Unlike [`Path::to_str`], this always returns a slice because the contents of a `Utf8Path` 648 | /// are guaranteed to be valid UTF-8. 649 | /// 650 | /// # Examples 651 | /// 652 | /// ``` 653 | /// use camino::Utf8Path; 654 | /// 655 | /// let s = Utf8Path::new("foo.txt").as_str(); 656 | /// assert_eq!(s, "foo.txt"); 657 | /// ``` 658 | /// 659 | /// [`str`]: str 660 | #[inline] 661 | #[must_use] 662 | pub fn as_str(&self) -> &str { 663 | // SAFETY: every Utf8Path constructor ensures that self is valid UTF-8 664 | unsafe { str_assume_utf8(self.as_os_str()) } 665 | } 666 | 667 | /// Yields the underlying [`OsStr`] slice. 668 | /// 669 | /// # Examples 670 | /// 671 | /// ``` 672 | /// use camino::Utf8Path; 673 | /// 674 | /// let os_str = Utf8Path::new("foo.txt").as_os_str(); 675 | /// assert_eq!(os_str, std::ffi::OsStr::new("foo.txt")); 676 | /// ``` 677 | #[inline] 678 | #[must_use] 679 | pub fn as_os_str(&self) -> &OsStr { 680 | self.0.as_os_str() 681 | } 682 | 683 | /// Converts a `Utf8Path` to an owned [`Utf8PathBuf`]. 684 | /// 685 | /// # Examples 686 | /// 687 | /// ``` 688 | /// use camino::{Utf8Path, Utf8PathBuf}; 689 | /// 690 | /// let path_buf = Utf8Path::new("foo.txt").to_path_buf(); 691 | /// assert_eq!(path_buf, Utf8PathBuf::from("foo.txt")); 692 | /// ``` 693 | #[inline] 694 | #[must_use = "this returns the result of the operation, \ 695 | without modifying the original"] 696 | pub fn to_path_buf(&self) -> Utf8PathBuf { 697 | Utf8PathBuf(self.0.to_path_buf()) 698 | } 699 | 700 | /// Returns `true` if the `Utf8Path` is absolute, i.e., if it is independent of 701 | /// the current directory. 702 | /// 703 | /// * On Unix, a path is absolute if it starts with the root, so 704 | /// `is_absolute` and [`has_root`] are equivalent. 705 | /// 706 | /// * On Windows, a path is absolute if it has a prefix and starts with the 707 | /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. 708 | /// 709 | /// # Examples 710 | /// 711 | /// ``` 712 | /// use camino::Utf8Path; 713 | /// 714 | /// assert!(!Utf8Path::new("foo.txt").is_absolute()); 715 | /// ``` 716 | /// 717 | /// [`has_root`]: Utf8Path::has_root 718 | #[inline] 719 | #[must_use] 720 | pub fn is_absolute(&self) -> bool { 721 | self.0.is_absolute() 722 | } 723 | 724 | /// Returns `true` if the `Utf8Path` is relative, i.e., not absolute. 725 | /// 726 | /// See [`is_absolute`]'s documentation for more details. 727 | /// 728 | /// # Examples 729 | /// 730 | /// ``` 731 | /// use camino::Utf8Path; 732 | /// 733 | /// assert!(Utf8Path::new("foo.txt").is_relative()); 734 | /// ``` 735 | /// 736 | /// [`is_absolute`]: Utf8Path::is_absolute 737 | #[inline] 738 | #[must_use] 739 | pub fn is_relative(&self) -> bool { 740 | self.0.is_relative() 741 | } 742 | 743 | /// Returns `true` if the `Utf8Path` has a root. 744 | /// 745 | /// * On Unix, a path has a root if it begins with `/`. 746 | /// 747 | /// * On Windows, a path has a root if it: 748 | /// * has no prefix and begins with a separator, e.g., `\windows` 749 | /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows` 750 | /// * has any non-disk prefix, e.g., `\\server\share` 751 | /// 752 | /// # Examples 753 | /// 754 | /// ``` 755 | /// use camino::Utf8Path; 756 | /// 757 | /// assert!(Utf8Path::new("/etc/passwd").has_root()); 758 | /// ``` 759 | #[inline] 760 | #[must_use] 761 | pub fn has_root(&self) -> bool { 762 | self.0.has_root() 763 | } 764 | 765 | /// Returns the `Path` without its final component, if there is one. 766 | /// 767 | /// Returns [`None`] if the path terminates in a root or prefix. 768 | /// 769 | /// # Examples 770 | /// 771 | /// ``` 772 | /// use camino::Utf8Path; 773 | /// 774 | /// let path = Utf8Path::new("/foo/bar"); 775 | /// let parent = path.parent().unwrap(); 776 | /// assert_eq!(parent, Utf8Path::new("/foo")); 777 | /// 778 | /// let grand_parent = parent.parent().unwrap(); 779 | /// assert_eq!(grand_parent, Utf8Path::new("/")); 780 | /// assert_eq!(grand_parent.parent(), None); 781 | /// ``` 782 | #[inline] 783 | #[must_use] 784 | pub fn parent(&self) -> Option<&Utf8Path> { 785 | self.0.parent().map(|path| { 786 | // SAFETY: self is valid UTF-8, so parent is valid UTF-8 as well 787 | unsafe { Utf8Path::assume_utf8(path) } 788 | }) 789 | } 790 | 791 | /// Produces an iterator over `Utf8Path` and its ancestors. 792 | /// 793 | /// The iterator will yield the `Utf8Path` that is returned if the [`parent`] method is used zero 794 | /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, 795 | /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns 796 | /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, 797 | /// namely `&self`. 798 | /// 799 | /// # Examples 800 | /// 801 | /// ``` 802 | /// use camino::Utf8Path; 803 | /// 804 | /// let mut ancestors = Utf8Path::new("/foo/bar").ancestors(); 805 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo/bar"))); 806 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo"))); 807 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/"))); 808 | /// assert_eq!(ancestors.next(), None); 809 | /// 810 | /// let mut ancestors = Utf8Path::new("../foo/bar").ancestors(); 811 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo/bar"))); 812 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo"))); 813 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new(".."))); 814 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new(""))); 815 | /// assert_eq!(ancestors.next(), None); 816 | /// ``` 817 | /// 818 | /// [`parent`]: Utf8Path::parent 819 | #[inline] 820 | pub fn ancestors(&self) -> Utf8Ancestors<'_> { 821 | Utf8Ancestors(self.0.ancestors()) 822 | } 823 | 824 | /// Returns the final component of the `Utf8Path`, if there is one. 825 | /// 826 | /// If the path is a normal file, this is the file name. If it's the path of a directory, this 827 | /// is the directory name. 828 | /// 829 | /// Returns [`None`] if the path terminates in `..`. 830 | /// 831 | /// # Examples 832 | /// 833 | /// ``` 834 | /// use camino::Utf8Path; 835 | /// 836 | /// assert_eq!(Some("bin"), Utf8Path::new("/usr/bin/").file_name()); 837 | /// assert_eq!(Some("foo.txt"), Utf8Path::new("tmp/foo.txt").file_name()); 838 | /// assert_eq!(Some("foo.txt"), Utf8Path::new("foo.txt/.").file_name()); 839 | /// assert_eq!(Some("foo.txt"), Utf8Path::new("foo.txt/.//").file_name()); 840 | /// assert_eq!(None, Utf8Path::new("foo.txt/..").file_name()); 841 | /// assert_eq!(None, Utf8Path::new("/").file_name()); 842 | /// ``` 843 | #[inline] 844 | #[must_use] 845 | pub fn file_name(&self) -> Option<&str> { 846 | self.0.file_name().map(|s| { 847 | // SAFETY: self is valid UTF-8, so file_name is valid UTF-8 as well 848 | unsafe { str_assume_utf8(s) } 849 | }) 850 | } 851 | 852 | /// Returns a path that, when joined onto `base`, yields `self`. 853 | /// 854 | /// # Errors 855 | /// 856 | /// If `base` is not a prefix of `self` (i.e., [`starts_with`] 857 | /// returns `false`), returns [`Err`]. 858 | /// 859 | /// [`starts_with`]: Utf8Path::starts_with 860 | /// 861 | /// # Examples 862 | /// 863 | /// ``` 864 | /// use camino::{Utf8Path, Utf8PathBuf}; 865 | /// 866 | /// let path = Utf8Path::new("/test/haha/foo.txt"); 867 | /// 868 | /// assert_eq!(path.strip_prefix("/"), Ok(Utf8Path::new("test/haha/foo.txt"))); 869 | /// assert_eq!(path.strip_prefix("/test"), Ok(Utf8Path::new("haha/foo.txt"))); 870 | /// assert_eq!(path.strip_prefix("/test/"), Ok(Utf8Path::new("haha/foo.txt"))); 871 | /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Utf8Path::new(""))); 872 | /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Utf8Path::new(""))); 873 | /// 874 | /// assert!(path.strip_prefix("test").is_err()); 875 | /// assert!(path.strip_prefix("/haha").is_err()); 876 | /// 877 | /// let prefix = Utf8PathBuf::from("/test/"); 878 | /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8Path::new("haha/foo.txt"))); 879 | /// ``` 880 | #[inline] 881 | pub fn strip_prefix(&self, base: impl AsRef) -> Result<&Utf8Path, StripPrefixError> { 882 | self.0.strip_prefix(base).map(|path| { 883 | // SAFETY: self is valid UTF-8, and strip_prefix returns a part of self (or an empty 884 | // string), so it is valid UTF-8 as well. 885 | unsafe { Utf8Path::assume_utf8(path) } 886 | }) 887 | } 888 | 889 | /// Determines whether `base` is a prefix of `self`. 890 | /// 891 | /// Only considers whole path components to match. 892 | /// 893 | /// # Examples 894 | /// 895 | /// ``` 896 | /// use camino::Utf8Path; 897 | /// 898 | /// let path = Utf8Path::new("/etc/passwd"); 899 | /// 900 | /// assert!(path.starts_with("/etc")); 901 | /// assert!(path.starts_with("/etc/")); 902 | /// assert!(path.starts_with("/etc/passwd")); 903 | /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay 904 | /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay 905 | /// 906 | /// assert!(!path.starts_with("/e")); 907 | /// assert!(!path.starts_with("/etc/passwd.txt")); 908 | /// 909 | /// assert!(!Utf8Path::new("/etc/foo.rs").starts_with("/etc/foo")); 910 | /// ``` 911 | #[inline] 912 | #[must_use] 913 | pub fn starts_with(&self, base: impl AsRef) -> bool { 914 | self.0.starts_with(base) 915 | } 916 | 917 | /// Determines whether `child` is a suffix of `self`. 918 | /// 919 | /// Only considers whole path components to match. 920 | /// 921 | /// # Examples 922 | /// 923 | /// ``` 924 | /// use camino::Utf8Path; 925 | /// 926 | /// let path = Utf8Path::new("/etc/resolv.conf"); 927 | /// 928 | /// assert!(path.ends_with("resolv.conf")); 929 | /// assert!(path.ends_with("etc/resolv.conf")); 930 | /// assert!(path.ends_with("/etc/resolv.conf")); 931 | /// 932 | /// assert!(!path.ends_with("/resolv.conf")); 933 | /// assert!(!path.ends_with("conf")); // use .extension() instead 934 | /// ``` 935 | #[inline] 936 | #[must_use] 937 | pub fn ends_with(&self, base: impl AsRef) -> bool { 938 | self.0.ends_with(base) 939 | } 940 | 941 | /// Extracts the stem (non-extension) portion of [`self.file_name`]. 942 | /// 943 | /// [`self.file_name`]: Utf8Path::file_name 944 | /// 945 | /// The stem is: 946 | /// 947 | /// * [`None`], if there is no file name; 948 | /// * The entire file name if there is no embedded `.`; 949 | /// * The entire file name if the file name begins with `.` and has no other `.`s within; 950 | /// * Otherwise, the portion of the file name before the final `.` 951 | /// 952 | /// # Examples 953 | /// 954 | /// ``` 955 | /// use camino::Utf8Path; 956 | /// 957 | /// assert_eq!("foo", Utf8Path::new("foo.rs").file_stem().unwrap()); 958 | /// assert_eq!("foo.tar", Utf8Path::new("foo.tar.gz").file_stem().unwrap()); 959 | /// ``` 960 | #[inline] 961 | #[must_use] 962 | pub fn file_stem(&self) -> Option<&str> { 963 | self.0.file_stem().map(|s| { 964 | // SAFETY: self is valid UTF-8, so file_stem is valid UTF-8 as well 965 | unsafe { str_assume_utf8(s) } 966 | }) 967 | } 968 | 969 | /// Extracts the extension of [`self.file_name`], if possible. 970 | /// 971 | /// The extension is: 972 | /// 973 | /// * [`None`], if there is no file name; 974 | /// * [`None`], if there is no embedded `.`; 975 | /// * [`None`], if the file name begins with `.` and has no other `.`s within; 976 | /// * Otherwise, the portion of the file name after the final `.` 977 | /// 978 | /// [`self.file_name`]: Utf8Path::file_name 979 | /// 980 | /// # Examples 981 | /// 982 | /// ``` 983 | /// use camino::Utf8Path; 984 | /// 985 | /// assert_eq!("rs", Utf8Path::new("foo.rs").extension().unwrap()); 986 | /// assert_eq!("gz", Utf8Path::new("foo.tar.gz").extension().unwrap()); 987 | /// ``` 988 | #[inline] 989 | #[must_use] 990 | pub fn extension(&self) -> Option<&str> { 991 | self.0.extension().map(|s| { 992 | // SAFETY: self is valid UTF-8, so extension is valid UTF-8 as well 993 | unsafe { str_assume_utf8(s) } 994 | }) 995 | } 996 | 997 | /// Creates an owned [`Utf8PathBuf`] with `path` adjoined to `self`. 998 | /// 999 | /// See [`Utf8PathBuf::push`] for more details on what it means to adjoin a path. 1000 | /// 1001 | /// # Examples 1002 | /// 1003 | /// ``` 1004 | /// use camino::{Utf8Path, Utf8PathBuf}; 1005 | /// 1006 | /// assert_eq!(Utf8Path::new("/etc").join("passwd"), Utf8PathBuf::from("/etc/passwd")); 1007 | /// ``` 1008 | #[inline] 1009 | #[must_use] 1010 | pub fn join(&self, path: impl AsRef) -> Utf8PathBuf { 1011 | Utf8PathBuf(self.0.join(&path.as_ref().0)) 1012 | } 1013 | 1014 | /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. 1015 | /// 1016 | /// See [`PathBuf::push`] for more details on what it means to adjoin a path. 1017 | /// 1018 | /// # Examples 1019 | /// 1020 | /// ``` 1021 | /// use camino::Utf8Path; 1022 | /// use std::path::PathBuf; 1023 | /// 1024 | /// assert_eq!(Utf8Path::new("/etc").join_os("passwd"), PathBuf::from("/etc/passwd")); 1025 | /// ``` 1026 | #[inline] 1027 | #[must_use] 1028 | pub fn join_os(&self, path: impl AsRef) -> PathBuf { 1029 | self.0.join(path) 1030 | } 1031 | 1032 | /// Creates an owned [`Utf8PathBuf`] like `self` but with the given file name. 1033 | /// 1034 | /// See [`Utf8PathBuf::set_file_name`] for more details. 1035 | /// 1036 | /// # Examples 1037 | /// 1038 | /// ``` 1039 | /// use camino::{Utf8Path, Utf8PathBuf}; 1040 | /// 1041 | /// let path = Utf8Path::new("/tmp/foo.txt"); 1042 | /// assert_eq!(path.with_file_name("bar.txt"), Utf8PathBuf::from("/tmp/bar.txt")); 1043 | /// 1044 | /// let path = Utf8Path::new("/tmp"); 1045 | /// assert_eq!(path.with_file_name("var"), Utf8PathBuf::from("/var")); 1046 | /// ``` 1047 | #[inline] 1048 | #[must_use] 1049 | pub fn with_file_name(&self, file_name: impl AsRef) -> Utf8PathBuf { 1050 | Utf8PathBuf(self.0.with_file_name(file_name.as_ref())) 1051 | } 1052 | 1053 | /// Creates an owned [`Utf8PathBuf`] like `self` but with the given extension. 1054 | /// 1055 | /// See [`Utf8PathBuf::set_extension`] for more details. 1056 | /// 1057 | /// # Examples 1058 | /// 1059 | /// ``` 1060 | /// use camino::{Utf8Path, Utf8PathBuf}; 1061 | /// 1062 | /// let path = Utf8Path::new("foo.rs"); 1063 | /// assert_eq!(path.with_extension("txt"), Utf8PathBuf::from("foo.txt")); 1064 | /// 1065 | /// let path = Utf8Path::new("foo.tar.gz"); 1066 | /// assert_eq!(path.with_extension(""), Utf8PathBuf::from("foo.tar")); 1067 | /// assert_eq!(path.with_extension("xz"), Utf8PathBuf::from("foo.tar.xz")); 1068 | /// assert_eq!(path.with_extension("").with_extension("txt"), Utf8PathBuf::from("foo.txt")); 1069 | /// ``` 1070 | #[inline] 1071 | pub fn with_extension(&self, extension: impl AsRef) -> Utf8PathBuf { 1072 | Utf8PathBuf(self.0.with_extension(extension.as_ref())) 1073 | } 1074 | 1075 | /// Produces an iterator over the [`Utf8Component`]s of the path. 1076 | /// 1077 | /// When parsing the path, there is a small amount of normalization: 1078 | /// 1079 | /// * Repeated separators are ignored, so `a/b` and `a//b` both have 1080 | /// `a` and `b` as components. 1081 | /// 1082 | /// * Occurrences of `.` are normalized away, except if they are at the 1083 | /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and 1084 | /// `a/b` all have `a` and `b` as components, but `./a/b` starts with 1085 | /// an additional [`CurDir`] component. 1086 | /// 1087 | /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. 1088 | /// 1089 | /// Note that no other normalization takes place; in particular, `a/c` 1090 | /// and `a/b/../c` are distinct, to account for the possibility that `b` 1091 | /// is a symbolic link (so its parent isn't `a`). 1092 | /// 1093 | /// # Examples 1094 | /// 1095 | /// ``` 1096 | /// use camino::{Utf8Component, Utf8Path}; 1097 | /// 1098 | /// let mut components = Utf8Path::new("/tmp/foo.txt").components(); 1099 | /// 1100 | /// assert_eq!(components.next(), Some(Utf8Component::RootDir)); 1101 | /// assert_eq!(components.next(), Some(Utf8Component::Normal("tmp"))); 1102 | /// assert_eq!(components.next(), Some(Utf8Component::Normal("foo.txt"))); 1103 | /// assert_eq!(components.next(), None) 1104 | /// ``` 1105 | /// 1106 | /// [`CurDir`]: Utf8Component::CurDir 1107 | #[inline] 1108 | pub fn components(&self) -> Utf8Components { 1109 | Utf8Components(self.0.components()) 1110 | } 1111 | 1112 | /// Produces an iterator over the path's components viewed as [`str`] 1113 | /// slices. 1114 | /// 1115 | /// For more information about the particulars of how the path is separated 1116 | /// into components, see [`components`]. 1117 | /// 1118 | /// [`components`]: Utf8Path::components 1119 | /// 1120 | /// # Examples 1121 | /// 1122 | /// ``` 1123 | /// use camino::Utf8Path; 1124 | /// 1125 | /// let mut it = Utf8Path::new("/tmp/foo.txt").iter(); 1126 | /// assert_eq!(it.next(), Some(std::path::MAIN_SEPARATOR.to_string().as_str())); 1127 | /// assert_eq!(it.next(), Some("tmp")); 1128 | /// assert_eq!(it.next(), Some("foo.txt")); 1129 | /// assert_eq!(it.next(), None) 1130 | /// ``` 1131 | #[inline] 1132 | pub fn iter(&self) -> Iter<'_> { 1133 | Iter { 1134 | inner: self.components(), 1135 | } 1136 | } 1137 | 1138 | /// Queries the file system to get information about a file, directory, etc. 1139 | /// 1140 | /// This function will traverse symbolic links to query information about the 1141 | /// destination file. 1142 | /// 1143 | /// This is an alias to [`fs::metadata`]. 1144 | /// 1145 | /// # Examples 1146 | /// 1147 | /// ```no_run 1148 | /// use camino::Utf8Path; 1149 | /// 1150 | /// let path = Utf8Path::new("/Minas/tirith"); 1151 | /// let metadata = path.metadata().expect("metadata call failed"); 1152 | /// println!("{:?}", metadata.file_type()); 1153 | /// ``` 1154 | #[inline] 1155 | pub fn metadata(&self) -> io::Result { 1156 | self.0.metadata() 1157 | } 1158 | 1159 | /// Queries the metadata about a file without following symlinks. 1160 | /// 1161 | /// This is an alias to [`fs::symlink_metadata`]. 1162 | /// 1163 | /// # Examples 1164 | /// 1165 | /// ```no_run 1166 | /// use camino::Utf8Path; 1167 | /// 1168 | /// let path = Utf8Path::new("/Minas/tirith"); 1169 | /// let metadata = path.symlink_metadata().expect("symlink_metadata call failed"); 1170 | /// println!("{:?}", metadata.file_type()); 1171 | /// ``` 1172 | #[inline] 1173 | pub fn symlink_metadata(&self) -> io::Result { 1174 | self.0.symlink_metadata() 1175 | } 1176 | 1177 | /// Returns the canonical, absolute form of the path with all intermediate 1178 | /// components normalized and symbolic links resolved. 1179 | /// 1180 | /// This returns a [`PathBuf`] because even if a symlink is valid Unicode, its target may not 1181 | /// be. For a version that returns a [`Utf8PathBuf`], see 1182 | /// [`canonicalize_utf8`](Self::canonicalize_utf8). 1183 | /// 1184 | /// This is an alias to [`fs::canonicalize`]. 1185 | /// 1186 | /// # Examples 1187 | /// 1188 | /// ```no_run 1189 | /// use camino::Utf8Path; 1190 | /// use std::path::PathBuf; 1191 | /// 1192 | /// let path = Utf8Path::new("/foo/test/../test/bar.rs"); 1193 | /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs")); 1194 | /// ``` 1195 | #[inline] 1196 | pub fn canonicalize(&self) -> io::Result { 1197 | self.0.canonicalize() 1198 | } 1199 | 1200 | /// Returns the canonical, absolute form of the path with all intermediate 1201 | /// components normalized and symbolic links resolved. 1202 | /// 1203 | /// This method attempts to convert the resulting [`PathBuf`] into a [`Utf8PathBuf`]. For a 1204 | /// version that does not attempt to do this conversion, see 1205 | /// [`canonicalize`](Self::canonicalize). 1206 | /// 1207 | /// # Errors 1208 | /// 1209 | /// The I/O operation may return an error: see the [`fs::canonicalize`] 1210 | /// documentation for more. 1211 | /// 1212 | /// If the resulting path is not UTF-8, an [`io::Error`] is returned with the 1213 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a 1214 | /// [`FromPathBufError`]. 1215 | /// 1216 | /// # Examples 1217 | /// 1218 | /// ```no_run 1219 | /// use camino::{Utf8Path, Utf8PathBuf}; 1220 | /// 1221 | /// let path = Utf8Path::new("/foo/test/../test/bar.rs"); 1222 | /// assert_eq!(path.canonicalize_utf8().unwrap(), Utf8PathBuf::from("/foo/test/bar.rs")); 1223 | /// ``` 1224 | pub fn canonicalize_utf8(&self) -> io::Result { 1225 | self.canonicalize() 1226 | .and_then(|path| path.try_into().map_err(FromPathBufError::into_io_error)) 1227 | } 1228 | 1229 | /// Reads a symbolic link, returning the file that the link points to. 1230 | /// 1231 | /// This returns a [`PathBuf`] because even if a symlink is valid Unicode, its target may not 1232 | /// be. For a version that returns a [`Utf8PathBuf`], see 1233 | /// [`read_link_utf8`](Self::read_link_utf8). 1234 | /// 1235 | /// This is an alias to [`fs::read_link`]. 1236 | /// 1237 | /// # Examples 1238 | /// 1239 | /// ```no_run 1240 | /// use camino::Utf8Path; 1241 | /// 1242 | /// let path = Utf8Path::new("/laputa/sky_castle.rs"); 1243 | /// let path_link = path.read_link().expect("read_link call failed"); 1244 | /// ``` 1245 | #[inline] 1246 | pub fn read_link(&self) -> io::Result { 1247 | self.0.read_link() 1248 | } 1249 | 1250 | /// Reads a symbolic link, returning the file that the link points to. 1251 | /// 1252 | /// This method attempts to convert the resulting [`PathBuf`] into a [`Utf8PathBuf`]. For a 1253 | /// version that does not attempt to do this conversion, see [`read_link`](Self::read_link). 1254 | /// 1255 | /// # Errors 1256 | /// 1257 | /// The I/O operation may return an error: see the [`fs::read_link`] 1258 | /// documentation for more. 1259 | /// 1260 | /// If the resulting path is not UTF-8, an [`io::Error`] is returned with the 1261 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a 1262 | /// [`FromPathBufError`]. 1263 | /// 1264 | /// # Examples 1265 | /// 1266 | /// ```no_run 1267 | /// use camino::Utf8Path; 1268 | /// 1269 | /// let path = Utf8Path::new("/laputa/sky_castle.rs"); 1270 | /// let path_link = path.read_link_utf8().expect("read_link call failed"); 1271 | /// ``` 1272 | pub fn read_link_utf8(&self) -> io::Result { 1273 | self.read_link() 1274 | .and_then(|path| path.try_into().map_err(FromPathBufError::into_io_error)) 1275 | } 1276 | 1277 | /// Returns an iterator over the entries within a directory. 1278 | /// 1279 | /// The iterator will yield instances of [`io::Result`]`<`[`fs::DirEntry`]`>`. New 1280 | /// errors may be encountered after an iterator is initially constructed. 1281 | /// 1282 | /// This is an alias to [`fs::read_dir`]. 1283 | /// 1284 | /// # Examples 1285 | /// 1286 | /// ```no_run 1287 | /// use camino::Utf8Path; 1288 | /// 1289 | /// let path = Utf8Path::new("/laputa"); 1290 | /// for entry in path.read_dir().expect("read_dir call failed") { 1291 | /// if let Ok(entry) = entry { 1292 | /// println!("{:?}", entry.path()); 1293 | /// } 1294 | /// } 1295 | /// ``` 1296 | #[inline] 1297 | pub fn read_dir(&self) -> io::Result { 1298 | self.0.read_dir() 1299 | } 1300 | 1301 | /// Returns an iterator over the entries within a directory. 1302 | /// 1303 | /// The iterator will yield instances of [`io::Result`]`<`[`Utf8DirEntry`]`>`. New 1304 | /// errors may be encountered after an iterator is initially constructed. 1305 | /// 1306 | /// # Errors 1307 | /// 1308 | /// The I/O operation may return an error: see the [`fs::read_dir`] 1309 | /// documentation for more. 1310 | /// 1311 | /// If a directory entry is not UTF-8, an [`io::Error`] is returned with the 1312 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a 1313 | /// [`FromPathBufError`]. 1314 | /// 1315 | /// # Examples 1316 | /// 1317 | /// ```no_run 1318 | /// use camino::Utf8Path; 1319 | /// 1320 | /// let path = Utf8Path::new("/laputa"); 1321 | /// for entry in path.read_dir_utf8().expect("read_dir call failed") { 1322 | /// if let Ok(entry) = entry { 1323 | /// println!("{}", entry.path()); 1324 | /// } 1325 | /// } 1326 | /// ``` 1327 | #[inline] 1328 | pub fn read_dir_utf8(&self) -> io::Result { 1329 | self.0.read_dir().map(|inner| ReadDirUtf8 { inner }) 1330 | } 1331 | 1332 | /// Returns `true` if the path points at an existing entity. 1333 | /// 1334 | /// Warning: this method may be error-prone, consider using [`try_exists()`] instead! 1335 | /// It also has a risk of introducing time-of-check to time-of-use (TOCTOU) bugs. 1336 | /// 1337 | /// This function will traverse symbolic links to query information about the 1338 | /// destination file. In case of broken symbolic links this will return `false`. 1339 | /// 1340 | /// If you cannot access the directory containing the file, e.g., because of a 1341 | /// permission error, this will return `false`. 1342 | /// 1343 | /// # Examples 1344 | /// 1345 | /// ```no_run 1346 | /// use camino::Utf8Path; 1347 | /// assert!(!Utf8Path::new("does_not_exist.txt").exists()); 1348 | /// ``` 1349 | /// 1350 | /// # See Also 1351 | /// 1352 | /// This is a convenience function that coerces errors to false. If you want to 1353 | /// check errors, call [`fs::metadata`]. 1354 | /// 1355 | /// [`try_exists()`]: Self::try_exists 1356 | #[must_use] 1357 | #[inline] 1358 | pub fn exists(&self) -> bool { 1359 | self.0.exists() 1360 | } 1361 | 1362 | /// Returns `Ok(true)` if the path points at an existing entity. 1363 | /// 1364 | /// This function will traverse symbolic links to query information about the 1365 | /// destination file. In case of broken symbolic links this will return `Ok(false)`. 1366 | /// 1367 | /// As opposed to the [`exists()`] method, this one doesn't silently ignore errors 1368 | /// unrelated to the path not existing. (E.g. it will return `Err(_)` in case of permission 1369 | /// denied on some of the parent directories.) 1370 | /// 1371 | /// Note that while this avoids some pitfalls of the `exists()` method, it still can not 1372 | /// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios 1373 | /// where those bugs are not an issue. 1374 | /// 1375 | /// # Examples 1376 | /// 1377 | /// ```no_run 1378 | /// use camino::Utf8Path; 1379 | /// assert!(!Utf8Path::new("does_not_exist.txt").try_exists().expect("Can't check existence of file does_not_exist.txt")); 1380 | /// assert!(Utf8Path::new("/root/secret_file.txt").try_exists().is_err()); 1381 | /// ``` 1382 | /// 1383 | /// [`exists()`]: Self::exists 1384 | #[inline] 1385 | pub fn try_exists(&self) -> io::Result { 1386 | // Note: this block is written this way rather than with a pattern guard to appease Rust 1387 | // 1.34. 1388 | match fs::metadata(self) { 1389 | Ok(_) => Ok(true), 1390 | Err(error) => { 1391 | if error.kind() == io::ErrorKind::NotFound { 1392 | Ok(false) 1393 | } else { 1394 | Err(error) 1395 | } 1396 | } 1397 | } 1398 | } 1399 | 1400 | /// Returns `true` if the path exists on disk and is pointing at a regular file. 1401 | /// 1402 | /// This function will traverse symbolic links to query information about the 1403 | /// destination file. In case of broken symbolic links this will return `false`. 1404 | /// 1405 | /// If you cannot access the directory containing the file, e.g., because of a 1406 | /// permission error, this will return `false`. 1407 | /// 1408 | /// # Examples 1409 | /// 1410 | /// ```no_run 1411 | /// use camino::Utf8Path; 1412 | /// assert_eq!(Utf8Path::new("./is_a_directory/").is_file(), false); 1413 | /// assert_eq!(Utf8Path::new("a_file.txt").is_file(), true); 1414 | /// ``` 1415 | /// 1416 | /// # See Also 1417 | /// 1418 | /// This is a convenience function that coerces errors to false. If you want to 1419 | /// check errors, call [`fs::metadata`] and handle its [`Result`]. Then call 1420 | /// [`fs::Metadata::is_file`] if it was [`Ok`]. 1421 | /// 1422 | /// When the goal is simply to read from (or write to) the source, the most 1423 | /// reliable way to test the source can be read (or written to) is to open 1424 | /// it. Only using `is_file` can break workflows like `diff <( prog_a )` on 1425 | /// a Unix-like system for example. See [`fs::File::open`] or 1426 | /// [`fs::OpenOptions::open`] for more information. 1427 | #[must_use] 1428 | #[inline] 1429 | pub fn is_file(&self) -> bool { 1430 | self.0.is_file() 1431 | } 1432 | 1433 | /// Returns `true` if the path exists on disk and is pointing at a directory. 1434 | /// 1435 | /// This function will traverse symbolic links to query information about the 1436 | /// destination file. In case of broken symbolic links this will return `false`. 1437 | /// 1438 | /// If you cannot access the directory containing the file, e.g., because of a 1439 | /// permission error, this will return `false`. 1440 | /// 1441 | /// # Examples 1442 | /// 1443 | /// ```no_run 1444 | /// use camino::Utf8Path; 1445 | /// assert_eq!(Utf8Path::new("./is_a_directory/").is_dir(), true); 1446 | /// assert_eq!(Utf8Path::new("a_file.txt").is_dir(), false); 1447 | /// ``` 1448 | /// 1449 | /// # See Also 1450 | /// 1451 | /// This is a convenience function that coerces errors to false. If you want to 1452 | /// check errors, call [`fs::metadata`] and handle its [`Result`]. Then call 1453 | /// [`fs::Metadata::is_dir`] if it was [`Ok`]. 1454 | #[must_use] 1455 | #[inline] 1456 | pub fn is_dir(&self) -> bool { 1457 | self.0.is_dir() 1458 | } 1459 | 1460 | /// Returns `true` if the path exists on disk and is pointing at a symbolic link. 1461 | /// 1462 | /// This function will not traverse symbolic links. 1463 | /// In case of a broken symbolic link this will also return true. 1464 | /// 1465 | /// If you cannot access the directory containing the file, e.g., because of a 1466 | /// permission error, this will return false. 1467 | /// 1468 | /// # Examples 1469 | /// 1470 | #[cfg_attr(unix, doc = "```no_run")] 1471 | #[cfg_attr(not(unix), doc = "```ignore")] 1472 | /// use camino::Utf8Path; 1473 | /// use std::os::unix::fs::symlink; 1474 | /// 1475 | /// let link_path = Utf8Path::new("link"); 1476 | /// symlink("/origin_does_not_exist/", link_path).unwrap(); 1477 | /// assert_eq!(link_path.is_symlink(), true); 1478 | /// assert_eq!(link_path.exists(), false); 1479 | /// ``` 1480 | /// 1481 | /// # See Also 1482 | /// 1483 | /// This is a convenience function that coerces errors to false. If you want to 1484 | /// check errors, call [`Utf8Path::symlink_metadata`] and handle its [`Result`]. Then call 1485 | /// [`fs::Metadata::is_symlink`] if it was [`Ok`]. 1486 | #[must_use] 1487 | pub fn is_symlink(&self) -> bool { 1488 | self.symlink_metadata() 1489 | .map(|m| m.file_type().is_symlink()) 1490 | .unwrap_or(false) 1491 | } 1492 | 1493 | /// Converts a `Box` into a [`Utf8PathBuf`] without copying or allocating. 1494 | #[must_use = "`self` will be dropped if the result is not used"] 1495 | #[inline] 1496 | pub fn into_path_buf(self: Box) -> Utf8PathBuf { 1497 | let ptr = Box::into_raw(self) as *mut Path; 1498 | // SAFETY: 1499 | // * self is valid UTF-8 1500 | // * ptr was constructed by consuming self so it represents an owned path. 1501 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from a *mut Utf8Path to a 1502 | // *mut Path is valid. 1503 | let boxed_path = unsafe { Box::from_raw(ptr) }; 1504 | Utf8PathBuf(boxed_path.into_path_buf()) 1505 | } 1506 | 1507 | // invariant: Path must be guaranteed to be utf-8 data 1508 | #[inline] 1509 | unsafe fn assume_utf8(path: &Path) -> &Utf8Path { 1510 | // SAFETY: Utf8Path is marked as #[repr(transparent)] so the conversion from a 1511 | // *const Path to a *const Utf8Path is valid. 1512 | &*(path as *const Path as *const Utf8Path) 1513 | } 1514 | 1515 | #[cfg(path_buf_deref_mut)] 1516 | #[inline] 1517 | unsafe fn assume_utf8_mut(path: &mut Path) -> &mut Utf8Path { 1518 | &mut *(path as *mut Path as *mut Utf8Path) 1519 | } 1520 | } 1521 | 1522 | impl Clone for Box { 1523 | fn clone(&self) -> Self { 1524 | let boxed: Box = self.0.into(); 1525 | let ptr = Box::into_raw(boxed) as *mut Utf8Path; 1526 | // SAFETY: 1527 | // * self is valid UTF-8 1528 | // * ptr was created by consuming a Box so it represents an rced pointer 1529 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *mut Path to 1530 | // *mut Utf8Path is valid 1531 | unsafe { Box::from_raw(ptr) } 1532 | } 1533 | } 1534 | 1535 | impl fmt::Display for Utf8Path { 1536 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1537 | fmt::Display::fmt(self.as_str(), f) 1538 | } 1539 | } 1540 | 1541 | impl fmt::Debug for Utf8Path { 1542 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1543 | fmt::Debug::fmt(self.as_str(), f) 1544 | } 1545 | } 1546 | 1547 | /// An iterator over [`Utf8Path`] and its ancestors. 1548 | /// 1549 | /// This `struct` is created by the [`ancestors`] method on [`Utf8Path`]. 1550 | /// See its documentation for more. 1551 | /// 1552 | /// # Examples 1553 | /// 1554 | /// ``` 1555 | /// use camino::Utf8Path; 1556 | /// 1557 | /// let path = Utf8Path::new("/foo/bar"); 1558 | /// 1559 | /// for ancestor in path.ancestors() { 1560 | /// println!("{}", ancestor); 1561 | /// } 1562 | /// ``` 1563 | /// 1564 | /// [`ancestors`]: Utf8Path::ancestors 1565 | #[derive(Copy, Clone)] 1566 | #[must_use = "iterators are lazy and do nothing unless consumed"] 1567 | #[repr(transparent)] 1568 | pub struct Utf8Ancestors<'a>(Ancestors<'a>); 1569 | 1570 | impl fmt::Debug for Utf8Ancestors<'_> { 1571 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1572 | fmt::Debug::fmt(&self.0, f) 1573 | } 1574 | } 1575 | 1576 | impl<'a> Iterator for Utf8Ancestors<'a> { 1577 | type Item = &'a Utf8Path; 1578 | 1579 | #[inline] 1580 | fn next(&mut self) -> Option { 1581 | self.0.next().map(|path| { 1582 | // SAFETY: Utf8Ancestors was constructed from a Utf8Path, so it is guaranteed to 1583 | // be valid UTF-8 1584 | unsafe { Utf8Path::assume_utf8(path) } 1585 | }) 1586 | } 1587 | } 1588 | 1589 | impl FusedIterator for Utf8Ancestors<'_> {} 1590 | 1591 | /// An iterator over the [`Utf8Component`]s of a [`Utf8Path`]. 1592 | /// 1593 | /// This `struct` is created by the [`components`] method on [`Utf8Path`]. 1594 | /// See its documentation for more. 1595 | /// 1596 | /// # Examples 1597 | /// 1598 | /// ``` 1599 | /// use camino::Utf8Path; 1600 | /// 1601 | /// let path = Utf8Path::new("/tmp/foo/bar.txt"); 1602 | /// 1603 | /// for component in path.components() { 1604 | /// println!("{:?}", component); 1605 | /// } 1606 | /// ``` 1607 | /// 1608 | /// [`components`]: Utf8Path::components 1609 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 1610 | #[must_use = "iterators are lazy and do nothing unless consumed"] 1611 | pub struct Utf8Components<'a>(Components<'a>); 1612 | 1613 | impl<'a> Utf8Components<'a> { 1614 | /// Extracts a slice corresponding to the portion of the path remaining for iteration. 1615 | /// 1616 | /// # Examples 1617 | /// 1618 | /// ``` 1619 | /// use camino::Utf8Path; 1620 | /// 1621 | /// let mut components = Utf8Path::new("/tmp/foo/bar.txt").components(); 1622 | /// components.next(); 1623 | /// components.next(); 1624 | /// 1625 | /// assert_eq!(Utf8Path::new("foo/bar.txt"), components.as_path()); 1626 | /// ``` 1627 | #[must_use] 1628 | #[inline] 1629 | pub fn as_path(&self) -> &'a Utf8Path { 1630 | // SAFETY: Utf8Components was constructed from a Utf8Path, so it is guaranteed to be valid 1631 | // UTF-8 1632 | unsafe { Utf8Path::assume_utf8(self.0.as_path()) } 1633 | } 1634 | } 1635 | 1636 | impl<'a> Iterator for Utf8Components<'a> { 1637 | type Item = Utf8Component<'a>; 1638 | 1639 | #[inline] 1640 | fn next(&mut self) -> Option { 1641 | self.0.next().map(|component| { 1642 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be 1643 | // valid UTF-8 1644 | unsafe { Utf8Component::new(component) } 1645 | }) 1646 | } 1647 | } 1648 | 1649 | impl FusedIterator for Utf8Components<'_> {} 1650 | 1651 | impl DoubleEndedIterator for Utf8Components<'_> { 1652 | #[inline] 1653 | fn next_back(&mut self) -> Option { 1654 | self.0.next_back().map(|component| { 1655 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be 1656 | // valid UTF-8 1657 | unsafe { Utf8Component::new(component) } 1658 | }) 1659 | } 1660 | } 1661 | 1662 | impl fmt::Debug for Utf8Components<'_> { 1663 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1664 | fmt::Debug::fmt(&self.0, f) 1665 | } 1666 | } 1667 | 1668 | impl AsRef for Utf8Components<'_> { 1669 | #[inline] 1670 | fn as_ref(&self) -> &Utf8Path { 1671 | self.as_path() 1672 | } 1673 | } 1674 | 1675 | impl AsRef for Utf8Components<'_> { 1676 | #[inline] 1677 | fn as_ref(&self) -> &Path { 1678 | self.as_path().as_ref() 1679 | } 1680 | } 1681 | 1682 | impl AsRef for Utf8Components<'_> { 1683 | #[inline] 1684 | fn as_ref(&self) -> &str { 1685 | self.as_path().as_ref() 1686 | } 1687 | } 1688 | 1689 | impl AsRef for Utf8Components<'_> { 1690 | #[inline] 1691 | fn as_ref(&self) -> &OsStr { 1692 | self.as_path().as_os_str() 1693 | } 1694 | } 1695 | 1696 | /// An iterator over the [`Utf8Component`]s of a [`Utf8Path`], as [`str`] slices. 1697 | /// 1698 | /// This `struct` is created by the [`iter`] method on [`Utf8Path`]. 1699 | /// See its documentation for more. 1700 | /// 1701 | /// [`iter`]: Utf8Path::iter 1702 | #[derive(Clone)] 1703 | #[must_use = "iterators are lazy and do nothing unless consumed"] 1704 | pub struct Iter<'a> { 1705 | inner: Utf8Components<'a>, 1706 | } 1707 | 1708 | impl fmt::Debug for Iter<'_> { 1709 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1710 | struct DebugHelper<'a>(&'a Utf8Path); 1711 | 1712 | impl fmt::Debug for DebugHelper<'_> { 1713 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1714 | f.debug_list().entries(self.0.iter()).finish() 1715 | } 1716 | } 1717 | 1718 | f.debug_tuple("Iter") 1719 | .field(&DebugHelper(self.as_path())) 1720 | .finish() 1721 | } 1722 | } 1723 | 1724 | impl<'a> Iter<'a> { 1725 | /// Extracts a slice corresponding to the portion of the path remaining for iteration. 1726 | /// 1727 | /// # Examples 1728 | /// 1729 | /// ``` 1730 | /// use camino::Utf8Path; 1731 | /// 1732 | /// let mut iter = Utf8Path::new("/tmp/foo/bar.txt").iter(); 1733 | /// iter.next(); 1734 | /// iter.next(); 1735 | /// 1736 | /// assert_eq!(Utf8Path::new("foo/bar.txt"), iter.as_path()); 1737 | /// ``` 1738 | #[must_use] 1739 | #[inline] 1740 | pub fn as_path(&self) -> &'a Utf8Path { 1741 | self.inner.as_path() 1742 | } 1743 | } 1744 | 1745 | impl AsRef for Iter<'_> { 1746 | #[inline] 1747 | fn as_ref(&self) -> &Utf8Path { 1748 | self.as_path() 1749 | } 1750 | } 1751 | 1752 | impl AsRef for Iter<'_> { 1753 | #[inline] 1754 | fn as_ref(&self) -> &Path { 1755 | self.as_path().as_ref() 1756 | } 1757 | } 1758 | 1759 | impl AsRef for Iter<'_> { 1760 | #[inline] 1761 | fn as_ref(&self) -> &str { 1762 | self.as_path().as_ref() 1763 | } 1764 | } 1765 | 1766 | impl AsRef for Iter<'_> { 1767 | #[inline] 1768 | fn as_ref(&self) -> &OsStr { 1769 | self.as_path().as_os_str() 1770 | } 1771 | } 1772 | 1773 | impl<'a> Iterator for Iter<'a> { 1774 | type Item = &'a str; 1775 | 1776 | #[inline] 1777 | fn next(&mut self) -> Option<&'a str> { 1778 | self.inner.next().map(|component| component.as_str()) 1779 | } 1780 | } 1781 | 1782 | impl<'a> DoubleEndedIterator for Iter<'a> { 1783 | #[inline] 1784 | fn next_back(&mut self) -> Option<&'a str> { 1785 | self.inner.next_back().map(|component| component.as_str()) 1786 | } 1787 | } 1788 | 1789 | impl FusedIterator for Iter<'_> {} 1790 | 1791 | /// A single component of a path. 1792 | /// 1793 | /// A `Utf8Component` roughly corresponds to a substring between path separators 1794 | /// (`/` or `\`). 1795 | /// 1796 | /// This `enum` is created by iterating over [`Utf8Components`], which in turn is 1797 | /// created by the [`components`](Utf8Path::components) method on [`Utf8Path`]. 1798 | /// 1799 | /// # Examples 1800 | /// 1801 | /// ```rust 1802 | /// use camino::{Utf8Component, Utf8Path}; 1803 | /// 1804 | /// let path = Utf8Path::new("/tmp/foo/bar.txt"); 1805 | /// let components = path.components().collect::>(); 1806 | /// assert_eq!(&components, &[ 1807 | /// Utf8Component::RootDir, 1808 | /// Utf8Component::Normal("tmp"), 1809 | /// Utf8Component::Normal("foo"), 1810 | /// Utf8Component::Normal("bar.txt"), 1811 | /// ]); 1812 | /// ``` 1813 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 1814 | pub enum Utf8Component<'a> { 1815 | /// A Windows path prefix, e.g., `C:` or `\\server\share`. 1816 | /// 1817 | /// There is a large variety of prefix types, see [`Utf8Prefix`]'s documentation 1818 | /// for more. 1819 | /// 1820 | /// Does not occur on Unix. 1821 | Prefix(Utf8PrefixComponent<'a>), 1822 | 1823 | /// The root directory component, appears after any prefix and before anything else. 1824 | /// 1825 | /// It represents a separator that designates that a path starts from root. 1826 | RootDir, 1827 | 1828 | /// A reference to the current directory, i.e., `.`. 1829 | CurDir, 1830 | 1831 | /// A reference to the parent directory, i.e., `..`. 1832 | ParentDir, 1833 | 1834 | /// A normal component, e.g., `a` and `b` in `a/b`. 1835 | /// 1836 | /// This variant is the most common one, it represents references to files 1837 | /// or directories. 1838 | Normal(&'a str), 1839 | } 1840 | 1841 | impl<'a> Utf8Component<'a> { 1842 | unsafe fn new(component: Component<'a>) -> Utf8Component<'a> { 1843 | match component { 1844 | Component::Prefix(prefix) => Utf8Component::Prefix(Utf8PrefixComponent(prefix)), 1845 | Component::RootDir => Utf8Component::RootDir, 1846 | Component::CurDir => Utf8Component::CurDir, 1847 | Component::ParentDir => Utf8Component::ParentDir, 1848 | Component::Normal(s) => Utf8Component::Normal(str_assume_utf8(s)), 1849 | } 1850 | } 1851 | 1852 | /// Extracts the underlying [`str`] slice. 1853 | /// 1854 | /// # Examples 1855 | /// 1856 | /// ``` 1857 | /// use camino::Utf8Path; 1858 | /// 1859 | /// let path = Utf8Path::new("./tmp/foo/bar.txt"); 1860 | /// let components: Vec<_> = path.components().map(|comp| comp.as_str()).collect(); 1861 | /// assert_eq!(&components, &[".", "tmp", "foo", "bar.txt"]); 1862 | /// ``` 1863 | #[must_use] 1864 | #[inline] 1865 | pub fn as_str(&self) -> &'a str { 1866 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be 1867 | // valid UTF-8 1868 | unsafe { str_assume_utf8(self.as_os_str()) } 1869 | } 1870 | 1871 | /// Extracts the underlying [`OsStr`] slice. 1872 | /// 1873 | /// # Examples 1874 | /// 1875 | /// ``` 1876 | /// use camino::Utf8Path; 1877 | /// 1878 | /// let path = Utf8Path::new("./tmp/foo/bar.txt"); 1879 | /// let components: Vec<_> = path.components().map(|comp| comp.as_os_str()).collect(); 1880 | /// assert_eq!(&components, &[".", "tmp", "foo", "bar.txt"]); 1881 | /// ``` 1882 | #[must_use] 1883 | pub fn as_os_str(&self) -> &'a OsStr { 1884 | match *self { 1885 | Utf8Component::Prefix(prefix) => prefix.as_os_str(), 1886 | Utf8Component::RootDir => Component::RootDir.as_os_str(), 1887 | Utf8Component::CurDir => Component::CurDir.as_os_str(), 1888 | Utf8Component::ParentDir => Component::ParentDir.as_os_str(), 1889 | Utf8Component::Normal(s) => OsStr::new(s), 1890 | } 1891 | } 1892 | } 1893 | 1894 | impl fmt::Debug for Utf8Component<'_> { 1895 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1896 | fmt::Debug::fmt(self.as_os_str(), f) 1897 | } 1898 | } 1899 | 1900 | impl fmt::Display for Utf8Component<'_> { 1901 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1902 | fmt::Display::fmt(self.as_str(), f) 1903 | } 1904 | } 1905 | 1906 | impl AsRef for Utf8Component<'_> { 1907 | #[inline] 1908 | fn as_ref(&self) -> &Utf8Path { 1909 | self.as_str().as_ref() 1910 | } 1911 | } 1912 | 1913 | impl AsRef for Utf8Component<'_> { 1914 | #[inline] 1915 | fn as_ref(&self) -> &Path { 1916 | self.as_os_str().as_ref() 1917 | } 1918 | } 1919 | 1920 | impl AsRef for Utf8Component<'_> { 1921 | #[inline] 1922 | fn as_ref(&self) -> &str { 1923 | self.as_str() 1924 | } 1925 | } 1926 | 1927 | impl AsRef for Utf8Component<'_> { 1928 | #[inline] 1929 | fn as_ref(&self) -> &OsStr { 1930 | self.as_os_str() 1931 | } 1932 | } 1933 | 1934 | /// Windows path prefixes, e.g., `C:` or `\\server\share`. 1935 | /// 1936 | /// Windows uses a variety of path prefix styles, including references to drive 1937 | /// volumes (like `C:`), network shared folders (like `\\server\share`), and 1938 | /// others. In addition, some path prefixes are "verbatim" (i.e., prefixed with 1939 | /// `\\?\`), in which case `/` is *not* treated as a separator and essentially 1940 | /// no normalization is performed. 1941 | /// 1942 | /// # Examples 1943 | /// 1944 | /// ``` 1945 | /// use camino::{Utf8Component, Utf8Path, Utf8Prefix}; 1946 | /// use camino::Utf8Prefix::*; 1947 | /// 1948 | /// fn get_path_prefix(s: &str) -> Utf8Prefix { 1949 | /// let path = Utf8Path::new(s); 1950 | /// match path.components().next().unwrap() { 1951 | /// Utf8Component::Prefix(prefix_component) => prefix_component.kind(), 1952 | /// _ => panic!(), 1953 | /// } 1954 | /// } 1955 | /// 1956 | /// # if cfg!(windows) { 1957 | /// assert_eq!(Verbatim("pictures"), get_path_prefix(r"\\?\pictures\kittens")); 1958 | /// assert_eq!(VerbatimUNC("server", "share"), get_path_prefix(r"\\?\UNC\server\share")); 1959 | /// assert_eq!(VerbatimDisk(b'C'), get_path_prefix(r"\\?\c:\")); 1960 | /// assert_eq!(DeviceNS("BrainInterface"), get_path_prefix(r"\\.\BrainInterface")); 1961 | /// assert_eq!(UNC("server", "share"), get_path_prefix(r"\\server\share")); 1962 | /// assert_eq!(Disk(b'C'), get_path_prefix(r"C:\Users\Rust\Pictures\Ferris")); 1963 | /// # } 1964 | /// ``` 1965 | #[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] 1966 | pub enum Utf8Prefix<'a> { 1967 | /// Verbatim prefix, e.g., `\\?\cat_pics`. 1968 | /// 1969 | /// Verbatim prefixes consist of `\\?\` immediately followed by the given 1970 | /// component. 1971 | Verbatim(&'a str), 1972 | 1973 | /// Verbatim prefix using Windows' _**U**niform **N**aming **C**onvention_, 1974 | /// e.g., `\\?\UNC\server\share`. 1975 | /// 1976 | /// Verbatim UNC prefixes consist of `\\?\UNC\` immediately followed by the 1977 | /// server's hostname and a share name. 1978 | VerbatimUNC(&'a str, &'a str), 1979 | 1980 | /// Verbatim disk prefix, e.g., `\\?\C:`. 1981 | /// 1982 | /// Verbatim disk prefixes consist of `\\?\` immediately followed by the 1983 | /// drive letter and `:`. 1984 | VerbatimDisk(u8), 1985 | 1986 | /// Device namespace prefix, e.g., `\\.\COM42`. 1987 | /// 1988 | /// Device namespace prefixes consist of `\\.\` immediately followed by the 1989 | /// device name. 1990 | DeviceNS(&'a str), 1991 | 1992 | /// Prefix using Windows' _**U**niform **N**aming **C**onvention_, e.g. 1993 | /// `\\server\share`. 1994 | /// 1995 | /// UNC prefixes consist of the server's hostname and a share name. 1996 | UNC(&'a str, &'a str), 1997 | 1998 | /// Prefix `C:` for the given disk drive. 1999 | Disk(u8), 2000 | } 2001 | 2002 | impl Utf8Prefix<'_> { 2003 | /// Determines if the prefix is verbatim, i.e., begins with `\\?\`. 2004 | /// 2005 | /// # Examples 2006 | /// 2007 | /// ``` 2008 | /// use camino::Utf8Prefix::*; 2009 | /// 2010 | /// assert!(Verbatim("pictures").is_verbatim()); 2011 | /// assert!(VerbatimUNC("server", "share").is_verbatim()); 2012 | /// assert!(VerbatimDisk(b'C').is_verbatim()); 2013 | /// assert!(!DeviceNS("BrainInterface").is_verbatim()); 2014 | /// assert!(!UNC("server", "share").is_verbatim()); 2015 | /// assert!(!Disk(b'C').is_verbatim()); 2016 | /// ``` 2017 | #[must_use] 2018 | pub fn is_verbatim(&self) -> bool { 2019 | use Utf8Prefix::*; 2020 | match self { 2021 | Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(..) => true, 2022 | _ => false, 2023 | } 2024 | } 2025 | } 2026 | 2027 | /// A structure wrapping a Windows path prefix as well as its unparsed string 2028 | /// representation. 2029 | /// 2030 | /// In addition to the parsed [`Utf8Prefix`] information returned by [`kind`], 2031 | /// `Utf8PrefixComponent` also holds the raw and unparsed [`str`] slice, 2032 | /// returned by [`as_str`]. 2033 | /// 2034 | /// Instances of this `struct` can be obtained by matching against the 2035 | /// [`Prefix` variant] on [`Utf8Component`]. 2036 | /// 2037 | /// Does not occur on Unix. 2038 | /// 2039 | /// # Examples 2040 | /// 2041 | /// ``` 2042 | /// # if cfg!(windows) { 2043 | /// use camino::{Utf8Component, Utf8Path, Utf8Prefix}; 2044 | /// use std::ffi::OsStr; 2045 | /// 2046 | /// let path = Utf8Path::new(r"c:\you\later\"); 2047 | /// match path.components().next().unwrap() { 2048 | /// Utf8Component::Prefix(prefix_component) => { 2049 | /// assert_eq!(Utf8Prefix::Disk(b'C'), prefix_component.kind()); 2050 | /// assert_eq!("c:", prefix_component.as_str()); 2051 | /// } 2052 | /// _ => unreachable!(), 2053 | /// } 2054 | /// # } 2055 | /// ``` 2056 | /// 2057 | /// [`as_str`]: Utf8PrefixComponent::as_str 2058 | /// [`kind`]: Utf8PrefixComponent::kind 2059 | /// [`Prefix` variant]: Utf8Component::Prefix 2060 | #[repr(transparent)] 2061 | #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] 2062 | pub struct Utf8PrefixComponent<'a>(PrefixComponent<'a>); 2063 | 2064 | impl<'a> Utf8PrefixComponent<'a> { 2065 | /// Returns the parsed prefix data. 2066 | /// 2067 | /// See [`Utf8Prefix`]'s documentation for more information on the different 2068 | /// kinds of prefixes. 2069 | #[must_use] 2070 | pub fn kind(&self) -> Utf8Prefix<'a> { 2071 | // SAFETY for all the below unsafe blocks: the path self was originally constructed from was 2072 | // UTF-8 so any parts of it are valid UTF-8 2073 | match self.0.kind() { 2074 | Prefix::Verbatim(prefix) => Utf8Prefix::Verbatim(unsafe { str_assume_utf8(prefix) }), 2075 | Prefix::VerbatimUNC(server, share) => { 2076 | let server = unsafe { str_assume_utf8(server) }; 2077 | let share = unsafe { str_assume_utf8(share) }; 2078 | Utf8Prefix::VerbatimUNC(server, share) 2079 | } 2080 | Prefix::VerbatimDisk(drive) => Utf8Prefix::VerbatimDisk(drive), 2081 | Prefix::DeviceNS(prefix) => Utf8Prefix::DeviceNS(unsafe { str_assume_utf8(prefix) }), 2082 | Prefix::UNC(server, share) => { 2083 | let server = unsafe { str_assume_utf8(server) }; 2084 | let share = unsafe { str_assume_utf8(share) }; 2085 | Utf8Prefix::UNC(server, share) 2086 | } 2087 | Prefix::Disk(drive) => Utf8Prefix::Disk(drive), 2088 | } 2089 | } 2090 | 2091 | /// Returns the [`str`] slice for this prefix. 2092 | #[must_use] 2093 | #[inline] 2094 | pub fn as_str(&self) -> &'a str { 2095 | // SAFETY: Utf8PrefixComponent was constructed from a Utf8Path, so it is guaranteed to be 2096 | // valid UTF-8 2097 | unsafe { str_assume_utf8(self.as_os_str()) } 2098 | } 2099 | 2100 | /// Returns the raw [`OsStr`] slice for this prefix. 2101 | #[must_use] 2102 | #[inline] 2103 | pub fn as_os_str(&self) -> &'a OsStr { 2104 | self.0.as_os_str() 2105 | } 2106 | } 2107 | 2108 | impl fmt::Debug for Utf8PrefixComponent<'_> { 2109 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 2110 | fmt::Debug::fmt(&self.0, f) 2111 | } 2112 | } 2113 | 2114 | impl fmt::Display for Utf8PrefixComponent<'_> { 2115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 2116 | fmt::Display::fmt(self.as_str(), f) 2117 | } 2118 | } 2119 | 2120 | // --- 2121 | // read_dir_utf8 2122 | // --- 2123 | 2124 | /// Iterator over the entries in a directory. 2125 | /// 2126 | /// This iterator is returned from [`Utf8Path::read_dir_utf8`] and will yield instances of 2127 | /// [io::Result]<[Utf8DirEntry]>. Through a [`Utf8DirEntry`] information like the entry's path 2128 | /// and possibly other metadata can be learned. 2129 | /// 2130 | /// The order in which this iterator returns entries is platform and filesystem 2131 | /// dependent. 2132 | /// 2133 | /// # Errors 2134 | /// 2135 | /// This [`io::Result`] will be an [`Err`] if there's some sort of intermittent 2136 | /// IO error during iteration. 2137 | /// 2138 | /// If a directory entry is not UTF-8, an [`io::Error`] is returned with the 2139 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a [`FromPathBufError`]. 2140 | #[derive(Debug)] 2141 | pub struct ReadDirUtf8 { 2142 | inner: fs::ReadDir, 2143 | } 2144 | 2145 | impl Iterator for ReadDirUtf8 { 2146 | type Item = io::Result; 2147 | 2148 | fn next(&mut self) -> Option> { 2149 | self.inner 2150 | .next() 2151 | .map(|entry| entry.and_then(Utf8DirEntry::new)) 2152 | } 2153 | } 2154 | 2155 | /// Entries returned by the [`ReadDirUtf8`] iterator. 2156 | /// 2157 | /// An instance of `Utf8DirEntry` represents an entry inside of a directory on the filesystem. Each 2158 | /// entry can be inspected via methods to learn about the full path or possibly other metadata. 2159 | #[derive(Debug)] 2160 | pub struct Utf8DirEntry { 2161 | inner: fs::DirEntry, 2162 | path: Utf8PathBuf, 2163 | } 2164 | 2165 | impl Utf8DirEntry { 2166 | fn new(inner: fs::DirEntry) -> io::Result { 2167 | let path = inner 2168 | .path() 2169 | .try_into() 2170 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; 2171 | Ok(Self { inner, path }) 2172 | } 2173 | 2174 | /// Returns the full path to the file that this entry represents. 2175 | /// 2176 | /// The full path is created by joining the original path to `read_dir` 2177 | /// with the filename of this entry. 2178 | /// 2179 | /// # Examples 2180 | /// 2181 | /// ```no_run 2182 | /// use camino::Utf8Path; 2183 | /// 2184 | /// fn main() -> std::io::Result<()> { 2185 | /// for entry in Utf8Path::new(".").read_dir_utf8()? { 2186 | /// let dir = entry?; 2187 | /// println!("{}", dir.path()); 2188 | /// } 2189 | /// Ok(()) 2190 | /// } 2191 | /// ``` 2192 | /// 2193 | /// This prints output like: 2194 | /// 2195 | /// ```text 2196 | /// ./whatever.txt 2197 | /// ./foo.html 2198 | /// ./hello_world.rs 2199 | /// ``` 2200 | /// 2201 | /// The exact text, of course, depends on what files you have in `.`. 2202 | #[inline] 2203 | pub fn path(&self) -> &Utf8Path { 2204 | &self.path 2205 | } 2206 | 2207 | /// Returns the metadata for the file that this entry points at. 2208 | /// 2209 | /// This function will not traverse symlinks if this entry points at a symlink. To traverse 2210 | /// symlinks use [`Utf8Path::metadata`] or [`fs::File::metadata`]. 2211 | /// 2212 | /// # Platform-specific behavior 2213 | /// 2214 | /// On Windows this function is cheap to call (no extra system calls 2215 | /// needed), but on Unix platforms this function is the equivalent of 2216 | /// calling `symlink_metadata` on the path. 2217 | /// 2218 | /// # Examples 2219 | /// 2220 | /// ``` 2221 | /// use camino::Utf8Path; 2222 | /// 2223 | /// if let Ok(entries) = Utf8Path::new(".").read_dir_utf8() { 2224 | /// for entry in entries { 2225 | /// if let Ok(entry) = entry { 2226 | /// // Here, `entry` is a `Utf8DirEntry`. 2227 | /// if let Ok(metadata) = entry.metadata() { 2228 | /// // Now let's show our entry's permissions! 2229 | /// println!("{}: {:?}", entry.path(), metadata.permissions()); 2230 | /// } else { 2231 | /// println!("Couldn't get metadata for {}", entry.path()); 2232 | /// } 2233 | /// } 2234 | /// } 2235 | /// } 2236 | /// ``` 2237 | #[inline] 2238 | pub fn metadata(&self) -> io::Result { 2239 | self.inner.metadata() 2240 | } 2241 | 2242 | /// Returns the file type for the file that this entry points at. 2243 | /// 2244 | /// This function will not traverse symlinks if this entry points at a 2245 | /// symlink. 2246 | /// 2247 | /// # Platform-specific behavior 2248 | /// 2249 | /// On Windows and most Unix platforms this function is free (no extra 2250 | /// system calls needed), but some Unix platforms may require the equivalent 2251 | /// call to `symlink_metadata` to learn about the target file type. 2252 | /// 2253 | /// # Examples 2254 | /// 2255 | /// ``` 2256 | /// use camino::Utf8Path; 2257 | /// 2258 | /// if let Ok(entries) = Utf8Path::new(".").read_dir_utf8() { 2259 | /// for entry in entries { 2260 | /// if let Ok(entry) = entry { 2261 | /// // Here, `entry` is a `DirEntry`. 2262 | /// if let Ok(file_type) = entry.file_type() { 2263 | /// // Now let's show our entry's file type! 2264 | /// println!("{}: {:?}", entry.path(), file_type); 2265 | /// } else { 2266 | /// println!("Couldn't get file type for {}", entry.path()); 2267 | /// } 2268 | /// } 2269 | /// } 2270 | /// } 2271 | /// ``` 2272 | #[inline] 2273 | pub fn file_type(&self) -> io::Result { 2274 | self.inner.file_type() 2275 | } 2276 | 2277 | /// Returns the bare file name of this directory entry without any other 2278 | /// leading path component. 2279 | /// 2280 | /// # Examples 2281 | /// 2282 | /// ``` 2283 | /// use camino::Utf8Path; 2284 | /// 2285 | /// if let Ok(entries) = Utf8Path::new(".").read_dir_utf8() { 2286 | /// for entry in entries { 2287 | /// if let Ok(entry) = entry { 2288 | /// // Here, `entry` is a `DirEntry`. 2289 | /// println!("{}", entry.file_name()); 2290 | /// } 2291 | /// } 2292 | /// } 2293 | /// ``` 2294 | pub fn file_name(&self) -> &str { 2295 | self.path 2296 | .file_name() 2297 | .expect("path created through DirEntry must have a filename") 2298 | } 2299 | 2300 | /// Returns the original [`fs::DirEntry`] within this [`Utf8DirEntry`]. 2301 | #[inline] 2302 | pub fn into_inner(self) -> fs::DirEntry { 2303 | self.inner 2304 | } 2305 | 2306 | /// Returns the full path to the file that this entry represents. 2307 | /// 2308 | /// This is analogous to [`path`], but moves ownership of the path. 2309 | /// 2310 | /// [`path`]: struct.Utf8DirEntry.html#method.path 2311 | #[inline] 2312 | #[must_use = "`self` will be dropped if the result is not used"] 2313 | pub fn into_path(self) -> Utf8PathBuf { 2314 | self.path 2315 | } 2316 | } 2317 | 2318 | impl From for Utf8PathBuf { 2319 | fn from(string: String) -> Utf8PathBuf { 2320 | Utf8PathBuf(string.into()) 2321 | } 2322 | } 2323 | 2324 | impl FromStr for Utf8PathBuf { 2325 | type Err = Infallible; 2326 | 2327 | fn from_str(s: &str) -> Result { 2328 | Ok(Utf8PathBuf(s.into())) 2329 | } 2330 | } 2331 | 2332 | // --- 2333 | // From impls: borrowed -> borrowed 2334 | // --- 2335 | 2336 | impl<'a> From<&'a str> for &'a Utf8Path { 2337 | fn from(s: &'a str) -> &'a Utf8Path { 2338 | Utf8Path::new(s) 2339 | } 2340 | } 2341 | 2342 | // --- 2343 | // From impls: borrowed -> owned 2344 | // --- 2345 | 2346 | impl> From<&T> for Utf8PathBuf { 2347 | fn from(s: &T) -> Utf8PathBuf { 2348 | Utf8PathBuf::from(s.as_ref().to_owned()) 2349 | } 2350 | } 2351 | 2352 | impl> From<&T> for Box { 2353 | fn from(s: &T) -> Box { 2354 | Utf8PathBuf::from(s).into_boxed_path() 2355 | } 2356 | } 2357 | 2358 | impl From<&'_ Utf8Path> for Arc { 2359 | fn from(path: &Utf8Path) -> Arc { 2360 | let arc: Arc = Arc::from(AsRef::::as_ref(path)); 2361 | let ptr = Arc::into_raw(arc) as *const Utf8Path; 2362 | // SAFETY: 2363 | // * path is valid UTF-8 2364 | // * ptr was created by consuming an Arc so it represents an arced pointer 2365 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to 2366 | // *const Utf8Path is valid 2367 | unsafe { Arc::from_raw(ptr) } 2368 | } 2369 | } 2370 | 2371 | impl From<&'_ Utf8Path> for Rc { 2372 | fn from(path: &Utf8Path) -> Rc { 2373 | let rc: Rc = Rc::from(AsRef::::as_ref(path)); 2374 | let ptr = Rc::into_raw(rc) as *const Utf8Path; 2375 | // SAFETY: 2376 | // * path is valid UTF-8 2377 | // * ptr was created by consuming an Rc so it represents an rced pointer 2378 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to 2379 | // *const Utf8Path is valid 2380 | unsafe { Rc::from_raw(ptr) } 2381 | } 2382 | } 2383 | 2384 | impl<'a> From<&'a Utf8Path> for Cow<'a, Utf8Path> { 2385 | fn from(path: &'a Utf8Path) -> Cow<'a, Utf8Path> { 2386 | Cow::Borrowed(path) 2387 | } 2388 | } 2389 | 2390 | impl From<&'_ Utf8Path> for Box { 2391 | fn from(path: &Utf8Path) -> Box { 2392 | AsRef::::as_ref(path).into() 2393 | } 2394 | } 2395 | 2396 | impl From<&'_ Utf8Path> for Arc { 2397 | fn from(path: &Utf8Path) -> Arc { 2398 | AsRef::::as_ref(path).into() 2399 | } 2400 | } 2401 | 2402 | impl From<&'_ Utf8Path> for Rc { 2403 | fn from(path: &Utf8Path) -> Rc { 2404 | AsRef::::as_ref(path).into() 2405 | } 2406 | } 2407 | 2408 | impl<'a> From<&'a Utf8Path> for Cow<'a, Path> { 2409 | fn from(path: &'a Utf8Path) -> Cow<'a, Path> { 2410 | Cow::Borrowed(path.as_ref()) 2411 | } 2412 | } 2413 | 2414 | // --- 2415 | // From impls: owned -> owned 2416 | // --- 2417 | 2418 | impl From> for Utf8PathBuf { 2419 | fn from(path: Box) -> Utf8PathBuf { 2420 | path.into_path_buf() 2421 | } 2422 | } 2423 | 2424 | impl From for Box { 2425 | fn from(path: Utf8PathBuf) -> Box { 2426 | path.into_boxed_path() 2427 | } 2428 | } 2429 | 2430 | impl<'a> From> for Utf8PathBuf { 2431 | fn from(path: Cow<'a, Utf8Path>) -> Utf8PathBuf { 2432 | path.into_owned() 2433 | } 2434 | } 2435 | 2436 | impl From for String { 2437 | fn from(path: Utf8PathBuf) -> String { 2438 | path.into_string() 2439 | } 2440 | } 2441 | 2442 | impl From for OsString { 2443 | fn from(path: Utf8PathBuf) -> OsString { 2444 | path.into_os_string() 2445 | } 2446 | } 2447 | 2448 | impl<'a> From for Cow<'a, Utf8Path> { 2449 | fn from(path: Utf8PathBuf) -> Cow<'a, Utf8Path> { 2450 | Cow::Owned(path) 2451 | } 2452 | } 2453 | 2454 | impl From for Arc { 2455 | fn from(path: Utf8PathBuf) -> Arc { 2456 | let arc: Arc = Arc::from(path.0); 2457 | let ptr = Arc::into_raw(arc) as *const Utf8Path; 2458 | // SAFETY: 2459 | // * path is valid UTF-8 2460 | // * ptr was created by consuming an Arc so it represents an arced pointer 2461 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to 2462 | // *const Utf8Path is valid 2463 | unsafe { Arc::from_raw(ptr) } 2464 | } 2465 | } 2466 | 2467 | impl From for Rc { 2468 | fn from(path: Utf8PathBuf) -> Rc { 2469 | let rc: Rc = Rc::from(path.0); 2470 | let ptr = Rc::into_raw(rc) as *const Utf8Path; 2471 | // SAFETY: 2472 | // * path is valid UTF-8 2473 | // * ptr was created by consuming an Rc so it represents an rced pointer 2474 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to 2475 | // *const Utf8Path is valid 2476 | unsafe { Rc::from_raw(ptr) } 2477 | } 2478 | } 2479 | 2480 | impl From for PathBuf { 2481 | fn from(path: Utf8PathBuf) -> PathBuf { 2482 | path.0 2483 | } 2484 | } 2485 | 2486 | impl From for Box { 2487 | fn from(path: Utf8PathBuf) -> Box { 2488 | PathBuf::from(path).into_boxed_path() 2489 | } 2490 | } 2491 | 2492 | impl From for Arc { 2493 | fn from(path: Utf8PathBuf) -> Arc { 2494 | PathBuf::from(path).into() 2495 | } 2496 | } 2497 | 2498 | impl From for Rc { 2499 | fn from(path: Utf8PathBuf) -> Rc { 2500 | PathBuf::from(path).into() 2501 | } 2502 | } 2503 | 2504 | impl<'a> From for Cow<'a, Path> { 2505 | fn from(path: Utf8PathBuf) -> Cow<'a, Path> { 2506 | PathBuf::from(path).into() 2507 | } 2508 | } 2509 | 2510 | // --- 2511 | // TryFrom impls 2512 | // --- 2513 | 2514 | impl TryFrom for Utf8PathBuf { 2515 | type Error = FromPathBufError; 2516 | 2517 | fn try_from(path: PathBuf) -> Result { 2518 | Utf8PathBuf::from_path_buf(path).map_err(|path| FromPathBufError { 2519 | path, 2520 | error: FromPathError(()), 2521 | }) 2522 | } 2523 | } 2524 | 2525 | /// Converts a [`Path`] to a [`Utf8Path`]. 2526 | /// 2527 | /// Returns [`FromPathError`] if the path is not valid UTF-8. 2528 | /// 2529 | /// # Examples 2530 | /// 2531 | /// ``` 2532 | /// use camino::Utf8Path; 2533 | /// use std::convert::TryFrom; 2534 | /// use std::ffi::OsStr; 2535 | /// # #[cfg(unix)] 2536 | /// use std::os::unix::ffi::OsStrExt; 2537 | /// use std::path::Path; 2538 | /// 2539 | /// let unicode_path = Path::new("/valid/unicode"); 2540 | /// <&Utf8Path>::try_from(unicode_path).expect("valid Unicode path succeeded"); 2541 | /// 2542 | /// // Paths on Unix can be non-UTF-8. 2543 | /// # #[cfg(unix)] 2544 | /// let non_unicode_str = OsStr::from_bytes(b"\xFF\xFF\xFF"); 2545 | /// # #[cfg(unix)] 2546 | /// let non_unicode_path = Path::new(non_unicode_str); 2547 | /// # #[cfg(unix)] 2548 | /// assert!(<&Utf8Path>::try_from(non_unicode_path).is_err(), "non-Unicode path failed"); 2549 | /// ``` 2550 | impl<'a> TryFrom<&'a Path> for &'a Utf8Path { 2551 | type Error = FromPathError; 2552 | 2553 | fn try_from(path: &'a Path) -> Result<&'a Utf8Path, Self::Error> { 2554 | Utf8Path::from_path(path).ok_or(FromPathError(())) 2555 | } 2556 | } 2557 | 2558 | /// A possible error value while converting a [`PathBuf`] to a [`Utf8PathBuf`]. 2559 | /// 2560 | /// Produced by the `TryFrom` implementation for [`Utf8PathBuf`]. 2561 | /// 2562 | /// # Examples 2563 | /// 2564 | /// ``` 2565 | /// use camino::{Utf8PathBuf, FromPathBufError}; 2566 | /// use std::convert::{TryFrom, TryInto}; 2567 | /// use std::ffi::OsStr; 2568 | /// # #[cfg(unix)] 2569 | /// use std::os::unix::ffi::OsStrExt; 2570 | /// use std::path::PathBuf; 2571 | /// 2572 | /// let unicode_path = PathBuf::from("/valid/unicode"); 2573 | /// let utf8_path_buf: Utf8PathBuf = unicode_path.try_into().expect("valid Unicode path succeeded"); 2574 | /// 2575 | /// // Paths on Unix can be non-UTF-8. 2576 | /// # #[cfg(unix)] 2577 | /// let non_unicode_str = OsStr::from_bytes(b"\xFF\xFF\xFF"); 2578 | /// # #[cfg(unix)] 2579 | /// let non_unicode_path = PathBuf::from(non_unicode_str); 2580 | /// # #[cfg(unix)] 2581 | /// let err: FromPathBufError = Utf8PathBuf::try_from(non_unicode_path.clone()) 2582 | /// .expect_err("non-Unicode path failed"); 2583 | /// # #[cfg(unix)] 2584 | /// assert_eq!(err.as_path(), &non_unicode_path); 2585 | /// # #[cfg(unix)] 2586 | /// assert_eq!(err.into_path_buf(), non_unicode_path); 2587 | /// ``` 2588 | #[derive(Clone, Debug, Eq, PartialEq)] 2589 | pub struct FromPathBufError { 2590 | path: PathBuf, 2591 | error: FromPathError, 2592 | } 2593 | 2594 | impl FromPathBufError { 2595 | /// Returns the [`Path`] slice that was attempted to be converted to [`Utf8PathBuf`]. 2596 | #[inline] 2597 | pub fn as_path(&self) -> &Path { 2598 | &self.path 2599 | } 2600 | 2601 | /// Returns the [`PathBuf`] that was attempted to be converted to [`Utf8PathBuf`]. 2602 | #[inline] 2603 | pub fn into_path_buf(self) -> PathBuf { 2604 | self.path 2605 | } 2606 | 2607 | /// Fetches a [`FromPathError`] for more about the conversion failure. 2608 | /// 2609 | /// At the moment this struct does not contain any additional information, but is provided for 2610 | /// completeness. 2611 | #[inline] 2612 | pub fn from_path_error(&self) -> FromPathError { 2613 | self.error 2614 | } 2615 | 2616 | /// Converts self into a [`std::io::Error`] with kind 2617 | /// [`InvalidData`](io::ErrorKind::InvalidData). 2618 | /// 2619 | /// Many users of `FromPathBufError` will want to convert it into an `io::Error`. This is a 2620 | /// convenience method to do that. 2621 | pub fn into_io_error(self) -> io::Error { 2622 | // NOTE: we don't currently implement `From for io::Error` because we want 2623 | // to ensure the user actually desires that conversion. 2624 | io::Error::new(io::ErrorKind::InvalidData, self) 2625 | } 2626 | } 2627 | 2628 | impl fmt::Display for FromPathBufError { 2629 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 2630 | write!(f, "PathBuf contains invalid UTF-8: {}", self.path.display()) 2631 | } 2632 | } 2633 | 2634 | impl error::Error for FromPathBufError { 2635 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 2636 | Some(&self.error) 2637 | } 2638 | } 2639 | 2640 | /// A possible error value while converting a [`Path`] to a [`Utf8Path`]. 2641 | /// 2642 | /// Produced by the `TryFrom<&Path>` implementation for [`&Utf8Path`](Utf8Path). 2643 | /// 2644 | /// 2645 | /// # Examples 2646 | /// 2647 | /// ``` 2648 | /// use camino::{Utf8Path, FromPathError}; 2649 | /// use std::convert::{TryFrom, TryInto}; 2650 | /// use std::ffi::OsStr; 2651 | /// # #[cfg(unix)] 2652 | /// use std::os::unix::ffi::OsStrExt; 2653 | /// use std::path::Path; 2654 | /// 2655 | /// let unicode_path = Path::new("/valid/unicode"); 2656 | /// let utf8_path: &Utf8Path = unicode_path.try_into().expect("valid Unicode path succeeded"); 2657 | /// 2658 | /// // Paths on Unix can be non-UTF-8. 2659 | /// # #[cfg(unix)] 2660 | /// let non_unicode_str = OsStr::from_bytes(b"\xFF\xFF\xFF"); 2661 | /// # #[cfg(unix)] 2662 | /// let non_unicode_path = Path::new(non_unicode_str); 2663 | /// # #[cfg(unix)] 2664 | /// let err: FromPathError = <&Utf8Path>::try_from(non_unicode_path) 2665 | /// .expect_err("non-Unicode path failed"); 2666 | /// ``` 2667 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2668 | pub struct FromPathError(()); 2669 | 2670 | impl FromPathError { 2671 | /// Converts self into a [`std::io::Error`] with kind 2672 | /// [`InvalidData`](io::ErrorKind::InvalidData). 2673 | /// 2674 | /// Many users of `FromPathError` will want to convert it into an `io::Error`. This is a 2675 | /// convenience method to do that. 2676 | pub fn into_io_error(self) -> io::Error { 2677 | // NOTE: we don't currently implement `From for io::Error` because we want 2678 | // to ensure the user actually desires that conversion. 2679 | io::Error::new(io::ErrorKind::InvalidData, self) 2680 | } 2681 | } 2682 | 2683 | impl fmt::Display for FromPathError { 2684 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 2685 | write!(f, "Path contains invalid UTF-8") 2686 | } 2687 | } 2688 | 2689 | impl error::Error for FromPathError { 2690 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 2691 | None 2692 | } 2693 | } 2694 | 2695 | // --- 2696 | // AsRef impls 2697 | // --- 2698 | 2699 | impl AsRef for Utf8Path { 2700 | #[inline] 2701 | fn as_ref(&self) -> &Utf8Path { 2702 | self 2703 | } 2704 | } 2705 | 2706 | impl AsRef for Utf8PathBuf { 2707 | #[inline] 2708 | fn as_ref(&self) -> &Utf8Path { 2709 | self.as_path() 2710 | } 2711 | } 2712 | 2713 | impl AsRef for str { 2714 | #[inline] 2715 | fn as_ref(&self) -> &Utf8Path { 2716 | Utf8Path::new(self) 2717 | } 2718 | } 2719 | 2720 | impl AsRef for String { 2721 | #[inline] 2722 | fn as_ref(&self) -> &Utf8Path { 2723 | Utf8Path::new(self) 2724 | } 2725 | } 2726 | 2727 | impl AsRef for Utf8Path { 2728 | #[inline] 2729 | fn as_ref(&self) -> &Path { 2730 | &self.0 2731 | } 2732 | } 2733 | 2734 | impl AsRef for Utf8PathBuf { 2735 | #[inline] 2736 | fn as_ref(&self) -> &Path { 2737 | &self.0 2738 | } 2739 | } 2740 | 2741 | impl AsRef for Utf8Path { 2742 | #[inline] 2743 | fn as_ref(&self) -> &str { 2744 | self.as_str() 2745 | } 2746 | } 2747 | 2748 | impl AsRef for Utf8PathBuf { 2749 | #[inline] 2750 | fn as_ref(&self) -> &str { 2751 | self.as_str() 2752 | } 2753 | } 2754 | 2755 | impl AsRef for Utf8Path { 2756 | #[inline] 2757 | fn as_ref(&self) -> &OsStr { 2758 | self.as_os_str() 2759 | } 2760 | } 2761 | 2762 | impl AsRef for Utf8PathBuf { 2763 | #[inline] 2764 | fn as_ref(&self) -> &OsStr { 2765 | self.as_os_str() 2766 | } 2767 | } 2768 | 2769 | // --- 2770 | // Borrow and ToOwned 2771 | // --- 2772 | 2773 | impl Borrow for Utf8PathBuf { 2774 | #[inline] 2775 | fn borrow(&self) -> &Utf8Path { 2776 | self.as_path() 2777 | } 2778 | } 2779 | 2780 | impl ToOwned for Utf8Path { 2781 | type Owned = Utf8PathBuf; 2782 | 2783 | #[inline] 2784 | fn to_owned(&self) -> Utf8PathBuf { 2785 | self.to_path_buf() 2786 | } 2787 | } 2788 | 2789 | impl> std::iter::FromIterator

for Utf8PathBuf { 2790 | fn from_iter>(iter: I) -> Utf8PathBuf { 2791 | let mut buf = Utf8PathBuf::new(); 2792 | buf.extend(iter); 2793 | buf 2794 | } 2795 | } 2796 | 2797 | // --- 2798 | // [Partial]Eq, [Partial]Ord, Hash 2799 | // --- 2800 | 2801 | impl PartialEq for Utf8PathBuf { 2802 | #[inline] 2803 | fn eq(&self, other: &Utf8PathBuf) -> bool { 2804 | self.components() == other.components() 2805 | } 2806 | } 2807 | 2808 | impl Eq for Utf8PathBuf {} 2809 | 2810 | impl Hash for Utf8PathBuf { 2811 | #[inline] 2812 | fn hash(&self, state: &mut H) { 2813 | self.as_path().hash(state) 2814 | } 2815 | } 2816 | 2817 | impl PartialOrd for Utf8PathBuf { 2818 | #[inline] 2819 | fn partial_cmp(&self, other: &Utf8PathBuf) -> Option { 2820 | Some(self.cmp(other)) 2821 | } 2822 | } 2823 | 2824 | impl Ord for Utf8PathBuf { 2825 | fn cmp(&self, other: &Utf8PathBuf) -> Ordering { 2826 | self.components().cmp(other.components()) 2827 | } 2828 | } 2829 | 2830 | impl PartialEq for Utf8Path { 2831 | #[inline] 2832 | fn eq(&self, other: &Utf8Path) -> bool { 2833 | self.components().eq(other.components()) 2834 | } 2835 | } 2836 | 2837 | impl Eq for Utf8Path {} 2838 | 2839 | impl Hash for Utf8Path { 2840 | fn hash(&self, state: &mut H) { 2841 | for component in self.components() { 2842 | component.hash(state) 2843 | } 2844 | } 2845 | } 2846 | 2847 | impl PartialOrd for Utf8Path { 2848 | #[inline] 2849 | fn partial_cmp(&self, other: &Utf8Path) -> Option { 2850 | Some(self.cmp(other)) 2851 | } 2852 | } 2853 | 2854 | impl Ord for Utf8Path { 2855 | fn cmp(&self, other: &Utf8Path) -> Ordering { 2856 | self.components().cmp(other.components()) 2857 | } 2858 | } 2859 | 2860 | impl<'a> IntoIterator for &'a Utf8PathBuf { 2861 | type Item = &'a str; 2862 | type IntoIter = Iter<'a>; 2863 | #[inline] 2864 | fn into_iter(self) -> Iter<'a> { 2865 | self.iter() 2866 | } 2867 | } 2868 | 2869 | impl<'a> IntoIterator for &'a Utf8Path { 2870 | type Item = &'a str; 2871 | type IntoIter = Iter<'a>; 2872 | #[inline] 2873 | fn into_iter(self) -> Iter<'a> { 2874 | self.iter() 2875 | } 2876 | } 2877 | 2878 | macro_rules! impl_cmp { 2879 | ($lhs:ty, $rhs: ty) => { 2880 | #[allow(clippy::extra_unused_lifetimes)] 2881 | impl<'a, 'b> PartialEq<$rhs> for $lhs { 2882 | #[inline] 2883 | fn eq(&self, other: &$rhs) -> bool { 2884 | ::eq(self, other) 2885 | } 2886 | } 2887 | 2888 | #[allow(clippy::extra_unused_lifetimes)] 2889 | impl<'a, 'b> PartialEq<$lhs> for $rhs { 2890 | #[inline] 2891 | fn eq(&self, other: &$lhs) -> bool { 2892 | ::eq(self, other) 2893 | } 2894 | } 2895 | 2896 | #[allow(clippy::extra_unused_lifetimes)] 2897 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { 2898 | #[inline] 2899 | fn partial_cmp(&self, other: &$rhs) -> Option { 2900 | ::partial_cmp(self, other) 2901 | } 2902 | } 2903 | 2904 | #[allow(clippy::extra_unused_lifetimes)] 2905 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { 2906 | #[inline] 2907 | fn partial_cmp(&self, other: &$lhs) -> Option { 2908 | ::partial_cmp(self, other) 2909 | } 2910 | } 2911 | }; 2912 | } 2913 | 2914 | impl_cmp!(Utf8PathBuf, Utf8Path); 2915 | impl_cmp!(Utf8PathBuf, &'a Utf8Path); 2916 | impl_cmp!(Cow<'a, Utf8Path>, Utf8Path); 2917 | impl_cmp!(Cow<'a, Utf8Path>, &'b Utf8Path); 2918 | impl_cmp!(Cow<'a, Utf8Path>, Utf8PathBuf); 2919 | 2920 | macro_rules! impl_cmp_std_path { 2921 | ($lhs:ty, $rhs: ty) => { 2922 | #[allow(clippy::extra_unused_lifetimes)] 2923 | impl<'a, 'b> PartialEq<$rhs> for $lhs { 2924 | #[inline] 2925 | fn eq(&self, other: &$rhs) -> bool { 2926 | ::eq(self.as_ref(), other) 2927 | } 2928 | } 2929 | 2930 | #[allow(clippy::extra_unused_lifetimes)] 2931 | impl<'a, 'b> PartialEq<$lhs> for $rhs { 2932 | #[inline] 2933 | fn eq(&self, other: &$lhs) -> bool { 2934 | ::eq(self, other.as_ref()) 2935 | } 2936 | } 2937 | 2938 | #[allow(clippy::extra_unused_lifetimes)] 2939 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { 2940 | #[inline] 2941 | fn partial_cmp(&self, other: &$rhs) -> Option { 2942 | ::partial_cmp(self.as_ref(), other) 2943 | } 2944 | } 2945 | 2946 | #[allow(clippy::extra_unused_lifetimes)] 2947 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { 2948 | #[inline] 2949 | fn partial_cmp(&self, other: &$lhs) -> Option { 2950 | ::partial_cmp(self, other.as_ref()) 2951 | } 2952 | } 2953 | }; 2954 | } 2955 | 2956 | impl_cmp_std_path!(Utf8PathBuf, Path); 2957 | impl_cmp_std_path!(Utf8PathBuf, &'a Path); 2958 | impl_cmp_std_path!(Utf8PathBuf, Cow<'a, Path>); 2959 | impl_cmp_std_path!(Utf8PathBuf, PathBuf); 2960 | impl_cmp_std_path!(Utf8Path, Path); 2961 | impl_cmp_std_path!(Utf8Path, &'a Path); 2962 | impl_cmp_std_path!(Utf8Path, Cow<'a, Path>); 2963 | impl_cmp_std_path!(Utf8Path, PathBuf); 2964 | impl_cmp_std_path!(&'a Utf8Path, Path); 2965 | impl_cmp_std_path!(&'a Utf8Path, Cow<'b, Path>); 2966 | impl_cmp_std_path!(&'a Utf8Path, PathBuf); 2967 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) 2968 | 2969 | macro_rules! impl_cmp_str { 2970 | ($lhs:ty, $rhs: ty) => { 2971 | #[allow(clippy::extra_unused_lifetimes)] 2972 | impl<'a, 'b> PartialEq<$rhs> for $lhs { 2973 | #[inline] 2974 | fn eq(&self, other: &$rhs) -> bool { 2975 | ::eq(self, Utf8Path::new(other)) 2976 | } 2977 | } 2978 | 2979 | #[allow(clippy::extra_unused_lifetimes)] 2980 | impl<'a, 'b> PartialEq<$lhs> for $rhs { 2981 | #[inline] 2982 | fn eq(&self, other: &$lhs) -> bool { 2983 | ::eq(Utf8Path::new(self), other) 2984 | } 2985 | } 2986 | 2987 | #[allow(clippy::extra_unused_lifetimes)] 2988 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { 2989 | #[inline] 2990 | fn partial_cmp(&self, other: &$rhs) -> Option { 2991 | ::partial_cmp(self, Utf8Path::new(other)) 2992 | } 2993 | } 2994 | 2995 | #[allow(clippy::extra_unused_lifetimes)] 2996 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { 2997 | #[inline] 2998 | fn partial_cmp(&self, other: &$lhs) -> Option { 2999 | ::partial_cmp(Utf8Path::new(self), other) 3000 | } 3001 | } 3002 | }; 3003 | } 3004 | 3005 | impl_cmp_str!(Utf8PathBuf, str); 3006 | impl_cmp_str!(Utf8PathBuf, &'a str); 3007 | impl_cmp_str!(Utf8PathBuf, Cow<'a, str>); 3008 | impl_cmp_str!(Utf8PathBuf, String); 3009 | impl_cmp_str!(Utf8Path, str); 3010 | impl_cmp_str!(Utf8Path, &'a str); 3011 | impl_cmp_str!(Utf8Path, Cow<'a, str>); 3012 | impl_cmp_str!(Utf8Path, String); 3013 | impl_cmp_str!(&'a Utf8Path, str); 3014 | impl_cmp_str!(&'a Utf8Path, Cow<'b, str>); 3015 | impl_cmp_str!(&'a Utf8Path, String); 3016 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) 3017 | 3018 | macro_rules! impl_cmp_os_str { 3019 | ($lhs:ty, $rhs: ty) => { 3020 | #[allow(clippy::extra_unused_lifetimes)] 3021 | impl<'a, 'b> PartialEq<$rhs> for $lhs { 3022 | #[inline] 3023 | fn eq(&self, other: &$rhs) -> bool { 3024 | ::eq(self.as_ref(), other.as_ref()) 3025 | } 3026 | } 3027 | 3028 | #[allow(clippy::extra_unused_lifetimes)] 3029 | impl<'a, 'b> PartialEq<$lhs> for $rhs { 3030 | #[inline] 3031 | fn eq(&self, other: &$lhs) -> bool { 3032 | ::eq(self.as_ref(), other.as_ref()) 3033 | } 3034 | } 3035 | 3036 | #[allow(clippy::extra_unused_lifetimes)] 3037 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { 3038 | #[inline] 3039 | fn partial_cmp(&self, other: &$rhs) -> Option { 3040 | ::partial_cmp(self.as_ref(), other.as_ref()) 3041 | } 3042 | } 3043 | 3044 | #[allow(clippy::extra_unused_lifetimes)] 3045 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { 3046 | #[inline] 3047 | fn partial_cmp(&self, other: &$lhs) -> Option { 3048 | ::partial_cmp(self.as_ref(), other.as_ref()) 3049 | } 3050 | } 3051 | }; 3052 | } 3053 | 3054 | impl_cmp_os_str!(Utf8PathBuf, OsStr); 3055 | impl_cmp_os_str!(Utf8PathBuf, &'a OsStr); 3056 | impl_cmp_os_str!(Utf8PathBuf, Cow<'a, OsStr>); 3057 | impl_cmp_os_str!(Utf8PathBuf, OsString); 3058 | impl_cmp_os_str!(Utf8Path, OsStr); 3059 | impl_cmp_os_str!(Utf8Path, &'a OsStr); 3060 | impl_cmp_os_str!(Utf8Path, Cow<'a, OsStr>); 3061 | impl_cmp_os_str!(Utf8Path, OsString); 3062 | impl_cmp_os_str!(&'a Utf8Path, OsStr); 3063 | impl_cmp_os_str!(&'a Utf8Path, Cow<'b, OsStr>); 3064 | impl_cmp_os_str!(&'a Utf8Path, OsString); 3065 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) 3066 | 3067 | /// Makes the path absolute without accessing the filesystem, converting it to a [`Utf8PathBuf`]. 3068 | /// 3069 | /// If the path is relative, the current directory is used as the base directory. All intermediate 3070 | /// components will be resolved according to platform-specific rules, but unlike 3071 | /// [`canonicalize`][Utf8Path::canonicalize] or [`canonicalize_utf8`](Utf8Path::canonicalize_utf8), 3072 | /// this does not resolve symlinks and may succeed even if the path does not exist. 3073 | /// 3074 | /// *Requires Rust 1.79 or newer.* 3075 | /// 3076 | /// # Errors 3077 | /// 3078 | /// Errors if: 3079 | /// 3080 | /// * The path is empty. 3081 | /// * The [current directory][std::env::current_dir] cannot be determined. 3082 | /// * The path is not valid UTF-8. 3083 | /// 3084 | /// # Examples 3085 | /// 3086 | /// ## POSIX paths 3087 | /// 3088 | /// ``` 3089 | /// # #[cfg(unix)] 3090 | /// fn main() -> std::io::Result<()> { 3091 | /// use camino::Utf8Path; 3092 | /// 3093 | /// // Relative to absolute 3094 | /// let absolute = camino::absolute_utf8("foo/./bar")?; 3095 | /// assert!(absolute.ends_with("foo/bar")); 3096 | /// 3097 | /// // Absolute to absolute 3098 | /// let absolute = camino::absolute_utf8("/foo//test/.././bar.rs")?; 3099 | /// assert_eq!(absolute, Utf8Path::new("/foo/test/../bar.rs")); 3100 | /// Ok(()) 3101 | /// } 3102 | /// # #[cfg(not(unix))] 3103 | /// # fn main() {} 3104 | /// ``` 3105 | /// 3106 | /// The path is resolved using [POSIX semantics][posix-semantics] except that it stops short of 3107 | /// resolving symlinks. This means it will keep `..` components and trailing slashes. 3108 | /// 3109 | /// ## Windows paths 3110 | /// 3111 | /// ``` 3112 | /// # #[cfg(windows)] 3113 | /// fn main() -> std::io::Result<()> { 3114 | /// use camino::Utf8Path; 3115 | /// 3116 | /// // Relative to absolute 3117 | /// let absolute = camino::absolute_utf8("foo/./bar")?; 3118 | /// assert!(absolute.ends_with(r"foo\bar")); 3119 | /// 3120 | /// // Absolute to absolute 3121 | /// let absolute = camino::absolute_utf8(r"C:\foo//test\..\./bar.rs")?; 3122 | /// 3123 | /// assert_eq!(absolute, Utf8Path::new(r"C:\foo\bar.rs")); 3124 | /// Ok(()) 3125 | /// } 3126 | /// # #[cfg(not(windows))] 3127 | /// # fn main() {} 3128 | /// ``` 3129 | /// 3130 | /// For verbatim paths this will simply return the path as given. For other paths this is currently 3131 | /// equivalent to calling [`GetFullPathNameW`][windows-path]. 3132 | /// 3133 | /// Note that this [may change in the future][changes]. 3134 | /// 3135 | /// [changes]: io#platform-specific-behavior 3136 | /// [posix-semantics]: 3137 | /// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 3138 | /// [windows-path]: 3139 | /// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew 3140 | #[cfg(absolute_path)] 3141 | pub fn absolute_utf8>(path: P) -> io::Result { 3142 | // Note that even if the passed in path is valid UTF-8, it is not guaranteed that the absolute 3143 | // path will be valid UTF-8. For example, the current directory may not be valid UTF-8. 3144 | // 3145 | // That's why we take `AsRef` instead of `AsRef` here -- we have to pay the cost 3146 | // of checking for valid UTF-8 anyway. 3147 | let path = path.as_ref(); 3148 | #[allow(clippy::incompatible_msrv)] 3149 | Utf8PathBuf::try_from(std::path::absolute(path)?).map_err(|error| error.into_io_error()) 3150 | } 3151 | 3152 | // invariant: OsStr must be guaranteed to be utf8 data 3153 | #[inline] 3154 | unsafe fn str_assume_utf8(string: &OsStr) -> &str { 3155 | #[cfg(os_str_bytes)] 3156 | { 3157 | // SAFETY: OsStr is guaranteed to be utf8 data from the invariant 3158 | unsafe { 3159 | std::str::from_utf8_unchecked( 3160 | #[allow(clippy::incompatible_msrv)] 3161 | string.as_encoded_bytes(), 3162 | ) 3163 | } 3164 | } 3165 | #[cfg(not(os_str_bytes))] 3166 | { 3167 | // Adapted from the source code for Option::unwrap_unchecked. 3168 | match string.to_str() { 3169 | Some(val) => val, 3170 | None => std::hint::unreachable_unchecked(), 3171 | } 3172 | } 3173 | } 3174 | -------------------------------------------------------------------------------- /src/proptest_impls.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! [proptest::Arbitrary](Arbitrary) implementation for `Utf8PathBuf` and `Box`. Note 5 | //! that implementions for `Rc` and `Arc` are not currently possible due to 6 | //! orphan rules - this crate doesn't define `Rc`/`Arc` nor `Arbitrary`, so it can't define those 7 | //! implementations. 8 | 9 | // NOTE: #[cfg(feature = "proptest1")] is specified here to work with `doc_cfg`. 10 | 11 | use crate::{Utf8Path, Utf8PathBuf}; 12 | use proptest::{arbitrary::StrategyFor, prelude::*, strategy::MapInto}; 13 | 14 | /// The [`Arbitrary`] impl for `Utf8PathBuf` returns a path with between 0 and 8 components, 15 | /// joined by the [`MAIN_SEPARATOR`](std::path::MAIN_SEPARATOR) for the platform. (Each component is 16 | /// randomly generated, and may itself contain one or more separators.) 17 | /// 18 | /// On Unix, this generates an absolute path half of the time and a relative path the other half. 19 | /// 20 | /// On Windows, this implementation doesn't currently generate 21 | /// [`Utf8PrefixComponent`](crate::Utf8PrefixComponent) instances, though in the future it might. 22 | #[cfg(feature = "proptest1")] 23 | impl Arbitrary for Utf8PathBuf { 24 | type Parameters = ::Parameters; 25 | type Strategy = BoxedStrategy; 26 | 27 | fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { 28 | ( 29 | any::(), 30 | prop::collection::vec(any_with::(args), 0..8), 31 | ) 32 | .prop_map(|(is_relative, components)| { 33 | let initial_component = if is_relative { 34 | Some(format!("{}", std::path::MAIN_SEPARATOR)) 35 | } else { 36 | None 37 | }; 38 | initial_component.into_iter().chain(components).collect() 39 | }) 40 | .boxed() 41 | } 42 | } 43 | 44 | /// The [`Arbitrary`] impl for `Box` returns a path with between 0 and 8 components, 45 | /// joined by the [`MAIN_SEPARATOR`](std::path::MAIN_SEPARATOR) for the platform. (Each component is 46 | /// randomly generated, and may itself contain one or more separators.) 47 | /// 48 | /// On Unix, this generates an absolute path half of the time and a relative path the other half. 49 | /// 50 | /// On Windows, this implementation doesn't currently generate 51 | /// [`Utf8PrefixComponent`](crate::Utf8PrefixComponent) instances, though in the future it might. 52 | #[cfg(feature = "proptest1")] 53 | impl Arbitrary for Box { 54 | type Parameters = ::Parameters; 55 | type Strategy = MapInto, Self>; 56 | 57 | fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { 58 | any_with::(args).prop_map_into() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/serde_impls.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! Serde implementations for `Utf8Path`. 5 | //! 6 | //! The Serde implementations for `Utf8PathBuf` are derived, but `Utf8Path` is an unsized type which 7 | //! the derive impls can't handle. Implement these by hand. 8 | 9 | use crate::{Utf8Path, Utf8PathBuf}; 10 | use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; 11 | use std::fmt; 12 | 13 | struct Utf8PathVisitor; 14 | 15 | impl<'a> de::Visitor<'a> for Utf8PathVisitor { 16 | type Value = &'a Utf8Path; 17 | 18 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 19 | formatter.write_str("a borrowed path") 20 | } 21 | 22 | fn visit_borrowed_str(self, v: &'a str) -> Result 23 | where 24 | E: de::Error, 25 | { 26 | Ok(v.as_ref()) 27 | } 28 | 29 | fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result 30 | where 31 | E: de::Error, 32 | { 33 | std::str::from_utf8(v) 34 | .map(AsRef::as_ref) 35 | .map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self)) 36 | } 37 | } 38 | 39 | impl<'de: 'a, 'a> Deserialize<'de> for &'a Utf8Path { 40 | fn deserialize(deserializer: D) -> Result 41 | where 42 | D: Deserializer<'de>, 43 | { 44 | deserializer.deserialize_str(Utf8PathVisitor) 45 | } 46 | } 47 | 48 | impl Serialize for Utf8Path { 49 | fn serialize(&self, serializer: S) -> Result 50 | where 51 | S: Serializer, 52 | { 53 | self.as_str().serialize(serializer) 54 | } 55 | } 56 | 57 | impl<'de> Deserialize<'de> for Box { 58 | fn deserialize(deserializer: D) -> Result 59 | where 60 | D: Deserializer<'de>, 61 | { 62 | Ok(Utf8PathBuf::deserialize(deserializer)?.into()) 63 | } 64 | } 65 | 66 | // impl Serialize for Box comes from impl Serialize for Utf8Path. 67 | 68 | // Can't provide impls for Arc/Rc due to orphan rule issues, but we could provide 69 | // `with` impls in the future as requested. 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | use crate::Utf8PathBuf; 75 | use serde_bytes::ByteBuf; 76 | 77 | #[test] 78 | fn valid_utf8() { 79 | let valid_utf8 = &["", "bar", "💩"]; 80 | for input in valid_utf8 { 81 | let encode = Encode { 82 | path: ByteBuf::from(*input), 83 | }; 84 | let encoded = bincode::serialize(&encode).expect("encoded correctly"); 85 | 86 | assert_valid_utf8::(input, &encoded); 87 | assert_valid_utf8::(input, &encoded); 88 | assert_valid_utf8::(input, &encoded); 89 | } 90 | } 91 | 92 | fn assert_valid_utf8<'de, T: TestTrait<'de>>(input: &str, encoded: &'de [u8]) { 93 | let output = bincode::deserialize::(encoded).expect("valid UTF-8 should be fine"); 94 | assert_eq!( 95 | output.path(), 96 | input, 97 | "for input, with {}, paths should match", 98 | T::description() 99 | ); 100 | let roundtrip = bincode::serialize(&output).expect("message should roundtrip"); 101 | assert_eq!(roundtrip, encoded, "encoded path matches"); 102 | } 103 | 104 | #[test] 105 | fn invalid_utf8() { 106 | let invalid_utf8: &[(&[u8], _, _)] = &[ 107 | (b"\xff", 0, 1), 108 | (b"foo\xfe", 3, 1), 109 | (b"a\xC3\xA9 \xED\xA0\xBD\xF0\x9F\x92\xA9", 4, 1), 110 | ]; 111 | 112 | for (input, valid_up_to, error_len) in invalid_utf8 { 113 | let encode = Encode { 114 | path: ByteBuf::from(*input), 115 | }; 116 | let encoded = bincode::serialize(&encode).expect("encoded correctly"); 117 | 118 | assert_invalid_utf8::(input, &encoded, *valid_up_to, *error_len); 119 | assert_invalid_utf8::(input, &encoded, *valid_up_to, *error_len); 120 | assert_invalid_utf8::(input, &encoded, *valid_up_to, *error_len); 121 | } 122 | } 123 | 124 | fn assert_invalid_utf8<'de, T: TestTrait<'de>>( 125 | input: &[u8], 126 | encoded: &'de [u8], 127 | valid_up_to: usize, 128 | error_len: usize, 129 | ) { 130 | let error = bincode::deserialize::(encoded).expect_err("invalid UTF-8 should error out"); 131 | let utf8_error = match *error { 132 | bincode::ErrorKind::InvalidUtf8Encoding(utf8_error) => utf8_error, 133 | other => panic!( 134 | "for input {:?}, with {}, expected ErrorKind::InvalidUtf8Encoding, found: {}", 135 | input, 136 | T::description(), 137 | other 138 | ), 139 | }; 140 | assert_eq!( 141 | utf8_error.valid_up_to(), 142 | valid_up_to, 143 | "for input {:?}, with {}, valid_up_to didn't match", 144 | input, 145 | T::description(), 146 | ); 147 | assert_eq!( 148 | utf8_error.error_len(), 149 | Some(error_len), 150 | "for input {:?}, with {}, error_len didn't match", 151 | input, 152 | T::description(), 153 | ); 154 | } 155 | 156 | #[derive(Serialize, Debug)] 157 | struct Encode { 158 | path: ByteBuf, 159 | } 160 | 161 | trait TestTrait<'de>: Serialize + Deserialize<'de> + fmt::Debug { 162 | fn description() -> &'static str; 163 | fn path(&self) -> &Utf8Path; 164 | } 165 | 166 | #[derive(Serialize, Deserialize, Debug)] 167 | #[allow(unused)] 168 | struct DecodeOwned { 169 | path: Utf8PathBuf, 170 | } 171 | 172 | impl TestTrait<'_> for DecodeOwned { 173 | fn description() -> &'static str { 174 | "DecodeOwned" 175 | } 176 | 177 | fn path(&self) -> &Utf8Path { 178 | &self.path 179 | } 180 | } 181 | 182 | #[derive(Serialize, Deserialize, Debug)] 183 | #[allow(unused)] 184 | struct DecodeBorrowed<'a> { 185 | #[serde(borrow)] 186 | path: &'a Utf8Path, 187 | } 188 | 189 | impl<'de> TestTrait<'de> for DecodeBorrowed<'de> { 190 | fn description() -> &'static str { 191 | "DecodeBorrowed" 192 | } 193 | 194 | fn path(&self) -> &Utf8Path { 195 | self.path 196 | } 197 | } 198 | 199 | #[derive(Serialize, Deserialize, Debug)] 200 | #[allow(unused)] 201 | struct DecodeBoxed { 202 | path: Box, 203 | } 204 | 205 | impl TestTrait<'_> for DecodeBoxed { 206 | fn description() -> &'static str { 207 | "DecodeBoxed" 208 | } 209 | 210 | fn path(&self) -> &Utf8Path { 211 | &self.path 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | // Test that all required impls exist. 5 | 6 | use crate::{Utf8Path, Utf8PathBuf}; 7 | use std::{ 8 | borrow::Cow, 9 | path::{Path, PathBuf}, 10 | rc::Rc, 11 | sync::Arc, 12 | }; 13 | 14 | macro_rules! all_into { 15 | ($t:ty, $x:ident) => { 16 | test_into::<$t, Utf8PathBuf>($x.clone()); 17 | test_into::<$t, Box>($x.clone()); 18 | test_into::<$t, Arc>($x.clone()); 19 | test_into::<$t, Rc>($x.clone()); 20 | test_into::<$t, Cow<'_, Utf8Path>>($x.clone()); 21 | test_into::<$t, PathBuf>($x.clone()); 22 | test_into::<$t, Box>($x.clone()); 23 | test_into::<$t, Arc>($x.clone()); 24 | test_into::<$t, Rc>($x.clone()); 25 | test_into::<$t, Cow<'_, Path>>($x.clone()); 26 | }; 27 | } 28 | 29 | #[test] 30 | fn test_borrowed_into() { 31 | let utf8_path = Utf8Path::new("test/path"); 32 | all_into!(&Utf8Path, utf8_path); 33 | } 34 | 35 | #[test] 36 | fn test_owned_into() { 37 | let utf8_path_buf = Utf8PathBuf::from("test/path"); 38 | all_into!(Utf8PathBuf, utf8_path_buf); 39 | } 40 | 41 | fn test_into(orig: T) 42 | where 43 | T: Into, 44 | { 45 | let _ = orig.into(); 46 | } 47 | 48 | #[cfg(path_buf_deref_mut)] 49 | #[test] 50 | fn test_deref_mut() { 51 | // This test is mostly for miri. 52 | let mut path_buf = Utf8PathBuf::from("foobar"); 53 | let _: &mut Utf8Path = &mut path_buf; 54 | } 55 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The camino Contributors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use camino::{Utf8Path, Utf8PathBuf}; 5 | use std::{ 6 | collections::hash_map::DefaultHasher, 7 | hash::{Hash, Hasher}, 8 | path::Path, 9 | }; 10 | 11 | static PATH_CORPUS: &[&str] = &[ 12 | "", 13 | "foo", 14 | "foo/bar", 15 | "foo//bar", 16 | "foo/bar/baz", 17 | "foo/bar/./baz", 18 | "foo/bar/../baz", 19 | "../foo/bar/./../baz", 20 | "/foo", 21 | "/foo/bar", 22 | "/", 23 | "///", 24 | // --- 25 | // Windows-only paths 26 | // --- 27 | #[cfg(windows)] 28 | "foo\\bar", 29 | #[cfg(windows)] 30 | "\\foo\\bar", 31 | #[cfg(windows)] 32 | "C:\\foo", 33 | #[cfg(windows)] 34 | "C:foo\\bar", 35 | #[cfg(windows)] 36 | "C:\\foo\\..\\.\\bar", 37 | #[cfg(windows)] 38 | "\\\\server\\foo\\bar", 39 | #[cfg(windows)] 40 | "\\\\.\\C:\\foo\\bar.txt", 41 | ]; 42 | 43 | #[test] 44 | fn test_borrow_eq_ord() { 45 | // Utf8PathBuf implements Borrow so equality and ordering comparisons should 46 | // match. 47 | for (idx, &path1) in PATH_CORPUS.iter().enumerate() { 48 | for &path2 in &PATH_CORPUS[idx..] { 49 | let borrowed1 = Utf8Path::new(path1); 50 | let borrowed2 = Utf8Path::new(path2); 51 | let owned1 = Utf8PathBuf::from(path1); 52 | let owned2 = Utf8PathBuf::from(path2); 53 | 54 | assert_eq!( 55 | borrowed1 == borrowed2, 56 | owned1 == owned2, 57 | "Eq impls match: {} == {}", 58 | borrowed1, 59 | borrowed2 60 | ); 61 | assert_eq!( 62 | borrowed1.cmp(borrowed2), 63 | owned1.cmp(&owned2), 64 | "Ord impls match: {} and {}", 65 | borrowed1, 66 | borrowed2 67 | ); 68 | 69 | // Also check against std paths. 70 | let std1 = Path::new(path1); 71 | let std2 = Path::new(path2); 72 | assert_eq!( 73 | borrowed1, std1, 74 | "Eq between Path and Utf8Path: {}", 75 | borrowed1 76 | ); 77 | assert_eq!( 78 | borrowed1 == borrowed2, 79 | std1 == std2, 80 | "Eq impl matches Path: {} == {}", 81 | borrowed1, 82 | borrowed2 83 | ); 84 | assert_eq!( 85 | borrowed1.cmp(borrowed2), 86 | std1.cmp(std2), 87 | "Ord impl matches Path: {} and {}", 88 | borrowed1, 89 | borrowed2 90 | ); 91 | } 92 | } 93 | } 94 | 95 | #[test] 96 | fn test_borrow_hash() { 97 | // Utf8PathBuf implements Borrow so hash comparisons should match. 98 | fn hash_output(x: impl Hash) -> u64 { 99 | let mut hasher = DefaultHasher::new(); 100 | x.hash(&mut hasher); 101 | hasher.finish() 102 | } 103 | 104 | for &path in PATH_CORPUS { 105 | let borrowed = Utf8Path::new(path); 106 | let owned = Utf8PathBuf::from(path); 107 | 108 | assert_eq!( 109 | hash_output(owned), 110 | hash_output(borrowed), 111 | "consistent Hash: {}", 112 | borrowed 113 | ); 114 | } 115 | } 116 | --------------------------------------------------------------------------------