| 1 | // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT | 
| 2 | // file at the top-level directory of this distribution and at | 
|---|
| 3 | // http://rust-lang.org/COPYRIGHT. | 
|---|
| 4 | // | 
|---|
| 5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | 
|---|
| 6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | 
|---|
| 7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | 
|---|
| 8 | // option. This file may not be copied, modified, or distributed | 
|---|
| 9 | // except according to those terms. | 
|---|
| 10 |  | 
|---|
| 11 | // Adapted from rustc's path_relative_from | 
|---|
| 12 | // https://github.com/rust-lang/rust/blob/e1d0de82cc40b666b88d4a6d2c9dcbc81d7ed27f/src/librustc_back/rpath.rs#L116-L158 | 
|---|
| 13 |  | 
|---|
| 14 | #![ cfg_attr(docsrs, feature(doc_cfg))] | 
|---|
| 15 |  | 
|---|
| 16 | use std::path::*; | 
|---|
| 17 |  | 
|---|
| 18 | /// Construct a relative path from a provided base directory path to the provided path. | 
|---|
| 19 | /// | 
|---|
| 20 | /// ```rust | 
|---|
| 21 | /// use pathdiff::diff_paths; | 
|---|
| 22 | /// use std::path::*; | 
|---|
| 23 | /// | 
|---|
| 24 | /// assert_eq!(diff_paths( "/foo/bar", "/foo/bar/baz"),  Some( "../".into())); | 
|---|
| 25 | /// assert_eq!(diff_paths( "/foo/bar/baz", "/foo/bar"),      Some( "baz".into())); | 
|---|
| 26 | /// assert_eq!(diff_paths( "/foo/bar/quux", "/foo/bar/baz"),  Some( "../quux".into())); | 
|---|
| 27 | /// assert_eq!(diff_paths( "/foo/bar/baz", "/foo/bar/quux"), Some( "../baz".into())); | 
|---|
| 28 | /// assert_eq!(diff_paths( "/foo/bar", "/foo/bar/quux"), Some( "../".into())); | 
|---|
| 29 | /// | 
|---|
| 30 | /// assert_eq!(diff_paths( "/foo/bar", "baz"),           Some( "/foo/bar".into())); | 
|---|
| 31 | /// assert_eq!(diff_paths( "/foo/bar", "/baz"),          Some( "../foo/bar".into())); | 
|---|
| 32 | /// assert_eq!(diff_paths( "foo", "bar"),           Some( "../foo".into())); | 
|---|
| 33 | /// | 
|---|
| 34 | /// assert_eq!( | 
|---|
| 35 | ///     diff_paths(& "/foo/bar/baz", "/foo/bar".to_string()), | 
|---|
| 36 | ///     Some( "baz".into()) | 
|---|
| 37 | /// ); | 
|---|
| 38 | /// assert_eq!( | 
|---|
| 39 | ///     diff_paths(Path::new( "/foo/bar/baz"), Path::new( "/foo/bar").to_path_buf()), | 
|---|
| 40 | ///     Some( "baz".into()) | 
|---|
| 41 | /// ); | 
|---|
| 42 | /// ``` | 
|---|
| 43 | pub fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf> | 
|---|
| 44 | where | 
|---|
| 45 | P: AsRef<Path>, | 
|---|
| 46 | B: AsRef<Path>, | 
|---|
| 47 | { | 
|---|
| 48 | let path = path.as_ref(); | 
|---|
| 49 | let base = base.as_ref(); | 
|---|
| 50 |  | 
|---|
| 51 | if path.is_absolute() != base.is_absolute() { | 
|---|
| 52 | if path.is_absolute() { | 
|---|
| 53 | Some(PathBuf::from(path)) | 
|---|
| 54 | } else { | 
|---|
| 55 | None | 
|---|
| 56 | } | 
|---|
| 57 | } else { | 
|---|
| 58 | let mut ita = path.components(); | 
|---|
| 59 | let mut itb = base.components(); | 
|---|
| 60 | let mut comps: Vec<Component> = vec![]; | 
|---|
| 61 | loop { | 
|---|
| 62 | match (ita.next(), itb.next()) { | 
|---|
| 63 | (None, None) => break, | 
|---|
| 64 | (Some(a), None) => { | 
|---|
| 65 | comps.push(a); | 
|---|
| 66 | comps.extend(ita.by_ref()); | 
|---|
| 67 | break; | 
|---|
| 68 | } | 
|---|
| 69 | (None, _) => comps.push(Component::ParentDir), | 
|---|
| 70 | (Some(a), Some(b)) if comps.is_empty() && a == b => (), | 
|---|
| 71 | (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), | 
|---|
| 72 | (Some(_), Some(b)) if b == Component::ParentDir => return None, | 
|---|
| 73 | (Some(a), Some(_)) => { | 
|---|
| 74 | comps.push(Component::ParentDir); | 
|---|
| 75 | for _ in itb { | 
|---|
| 76 | comps.push(Component::ParentDir); | 
|---|
| 77 | } | 
|---|
| 78 | comps.push(a); | 
|---|
| 79 | comps.extend(ita.by_ref()); | 
|---|
| 80 | break; | 
|---|
| 81 | } | 
|---|
| 82 | } | 
|---|
| 83 | } | 
|---|
| 84 | Some(comps.iter().map(|c| c.as_os_str()).collect()) | 
|---|
| 85 | } | 
|---|
| 86 | } | 
|---|
| 87 |  | 
|---|
| 88 | #[ cfg(feature = "camino")] | 
|---|
| 89 | mod utf8_paths { | 
|---|
| 90 | use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; | 
|---|
| 91 |  | 
|---|
| 92 | /// Construct a relative UTF-8 path from a provided base directory path to the provided path. | 
|---|
| 93 | /// | 
|---|
| 94 | /// Requires the `camino` feature. | 
|---|
| 95 | /// | 
|---|
| 96 | /// ```rust | 
|---|
| 97 | /// # extern crate camino; | 
|---|
| 98 | /// use camino::*; | 
|---|
| 99 | /// use pathdiff::diff_utf8_paths; | 
|---|
| 100 | /// | 
|---|
| 101 | /// assert_eq!(diff_utf8_paths("/foo/bar",      "/foo/bar/baz"),  Some("../".into())); | 
|---|
| 102 | /// assert_eq!(diff_utf8_paths("/foo/bar/baz",  "/foo/bar"),      Some("baz".into())); | 
|---|
| 103 | /// assert_eq!(diff_utf8_paths("/foo/bar/quux", "/foo/bar/baz"),  Some("../quux".into())); | 
|---|
| 104 | /// assert_eq!(diff_utf8_paths("/foo/bar/baz",  "/foo/bar/quux"), Some("../baz".into())); | 
|---|
| 105 | /// assert_eq!(diff_utf8_paths("/foo/bar",      "/foo/bar/quux"), Some("../".into())); | 
|---|
| 106 | /// | 
|---|
| 107 | /// assert_eq!(diff_utf8_paths("/foo/bar",      "baz"),           Some("/foo/bar".into())); | 
|---|
| 108 | /// assert_eq!(diff_utf8_paths("/foo/bar",      "/baz"),          Some("../foo/bar".into())); | 
|---|
| 109 | /// assert_eq!(diff_utf8_paths("foo",           "bar"),           Some("../foo".into())); | 
|---|
| 110 | /// | 
|---|
| 111 | /// assert_eq!( | 
|---|
| 112 | ///     diff_utf8_paths(&"/foo/bar/baz", "/foo/bar".to_string()), | 
|---|
| 113 | ///     Some("baz".into()) | 
|---|
| 114 | /// ); | 
|---|
| 115 | /// assert_eq!( | 
|---|
| 116 | ///     diff_utf8_paths(Utf8Path::new("/foo/bar/baz"), Utf8Path::new("/foo/bar").to_path_buf()), | 
|---|
| 117 | ///     Some("baz".into()) | 
|---|
| 118 | /// ); | 
|---|
| 119 | /// ``` | 
|---|
| 120 | #[ cfg_attr(docsrs, doc(cfg(feature = "camino")))] | 
|---|
| 121 | pub fn diff_utf8_paths<P, B>(path: P, base: B) -> Option<Utf8PathBuf> | 
|---|
| 122 | where | 
|---|
| 123 | P: AsRef<Utf8Path>, | 
|---|
| 124 | B: AsRef<Utf8Path>, | 
|---|
| 125 | { | 
|---|
| 126 | let path = path.as_ref(); | 
|---|
| 127 | let base = base.as_ref(); | 
|---|
| 128 |  | 
|---|
| 129 | if path.is_absolute() != base.is_absolute() { | 
|---|
| 130 | if path.is_absolute() { | 
|---|
| 131 | Some(Utf8PathBuf::from(path)) | 
|---|
| 132 | } else { | 
|---|
| 133 | None | 
|---|
| 134 | } | 
|---|
| 135 | } else { | 
|---|
| 136 | let mut ita = path.components(); | 
|---|
| 137 | let mut itb = base.components(); | 
|---|
| 138 | let mut comps: Vec<Utf8Component> = vec![]; | 
|---|
| 139 | loop { | 
|---|
| 140 | match (ita.next(), itb.next()) { | 
|---|
| 141 | (None, None) => break, | 
|---|
| 142 | (Some(a), None) => { | 
|---|
| 143 | comps.push(a); | 
|---|
| 144 | comps.extend(ita.by_ref()); | 
|---|
| 145 | break; | 
|---|
| 146 | } | 
|---|
| 147 | (None, _) => comps.push(Utf8Component::ParentDir), | 
|---|
| 148 | (Some(a), Some(b)) if comps.is_empty() && a == b => (), | 
|---|
| 149 | (Some(a), Some(b)) if b == Utf8Component::CurDir => comps.push(a), | 
|---|
| 150 | (Some(_), Some(b)) if b == Utf8Component::ParentDir => return None, | 
|---|
| 151 | (Some(a), Some(_)) => { | 
|---|
| 152 | comps.push(Utf8Component::ParentDir); | 
|---|
| 153 | for _ in itb { | 
|---|
| 154 | comps.push(Utf8Component::ParentDir); | 
|---|
| 155 | } | 
|---|
| 156 | comps.push(a); | 
|---|
| 157 | comps.extend(ita.by_ref()); | 
|---|
| 158 | break; | 
|---|
| 159 | } | 
|---|
| 160 | } | 
|---|
| 161 | } | 
|---|
| 162 | Some(comps.iter().map(|c| c.as_str()).collect()) | 
|---|
| 163 | } | 
|---|
| 164 | } | 
|---|
| 165 | } | 
|---|
| 166 |  | 
|---|
| 167 | #[ cfg(feature = "camino")] | 
|---|
| 168 | pub use crate::utf8_paths::*; | 
|---|
| 169 |  | 
|---|
| 170 | #[ cfg(test)] | 
|---|
| 171 | mod tests { | 
|---|
| 172 | use super::*; | 
|---|
| 173 | use cfg_if::cfg_if; | 
|---|
| 174 |  | 
|---|
| 175 | #[ test] | 
|---|
| 176 | fn test_absolute() { | 
|---|
| 177 | fn abs(path: &str) -> String { | 
|---|
| 178 | // Absolute paths look different on Windows vs Unix. | 
|---|
| 179 | cfg_if! { | 
|---|
| 180 | if #[cfg(windows)] { | 
|---|
| 181 | format!( "C:\\ {}", path) | 
|---|
| 182 | } else { | 
|---|
| 183 | format!( "/{}", path) | 
|---|
| 184 | } | 
|---|
| 185 | } | 
|---|
| 186 | } | 
|---|
| 187 |  | 
|---|
| 188 | assert_diff_paths(&abs( "foo"), &abs( "bar"), Some( "../foo")); | 
|---|
| 189 | assert_diff_paths(&abs( "foo"), "bar", Some(&abs( "foo"))); | 
|---|
| 190 | assert_diff_paths( "foo", &abs( "bar"), None); | 
|---|
| 191 | assert_diff_paths( "foo", "bar", Some( "../foo")); | 
|---|
| 192 | } | 
|---|
| 193 |  | 
|---|
| 194 | #[ test] | 
|---|
| 195 | fn test_identity() { | 
|---|
| 196 | assert_diff_paths( ".", ".", Some( "")); | 
|---|
| 197 | assert_diff_paths( "../foo", "../foo", Some( "")); | 
|---|
| 198 | assert_diff_paths( "./foo", "./foo", Some( "")); | 
|---|
| 199 | assert_diff_paths( "/foo", "/foo", Some( "")); | 
|---|
| 200 | assert_diff_paths( "foo", "foo", Some( "")); | 
|---|
| 201 |  | 
|---|
| 202 | assert_diff_paths( "../foo/bar/baz", "../foo/bar/baz", Some( "".into())); | 
|---|
| 203 | assert_diff_paths( "foo/bar/baz", "foo/bar/baz", Some( "")); | 
|---|
| 204 | } | 
|---|
| 205 |  | 
|---|
| 206 | #[ test] | 
|---|
| 207 | fn test_subset() { | 
|---|
| 208 | assert_diff_paths( "foo", "fo", Some( "../foo")); | 
|---|
| 209 | assert_diff_paths( "fo", "foo", Some( "../fo")); | 
|---|
| 210 | } | 
|---|
| 211 |  | 
|---|
| 212 | #[ test] | 
|---|
| 213 | fn test_empty() { | 
|---|
| 214 | assert_diff_paths( "", "", Some( "")); | 
|---|
| 215 | assert_diff_paths( "foo", "", Some( "foo")); | 
|---|
| 216 | assert_diff_paths( "", "foo", Some( "..")); | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | #[ test] | 
|---|
| 220 | fn test_relative() { | 
|---|
| 221 | assert_diff_paths( "../foo", "../bar", Some( "../foo")); | 
|---|
| 222 | assert_diff_paths( "../foo", "../foo/bar/baz", Some( "../..")); | 
|---|
| 223 | assert_diff_paths( "../foo/bar/baz", "../foo", Some( "bar/baz")); | 
|---|
| 224 |  | 
|---|
| 225 | assert_diff_paths( "foo/bar/baz", "foo", Some( "bar/baz")); | 
|---|
| 226 | assert_diff_paths( "foo/bar/baz", "foo/bar", Some( "baz")); | 
|---|
| 227 | assert_diff_paths( "foo/bar/baz", "foo/bar/baz", Some( "")); | 
|---|
| 228 | assert_diff_paths( "foo/bar/baz", "foo/bar/baz/", Some( "")); | 
|---|
| 229 |  | 
|---|
| 230 | assert_diff_paths( "foo/bar/baz/", "foo", Some( "bar/baz")); | 
|---|
| 231 | assert_diff_paths( "foo/bar/baz/", "foo/bar", Some( "baz")); | 
|---|
| 232 | assert_diff_paths( "foo/bar/baz/", "foo/bar/baz", Some( "")); | 
|---|
| 233 | assert_diff_paths( "foo/bar/baz/", "foo/bar/baz/", Some( "")); | 
|---|
| 234 |  | 
|---|
| 235 | assert_diff_paths( "foo/bar/baz", "foo/", Some( "bar/baz")); | 
|---|
| 236 | assert_diff_paths( "foo/bar/baz", "foo/bar/", Some( "baz")); | 
|---|
| 237 | assert_diff_paths( "foo/bar/baz", "foo/bar/baz", Some( "")); | 
|---|
| 238 | } | 
|---|
| 239 |  | 
|---|
| 240 | #[ test] | 
|---|
| 241 | fn test_current_directory() { | 
|---|
| 242 | assert_diff_paths( ".", "foo", Some( "../.")); | 
|---|
| 243 | assert_diff_paths( "foo", ".", Some( "foo")); | 
|---|
| 244 | assert_diff_paths( "/foo", "/.", Some( "foo")); | 
|---|
| 245 | } | 
|---|
| 246 |  | 
|---|
| 247 | fn assert_diff_paths(path: &str, base: &str, expected: Option<&str>) { | 
|---|
| 248 | assert_eq!(diff_paths(path, base), expected.map(|s| s.into())); | 
|---|
| 249 | #[ cfg(feature = "camino")] | 
|---|
| 250 | assert_eq!(diff_utf8_paths(path, base), expected.map(|s| s.into())); | 
|---|
| 251 | } | 
|---|
| 252 | } | 
|---|
| 253 |  | 
|---|