Skip to content

Commit

Permalink
Merge pull request #4 from sunshowers/camino
Browse files Browse the repository at this point in the history
add support for diffing UTF-8 paths
  • Loading branch information
Manishearth authored Sep 27, 2021
2 parents 77b4a95 + f1bbc71 commit 300c95f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 37 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ jobs:
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build (all features)
run: cargo build --all-features --verbose
- name: Run tests (all features)
run: cargo test --all-features --verbose
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ repository = "https://github.com/Manishearth/pathdiff"
keywords = ["path", "relative"]
license = "MIT/Apache-2.0"

[package.metadata.docs.rs]
all-features = true

[dependencies]
camino = { version = "1.0.5", optional = true }
156 changes: 119 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
// Adapted from rustc's path_relative_from
// https://github.com/rust-lang/rust/blob/e1d0de82cc40b666b88d4a6d2c9dcbc81d7ed27f/src/librustc_back/rpath.rs#L116-L158

#[cfg(feature = "camino")]
extern crate camino;

use std::path::*;

/// Construct a relative path from a provided base directory path to the provided path.
Expand Down Expand Up @@ -76,68 +79,147 @@ where
}
}

#[cfg(feature = "camino")]
mod utf8_paths {
use camino::{Utf8Component, Utf8Path, Utf8PathBuf};

/// Construct a relative UTF-8 path from a provided base directory path to the provided path.
///
/// ```rust
/// # extern crate camino;
/// use camino::*;
/// use pathdiff::diff_utf8_paths;
///
/// let baz = "/foo/bar/baz";
/// let bar = "/foo/bar";
/// let quux = "/foo/bar/quux";
/// assert_eq!(diff_utf8_paths(bar, baz), Some("../".into()));
/// assert_eq!(diff_utf8_paths(baz, bar), Some("baz".into()));
/// assert_eq!(diff_utf8_paths(quux, baz), Some("../quux".into()));
/// assert_eq!(diff_utf8_paths(baz, quux), Some("../baz".into()));
/// assert_eq!(diff_utf8_paths(bar, quux), Some("../".into()));
///
/// assert_eq!(diff_utf8_paths(&baz, &bar.to_string()), Some("baz".into()));
/// assert_eq!(diff_utf8_paths(Utf8Path::new(baz), Utf8Path::new(bar).to_path_buf()), Some("baz".into()));
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "camino")))]
pub fn diff_utf8_paths<P, B>(path: P, base: B) -> Option<Utf8PathBuf>
where
P: AsRef<Utf8Path>,
B: AsRef<Utf8Path>,
{
let path = path.as_ref();
let base = base.as_ref();

if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(Utf8PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Utf8Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita.by_ref());
break;
}
(None, _) => comps.push(Utf8Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(b)) if b == Utf8Component::CurDir => comps.push(a),
(Some(_), Some(b)) if b == Utf8Component::ParentDir => return None,
(Some(a), Some(_)) => {
comps.push(Utf8Component::ParentDir);
for _ in itb {
comps.push(Utf8Component::ParentDir);
}
comps.push(a);
comps.extend(ita.by_ref());
break;
}
}
}
Some(comps.iter().map(|c| c.as_str()).collect())
}
}
}

#[cfg(feature = "camino")]
pub use utf8_paths::*;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_absolute() {
assert_eq!(diff_paths("/foo", "/bar"), Some("../foo".into()));
assert_eq!(diff_paths("/foo", "bar"), Some("/foo".into()));
assert_eq!(diff_paths("foo", "/bar"), None);
assert_eq!(diff_paths("foo", "bar"), Some("../foo".into()));
assert_diff_paths("/foo", "/bar", Some("../foo"));
assert_diff_paths("/foo", "bar", Some("/foo"));
assert_diff_paths("foo", "/bar", None);
assert_diff_paths("foo", "bar", Some("../foo"));
}

