• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 /// ```
diff_paths<P, B>(path: P, base: B) -> Option<PathBuf> where P: AsRef<Path>, B: AsRef<Path>,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")))]
diff_utf8_paths<P, B>(path: P, base: B) -> Option<Utf8PathBuf> where P: AsRef<Utf8Path>, B: AsRef<Utf8Path>,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]
test_absolute()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]
test_identity()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]
test_subset()207     fn test_subset() {
208         assert_diff_paths("foo", "fo", Some("../foo"));
209         assert_diff_paths("fo", "foo", Some("../fo"));
210     }
211 
212     #[test]
test_empty()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]
test_relative()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]
test_current_directory()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 
assert_diff_paths(path: &str, base: &str, expected: Option<&str>)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