relative-path-1.9.3/.cargo_vcs_info.json0000644000000001530000000000100136120ustar { "git": { "sha1": "6d267fbd85b257e4416c9f020131c6da168e1d3d" }, "path_in_vcs": "relative-path" }relative-path-1.9.3/Cargo.toml0000644000000022070000000000100116120ustar # 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 = "2021" rust-version = "1.66" name = "relative-path" version = "1.9.3" 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 OR Apache-2.0" repository = "https://github.com/udoprog/relative-path" [package.metadata.docs.rs] all-features = true [dependencies.serde] version = "1.0.160" optional = true [dev-dependencies.anyhow] version = "1.0.76" [dev-dependencies.serde] version = "1.0.160" features = ["derive"] [features] default = [] relative-path-1.9.3/Cargo.toml.orig000064400000000000000000000012460072674642500153250ustar 00000000000000[package] name = "relative-path" version = "1.9.3" authors = ["John-John Tedro "] edition = "2021" rust-version = "1.66" 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 OR Apache-2.0" keywords = ["path"] categories = ["filesystem"] [features] default = [] [dependencies] serde = { version = "1.0.160", optional = true } [dev-dependencies] anyhow = "1.0.76" serde = { version = "1.0.160", features = ["derive"] } [package.metadata.docs.rs] all-features = true relative-path-1.9.3/README.md000064400000000000000000000230150072674642500137130ustar 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. For more utilities to manipulate relative paths, see the [`relative-path-utils` crate].
## Usage Add `relative-path` to your `Cargo.toml`: ```toml relative-path = "1.9.2" ``` 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-utils` crate]: https://docs.rs/relative-path-utils relative-path-1.9.3/src/lib.rs000064400000000000000000002144600072674642500143450ustar 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. //! //! For more utilities to manipulate relative paths, see the //! [`relative-path-utils` crate]. //! //!
//! //! ## Usage //! //! Add `relative-path` to your `Cargo.toml`: //! //! ```toml //! relative-path = "1.9.2" //! ``` //! //! 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 //! [`relative-path-utils` crate]: https://docs.rs/relative-path-utils // 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 #![allow(clippy::manual_let_else)] #![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; 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(_) | None, Some(_)) => return None, (Some(_) | None, None) => return Some(iter), } 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`] slice. /// /// [`str`]: prim@str /// /// # 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"]); /// ``` #[must_use] pub fn as_str(self) -> &'a str { use self::Component::{CurDir, Normal, ParentDir}; 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::{CurDir, Normal, ParentDir}; 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()); /// ``` #[must_use] #[inline] 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`] /// slices. /// /// This `struct` is created by the [`iter`][RelativePath::iter] method. /// /// [`str`]: prim@str #[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()); /// ``` #[must_use] 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. #[must_use] 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")) /// ); /// ``` /// /// # Errors /// /// This will error in case the provided path is not a relative path, which /// is identifier by it having a [`Prefix`] or [`RootDir`] component. /// /// [`Prefix`]: std::path::Component::Prefix /// [`RootDir`]: std::path::Component::RootDir pub fn from_path>(path: P) -> Result { use std::path::Component::{CurDir, Normal, ParentDir, Prefix, RootDir}; 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`. /// /// # Examples /// /// ``` /// use relative_path::RelativePathBuf; /// /// let mut path = RelativePathBuf::new(); /// path.push("foo"); /// path.push("bar"); /// /// assert_eq!("foo/bar", path); /// /// let mut path = RelativePathBuf::new(); /// path.push("foo"); /// path.push("/bar"); /// /// assert_eq!("foo/bar", path); /// ``` pub fn push

(&mut self, path: P) where P: AsRef, { 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. #[must_use] 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()); /// ``` #[must_use] pub fn into_string(self) -> String { self.inner } /// Converts this `RelativePathBuf` into a [boxed][std::boxed::Box] /// [`RelativePath`]. #[must_use] 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 { #[inline] fn borrow(&self) -> &RelativePath { self } } 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 { #[inline] fn partial_cmp(&self, other: &RelativePathBuf) -> Option { Some(self.cmp(other)) } } impl cmp::Ord for RelativePathBuf { #[inline] 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 where P: AsRef, { #[inline] fn extend>(&mut self, iter: I) { iter.into_iter().for_each(move |p| self.push(p.as_ref())); } } impl

FromIterator

for RelativePathBuf where P: AsRef, { #[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::{CurDir, Normal, ParentDir, Prefix, RootDir}; 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) | (ParentDir, Component::ParentDir) => continue, (Normal(a), Component::Normal(b)) if a == b => continue, _ => return Err(FromPathErrorKind::BadSeparator.into()), } } Ok(rel) } /// Yields the underlying [`str`] slice. /// /// [`str`]: prim@str /// /// # Examples /// /// ``` /// use relative_path::RelativePath; /// /// assert_eq!(RelativePath::new("foo.txt").as_str(), "foo.txt"); /// ``` #[must_use] 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")] #[must_use] 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 where P: AsRef, { 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()); /// ``` #[must_use] pub fn components(&self) -> Components { Components::new(&self.inner) } /// Produces an iterator over the path's components viewed as [`str`] /// slices. /// /// For more information about the particulars of how the path is separated /// into components, see [`components`][Self::components]. /// /// [`str`]: prim@str /// /// # 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) /// ``` #[must_use] pub fn iter(&self) -> Iter { Iter { inner: self.components(), } } /// Convert to an owned [`RelativePathBuf`]. #[must_use] 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`] 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`]. /// /// [`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::{CurDir, Normal, ParentDir}; 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()); /// ``` #[must_use] pub fn parent(&self) -> Option<&RelativePath> { use self::Component::CurDir; 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()); /// ``` #[must_use] pub fn file_name(&self) -> Option<&str> { use self::Component::{CurDir, Normal, ParentDir}; let mut it = self.components(); while let Some(c) = it.next_back() { return match c { CurDir => continue, Normal(name) => Some(name), ParentDir => 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`] returns /// `false`), returns [`Err`]. /// /// [`starts_with`]: Self::starts_with /// /// # 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> where P: AsRef, { 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 where P: AsRef, { 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 where P: AsRef, { 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()); /// ``` #[must_use] 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`] for more details. /// /// [`set_file_name`]: RelativePathBuf::set_file_name /// /// # 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`] for more details. /// /// [`set_extension`]: RelativePathBuf::set_extension /// /// # 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 where P: AsRef, { 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() /// ); /// ``` #[must_use] 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 where P: AsRef, { 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) } } impl<'a> IntoIterator for &'a RelativePath { type IntoIter = Iter<'a>; type Item = &'a str; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } /// 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 { Some(self.cmp(other)) } } 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.3/src/path_ext.rs000064400000000000000000000277620072674642500154220ustar 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>(()) /// ``` /// /// # Errors /// /// Errors in case the provided path contains components which cannot be /// converted into a relative path as needed, such as non-utf8 data. fn relative_to

(&self, root: P) -> Result where P: AsRef; } impl PathExt for Path { fn relative_to

(&self, root: P) -> Result where P: AsRef, { use std::path::Component::{CurDir, Normal, ParentDir, Prefix, RootDir}; // 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 = if let Some(a) = a_it.next() { a } else { 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 where P: AsRef, { 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.3/src/tests.rs000064400000000000000000000457460072674642500147520ustar 00000000000000#![allow(clippy::too_many_lines)] use 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() .copied() .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")) ); }