#[test]
fn test_identity() {
assert_eq!(diff_paths(".", "."), Some("".into()));
assert_eq!(diff_paths("../foo", "../foo"), Some("".into()));
assert_eq!(diff_paths("./foo", "./foo"), Some("".into()));
assert_eq!(diff_paths("/foo", "/foo"), Some("".into()));
assert_eq!(diff_paths("foo", "foo"), Some("".into()));

assert_eq!(diff_paths("../foo/bar/baz", "../foo/bar/baz"), Some("".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar/baz"), Some("".into()));
assert_diff_paths(".", ".", Some(""));
assert_diff_paths("../foo", "../foo", Some(""));
assert_diff_paths("./foo", "./foo", Some(""));
assert_diff_paths("/foo", "/foo", Some(""));
assert_diff_paths("foo", "foo", Some(""));

assert_diff_paths("../foo/bar/baz", "../foo/bar/baz", Some("".into()));
assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
}

#[test]
fn test_subset() {
assert_eq!(diff_paths("foo", "fo"), Some("../foo".into()));
assert_eq!(diff_paths("fo", "foo"), Some("../fo".into()));
assert_diff_paths("foo", "fo", Some("../foo"));
assert_diff_paths("fo", "foo", Some("../fo"));
}

#[test]
fn test_empty() {
assert_eq!(diff_paths("", ""), Some("".into()));
assert_eq!(diff_paths("foo", ""), Some("foo".into()));
assert_eq!(diff_paths("", "foo"), Some("..".into()));
assert_diff_paths("", "", Some(""));
assert_diff_paths("foo", "", Some("foo"));
assert_diff_paths("", "foo", Some(".."));
}

#[test]
fn test_relative() {
assert_eq!(diff_paths("../foo", "../bar"), Some("../foo".into()));
assert_eq!(diff_paths("../foo", "../foo/bar/baz"), Some("../..".into()));
assert_eq!(diff_paths("../foo/bar/baz", "../foo"), Some("bar/baz".into()));

assert_eq!(diff_paths("foo/bar/baz", "foo"), Some("bar/baz".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar"), Some("baz".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar/baz"), Some("".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar/baz/"), Some("".into()));

assert_eq!(diff_paths("foo/bar/baz/", "foo"), Some("bar/baz".into()));
assert_eq!(diff_paths("foo/bar/baz/", "foo/bar"), Some("baz".into()));
assert_eq!(diff_paths("foo/bar/baz/", "foo/bar/baz"), Some("".into()));
assert_eq!(diff_paths("foo/bar/baz/", "foo/bar/baz/"), Some("".into()));

assert_eq!(diff_paths("foo/bar/baz", "foo/"), Some("bar/baz".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar/"), Some("baz".into()));
assert_eq!(diff_paths("foo/bar/baz", "foo/bar/baz"), Some("".into()));
assert_diff_paths("../foo", "../bar", Some("../foo"));
assert_diff_paths("../foo", "../foo/bar/baz", Some("../.."));
assert_diff_paths("../foo/bar/baz", "../foo", Some("bar/baz"));

assert_diff_paths("foo/bar/baz", "foo", Some("bar/baz"));
assert_diff_paths("foo/bar/baz", "foo/bar", Some("baz"));
assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
assert_diff_paths("foo/bar/baz", "foo/bar/baz/", Some(""));

assert_diff_paths("foo/bar/baz/", "foo", Some("bar/baz"));
assert_diff_paths("foo/bar/baz/", "foo/bar", Some("baz"));
assert_diff_paths("foo/bar/baz/", "foo/bar/baz", Some(""));
assert_diff_paths("foo/bar/baz/", "foo/bar/baz/", Some(""));

assert_diff_paths("foo/bar/baz", "foo/", Some("bar/baz"));
assert_diff_paths("foo/bar/baz", "foo/bar/", Some("baz"));
assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
}

#[test]
fn test_current_directory() {
assert_eq!(diff_paths(".", "foo"), Some("../.".into()));
assert_eq!(diff_paths("foo", "."), Some("foo".into()));
assert_eq!(diff_paths("/foo", "/."), Some("foo".into()));
assert_diff_paths(".", "foo", Some("../."));
assert_diff_paths("foo", ".", Some("foo"));
assert_diff_paths("/foo", "/.", Some("foo"));
}

fn assert_diff_paths(path: &str, base: &str, expected: Option<&str>) {
assert_eq!(diff_paths(path, base), expected.map(|s| s.into()));
#[cfg(feature = "camino")]
assert_eq!(diff_utf8_paths(path, base), expected.map(|s| s.into()));
}
}

0 comments on commit 300c95f

Please sign in to comment.