relative-path-1.9.0/.cargo_vcs_info.json0000644000000001360000000000100136100ustar { "git": { "sha1": "e66b564b45a4c5d40f045293ac2f828c2755f075" }, "path_in_vcs": "" }relative-path-1.9.0/.github/workflows/ci.yml000064400000000000000000000020550072674642500171450ustar 00000000000000name: CI on: pull_request: {} push: branches: - main jobs: test: runs-on: ${{matrix.os}} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] toolchain: ["1.56", stable] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.toolchain}} - run: cargo test --workspace --all-targets - run: cargo test --workspace --all-targets --all-features - run: cargo test --workspace --all-targets --no-default-features - run: cargo test --workspace --doc --all-features clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@1.56 with: components: clippy - run: cargo clippy --workspace --all-features --all-targets -- -D warnings rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --check --all relative-path-1.9.0/.gitignore000064400000000000000000000000400072674642500144120ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock relative-path-1.9.0/CHANGELOG.md000064400000000000000000000107500072674642500142440ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.8.0] - 2023-03-05 ### Added * Deserialize implementation and conversions for `Box`. * `From<&str>`, `From>`, for `Box`. * Added tests for most trait implementations. * Add `Deserialize` implementation for `&RelativePath` (#19). * Add `AsRef` for `Component` (#24). ## [1.7.3] - 2023-01-02 ### Changed * Fixed build badge in repo. ## [1.7.1] - 2022-07-10 ### Changed * Updated documentation to be easier to follow. ## [1.7.0] - 2022-03-22 ### Added * Added `Clone` implementation for `Box` with `RelativePathBuf::into_boxed_relative_path` ([#37]). [#37]: https://github.com/udoprog/relative-path/pull/37 ## [1.6.1] - 2022-02-07 ### Changed * Updated documentation. ## [1.6.0] - 2021-12-03 ### Added * Added `RelativePath::is_normalized` to check for normalization ([#28]). * Added `impl From<&RelativePath> for Box` ([#26]). * Added `impl From for Box` ([#26]). * Added `impl From<&RelativePath> for Arc` ([#26]). * Added `impl From for Arc` ([#26]). * Added `impl From<&RelativePath> for Rc` ([#26]). * Added `impl From for Rc` ([#26]). ### Fixed * Changed `to_path` and `to_logical_path` to treat empty paths better ([#29]). [#29]: https://github.com/udoprog/relative-path/pull/29 [#28]: https://github.com/udoprog/relative-path/pull/28 [#26]: https://github.com/udoprog/relative-path/pull/26 ## [1.5.0] - 2021-07-29 ### Added * Implement Extend and FromIterator for RelativePathBuf ([#25]). [#25]: https://github.com/udoprog/relative-path/pull/25 ## [1.4.0] - 2021-05-04 ### Added * `to_logical_path` as an alternative method of converting into a path. ### Changed * `set_extension` no longer needs to allocate. * `set_file_name` implementation to more closely match `std::path::PathBuf::set_file_name`. ## [1.3.1], [1.3.2] - 2020-07-12 ### Changed * Minor documentation fixes. ## [1.3.0] - 2020-07-12 ### Fixed * Changed `to_path` to ignore platform-specific absolute components ([#18]). [#18]: https://github.com/udoprog/relative-path/pull/18 ## [1.2.1] - 2020-06-16 ### Changed * Change signature of `RelativePath::strip_prefix` to be the same as `std::path::Path` ([#16]). ## [1.2.0] - 2020-06-13 ### Added * Added `FromPathError::kind` to get more detailed error information ([#15]). ### Changed * Marked `FromPathErrorKind` `#[non_exhaustive]` which technically is a breaking change. But since it was not accessible from API code of this library, anyone who used it outside are on their own. ## [1.1.1] - 2020-06-13 ### Changed * Deprecated use of `std::error::Error::description` in favor of just having a `std::fmt::Display` impl. ## [1.1.0] - 2020-06-13 ### Added * Added `RelativePath::relative` to render a path relative from one path to another ([#14]). [#16]: https://github.com/udoprog/relative-path/pull/16 [#15]: https://github.com/udoprog/relative-path/pull/15 [#14]: https://github.com/udoprog/relative-path/pull/14 [Unreleased]: https://github.com/udoprog/relative-path/compare/1.8.0...master [1.8.0]: https://github.com/udoprog/relative-path/compare/1.7.3...1.8.0 [1.7.3]: https://github.com/udoprog/relative-path/compare/1.7.2...1.7.3 [1.7.2]: https://github.com/udoprog/relative-path/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/udoprog/relative-path/compare/1.7.0...1.7.1 [1.7.0]: https://github.com/udoprog/relative-path/compare/1.6.1...1.7.0 [1.6.1]: https://github.com/udoprog/relative-path/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/udoprog/relative-path/compare/1.5.0...1.6.0 [1.5.0]: https://github.com/udoprog/relative-path/compare/1.4.0...1.5.0 [1.4.0]: https://github.com/udoprog/relative-path/compare/1.3.2...1.4.0 [1.3.2]: https://github.com/udoprog/relative-path/compare/1.3.1...1.3.2 [1.3.1]: https://github.com/udoprog/relative-path/compare/1.3.0...1.3.1 [1.3.0]: https://github.com/udoprog/relative-path/compare/1.2.1...1.3.0 [1.2.1]: https://github.com/udoprog/relative-path/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/udoprog/relative-path/compare/1.1.1...1.2.0 [1.1.1]: https://github.com/udoprog/relative-path/compare/1.1.0...1.1.1 [1.1.0]: https://github.com/udoprog/relative-path/compare/1.0.0...1.1.0 relative-path-1.9.0/Cargo.toml0000644000000020460000000000100116100ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.56" name = "relative-path" version = "1.9.0" authors = ["John-John Tedro "] description = "Portable, relative paths for Rust." homepage = "https://github.com/udoprog/relative-path" documentation = "https://docs.rs/relative-path" readme = "README.md" keywords = ["path"] categories = ["filesystem"] license = "MIT/Apache-2.0" repository = "https://github.com/udoprog/relative-path" [dependencies.serde] version = "1.0.160" optional = true [dev-dependencies.serde] version = "1.0.160" features = ["derive"] [features] default = [] relative-path-1.9.0/Cargo.toml.orig000064400000000000000000000011410072674642500153140ustar 00000000000000[package] name = "relative-path" version = "1.9.0" authors = ["John-John Tedro "] edition = "2018" rust-version = "1.56" description = "Portable, relative paths for Rust." documentation = "https://docs.rs/relative-path" readme = "README.md" homepage = "https://github.com/udoprog/relative-path" repository = "https://github.com/udoprog/relative-path" license = "MIT/Apache-2.0" keywords = ["path"] categories = ["filesystem"] [features] default = [] [dependencies] serde = { version = "1.0.160", optional = true } [dev-dependencies] serde = { version = "1.0.160", features = ["derive"] } relative-path-1.9.0/LICENSE-APACHE000064400000000000000000000251370072674642500143640ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. relative-path-1.9.0/LICENSE-MIT000064400000000000000000000020570072674642500140700ustar 00000000000000Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. relative-path-1.9.0/README.md000064400000000000000000000225610072674642500137150ustar 00000000000000# relative-path [github](https://github.com/udoprog/relative-path) [crates.io](https://crates.io/crates/relative-path) [docs.rs](https://docs.rs/relative-path) [build status](https://github.com/udoprog/relative-path/actions?query=branch%3Amain) Portable relative UTF-8 paths for Rust. This crate provides a module analogous to [`std::path`], with the following characteristics: * The path separator is set to a fixed character (`/`), regardless of platform. * Relative paths cannot represent a path in the filesystem without first specifying *what they are relative to* using functions such as [`to_path`] and [`to_logical_path`]. * Relative paths are always guaranteed to be valid UTF-8 strings. On top of this we support many operations that guarantee the same behavior across platforms.
## Usage Add `relative-path` to your `Cargo.toml`: ```toml relative-path = "1.9.0" ``` Start using relative paths: ```rust use serde::{Serialize, Deserialize}; use relative_path::RelativePath; #[derive(Serialize, Deserialize)] struct Manifest<'a> { #[serde(borrow)] source: &'a RelativePath, } ```
## Serde Support This library includes serde support that can be enabled with the `serde` feature.
## Why is `std::path` a portability hazard? Path representations differ across platforms. * Windows permits using drive volumes (multiple roots) as a prefix (e.g. `"c:\"`) and backslash (`\`) as a separator. * Unix references absolute paths from a single root and uses forward slash (`/`) as a separator. If we use `PathBuf`, Storing paths in a manifest would allow our application to build and run on one platform but potentially not others. Consider the following data model and corresponding toml for a manifest: ```rust use std::path::PathBuf; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct Manifest { source: PathBuf, } ``` ```toml source = "C:\\Users\\udoprog\\repo\\data\\source" ``` This will run for you (assuming `source` exists). So you go ahead and check the manifest into git. The next day your Linux colleague calls you and wonders what they have ever done to wrong you? So what went wrong? Well two things. You forgot to make the `source` relative, so anyone at the company which has a different username than you won't be able to use it. So you go ahead and fix that: ```toml source = "data\\source" ``` But there is still one problem! A backslash (`\`) is only a legal path separator on Windows. Luckily you learn that forward slashes are supported both on Windows *and* Linux. So you opt for: ```toml source = "data/source" ``` Things are working now. So all is well... Right? Sure, but we can do better. This crate provides types that work with *portable relative paths* (hence the name). So by using [`RelativePath`] we can systematically help avoid portability issues like the one above. Avoiding issues at the source is preferably over spending 5 minutes of onboarding time on a theoretical problem, hoping that your new hires will remember what to do if they ever encounter it. Using [`RelativePathBuf`] we can fix our data model like this: ```rust use relative_path::RelativePathBuf; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct Manifest { source: RelativePathBuf, } ``` And where it's used: ```rust use std::fs; use std::env::current_dir; let manifest: Manifest = todo!(); let root = current_dir()?; let source = manifest.source.to_path(&root); let content = fs::read(&source)?; ```
## Overview Conversion to a platform-specific [`Path`] happens through the [`to_path`] and [`to_logical_path`] functions. Where you are required to specify the path that prefixes the relative path. This can come from a function such as [`std::env::current_dir`]. ```rust use std::env::current_dir; use std::path::Path; use relative_path::RelativePath; let root = current_dir()?; // to_path unconditionally concatenates a relative path with its base: let relative_path = RelativePath::new("../foo/./bar"); let full_path = relative_path.to_path(&root); assert_eq!(full_path, root.join("..\\foo\\.\\bar")); // to_logical_path tries to apply the logical operations that the relative // path corresponds to: let relative_path = RelativePath::new("../foo/./bar"); let full_path = relative_path.to_logical_path(&root); // Replicate the operation performed by `to_logical_path`. let mut parent = root.clone(); parent.pop(); assert_eq!(full_path, parent.join("foo\\bar")); ``` When two relative paths are compared to each other, their exact component makeup determines equality. ```rust use relative_path::RelativePath; assert_ne!( RelativePath::new("foo/bar/../baz"), RelativePath::new("foo/baz") ); ``` Using platform-specific path separators to construct relative paths is not supported. Path separators from other platforms are simply treated as part of a component: ```rust use relative_path::RelativePath; assert_ne!( RelativePath::new("foo/bar"), RelativePath::new("foo\\bar") ); assert_eq!(1, RelativePath::new("foo\\bar").components().count()); assert_eq!(2, RelativePath::new("foo/bar").components().count()); ``` To see if two relative paths are equivalent you can use [`normalize`]: ```rust use relative_path::RelativePath; assert_eq!( RelativePath::new("foo/bar/../baz").normalize(), RelativePath::new("foo/baz").normalize(), ); ```
## Additional portability notes While relative paths avoid the most egregious portability issue, that absolute paths will work equally unwell on all platforms. We cannot avoid all. This section tries to document additional portability hazards that we are aware of. [`RelativePath`], similarly to [`Path`], makes no guarantees that its constituent components make up legal file names. While components are strictly separated by slashes, we can still store things in them which may not be used as legal paths on all platforms. * A `NUL` character is not permitted on unix platforms - this is a terminator in C-based filesystem APIs. Slash (`/`) is also used as a path separator. * Windows has a number of [reserved characters and names][windows-reserved] (like `CON`, `PRN`, and `AUX`) which cannot legally be part of a filesystem component. * Windows paths are [case-insensitive by default][windows-case]. So, `Foo.txt` and `foo.txt` are the same files on windows. But they are considered different paths on most unix systems. A relative path that *accidentally* contains a platform-specific components will largely result in a nonsensical paths being generated in the hope that they will fail fast during development and testing. ```rust use relative_path::{RelativePath, PathExt}; use std::path::Path; if cfg!(windows) { assert_eq!( Path::new("foo\\c:\\bar\\baz"), RelativePath::new("c:\\bar\\baz").to_path("foo") ); } if cfg!(unix) { assert_eq!( Path::new("foo/bar/baz"), RelativePath::new("/bar/baz").to_path("foo") ); } assert_eq!( Path::new("foo").relative_to("bar")?, RelativePath::new("../foo"), ); ``` [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html [`normalize`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.normalize [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html [`RelativePath`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html [`RelativePathBuf`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePathBuf.html [`std::env::current_dir`]: https://doc.rust-lang.org/std/env/fn.current_dir.html [`std::path`]: https://doc.rust-lang.org/std/path/index.html [`to_logical_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_logical_path [`to_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_path [windows-reserved]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx [windows-case]: https://learn.microsoft.com/en-us/windows/wsl/case-sensitivity relative-path-1.9.0/src/lib.rs000064400000000000000000002115420072674642500143400ustar 00000000000000//! [github](https://github.com/udoprog/relative-path) //! [crates.io](https://crates.io/crates/relative-path) //! [docs.rs](https://docs.rs/relative-path) //! //! Portable relative UTF-8 paths for Rust. //! //! This crate provides a module analogous to [`std::path`], with the following //! characteristics: //! //! * The path separator is set to a fixed character (`/`), regardless of //! platform. //! * Relative paths cannot represent a path in the filesystem without first //! specifying *what they are relative to* using functions such as [`to_path`] //! and [`to_logical_path`]. //! * Relative paths are always guaranteed to be valid UTF-8 strings. //! //! On top of this we support many operations that guarantee the same behavior //! across platforms. //! //!
//! //! ## Usage //! //! Add `relative-path` to your `Cargo.toml`: //! //! ```toml //! relative-path = "1.9.0" //! ``` //! //! Start using relative paths: //! //! ``` //! use serde::{Serialize, Deserialize}; //! use relative_path::RelativePath; //! //! #[derive(Serialize, Deserialize)] //! struct Manifest<'a> { //! #[serde(borrow)] //! source: &'a RelativePath, //! } //! //! # Ok::<_, Box>(()) //! ``` //! //!
//! //! ## Serde Support //! //! This library includes serde support that can be enabled with the `serde` //! feature. //! //!
//! //! ## Why is `std::path` a portability hazard? //! //! Path representations differ across platforms. //! //! * Windows permits using drive volumes (multiple roots) as a prefix (e.g. //! `"c:\"`) and backslash (`\`) as a separator. //! * Unix references absolute paths from a single root and uses forward slash //! (`/`) as a separator. //! //! If we use `PathBuf`, Storing paths in a manifest would allow our application //! to build and run on one platform but potentially not others. //! //! Consider the following data model and corresponding toml for a manifest: //! //! ```rust //! use std::path::PathBuf; //! //! use serde::{Serialize, Deserialize}; //! //! #[derive(Serialize, Deserialize)] //! struct Manifest { //! source: PathBuf, //! } //! ``` //! //! ```toml //! source = "C:\\Users\\udoprog\\repo\\data\\source" //! ``` //! //! This will run for you (assuming `source` exists). So you go ahead and check //! the manifest into git. The next day your Linux colleague calls you and //! wonders what they have ever done to wrong you? //! //! So what went wrong? Well two things. You forgot to make the `source` //! relative, so anyone at the company which has a different username than you //! won't be able to use it. So you go ahead and fix that: //! //! ```toml //! source = "data\\source" //! ``` //! //! But there is still one problem! A backslash (`\`) is only a legal path //! separator on Windows. Luckily you learn that forward slashes are supported //! both on Windows *and* Linux. So you opt for: //! //! ```toml //! source = "data/source" //! ``` //! //! Things are working now. So all is well... Right? Sure, but we can do better. //! //! This crate provides types that work with *portable relative paths* (hence //! the name). So by using [`RelativePath`] we can systematically help avoid //! portability issues like the one above. Avoiding issues at the source is //! preferably over spending 5 minutes of onboarding time on a theoretical //! problem, hoping that your new hires will remember what to do if they ever //! encounter it. //! //! Using [`RelativePathBuf`] we can fix our data model like this: //! //! ```rust //! use relative_path::RelativePathBuf; //! use serde::{Serialize, Deserialize}; //! //! #[derive(Serialize, Deserialize)] //! pub struct Manifest { //! source: RelativePathBuf, //! } //! ``` //! //! And where it's used: //! //! ```rust,no_run //! # use relative_path::RelativePathBuf; //! # use serde::{Serialize, Deserialize}; //! # #[derive(Serialize, Deserialize)] pub struct Manifest { source: RelativePathBuf } //! use std::fs; //! use std::env::current_dir; //! //! let manifest: Manifest = todo!(); //! //! let root = current_dir()?; //! let source = manifest.source.to_path(&root); //! let content = fs::read(&source)?; //! # Ok::<_, Box>(()) //! ``` //! //!
//! //! ## Overview //! //! Conversion to a platform-specific [`Path`] happens through the [`to_path`] //! and [`to_logical_path`] functions. Where you are required to specify the //! path that prefixes the relative path. This can come from a function such as //! [`std::env::current_dir`]. //! //! ```rust //! use std::env::current_dir; //! use std::path::Path; //! //! use relative_path::RelativePath; //! //! let root = current_dir()?; //! //! # if cfg!(windows) { //! // to_path unconditionally concatenates a relative path with its base: //! let relative_path = RelativePath::new("../foo/./bar"); //! let full_path = relative_path.to_path(&root); //! assert_eq!(full_path, root.join("..\\foo\\.\\bar")); //! //! // to_logical_path tries to apply the logical operations that the relative //! // path corresponds to: //! let relative_path = RelativePath::new("../foo/./bar"); //! let full_path = relative_path.to_logical_path(&root); //! //! // Replicate the operation performed by `to_logical_path`. //! let mut parent = root.clone(); //! parent.pop(); //! assert_eq!(full_path, parent.join("foo\\bar")); //! # } //! # Ok::<_, std::io::Error>(()) //! ``` //! //! When two relative paths are compared to each other, their exact component //! makeup determines equality. //! //! ```rust //! use relative_path::RelativePath; //! //! assert_ne!( //! RelativePath::new("foo/bar/../baz"), //! RelativePath::new("foo/baz") //! ); //! ``` //! //! Using platform-specific path separators to construct relative paths is not //! supported. //! //! Path separators from other platforms are simply treated as part of a //! component: //! //! ```rust //! use relative_path::RelativePath; //! //! assert_ne!( //! RelativePath::new("foo/bar"), //! RelativePath::new("foo\\bar") //! ); //! //! assert_eq!(1, RelativePath::new("foo\\bar").components().count()); //! assert_eq!(2, RelativePath::new("foo/bar").components().count()); //! ``` //! //! To see if two relative paths are equivalent you can use [`normalize`]: //! //! ```rust //! use relative_path::RelativePath; //! //! assert_eq!( //! RelativePath::new("foo/bar/../baz").normalize(), //! RelativePath::new("foo/baz").normalize(), //! ); //! ``` //! //!
//! //! ## Additional portability notes //! //! While relative paths avoid the most egregious portability issue, that //! absolute paths will work equally unwell on all platforms. We cannot avoid //! all. This section tries to document additional portability hazards that we //! are aware of. //! //! [`RelativePath`], similarly to [`Path`], makes no guarantees that its //! constituent components make up legal file names. While components are //! strictly separated by slashes, we can still store things in them which may //! not be used as legal paths on all platforms. //! //! * A `NUL` character is not permitted on unix platforms - this is a //! terminator in C-based filesystem APIs. Slash (`/`) is also used as a path //! separator. //! * Windows has a number of [reserved characters and names][windows-reserved] //! (like `CON`, `PRN`, and `AUX`) which cannot legally be part of a //! filesystem component. //! * Windows paths are [case-insensitive by default][windows-case]. So, //! `Foo.txt` and `foo.txt` are the same files on windows. But they are //! considered different paths on most unix systems. //! //! A relative path that *accidentally* contains a platform-specific components //! will largely result in a nonsensical paths being generated in the hope that //! they will fail fast during development and testing. //! //! ```rust //! use relative_path::{RelativePath, PathExt}; //! use std::path::Path; //! //! if cfg!(windows) { //! assert_eq!( //! Path::new("foo\\c:\\bar\\baz"), //! RelativePath::new("c:\\bar\\baz").to_path("foo") //! ); //! } //! //! if cfg!(unix) { //! assert_eq!( //! Path::new("foo/bar/baz"), //! RelativePath::new("/bar/baz").to_path("foo") //! ); //! } //! //! assert_eq!( //! Path::new("foo").relative_to("bar")?, //! RelativePath::new("../foo"), //! ); //! # Ok::<_, Box>(()) //! ``` //! //! [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html //! [`normalize`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.normalize //! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html //! [`RelativePath`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html //! [`RelativePathBuf`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePathBuf.html //! [`std::env::current_dir`]: https://doc.rust-lang.org/std/env/fn.current_dir.html //! [`std::path`]: https://doc.rust-lang.org/std/path/index.html //! [`to_logical_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_logical_path //! [`to_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_path //! [windows-reserved]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx //! [windows-case]: https://learn.microsoft.com/en-us/windows/wsl/case-sensitivity // This file contains parts that are Copyright 2015 The Rust Project Developers, copied from: // https://github.com/rust-lang/rust // cb2a656cdfb6400ac0200c661267f91fabf237e2 src/libstd/path.rs #![deny(missing_docs)] mod path_ext; #[cfg(test)] mod tests; pub use path_ext::{PathExt, RelativeToError}; use std::borrow::{Borrow, Cow}; use std::cmp; use std::error; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter::FromIterator; use std::mem; use std::ops::{self, Deref}; use std::path; use std::rc::Rc; use std::str; use std::sync::Arc; const STEM_SEP: char = '.'; const CURRENT_STR: &str = "."; const PARENT_STR: &str = ".."; const SEP: char = '/'; fn split_file_at_dot(input: &str) -> (Option<&str>, Option<&str>) { if input == PARENT_STR { return (Some(input), None); } let mut iter = input.rsplitn(2, STEM_SEP); let after = iter.next(); let before = iter.next(); if before == Some("") { (Some(input), None) } else { (before, after) } } // Iterate through `iter` while it matches `prefix`; return `None` if `prefix` // is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving // `iter` after having exhausted `prefix`. fn iter_after<'a, 'b, I, J>(mut iter: I, mut prefix: J) -> Option where I: Iterator> + Clone, J: Iterator>, { loop { let mut iter_next = iter.clone(); match (iter_next.next(), prefix.next()) { (Some(x), Some(y)) if x == y => (), (Some(_), Some(_)) => return None, (Some(_), None) => return Some(iter), (None, None) => return Some(iter), (None, Some(_)) => return None, } iter = iter_next; } } /// A single path component. /// /// Accessed using the [RelativePath::components] iterator. /// /// # Examples /// /// ``` /// use relative_path::{Component, RelativePath}; /// /// let path = RelativePath::new("foo/../bar/./baz"); /// let mut it = path.components(); /// /// assert_eq!(Some(Component::Normal("foo")), it.next()); /// assert_eq!(Some(Component::ParentDir), it.next()); /// assert_eq!(Some(Component::Normal("bar")), it.next()); /// assert_eq!(Some(Component::CurDir), it.next()); /// assert_eq!(Some(Component::Normal("baz")), it.next()); /// assert_eq!(None, it.next()); /// ``` #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Component<'a> { /// The current directory `.`. CurDir, /// The parent directory `..`. ParentDir, /// A normal path component as a string. Normal(&'a str), } impl<'a> Component<'a> { /// Extracts the underlying [`str`][std::str] slice. /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, Component}; /// /// let path = RelativePath::new("./tmp/../foo/bar.txt"); /// let components: Vec<_> = path.components().map(Component::as_str).collect(); /// assert_eq!(&components, &[".", "tmp", "..", "foo", "bar.txt"]); /// ``` pub fn as_str(self) -> &'a str { use self::Component::*; match self { CurDir => CURRENT_STR, ParentDir => PARENT_STR, Normal(name) => name, } } } /// [`AsRef`] implementation for [`Component`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let mut it = RelativePath::new("../foo/bar").components(); /// /// let a = it.next().ok_or("a")?; /// let b = it.next().ok_or("b")?; /// let c = it.next().ok_or("c")?; /// /// let a: &RelativePath = a.as_ref(); /// let b: &RelativePath = b.as_ref(); /// let c: &RelativePath = c.as_ref(); /// /// assert_eq!(a, ".."); /// assert_eq!(b, "foo"); /// assert_eq!(c, "bar"); /// /// # Ok::<_, Box>(()) /// ``` impl AsRef for Component<'_> { #[inline] fn as_ref(&self) -> &RelativePath { self.as_str().as_ref() } } /// Traverse the given components and apply to the provided stack. /// /// This takes '.', and '..' into account. Where '.' doesn't change the stack, and '..' pops the /// last item or further adds parent components. #[inline(always)] fn relative_traversal<'a, C>(buf: &mut RelativePathBuf, components: C) where C: IntoIterator>, { use self::Component::*; for c in components { match c { CurDir => (), ParentDir => match buf.components().next_back() { Some(Component::ParentDir) | None => { buf.push(PARENT_STR); } _ => { buf.pop(); } }, Normal(name) => { buf.push(name); } } } } /// Iterator over all the components in a relative path. #[derive(Clone)] pub struct Components<'a> { source: &'a str, } impl<'a> Iterator for Components<'a> { type Item = Component<'a>; fn next(&mut self) -> Option { self.source = self.source.trim_start_matches(SEP); let slice = match self.source.find(SEP) { Some(i) => { let (slice, rest) = self.source.split_at(i); self.source = rest.trim_start_matches(SEP); slice } None => mem::take(&mut self.source), }; match slice { "" => None, CURRENT_STR => Some(Component::CurDir), PARENT_STR => Some(Component::ParentDir), slice => Some(Component::Normal(slice)), } } } impl<'a> DoubleEndedIterator for Components<'a> { fn next_back(&mut self) -> Option { self.source = self.source.trim_end_matches(SEP); let slice = match self.source.rfind(SEP) { Some(i) => { let (rest, slice) = self.source.split_at(i + 1); self.source = rest.trim_end_matches(SEP); slice } None => mem::take(&mut self.source), }; match slice { "" => None, CURRENT_STR => Some(Component::CurDir), PARENT_STR => Some(Component::ParentDir), slice => Some(Component::Normal(slice)), } } } impl<'a> Components<'a> { /// Construct a new component from the given string. fn new(source: &'a str) -> Components<'a> { Self { source } } /// Extracts a slice corresponding to the portion of the path remaining for iteration. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let mut components = RelativePath::new("tmp/foo/bar.txt").components(); /// components.next(); /// components.next(); /// /// assert_eq!("bar.txt", components.as_relative_path()); /// ``` pub fn as_relative_path(&self) -> &'a RelativePath { RelativePath::new(self.source) } } impl<'a> cmp::PartialEq for Components<'a> { fn eq(&self, other: &Components<'a>) -> bool { Iterator::eq(self.clone(), other.clone()) } } /// An iterator over the [`Component`]s of a [`RelativePath`], as /// [`str`][std::str] slices. /// /// This `struct` is created by the [`iter`][RelativePath::iter] method. #[derive(Clone)] pub struct Iter<'a> { inner: Components<'a>, } impl<'a> Iterator for Iter<'a> { type Item = &'a str; fn next(&mut self) -> Option<&'a str> { self.inner.next().map(Component::as_str) } } impl<'a> DoubleEndedIterator for Iter<'a> { fn next_back(&mut self) -> Option<&'a str> { self.inner.next_back().map(Component::as_str) } } /// Error kind for [`FromPathError`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum FromPathErrorKind { /// Non-relative component in path. NonRelative, /// Non-utf8 component in path. NonUtf8, /// Trying to convert a platform-specific path which uses a platform-specific separator. BadSeparator, } /// An error raised when attempting to convert a path using /// [`RelativePathBuf::from_path`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct FromPathError { kind: FromPathErrorKind, } impl FromPathError { /// Gets the underlying [`FromPathErrorKind`] that provides more details on /// what went wrong. /// /// # Examples /// /// ``` /// use std::path::Path; /// use relative_path::{FromPathErrorKind, RelativePathBuf}; /// /// let result = RelativePathBuf::from_path(Path::new("/hello/world")); /// let e = result.unwrap_err(); /// /// assert_eq!(FromPathErrorKind::NonRelative, e.kind()); /// ``` pub fn kind(&self) -> FromPathErrorKind { self.kind } } impl From for FromPathError { fn from(value: FromPathErrorKind) -> Self { Self { kind: value } } } impl fmt::Display for FromPathError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self.kind { FromPathErrorKind::NonRelative => "path contains non-relative component".fmt(fmt), FromPathErrorKind::NonUtf8 => "path contains non-utf8 component".fmt(fmt), FromPathErrorKind::BadSeparator => { "path contains platform-specific path separator".fmt(fmt) } } } } impl error::Error for FromPathError {} /// An owned, mutable relative path. /// /// This type provides methods to manipulate relative path objects. #[derive(Clone)] pub struct RelativePathBuf { inner: String, } impl RelativePathBuf { /// Create a new relative path buffer. pub fn new() -> RelativePathBuf { RelativePathBuf { inner: String::new(), } } /// Internal constructor to allocate a relative path buf with the given capacity. fn with_capacity(cap: usize) -> RelativePathBuf { RelativePathBuf { inner: String::with_capacity(cap), } } /// Try to convert a [`Path`] to a [`RelativePathBuf`]. /// /// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, RelativePathBuf, FromPathErrorKind}; /// use std::path::Path; /// /// assert_eq!( /// Ok(RelativePath::new("foo/bar").to_owned()), /// RelativePathBuf::from_path(Path::new("foo/bar")) /// ); /// ``` pub fn from_path>(path: P) -> Result { use std::path::Component::*; let mut buffer = RelativePathBuf::new(); for c in path.as_ref().components() { match c { Prefix(_) | RootDir => return Err(FromPathErrorKind::NonRelative.into()), CurDir => continue, ParentDir => buffer.push(PARENT_STR), Normal(s) => buffer.push(s.to_str().ok_or(FromPathErrorKind::NonUtf8)?), } } Ok(buffer) } /// Extends `self` with `path`. /// /// If `path` is absolute, it replaces the current path. /// /// # Examples /// /// ``` /// use relative_path::{RelativePathBuf, RelativePath}; /// /// let mut path = RelativePathBuf::new(); /// path.push("foo"); /// path.push("bar"); /// /// assert_eq!("foo/bar", path); /// ``` pub fn push>(&mut self, path: P) { let other = path.as_ref(); let other = if other.starts_with_sep() { &other.inner[1..] } else { &other.inner[..] }; if !self.inner.is_empty() && !self.ends_with_sep() { self.inner.push(SEP); } self.inner.push_str(other) } /// Updates [`file_name`] to `file_name`. /// /// If [`file_name`] was [`None`], this is equivalent to pushing /// `file_name`. /// /// Otherwise it is equivalent to calling [`pop`] and then pushing /// `file_name`. The new path will be a sibling of the original path. (That /// is, it will have the same parent.) /// /// [`file_name`]: RelativePath::file_name /// [`pop`]: RelativePathBuf::pop /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html /// /// # Examples /// /// ``` /// use relative_path::RelativePathBuf; /// /// let mut buf = RelativePathBuf::from(""); /// assert!(buf.file_name() == None); /// buf.set_file_name("bar"); /// assert_eq!(RelativePathBuf::from("bar"), buf); /// /// assert!(buf.file_name().is_some()); /// buf.set_file_name("baz.txt"); /// assert_eq!(RelativePathBuf::from("baz.txt"), buf); /// /// buf.push("bar"); /// assert!(buf.file_name().is_some()); /// buf.set_file_name("bar.txt"); /// assert_eq!(RelativePathBuf::from("baz.txt/bar.txt"), buf); /// ``` pub fn set_file_name>(&mut self, file_name: S) { if self.file_name().is_some() { let popped = self.pop(); debug_assert!(popped); } self.push(file_name.as_ref()); } /// Updates [`extension`] to `extension`. /// /// Returns `false` and does nothing if /// [`file_name`][RelativePath::file_name] is [`None`], returns `true` and /// updates the extension otherwise. /// /// If [`extension`] is [`None`], the extension is added; otherwise it is /// replaced. /// /// [`extension`]: RelativePath::extension /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let mut p = RelativePathBuf::from("feel/the"); /// /// p.set_extension("force"); /// assert_eq!(RelativePath::new("feel/the.force"), p); /// /// p.set_extension("dark_side"); /// assert_eq!(RelativePath::new("feel/the.dark_side"), p); /// /// assert!(p.pop()); /// p.set_extension("nothing"); /// assert_eq!(RelativePath::new("feel.nothing"), p); /// ``` pub fn set_extension>(&mut self, extension: S) -> bool { let file_stem = match self.file_stem() { Some(stem) => stem, None => return false, }; let end_file_stem = file_stem[file_stem.len()..].as_ptr() as usize; let start = self.inner.as_ptr() as usize; self.inner.truncate(end_file_stem.wrapping_sub(start)); let extension = extension.as_ref(); if !extension.is_empty() { self.inner.push(STEM_SEP); self.inner.push_str(extension); } true } /// Truncates `self` to [`parent`][RelativePath::parent]. /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let mut p = RelativePathBuf::from("test/test.rs"); /// /// assert_eq!(true, p.pop()); /// assert_eq!(RelativePath::new("test"), p); /// assert_eq!(true, p.pop()); /// assert_eq!(RelativePath::new(""), p); /// assert_eq!(false, p.pop()); /// assert_eq!(RelativePath::new(""), p); /// ``` pub fn pop(&mut self) -> bool { match self.parent().map(|p| p.inner.len()) { Some(len) => { self.inner.truncate(len); true } None => false, } } /// Coerce to a [`RelativePath`] slice. pub fn as_relative_path(&self) -> &RelativePath { self } /// Consumes the `RelativePathBuf`, yielding its internal [`String`] storage. /// /// # Examples /// /// ``` /// use relative_path::RelativePathBuf; /// /// let p = RelativePathBuf::from("/the/head"); /// let string = p.into_string(); /// assert_eq!(string, "/the/head".to_owned()); /// ``` pub fn into_string(self) -> String { self.inner } /// Converts this `RelativePathBuf` into a [boxed][std::boxed::Box] /// [`RelativePath`]. pub fn into_boxed_relative_path(self) -> Box { let rw = Box::into_raw(self.inner.into_boxed_str()) as *mut RelativePath; unsafe { Box::from_raw(rw) } } } impl Default for RelativePathBuf { fn default() -> Self { RelativePathBuf::new() } } impl<'a> From<&'a RelativePath> for Cow<'a, RelativePath> { #[inline] fn from(s: &'a RelativePath) -> Cow<'a, RelativePath> { Cow::Borrowed(s) } } impl<'a> From for Cow<'a, RelativePath> { #[inline] fn from(s: RelativePathBuf) -> Cow<'a, RelativePath> { Cow::Owned(s) } } impl fmt::Debug for RelativePathBuf { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{:?}", &self.inner) } } impl AsRef for RelativePathBuf { fn as_ref(&self) -> &RelativePath { RelativePath::new(&self.inner) } } impl AsRef for RelativePath { fn as_ref(&self) -> &str { &self.inner } } impl Borrow for RelativePathBuf { fn borrow(&self) -> &RelativePath { self.deref() } } impl<'a, T: ?Sized + AsRef> From<&'a T> for RelativePathBuf { fn from(path: &'a T) -> RelativePathBuf { RelativePathBuf { inner: path.as_ref().to_owned(), } } } impl From for RelativePathBuf { fn from(path: String) -> RelativePathBuf { RelativePathBuf { inner: path } } } impl From for String { fn from(path: RelativePathBuf) -> String { path.into_string() } } impl ops::Deref for RelativePathBuf { type Target = RelativePath; fn deref(&self) -> &RelativePath { RelativePath::new(&self.inner) } } impl cmp::PartialEq for RelativePathBuf { fn eq(&self, other: &RelativePathBuf) -> bool { self.components() == other.components() } } impl cmp::Eq for RelativePathBuf {} impl cmp::PartialOrd for RelativePathBuf { fn partial_cmp(&self, other: &RelativePathBuf) -> Option { self.components().partial_cmp(other.components()) } } impl cmp::Ord for RelativePathBuf { fn cmp(&self, other: &RelativePathBuf) -> cmp::Ordering { self.components().cmp(other.components()) } } impl Hash for RelativePathBuf { fn hash(&self, h: &mut H) { self.as_relative_path().hash(h) } } impl> Extend

