| 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 | |