for RelativePathBuf { #[inline] fn extend>(&mut self, iter: I) { iter.into_iter().for_each(move |p| self.push(p.as_ref())); } } impl> FromIterator

for RelativePathBuf { #[inline] fn from_iter>(iter: I) -> RelativePathBuf { let mut buf = RelativePathBuf::new(); buf.extend(iter); buf } } /// A borrowed, immutable relative path. #[repr(transparent)] pub struct RelativePath { inner: str, } /// An error returned from [strip_prefix] if the prefix was not found. /// /// [strip_prefix]: RelativePath::strip_prefix #[derive(Debug, Clone, PartialEq, Eq)] pub struct StripPrefixError(()); impl RelativePath { /// Directly wraps a string slice as a `RelativePath` slice. pub fn new + ?Sized>(s: &S) -> &RelativePath { unsafe { &*(s.as_ref() as *const str as *const RelativePath) } } /// Try to convert a [`Path`] to a [`RelativePath`] without allocating a buffer. /// /// [`Path`]: std::path::Path /// /// # Errors /// /// This requires the path to be a legal, platform-neutral relative path. /// Otherwise various forms of [`FromPathError`] will be returned as an /// [`Err`]. /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, FromPathErrorKind}; /// /// assert_eq!( /// Ok(RelativePath::new("foo/bar")), /// RelativePath::from_path("foo/bar") /// ); /// /// // Note: absolute paths are different depending on platform. /// if cfg!(windows) { /// let e = RelativePath::from_path("c:\\foo\\bar").unwrap_err(); /// assert_eq!(FromPathErrorKind::NonRelative, e.kind()); /// } /// /// if cfg!(unix) { /// let e = RelativePath::from_path("/foo/bar").unwrap_err(); /// assert_eq!(FromPathErrorKind::NonRelative, e.kind()); /// } /// ``` pub fn from_path>( path: &P, ) -> Result<&RelativePath, FromPathError> { use std::path::Component::*; let other = path.as_ref(); let s = match other.to_str() { Some(s) => s, None => return Err(FromPathErrorKind::NonUtf8.into()), }; let rel = RelativePath::new(s); // check that the component compositions are equal. for (a, b) in other.components().zip(rel.components()) { match (a, b) { (Prefix(_), _) | (RootDir, _) => return Err(FromPathErrorKind::NonRelative.into()), (CurDir, Component::CurDir) => continue, (ParentDir, Component::ParentDir) => continue, (Normal(a), Component::Normal(b)) if a == b => continue, _ => return Err(FromPathErrorKind::BadSeparator.into()), } } Ok(rel) } /// Yields the underlying [`str`][std::str] slice. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!(RelativePath::new("foo.txt").as_str(), "foo.txt"); /// ``` pub fn as_str(&self) -> &str { &self.inner } /// Returns an object that implements [`Display`][std::fmt::Display]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("tmp/foo.rs"); /// /// println!("{}", path.display()); /// ``` #[deprecated(note = "RelativePath implements std::fmt::Display directly")] pub fn display(&self) -> Display { Display { path: self } } /// Creates an owned [`RelativePathBuf`] with path adjoined to self. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("foo/bar"); /// assert_eq!("foo/bar/baz", path.join("baz")); /// ``` pub fn join>(&self, path: P) -> RelativePathBuf { let mut out = self.to_relative_path_buf(); out.push(path); out } /// Iterate over all components in this relative path. /// /// # Examples /// /// ``` /// use relative_path::{Component, RelativePath}; /// /// let path = RelativePath::new("foo/bar/baz"); /// let mut it = path.components(); /// /// assert_eq!(Some(Component::Normal("foo")), it.next()); /// assert_eq!(Some(Component::Normal("bar")), it.next()); /// assert_eq!(Some(Component::Normal("baz")), it.next()); /// assert_eq!(None, it.next()); /// ``` pub fn components(&self) -> Components { Components::new(&self.inner) } /// Produces an iterator over the path's components viewed as /// [`str`][std::str] slices. /// /// For more information about the particulars of how the path is separated /// into components, see [`components`][Self::components]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let mut it = RelativePath::new("/tmp/foo.txt").iter(); /// assert_eq!(it.next(), Some("tmp")); /// assert_eq!(it.next(), Some("foo.txt")); /// assert_eq!(it.next(), None) /// ``` pub fn iter(&self) -> Iter { Iter { inner: self.components(), } } /// Convert to an owned [`RelativePathBuf`]. pub fn to_relative_path_buf(&self) -> RelativePathBuf { RelativePathBuf::from(self.inner.to_owned()) } /// Build an owned [`PathBuf`] relative to `base` for the current relative /// path. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// use std::path::Path; /// /// let path = RelativePath::new("foo/bar").to_path("."); /// assert_eq!(Path::new("./foo/bar"), path); /// /// let path = RelativePath::new("foo/bar").to_path(""); /// assert_eq!(Path::new("foo/bar"), path); /// ``` /// /// # Encoding an absolute path /// /// Absolute paths are, in contrast to when using [`PathBuf::push`] *ignored* /// and will be added unchanged to the buffer. /// /// This is to preserve the probability of a path conversion failing if the /// relative path contains platform-specific absolute path components. /// /// ``` /// use relative_path::RelativePath; /// use std::path::Path; /// /// if cfg!(windows) { /// let path = RelativePath::new("/bar/baz").to_path("foo"); /// assert_eq!(Path::new("foo\\bar\\baz"), path); /// /// let path = RelativePath::new("c:\\bar\\baz").to_path("foo"); /// assert_eq!(Path::new("foo\\c:\\bar\\baz"), path); /// } /// /// if cfg!(unix) { /// let path = RelativePath::new("/bar/baz").to_path("foo"); /// assert_eq!(Path::new("foo/bar/baz"), path); /// /// let path = RelativePath::new("c:\\bar\\baz").to_path("foo"); /// assert_eq!(Path::new("foo/c:\\bar\\baz"), path); /// } /// ``` /// /// [`PathBuf`]: std::path::PathBuf /// [`PathBuf::push`]: std::path::PathBuf::push pub fn to_path>(&self, base: P) -> path::PathBuf { let mut p = base.as_ref().to_path_buf().into_os_string(); for c in self.components() { if !p.is_empty() { p.push(path::MAIN_SEPARATOR.encode_utf8(&mut [0u8, 0u8, 0u8, 0u8])); } p.push(c.as_str()); } path::PathBuf::from(p) } /// Build an owned [`PathBuf`] relative to `base` for the current relative /// path. /// /// This is similar to [`to_path`][RelativePath::to_path] except that it /// doesn't just unconditionally append one path to the other, instead it /// performs the following operations depending on its own components: /// /// * [Component::CurDir] leaves the `base` unmodified. /// * [Component::ParentDir] removes a component from `base` using /// [path::PathBuf::pop]. /// * [Component::Normal] pushes the given path component onto `base` using /// the same mechanism as [`to_path`][RelativePath::to_path]. /// /// Note that the exact semantics of the path operation is determined by the /// corresponding [`PathBuf`] operation. E.g. popping a component off a path /// like `.` will result in an empty path. /// /// ``` /// use relative_path::RelativePath; /// use std::path::Path; /// /// let path = RelativePath::new("..").to_logical_path("."); /// assert_eq!(path, Path::new("")); /// ``` /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// use std::path::Path; /// /// let path = RelativePath::new("..").to_logical_path("foo/bar"); /// assert_eq!(path, Path::new("foo")); /// ``` /// /// # Encoding an absolute path /// /// Behaves the same as [`to_path`][RelativePath::to_path] when encoding /// absolute paths. /// /// Absolute paths are, in contrast to when using [`PathBuf::push`] *ignored* /// and will be added unchanged to the buffer. /// /// This is to preserve the probability of a path conversion failing if the /// relative path contains platform-specific absolute path components. /// /// ``` /// use relative_path::RelativePath; /// use std::path::Path; /// /// if cfg!(windows) { /// let path = RelativePath::new("/bar/baz").to_logical_path("foo"); /// assert_eq!(Path::new("foo\\bar\\baz"), path); /// /// let path = RelativePath::new("c:\\bar\\baz").to_logical_path("foo"); /// assert_eq!(Path::new("foo\\c:\\bar\\baz"), path); /// /// let path = RelativePath::new("foo/bar").to_logical_path(""); /// assert_eq!(Path::new("foo\\bar"), path); /// } /// /// if cfg!(unix) { /// let path = RelativePath::new("/bar/baz").to_logical_path("foo"); /// assert_eq!(Path::new("foo/bar/baz"), path); /// /// let path = RelativePath::new("c:\\bar\\baz").to_logical_path("foo"); /// assert_eq!(Path::new("foo/c:\\bar\\baz"), path); /// /// let path = RelativePath::new("foo/bar").to_logical_path(""); /// assert_eq!(Path::new("foo/bar"), path); /// } /// ``` /// /// [`PathBuf`]: std::path::PathBuf /// [`PathBuf::push`]: std::path::PathBuf::push pub fn to_logical_path>(&self, base: P) -> path::PathBuf { use self::Component::*; let mut p = base.as_ref().to_path_buf().into_os_string(); for c in self.components() { match c { CurDir => continue, ParentDir => { let mut temp = path::PathBuf::from(std::mem::take(&mut p)); temp.pop(); p = temp.into_os_string(); } Normal(c) => { if !p.is_empty() { p.push(path::MAIN_SEPARATOR.encode_utf8(&mut [0u8, 0u8, 0u8, 0u8])); } p.push(c); } } } path::PathBuf::from(p) } /// Returns a relative path, without its final [`Component`] if there is one. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!(Some(RelativePath::new("foo")), RelativePath::new("foo/bar").parent()); /// assert_eq!(Some(RelativePath::new("")), RelativePath::new("foo").parent()); /// assert_eq!(None, RelativePath::new("").parent()); /// ``` pub fn parent(&self) -> Option<&RelativePath> { use self::Component::*; if self.inner.is_empty() { return None; } let mut it = self.components(); while let Some(CurDir) = it.next_back() {} Some(it.as_relative_path()) } /// Returns the final component of the `RelativePath`, if there is one. /// /// If the path is a normal file, this is the file name. If it's the path of /// a directory, this is the directory name. /// /// Returns [`None`] If the path terminates in `..`. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!(Some("bin"), RelativePath::new("usr/bin/").file_name()); /// assert_eq!(Some("foo.txt"), RelativePath::new("tmp/foo.txt").file_name()); /// assert_eq!(Some("foo.txt"), RelativePath::new("tmp/foo.txt/").file_name()); /// assert_eq!(Some("foo.txt"), RelativePath::new("foo.txt/.").file_name()); /// assert_eq!(Some("foo.txt"), RelativePath::new("foo.txt/.//").file_name()); /// assert_eq!(None, RelativePath::new("foo.txt/..").file_name()); /// assert_eq!(None, RelativePath::new("/").file_name()); /// ``` pub fn file_name(&self) -> Option<&str> { use self::Component::*; let mut it = self.components(); while let Some(c) = it.next_back() { return match c { CurDir => continue, Normal(name) => Some(name), _ => None, }; } None } /// Returns a relative path that, when joined onto `base`, yields `self`. /// /// # Errors /// /// If `base` is not a prefix of `self` (i.e. /// [starts_with][Self::starts_with] returns `false`), returns [`Err`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("test/haha/foo.txt"); /// /// assert_eq!(path.strip_prefix("test"), Ok(RelativePath::new("haha/foo.txt"))); /// assert_eq!(path.strip_prefix("test").is_ok(), true); /// assert_eq!(path.strip_prefix("haha").is_ok(), false); /// ``` pub fn strip_prefix>( &self, base: P, ) -> Result<&RelativePath, StripPrefixError> { iter_after(self.components(), base.as_ref().components()) .map(|c| c.as_relative_path()) .ok_or(StripPrefixError(())) } /// Determines whether `base` is a prefix of `self`. /// /// Only considers whole path components to match. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("etc/passwd"); /// /// assert!(path.starts_with("etc")); /// /// assert!(!path.starts_with("e")); /// ``` pub fn starts_with>(&self, base: P) -> bool { iter_after(self.components(), base.as_ref().components()).is_some() } /// Determines whether `child` is a suffix of `self`. /// /// Only considers whole path components to match. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("etc/passwd"); /// /// assert!(path.ends_with("passwd")); /// ``` pub fn ends_with>(&self, child: P) -> bool { iter_after(self.components().rev(), child.as_ref().components().rev()).is_some() } /// Determines whether `self` is normalized. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// // These are normalized. /// assert!(RelativePath::new("").is_normalized()); /// assert!(RelativePath::new("baz.txt").is_normalized()); /// assert!(RelativePath::new("foo/bar/baz.txt").is_normalized()); /// assert!(RelativePath::new("..").is_normalized()); /// assert!(RelativePath::new("../..").is_normalized()); /// assert!(RelativePath::new("../../foo/bar/baz.txt").is_normalized()); /// /// // These are not normalized. /// assert!(!RelativePath::new(".").is_normalized()); /// assert!(!RelativePath::new("./baz.txt").is_normalized()); /// assert!(!RelativePath::new("foo/..").is_normalized()); /// assert!(!RelativePath::new("foo/../baz.txt").is_normalized()); /// assert!(!RelativePath::new("foo/.").is_normalized()); /// assert!(!RelativePath::new("foo/./baz.txt").is_normalized()); /// assert!(!RelativePath::new("../foo/./bar/../baz.txt").is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.components() .skip_while(|c| matches!(c, Component::ParentDir)) .all(|c| matches!(c, Component::Normal(_))) } /// Creates an owned [`RelativePathBuf`] like `self` but with the given file /// name. /// /// See [set_file_name][RelativePathBuf::set_file_name] for more details. /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let path = RelativePath::new("tmp/foo.txt"); /// assert_eq!(path.with_file_name("bar.txt"), RelativePathBuf::from("tmp/bar.txt")); /// /// let path = RelativePath::new("tmp"); /// assert_eq!(path.with_file_name("var"), RelativePathBuf::from("var")); /// ``` pub fn with_file_name>(&self, file_name: S) -> RelativePathBuf { let mut buf = self.to_relative_path_buf(); buf.set_file_name(file_name); buf } /// Extracts the stem (non-extension) portion of [`file_name`][Self::file_name]. /// /// The stem is: /// /// * [`None`], if there is no file name; /// * The entire file name if there is no embedded `.`; /// * The entire file name if the file name begins with `.` and has no other `.`s within; /// * Otherwise, the portion of the file name before the final `.` /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("foo.rs"); /// /// assert_eq!("foo", path.file_stem().unwrap()); /// ``` pub fn file_stem(&self) -> Option<&str> { self.file_name() .map(split_file_at_dot) .and_then(|(before, after)| before.or(after)) } /// Extracts the extension of [`file_name`][Self::file_name], if possible. /// /// The extension is: /// /// * [`None`], if there is no file name; /// * [`None`], if there is no embedded `.`; /// * [`None`], if the file name begins with `.` and has no other `.`s within; /// * Otherwise, the portion of the file name after the final `.` /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!(Some("rs"), RelativePath::new("foo.rs").extension()); /// assert_eq!(None, RelativePath::new(".rs").extension()); /// assert_eq!(Some("rs"), RelativePath::new("foo.rs/.").extension()); /// ``` pub fn extension(&self) -> Option<&str> { self.file_name() .map(split_file_at_dot) .and_then(|(before, after)| before.and(after)) } /// Creates an owned [`RelativePathBuf`] like `self` but with the given /// extension. /// /// See [set_extension][RelativePathBuf::set_extension] for more details. /// /// # Examples /// /// ``` /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let path = RelativePath::new("foo.rs"); /// assert_eq!(path.with_extension("txt"), RelativePathBuf::from("foo.txt")); /// ``` pub fn with_extension>(&self, extension: S) -> RelativePathBuf { let mut buf = self.to_relative_path_buf(); buf.set_extension(extension); buf } /// Build an owned [`RelativePathBuf`], joined with the given path and /// normalized. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!( /// RelativePath::new("foo/baz.txt"), /// RelativePath::new("foo/bar").join_normalized("../baz.txt").as_relative_path() /// ); /// /// assert_eq!( /// RelativePath::new("../foo/baz.txt"), /// RelativePath::new("../foo/bar").join_normalized("../baz.txt").as_relative_path() /// ); /// ``` pub fn join_normalized>(&self, path: P) -> RelativePathBuf { let mut buf = RelativePathBuf::new(); relative_traversal(&mut buf, self.components()); relative_traversal(&mut buf, path.as_ref().components()); buf } /// Return an owned [`RelativePathBuf`], with all non-normal components /// moved to the beginning of the path. /// /// This permits for a normalized representation of different relative /// components. /// /// Normalization is a _destructive_ operation if the path references an /// actual filesystem path. An example of this is symlinks under unix, a /// path like `foo/../bar` might reference a different location other than /// `./bar`. /// /// Normalization is a logical operation and does not guarantee that the /// constructed path corresponds to what the filesystem would do. On Linux /// for example symbolic links could mean that the logical path doesn't /// correspond to the filesystem path. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!( /// "../foo/baz.txt", /// RelativePath::new("../foo/./bar/../baz.txt").normalize() /// ); /// /// assert_eq!( /// "", /// RelativePath::new(".").normalize() /// ); /// ``` pub fn normalize(&self) -> RelativePathBuf { let mut buf = RelativePathBuf::with_capacity(self.inner.len()); relative_traversal(&mut buf, self.components()); buf } /// Constructs a relative path from the current path, to `path`. /// /// This function will return the empty [`RelativePath`] `""` if this source /// contains unnamed components like `..` that would have to be traversed to /// reach the destination `path`. This is necessary since we have no way of /// knowing what the names of those components are when we're building the /// new relative path. /// /// ``` /// use relative_path::RelativePath; /// /// // Here we don't know what directories `../..` refers to, so there's no /// // way to construct a path back to `bar` in the current directory from /// // `../..`. /// let from = RelativePath::new("../../foo/relative-path"); /// let to = RelativePath::new("bar"); /// assert_eq!("", from.relative(to)); /// ``` /// /// One exception to this is when two paths contains a common prefix at /// which point there's no need to know what the names of those unnamed /// components are. /// /// ``` /// use relative_path::RelativePath; /// /// let from = RelativePath::new("../../foo/bar"); /// let to = RelativePath::new("../../foo/baz"); /// /// assert_eq!("../baz", from.relative(to)); /// /// let from = RelativePath::new("../a/../../foo/bar"); /// let to = RelativePath::new("../../foo/baz"); /// /// assert_eq!("../baz", from.relative(to)); /// ``` /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!( /// "../../e/f", /// RelativePath::new("a/b/c/d").relative(RelativePath::new("a/b/e/f")) /// ); /// /// assert_eq!( /// "../bbb", /// RelativePath::new("a/../aaa").relative(RelativePath::new("b/../bbb")) /// ); /// /// let a = RelativePath::new("git/relative-path"); /// let b = RelativePath::new("git"); /// assert_eq!("relative-path", b.relative(a)); /// assert_eq!("..", a.relative(b)); /// /// let a = RelativePath::new("foo/bar/bap/foo.h"); /// let b = RelativePath::new("../arch/foo.h"); /// assert_eq!("../../../../../arch/foo.h", a.relative(b)); /// assert_eq!("", b.relative(a)); /// ``` pub fn relative>(&self, path: P) -> RelativePathBuf { let mut from = RelativePathBuf::with_capacity(self.inner.len()); let mut to = RelativePathBuf::with_capacity(path.as_ref().inner.len()); relative_traversal(&mut from, self.components()); relative_traversal(&mut to, path.as_ref().components()); let mut it_from = from.components(); let mut it_to = to.components(); // Strip a common prefixes - if any. let (lead_from, lead_to) = loop { match (it_from.next(), it_to.next()) { (Some(f), Some(t)) if f == t => continue, (f, t) => { break (f, t); } } }; // Special case: The path we are traversing from can't contain unnamed // components. A relative path might be any path, like `/`, or // `/foo/bar/baz`, and these components cannot be named in the relative // traversal. // // Also note that `relative_traversal` guarantees that all ParentDir // components are at the head of the path being built. if lead_from == Some(Component::ParentDir) { return RelativePathBuf::new(); } let head = lead_from.into_iter().chain(it_from); let tail = lead_to.into_iter().chain(it_to); let mut buf = RelativePathBuf::with_capacity(usize::max(from.inner.len(), to.inner.len())); for c in head.map(|_| Component::ParentDir).chain(tail) { buf.push(c.as_str()); } buf } /// Check if path starts with a path separator. #[inline] fn starts_with_sep(&self) -> bool { self.inner.starts_with(SEP) } /// Check if path ends with a path separator. #[inline] fn ends_with_sep(&self) -> bool { self.inner.ends_with(SEP) } } /// Conversion from a [`Box`] reference to a [`Box`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path: Box = Box::::from("foo/bar").into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From> for Box { #[inline] fn from(boxed: Box) -> Box { let rw = Box::into_raw(boxed) as *mut RelativePath; unsafe { Box::from_raw(rw) } } } /// Conversion from a [`str`] reference to a [`Box`]. /// /// [`str`]: prim@str /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path: Box = "foo/bar".into(); /// assert_eq!(&*path, "foo/bar"); /// /// let path: Box = RelativePath::new("foo/bar").into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From<&T> for Box where T: ?Sized + AsRef, { #[inline] fn from(path: &T) -> Box { Box::::from(Box::::from(path.as_ref())) } } /// Conversion from [`RelativePathBuf`] to [`Box`]. /// /// # Examples /// /// ``` /// use std::sync::Arc; /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let path = RelativePathBuf::from("foo/bar"); /// let path: Box = path.into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From for Box { #[inline] fn from(path: RelativePathBuf) -> Box { let boxed: Box = path.inner.into(); let rw = Box::into_raw(boxed) as *mut RelativePath; unsafe { Box::from_raw(rw) } } } /// Clone implementation for [`Box`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path: Box = RelativePath::new("foo/bar").into(); /// let path2 = path.clone(); /// assert_eq!(&*path, &*path2); /// ``` impl Clone for Box { #[inline] fn clone(&self) -> Self { self.to_relative_path_buf().into_boxed_relative_path() } } /// Conversion from [RelativePath] to [`Arc`]. /// /// # Examples /// /// ``` /// use std::sync::Arc; /// use relative_path::RelativePath; /// /// let path: Arc = RelativePath::new("foo/bar").into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From<&RelativePath> for Arc { #[inline] fn from(path: &RelativePath) -> Arc { let arc: Arc = path.inner.into(); let rw = Arc::into_raw(arc) as *const RelativePath; unsafe { Arc::from_raw(rw) } } } /// Conversion from [`RelativePathBuf`] to [`Arc`]. /// /// # Examples /// /// ``` /// use std::sync::Arc; /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let path = RelativePathBuf::from("foo/bar"); /// let path: Arc = path.into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From for Arc { #[inline] fn from(path: RelativePathBuf) -> Arc { let arc: Arc = path.inner.into(); let rw = Arc::into_raw(arc) as *const RelativePath; unsafe { Arc::from_raw(rw) } } } /// Conversion from [`RelativePathBuf`] to [`Arc`]. /// /// # Examples /// /// ``` /// use std::rc::Rc; /// use relative_path::RelativePath; /// /// let path: Rc = RelativePath::new("foo/bar").into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From<&RelativePath> for Rc { #[inline] fn from(path: &RelativePath) -> Rc { let rc: Rc = path.inner.into(); let rw = Rc::into_raw(rc) as *const RelativePath; unsafe { Rc::from_raw(rw) } } } /// Conversion from [`RelativePathBuf`] to [`Rc`]. /// /// # Examples /// /// ``` /// use std::rc::Rc; /// use relative_path::{RelativePath, RelativePathBuf}; /// /// let path = RelativePathBuf::from("foo/bar"); /// let path: Rc = path.into(); /// assert_eq!(&*path, "foo/bar"); /// ``` impl From for Rc { #[inline] fn from(path: RelativePathBuf) -> Rc { let rc: Rc = path.inner.into(); let rw = Rc::into_raw(rc) as *const RelativePath; unsafe { Rc::from_raw(rw) } } } /// [`ToOwned`] implementation for [`RelativePath`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path = RelativePath::new("foo/bar").to_owned(); /// assert_eq!(path, "foo/bar"); /// ``` impl ToOwned for RelativePath { type Owned = RelativePathBuf; #[inline] fn to_owned(&self) -> RelativePathBuf { self.to_relative_path_buf() } } impl fmt::Debug for RelativePath { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{:?}", &self.inner) } } /// [`AsRef`] implementation for [`RelativePathBuf`]. /// /// # Examples /// /// ``` /// use relative_path::RelativePathBuf; /// /// let path = RelativePathBuf::from("foo/bar"); /// let string: &str = path.as_ref(); /// assert_eq!(string, "foo/bar"); /// ``` impl AsRef for RelativePathBuf { #[inline] fn as_ref(&self) -> &str { &self.inner } } /// [`AsRef`] implementation for [String]. /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path: String = format!("foo/bar"); /// let path: &RelativePath = path.as_ref(); /// assert_eq!(path, "foo/bar"); /// ``` impl AsRef for String { #[inline] fn as_ref(&self) -> &RelativePath { RelativePath::new(self) } } /// [`AsRef`] implementation for [`str`]. /// /// [`str`]: prim@str /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// let path: &RelativePath = "foo/bar".as_ref(); /// assert_eq!(path, RelativePath::new("foo/bar")); /// ``` impl AsRef for str { #[inline] fn as_ref(&self) -> &RelativePath { RelativePath::new(self) } } impl AsRef for RelativePath { #[inline] fn as_ref(&self) -> &RelativePath { self } } impl cmp::PartialEq for RelativePath { #[inline] fn eq(&self, other: &RelativePath) -> bool { self.components() == other.components() } } impl cmp::Eq for RelativePath {} impl cmp::PartialOrd for RelativePath { #[inline] fn partial_cmp(&self, other: &RelativePath) -> Option { self.components().partial_cmp(other.components()) } } impl cmp::Ord for RelativePath { #[inline] fn cmp(&self, other: &RelativePath) -> cmp::Ordering { self.components().cmp(other.components()) } } impl Hash for RelativePath { #[inline] fn hash(&self, h: &mut H) { for c in self.components() { c.hash(h); } } } impl fmt::Display for RelativePath { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.inner, f) } } impl fmt::Display for RelativePathBuf { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.inner, f) } } /// Helper struct for printing relative paths. /// /// This is not strictly necessary in the same sense as it is for [`Display`], /// because relative paths are guaranteed to be valid UTF-8. But the behavior is /// preserved to simplify the transition between [`Path`] and [`RelativePath`]. /// /// [`Path`]: std::path::Path /// [`Display`]: std::fmt::Display pub struct Display<'a> { path: &'a RelativePath, } impl<'a> fmt::Debug for Display<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.path, f) } } impl<'a> fmt::Display for Display<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.path, f) } } /// [serde::ser::Serialize] implementation for [`RelativePathBuf`]. /// /// ``` /// use serde::Serialize; /// use relative_path::RelativePathBuf; /// /// #[derive(Serialize)] /// struct Document { /// path: RelativePathBuf, /// } /// ``` #[cfg(feature = "serde")] impl serde::ser::Serialize for RelativePathBuf { #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { serializer.serialize_str(&self.inner) } } /// [`serde::de::Deserialize`] implementation for [`RelativePathBuf`]. /// /// ``` /// use serde::Deserialize; /// use relative_path::RelativePathBuf; /// /// #[derive(Deserialize)] /// struct Document { /// path: RelativePathBuf, /// } /// ``` #[cfg(feature = "serde")] impl<'de> serde::de::Deserialize<'de> for RelativePathBuf { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = RelativePathBuf; #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a relative path") } #[inline] fn visit_string(self, input: String) -> Result where E: serde::de::Error, { Ok(RelativePathBuf::from(input)) } #[inline] fn visit_str(self, input: &str) -> Result where E: serde::de::Error, { Ok(RelativePathBuf::from(input.to_owned())) } } deserializer.deserialize_str(Visitor) } } /// [`serde::de::Deserialize`] implementation for [`Box`]. /// /// ``` /// use serde::Deserialize; /// use relative_path::RelativePath; /// /// #[derive(Deserialize)] /// struct Document { /// path: Box, /// } /// ``` #[cfg(feature = "serde")] impl<'de> serde::de::Deserialize<'de> for Box { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = Box; #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a relative path") } #[inline] fn visit_string(self, input: String) -> Result where E: serde::de::Error, { Ok(Box::::from(input.into_boxed_str())) } #[inline] fn visit_str(self, input: &str) -> Result where E: serde::de::Error, { Ok(Box::::from(input)) } } deserializer.deserialize_str(Visitor) } } /// [`serde::de::Deserialize`] implementation for a [RelativePath] reference. /// /// ``` /// use serde::Deserialize; /// use relative_path::RelativePath; /// /// #[derive(Deserialize)] /// struct Document<'a> { /// #[serde(borrow)] /// path: &'a RelativePath, /// } /// ``` #[cfg(feature = "serde")] impl<'de: 'a, 'a> serde::de::Deserialize<'de> for &'a RelativePath { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { struct Visitor; impl<'a> serde::de::Visitor<'a> for Visitor { type Value = &'a RelativePath; #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a borrowed relative path") } #[inline] fn visit_borrowed_str(self, v: &'a str) -> Result where E: serde::de::Error, { Ok(RelativePath::new(v)) } #[inline] fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result where E: serde::de::Error, { let string = str::from_utf8(v).map_err(|_| { serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(v), &self) })?; Ok(RelativePath::new(string)) } } deserializer.deserialize_str(Visitor) } } /// [serde::ser::Serialize] implementation for [RelativePath]. /// /// ``` /// use serde::Serialize; /// use relative_path::RelativePath; /// /// #[derive(Serialize)] /// struct Document<'a> { /// path: &'a RelativePath, /// } /// ``` #[cfg(feature = "serde")] impl serde::ser::Serialize for RelativePath { #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { serializer.serialize_str(&self.inner) } } macro_rules! impl_cmp { ($lhs:ty, $rhs:ty) => { impl<'a, 'b> PartialEq<$rhs> for $lhs { #[inline] fn eq(&self, other: &$rhs) -> bool { ::eq(self, other) } } impl<'a, 'b> PartialEq<$lhs> for $rhs { #[inline] fn eq(&self, other: &$lhs) -> bool { ::eq(self, other) } } impl<'a, 'b> PartialOrd<$rhs> for $lhs { #[inline] fn partial_cmp(&self, other: &$rhs) -> Option { ::partial_cmp(self, other) } } impl<'a, 'b> PartialOrd<$lhs> for $rhs { #[inline] fn partial_cmp(&self, other: &$lhs) -> Option { ::partial_cmp(self, other) } } }; } impl_cmp!(RelativePathBuf, RelativePath); impl_cmp!(RelativePathBuf, &'a RelativePath); impl_cmp!(Cow<'a, RelativePath>, RelativePath); impl_cmp!(Cow<'a, RelativePath>, &'b RelativePath); impl_cmp!(Cow<'a, RelativePath>, RelativePathBuf); macro_rules! impl_cmp_str { ($lhs:ty, $rhs:ty) => { impl<'a, 'b> PartialEq<$rhs> for $lhs { #[inline] fn eq(&self, other: &$rhs) -> bool { ::eq(self, other.as_ref()) } } impl<'a, 'b> PartialEq<$lhs> for $rhs { #[inline] fn eq(&self, other: &$lhs) -> bool { ::eq(self.as_ref(), other) } } impl<'a, 'b> PartialOrd<$rhs> for $lhs { #[inline] fn partial_cmp(&self, other: &$rhs) -> Option { ::partial_cmp(self, other.as_ref()) } } impl<'a, 'b> PartialOrd<$lhs> for $rhs { #[inline] fn partial_cmp(&self, other: &$lhs) -> Option { ::partial_cmp(self.as_ref(), other) } } }; } impl_cmp_str!(RelativePathBuf, str); impl_cmp_str!(RelativePathBuf, &'a str); impl_cmp_str!(RelativePathBuf, String); impl_cmp_str!(RelativePath, str); impl_cmp_str!(RelativePath, &'a str); impl_cmp_str!(RelativePath, String); impl_cmp_str!(&'a RelativePath, str); impl_cmp_str!(&'a RelativePath, String); relative-path-1.9.0/src/path_ext.rs000064400000000000000000000273620072674642500154130ustar 00000000000000// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Ported from the pathdiff crate, which adapted the original rustc's // path_relative_from // https://github.com/Manishearth/pathdiff/blob/master/src/lib.rs // https://github.com/rust-lang/rust/blob/e1d0de82cc40b666b88d4a6d2c9dcbc81d7ed27f/src/librustc_back/rpath.rs#L116-L158 use std::error; use std::fmt; use std::path::{Path, PathBuf}; use crate::{Component, RelativePathBuf}; // Prevent downstream implementations, so methods may be added without backwards // breaking changes. mod sealed { use std::path::{Path, PathBuf}; pub trait Sealed {} impl Sealed for Path {} impl Sealed for PathBuf {} } /// An error raised when attempting to convert a path using /// [`PathExt::relative_to`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RelativeToError { kind: RelativeToErrorKind, } /// Error kind for [`RelativeToError`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] enum RelativeToErrorKind { /// Non-utf8 component in path. NonUtf8, /// Mismatching path prefixes. PrefixMismatch, /// A provided path is ambiguous, in that there is no way to determine which /// components should be added from one path to the other to traverse it. /// /// For example, `.` is ambiguous relative to `../..` because we don't know /// the names of the components being traversed. AmbiguousTraversal, /// This is a catch-all error since we don't control the `std::path` API a /// Components iterator might decide (intentionally or not) to produce /// components which violates its own contract. /// /// In particular we rely on only relative components being produced after /// the absolute prefix has been consumed. IllegalComponent, } impl fmt::Display for RelativeToError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self.kind { RelativeToErrorKind::NonUtf8 => "path contains non-utf8 component".fmt(fmt), RelativeToErrorKind::PrefixMismatch => { "paths contain different absolute prefixes".fmt(fmt) } RelativeToErrorKind::AmbiguousTraversal => { "path traversal cannot be determined".fmt(fmt) } RelativeToErrorKind::IllegalComponent => "path contains illegal components".fmt(fmt), } } } impl error::Error for RelativeToError {} impl From for RelativeToError { #[inline] fn from(kind: RelativeToErrorKind) -> Self { Self { kind } } } /// Extension methods for [`Path`] and [`PathBuf`] to for building and /// interacting with [`RelativePath`]. /// /// [`RelativePath`]: crate::RelativePath pub trait PathExt: sealed::Sealed { /// Build a relative path from the provided directory to `self`. /// /// Producing a relative path like this is a logical operation and does not /// guarantee that the constructed path corresponds to what the filesystem /// would do. On Linux for example symbolic links could mean that the /// logical path doesn't correspond to the filesystem path. /// /// # Examples /// /// ``` /// use std::path::Path; /// use relative_path::{RelativePath, PathExt}; /// /// let baz = Path::new("/foo/bar/baz"); /// let bar = Path::new("/foo/bar"); /// let qux = Path::new("/foo/bar/qux"); /// /// assert_eq!(bar.relative_to(baz)?, RelativePath::new("../")); /// assert_eq!(baz.relative_to(bar)?, RelativePath::new("baz")); /// assert_eq!(qux.relative_to(baz)?, RelativePath::new("../qux")); /// assert_eq!(baz.relative_to(qux)?, RelativePath::new("../baz")); /// assert_eq!(bar.relative_to(qux)?, RelativePath::new("../")); /// # Ok::<_, relative_path::RelativeToError>(()) /// ``` fn relative_to>(&self, root: P) -> Result; } impl PathExt for Path { fn relative_to>(&self, root: P) -> Result { use std::path::Component::*; // Helper function to convert from a std::path::Component to a // relative_path::Component. fn std_to_c(c: std::path::Component<'_>) -> Result, RelativeToError> { Ok(match c { CurDir => Component::CurDir, ParentDir => Component::ParentDir, Normal(n) => Component::Normal(n.to_str().ok_or(RelativeToErrorKind::NonUtf8)?), _ => return Err(RelativeToErrorKind::IllegalComponent.into()), }) } let root = root.as_ref(); let mut a_it = self.components(); let mut b_it = root.components(); // Ensure that the two paths are both either relative, or have the same // prefix. Strips any common prefix the two paths do have. Prefixes are // platform dependent, but different prefixes would for example indicate // paths for different drives on Windows. let (a_head, b_head) = loop { match (a_it.next(), b_it.next()) { (Some(RootDir), Some(RootDir)) => (), (Some(Prefix(a)), Some(Prefix(b))) if a == b => (), (Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => { return Err(RelativeToErrorKind::PrefixMismatch.into()); } (None, None) => break (None, None), (a, b) if a != b => break (a, b), _ => (), } }; let mut a_it = a_head.into_iter().chain(a_it); let mut b_it = b_head.into_iter().chain(b_it); let mut buf = RelativePathBuf::new(); loop { let a = match a_it.next() { Some(a) => a, None => { for _ in b_it { buf.push(Component::ParentDir); } break; } }; match b_it.next() { Some(CurDir) => buf.push(std_to_c(a)?), Some(ParentDir) => { return Err(RelativeToErrorKind::AmbiguousTraversal.into()); } root => { if root.is_some() { buf.push(Component::ParentDir); } for comp in b_it { match comp { ParentDir => { if !buf.pop() { return Err(RelativeToErrorKind::AmbiguousTraversal.into()); } } CurDir => (), _ => buf.push(Component::ParentDir), } } buf.push(std_to_c(a)?); for c in a_it { buf.push(std_to_c(c)?); } break; } } } Ok(buf) } } impl PathExt for PathBuf { #[inline] fn relative_to>(&self, root: P) -> Result { self.as_path().relative_to(root) } } #[cfg(test)] mod tests { use std::path::Path; use super::{PathExt, RelativeToErrorKind}; use crate::{RelativePathBuf, RelativeToError}; macro_rules! assert_relative_to { ($path:expr, $base:expr, Ok($expected:expr) $(,)?) => { assert_eq!( Path::new($path).relative_to($base), Ok(RelativePathBuf::from($expected)) ); }; ($path:expr, $base:expr, Err($expected:ident) $(,)?) => { assert_eq!( Path::new($path).relative_to($base), Err(RelativeToError::from(RelativeToErrorKind::$expected)) ); }; } #[cfg(windows)] macro_rules! abs { ($path:expr) => { Path::new(concat!("C:\\", $path)) }; } #[cfg(not(windows))] macro_rules! abs { ($path:expr) => { Path::new(concat!("/", $path)) }; } #[test] #[cfg(windows)] fn test_different_prefixes() { assert_relative_to!("C:\\repo", "D:\\repo", Err(PrefixMismatch),); assert_relative_to!("C:\\repo", "C:\\repo", Ok("")); assert_relative_to!( "\\\\server\\share\\repo", "\\\\server2\\share\\repo", Err(PrefixMismatch), ); } #[test] fn test_absolute() { assert_relative_to!(abs!("foo"), abs!("bar"), Ok("../foo")); assert_relative_to!("foo", "bar", Ok("../foo")); assert_relative_to!(abs!("foo"), "bar", Err(PrefixMismatch)); assert_relative_to!("foo", abs!("bar"), Err(PrefixMismatch)); } #[test] fn test_identity() { assert_relative_to!(".", ".", Ok("")); assert_relative_to!("../foo", "../foo", Ok("")); assert_relative_to!("./foo", "./foo", Ok("")); assert_relative_to!("/foo", "/foo", Ok("")); assert_relative_to!("foo", "foo", Ok("")); assert_relative_to!("../foo/bar/baz", "../foo/bar/baz", Ok("")); assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok("")); } #[test] fn test_subset() { assert_relative_to!("foo", "fo", Ok("../foo")); assert_relative_to!("fo", "foo", Ok("../fo")); } #[test] fn test_empty() { assert_relative_to!("", "", Ok("")); assert_relative_to!("foo", "", Ok("foo")); assert_relative_to!("", "foo", Ok("..")); } #[test] fn test_relative() { assert_relative_to!("../foo", "../bar", Ok("../foo")); assert_relative_to!("../foo", "../foo/bar/baz", Ok("../..")); assert_relative_to!("../foo/bar/baz", "../foo", Ok("bar/baz")); assert_relative_to!("foo/bar/baz", "foo", Ok("bar/baz")); assert_relative_to!("foo/bar/baz", "foo/bar", Ok("baz")); assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok("")); assert_relative_to!("foo/bar/baz", "foo/bar/baz/", Ok("")); assert_relative_to!("foo/bar/baz/", "foo", Ok("bar/baz")); assert_relative_to!("foo/bar/baz/", "foo/bar", Ok("baz")); assert_relative_to!("foo/bar/baz/", "foo/bar/baz", Ok("")); assert_relative_to!("foo/bar/baz/", "foo/bar/baz/", Ok("")); assert_relative_to!("foo/bar/baz", "foo/", Ok("bar/baz")); assert_relative_to!("foo/bar/baz", "foo/bar/", Ok("baz")); assert_relative_to!("foo/bar/baz", "foo/bar/baz", Ok("")); } #[test] fn test_current_directory() { assert_relative_to!(".", "foo", Ok("../.")); assert_relative_to!("foo", ".", Ok("foo")); assert_relative_to!("/foo", "/.", Ok("foo")); } #[test] fn assert_does_not_skip_parents() { assert_relative_to!("some/path", "some/foo/baz/path", Ok("../../../path")); assert_relative_to!("some/path", "some/foo/bar/../baz/path", Ok("../../../path")); } #[test] fn test_ambiguous_paths() { // Parent directory name is unknown, so trying to make current directory // relative to it is impossible. assert_relative_to!(".", "../..", Err(AmbiguousTraversal)); assert_relative_to!(".", "a/../..", Err(AmbiguousTraversal)); // Common prefixes are ok. assert_relative_to!("../a/..", "../a/../b", Ok("..")); assert_relative_to!("../a/../b", "../a/..", Ok("b")); } } relative-path-1.9.0/src/tests.rs000064400000000000000000000457030072674642500147400ustar 00000000000000use super::*; use std::path::Path; use std::rc::Rc; use std::sync::Arc; macro_rules! t( ($path:expr, iter: $iter:expr) => ( { let path = RelativePath::new($path); // Forward iteration let comps = path.iter().map(str::to_string).collect::>(); let exp: &[&str] = &$iter; let exps = exp.iter().map(|s| s.to_string()).collect::>(); assert!(comps == exps, "iter: Expected {:?}, found {:?}", exps, comps); // Reverse iteration let comps = RelativePath::new($path).iter().rev().map(str::to_string) .collect::>(); let exps = exps.into_iter().rev().collect::>(); assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}", exps, comps); } ); ($path:expr, parent: $parent:expr, file_name: $file:expr) => ( { let path = RelativePath::new($path); let parent = path.parent().map(|p| p.as_str()); let exp_parent: Option<&str> = $parent; assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}", exp_parent, parent); let file = path.file_name(); let exp_file: Option<&str> = $file; assert!(file == exp_file, "file_name: Expected {:?}, found {:?}", exp_file, file); } ); ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( { let path = RelativePath::new($path); let stem = path.file_stem(); let exp_stem: Option<&str> = $file_stem; assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}", exp_stem, stem); let ext = path.extension(); let exp_ext: Option<&str> = $extension; assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", exp_ext, ext); } ); ($path:expr, iter: $iter:expr, parent: $parent:expr, file_name: $file:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( { t!($path, iter: $iter); t!($path, parent: $parent, file_name: $file); t!($path, file_stem: $file_stem, extension: $extension); } ); ); fn assert_components(components: &[&str], path: &RelativePath) { let components = components .iter() .cloned() .map(Component::Normal) .collect::>(); let result: Vec<_> = path.components().collect(); assert_eq!(&components[..], &result[..]); } fn rp(input: &str) -> &RelativePath { RelativePath::new(input) } #[test] #[allow(clippy::cognitive_complexity)] pub fn test_decompositions() { t!("", iter: [], parent: None, file_name: None, file_stem: None, extension: None ); t!("foo", iter: ["foo"], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("/", iter: [], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("/foo", iter: ["foo"], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("foo/", iter: ["foo"], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("/foo/", iter: ["foo"], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("foo/bar", iter: ["foo", "bar"], parent: Some("foo"), file_name: Some("bar"), file_stem: Some("bar"), extension: None ); t!("/foo/bar", iter: ["foo", "bar"], parent: Some("/foo"), file_name: Some("bar"), file_stem: Some("bar"), extension: None ); t!("///foo///", iter: ["foo"], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("///foo///bar", iter: ["foo", "bar"], parent: Some("///foo"), file_name: Some("bar"), file_stem: Some("bar"), extension: None ); t!("./.", iter: [".", "."], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("/..", iter: [".."], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("../", iter: [".."], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("foo/.", iter: ["foo", "."], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("foo/..", iter: ["foo", ".."], parent: Some("foo"), file_name: None, file_stem: None, extension: None ); t!("foo/./", iter: ["foo", "."], parent: Some(""), file_name: Some("foo"), file_stem: Some("foo"), extension: None ); t!("foo/./bar", iter: ["foo", ".", "bar"], parent: Some("foo/."), file_name: Some("bar"), file_stem: Some("bar"), extension: None ); t!("foo/../", iter: ["foo", ".."], parent: Some("foo"), file_name: None, file_stem: None, extension: None ); t!("foo/../bar", iter: ["foo", "..", "bar"], parent: Some("foo/.."), file_name: Some("bar"), file_stem: Some("bar"), extension: None ); t!("./a", iter: [".", "a"], parent: Some("."), file_name: Some("a"), file_stem: Some("a"), extension: None ); t!(".", iter: ["."], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("./", iter: ["."], parent: Some(""), file_name: None, file_stem: None, extension: None ); t!("a/b", iter: ["a", "b"], parent: Some("a"), file_name: Some("b"), file_stem: Some("b"), extension: None ); t!("a//b", iter: ["a", "b"], parent: Some("a"), file_name: Some("b"), file_stem: Some("b"), extension: None ); t!("a/./b", iter: ["a", ".", "b"], parent: Some("a/."), file_name: Some("b"), file_stem: Some("b"), extension: None ); t!("a/b/c", iter: ["a", "b", "c"], parent: Some("a/b"), file_name: Some("c"), file_stem: Some("c"), extension: None ); t!(".foo", iter: [".foo"], parent: Some(""), file_name: Some(".foo"), file_stem: Some(".foo"), extension: None ); } #[test] pub fn test_stem_ext() { t!("foo", file_stem: Some("foo"), extension: None ); t!("foo.", file_stem: Some("foo"), extension: Some("") ); t!(".foo", file_stem: Some(".foo"), extension: None ); t!("foo.txt", file_stem: Some("foo"), extension: Some("txt") ); t!("foo.bar.txt", file_stem: Some("foo.bar"), extension: Some("txt") ); t!("foo.bar.", file_stem: Some("foo.bar"), extension: Some("") ); t!(".", file_stem: None, extension: None); t!("..", file_stem: None, extension: None); t!("", file_stem: None, extension: None); } #[test] pub fn test_set_file_name() { macro_rules! tfn( ($path:expr, $file:expr, $expected:expr) => ( { let mut p = RelativePathBuf::from($path); p.set_file_name($file); assert!(p.as_str() == $expected, "setting file name of {:?} to {:?}: Expected {:?}, got {:?}", $path, $file, $expected, p.as_str()); }); ); tfn!("foo", "foo", "foo"); tfn!("foo", "bar", "bar"); tfn!("foo", "", ""); tfn!("", "foo", "foo"); tfn!(".", "foo", "./foo"); tfn!("foo/", "bar", "bar"); tfn!("foo/.", "bar", "bar"); tfn!("..", "foo", "../foo"); tfn!("foo/..", "bar", "foo/../bar"); tfn!("/", "foo", "/foo"); } #[test] pub fn test_set_extension() { macro_rules! tse( ($path:expr, $ext:expr, $expected:expr, $output:expr) => ( { let mut p = RelativePathBuf::from($path); let output = p.set_extension($ext); assert!(p.as_str() == $expected && output == $output, "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", $path, $ext, $expected, $output, p.as_str(), output); }); ); tse!("foo", "txt", "foo.txt", true); tse!("foo.bar", "txt", "foo.txt", true); tse!("foo.bar.baz", "txt", "foo.bar.txt", true); tse!(".test", "txt", ".test.txt", true); tse!("foo.txt", "", "foo", true); tse!("foo", "", "foo", true); tse!("", "foo", "", false); tse!(".", "foo", ".", false); tse!("foo/", "bar", "foo.bar", true); tse!("foo/.", "bar", "foo.bar", true); tse!("..", "foo", "..", false); tse!("foo/..", "bar", "foo/..", false); tse!("/", "foo", "/", false); } #[test] fn test_eq_recievers() { use std::borrow::Cow; let borrowed: &RelativePath = RelativePath::new("foo/bar"); let mut owned: RelativePathBuf = RelativePathBuf::new(); owned.push("foo"); owned.push("bar"); let borrowed_cow: Cow = borrowed.into(); let owned_cow: Cow = owned.clone().into(); macro_rules! t { ($($current:expr),+) => { $( assert_eq!($current, borrowed); assert_eq!($current, owned); assert_eq!($current, borrowed_cow); assert_eq!($current, owned_cow); )+ } } t!(borrowed, owned, borrowed_cow, owned_cow); } #[test] #[allow(clippy::cognitive_complexity)] pub fn test_compare() { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; fn hash(t: T) -> u64 { let mut s = DefaultHasher::new(); t.hash(&mut s); s.finish() } macro_rules! tc( ($path1:expr, $path2:expr, eq: $eq:expr, starts_with: $starts_with:expr, ends_with: $ends_with:expr, relative_from: $relative_from:expr) => ({ let path1 = RelativePath::new($path1); let path2 = RelativePath::new($path2); let eq = path1 == path2; assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", $path1, $path2, $eq, eq); assert!($eq == (hash(path1) == hash(path2)), "{:?} == {:?}, expected {:?}, got {} and {}", $path1, $path2, $eq, hash(path1), hash(path2)); let starts_with = path1.starts_with(path2); assert!(starts_with == $starts_with, "{:?}.starts_with({:?}), expected {:?}, got {:?}", $path1, $path2, $starts_with, starts_with); let ends_with = path1.ends_with(path2); assert!(ends_with == $ends_with, "{:?}.ends_with({:?}), expected {:?}, got {:?}", $path1, $path2, $ends_with, ends_with); let relative_from = path1.strip_prefix(path2) .map(|p| p.as_str()) .ok(); let exp: Option<&str> = $relative_from; assert!(relative_from == exp, "{:?}.strip_prefix({:?}), expected {:?}, got {:?}", $path1, $path2, exp, relative_from); }); ); tc!("", "", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); tc!("foo", "", eq: false, starts_with: true, ends_with: true, relative_from: Some("foo") ); tc!("", "foo", eq: false, starts_with: false, ends_with: false, relative_from: None ); tc!("foo", "foo", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); tc!("foo/", "foo", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); tc!("foo/bar", "foo", eq: false, starts_with: true, ends_with: false, relative_from: Some("bar") ); tc!("foo/bar/baz", "foo/bar", eq: false, starts_with: true, ends_with: false, relative_from: Some("baz") ); tc!("foo/bar", "foo/bar/baz", eq: false, starts_with: false, ends_with: false, relative_from: None ); } #[test] fn test_join() { assert_components(&["foo", "bar", "baz"], &rp("foo/bar").join("baz///")); assert_components( &["hello", "world", "foo", "bar", "baz"], &rp("hello/world").join("///foo/bar/baz"), ); assert_components(&["foo", "bar", "baz"], &rp("").join("foo/bar/baz")); } #[test] fn test_components_iterator() { use self::Component::*; assert_eq!( vec![Normal("hello"), Normal("world")], rp("/hello///world//").components().collect::>() ); } #[test] fn test_to_path_buf() { let path = rp("/hello///world//"); let path_buf = path.to_path("."); let expected = Path::new(".").join("hello").join("world"); assert_eq!(expected, path_buf); } #[test] fn test_eq() { assert_eq!(rp("//foo///bar"), rp("/foo/bar")); assert_eq!(rp("foo///bar"), rp("foo/bar")); assert_eq!(rp("foo"), rp("foo")); assert_eq!(rp("foo"), rp("foo").to_relative_path_buf()); } #[test] fn test_next_back() { use self::Component::*; let mut it = rp("baz/bar///foo").components(); assert_eq!(Some(Normal("foo")), it.next_back()); assert_eq!(Some(Normal("bar")), it.next_back()); assert_eq!(Some(Normal("baz")), it.next_back()); assert_eq!(None, it.next_back()); } #[test] fn test_parent() { let path = rp("baz/./bar/foo//./."); assert_eq!(Some(rp("baz/./bar")), path.parent()); assert_eq!( Some(rp("baz/.")), path.parent().and_then(RelativePath::parent) ); assert_eq!( Some(rp("")), path.parent() .and_then(RelativePath::parent) .and_then(RelativePath::parent) ); assert_eq!( None, path.parent() .and_then(RelativePath::parent) .and_then(RelativePath::parent) .and_then(RelativePath::parent) ); } #[test] fn test_relative_path_buf() { assert_eq!( rp("hello/world/."), rp("/hello///world//").to_owned().join(".") ); } #[test] fn test_normalize() { assert_eq!(rp("c/d"), rp("a/.././b/../c/d").normalize()); } #[test] fn test_relative_to() { assert_eq!( rp("foo/foo/bar"), rp("foo/bar").join_normalized("../foo/bar") ); assert_eq!( rp("../c/e"), rp("x/y").join_normalized("../../a/b/../../../c/d/../e") ); } #[test] fn test_from() { assert_eq!( rp("foo/bar").to_owned(), RelativePathBuf::from(String::from("foo/bar")), ); assert_eq!( RelativePathBuf::from(rp("foo/bar")), RelativePathBuf::from("foo/bar"), ); assert_eq!(rp("foo/bar").to_owned(), RelativePathBuf::from("foo/bar"),); assert_eq!(&*Box::::from(rp("foo/bar")), rp("foo/bar")); assert_eq!( &*Box::::from(RelativePathBuf::from("foo/bar")), rp("foo/bar") ); assert_eq!(&*Arc::::from(rp("foo/bar")), rp("foo/bar")); assert_eq!( &*Arc::::from(RelativePathBuf::from("foo/bar")), rp("foo/bar") ); assert_eq!(&*Rc::::from(rp("foo/bar")), rp("foo/bar")); assert_eq!( &*Rc::::from(RelativePathBuf::from("foo/bar")), rp("foo/bar") ); } #[test] fn test_relative_path_asref_str() { assert_eq!( >::as_ref(rp("foo/bar")), "foo/bar" ); } #[test] fn test_default() { assert_eq!(RelativePathBuf::new(), RelativePathBuf::default(),); } #[test] pub fn test_push() { macro_rules! tp( ($path:expr, $push:expr, $expected:expr) => ( { let mut actual = RelativePathBuf::from($path); actual.push($push); assert!(actual.as_str() == $expected, "pushing {:?} onto {:?}: Expected {:?}, got {:?}", $push, $path, $expected, actual.as_str()); }); ); tp!("", "foo", "foo"); tp!("foo", "bar", "foo/bar"); tp!("foo/", "bar", "foo/bar"); tp!("foo//", "bar", "foo//bar"); tp!("foo/.", "bar", "foo/./bar"); tp!("foo./.", "bar", "foo././bar"); tp!("foo", "", "foo/"); tp!("foo", ".", "foo/."); tp!("foo", "..", "foo/.."); } #[test] pub fn test_pop() { macro_rules! tp( ($path:expr, $expected:expr, $output:expr) => ( { let mut actual = RelativePathBuf::from($path); let output = actual.pop(); assert!(actual.as_str() == $expected && output == $output, "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}", $path, $expected, $output, actual.as_str(), output); }); ); tp!("", "", false); tp!("/", "", true); tp!("foo", "", true); tp!(".", "", true); tp!("/foo", "", true); tp!("/foo/bar", "/foo", true); tp!("/foo/bar/.", "/foo", true); tp!("foo/bar", "foo", true); tp!("foo/.", "", true); tp!("foo//bar", "foo", true); } #[test] pub fn test_display() { // NB: display delegated to the underlying string. assert_eq!(RelativePathBuf::from("foo/bar").to_string(), "foo/bar"); assert_eq!(RelativePath::new("foo/bar").to_string(), "foo/bar"); assert_eq!(format!("{}", RelativePathBuf::from("foo/bar")), "foo/bar"); assert_eq!(format!("{}", RelativePath::new("foo/bar")), "foo/bar"); } #[cfg(unix)] #[test] pub fn test_unix_from_path() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; assert_eq!( Err(FromPathErrorKind::NonRelative.into()), RelativePath::from_path("/foo/bar") ); // Continuation byte without continuation. let non_utf8 = OsStr::from_bytes(&[0x80u8]); assert_eq!( Err(FromPathErrorKind::NonUtf8.into()), RelativePath::from_path(non_utf8) ); } #[cfg(windows)] #[test] pub fn test_windows_from_path() { assert_eq!( Err(FromPathErrorKind::NonRelative.into()), RelativePath::from_path("c:\\foo\\bar") ); assert_eq!( Err(FromPathErrorKind::BadSeparator.into()), RelativePath::from_path("foo\\bar") ); } #[cfg(unix)] #[test] pub fn test_unix_owned_from_path() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; assert_eq!( Err(FromPathErrorKind::NonRelative.into()), RelativePathBuf::from_path(Path::new("/foo/bar")) ); // Continuation byte without continuation. let non_utf8 = OsStr::from_bytes(&[0x80u8]); assert_eq!( Err(FromPathErrorKind::NonUtf8.into()), RelativePathBuf::from_path(Path::new(non_utf8)) ); } #[cfg(windows)] #[test] pub fn test_windows_owned_from_path() { assert_eq!( Err(FromPathErrorKind::NonRelative.into()), RelativePathBuf::from_path(Path::new("c:\\foo\\bar")) ); }