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 use std::path::*;
15 
16 /// Construct a relative path from a provided base directory path to the provided path.
17 ///
18 /// ```rust
19 /// use pathdiff::diff_paths;
20 /// use std::path::*;
21 ///
22 /// let baz = "/foo/bar/baz";
23 /// let bar = "/foo/bar";
24 /// let quux = "/foo/bar/quux";
25 /// assert_eq!(diff_paths(bar, baz), Some("../".into()));
26 /// assert_eq!(diff_paths(baz, bar), Some("baz".into()));
27 /// assert_eq!(diff_paths(quux, baz), Some("../quux".into()));
28 /// assert_eq!(diff_paths(baz, quux), Some("../baz".into()));
29 /// assert_eq!(diff_paths(bar, quux), Some("../".into()));
30 ///
31 /// assert_eq!(diff_paths(&baz, &bar.to_string()), Some("baz".into()));
32 /// assert_eq!(diff_paths(Path::new(baz), Path::new(bar).to_path_buf()), Some("baz".into()));
33 /// ```
diff_paths<P, B>(path: P, base: B) -> Option<PathBuf> where P: AsRef<Path>, B: AsRef<Path>,34 pub fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf>
35 where
36     P: AsRef<Path>,
37     B: AsRef<Path>,
38 {
39     let path = path.as_ref();
40     let base = base.as_ref();
41 
42     if path.is_absolute() != base.is_absolute() {
43         if path.is_absolute() {
44             Some(PathBuf::from(path))
45         } else {
46             None
47         }
48     } else {
49         let mut ita = path.components();
50         let mut itb = base.components();
51         let mut comps: Vec<Component> = vec![];
52         loop {
53             match (ita.next(), itb.next()) {
54                 (None, None) => break,
55                 (Some(a), None) => {
56                     comps.push(a);
57                     comps.extend(ita.by_ref());
58                     break;
59                 }
60                 (None, _) => comps.push(Component::ParentDir),
61                 (Some(a), Some(b)) if comps.is_empty() && a == b => (),
62                 (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
63                 (Some(_), Some(b)) if b == Component::ParentDir => return None,
64                 (Some(a), Some(_)) => {
65                     comps.push(Component::ParentDir);
66                     for _ in itb {
67                         comps.push(Component::ParentDir);
68                     }
69                     comps.push(a);
70                     comps.extend(ita.by_ref());
71                     break;
72                 }
73             }
74         }
75         Some(comps.iter().map(|c| c.as_os_str()).collect())
76     }
77 }
78 
79 #[cfg(feature = "camino")]
80 mod utf8_paths {
81     use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
82 
83     /// Construct a relative UTF-8 path from a provided base directory path to the provided path.
84     ///
85     /// ```rust
86     /// # extern crate camino;
87     /// use camino::*;
88     /// use pathdiff::diff_utf8_paths;
89     ///
90     /// let baz = "/foo/bar/baz";
91     /// let bar = "/foo/bar";
92     /// let quux = "/foo/bar/quux";
93     /// assert_eq!(diff_utf8_paths(bar, baz), Some("../".into()));
94     /// assert_eq!(diff_utf8_paths(baz, bar), Some("baz".into()));
95     /// assert_eq!(diff_utf8_paths(quux, baz), Some("../quux".into()));
96     /// assert_eq!(diff_utf8_paths(baz, quux), Some("../baz".into()));
97     /// assert_eq!(diff_utf8_paths(bar, quux), Some("../".into()));
98     ///
99     /// assert_eq!(diff_utf8_paths(&baz, &bar.to_string()), Some("baz".into()));
100     /// assert_eq!(diff_utf8_paths(Utf8Path::new(baz), Utf8Path::new(bar).to_path_buf()), Some("baz".into()));
101     /// ```
102     #[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>,103     pub fn diff_utf8_paths<P, B>(path: P, base: B) -> Option<Utf8PathBuf>
104     where
105         P: AsRef<Utf8Path>,
106         B: AsRef<Utf8Path>,
107     {
108         let path = path.as_ref();
109         let base = base.as_ref();
110 
111         if path.is_absolute() != base.is_absolute() {
112             if path.is_absolute() {
113                 Some(Utf8PathBuf::from(path))
114             } else {
115                 None
116             }
117         } else {
118             let mut ita = path.components();
119             let mut itb = base.components();
120             let mut comps: Vec<Utf8Component> = vec![];
121             loop {
122                 match (ita.next(), itb.next()) {
123                     (None, None) => break,
124                     (Some(a), None) => {
125                         comps.push(a);
126                         comps.extend(ita.by_ref());
127                         break;
128                     }
129                     (None, _) => comps.push(Utf8Component::ParentDir),
130                     (Some(a), Some(b)) if comps.is_empty() && a == b => (),
131                     (Some(a), Some(b)) if b == Utf8Component::CurDir => comps.push(a),
132                     (Some(_), Some(b)) if b == Utf8Component::ParentDir => return None,
133                     (Some(a), Some(_)) => {
134                         comps.push(Utf8Component::ParentDir);
135                         for _ in itb {
136                             comps.push(Utf8Component::ParentDir);
137                         }
138                         comps.push(a);
139                         comps.extend(ita.by_ref());
140                         break;
141                     }
142                 }
143             }
144             Some(comps.iter().map(|c| c.as_str()).collect())
145         }
146     }
147 }
148 
149 #[cfg(feature = "camino")]
150 pub use crate::utf8_paths::*;
151 
152 #[cfg(test)]
153 mod tests {
154     use super::*;
155 
156     #[test]
test_absolute()157     fn test_absolute() {
158         assert_diff_paths("/foo", "/bar", Some("../foo"));
159         assert_diff_paths("/foo", "bar", Some("/foo"));
160         assert_diff_paths("foo", "/bar", None);
161         assert_diff_paths("foo", "bar", Some("../foo"));
162     }
163 
164     #[test]
test_identity()165     fn test_identity() {
166         assert_diff_paths(".", ".", Some(""));
167         assert_diff_paths("../foo", "../foo", Some(""));
168         assert_diff_paths("./foo", "./foo", Some(""));
169         assert_diff_paths("/foo", "/foo", Some(""));
170         assert_diff_paths("foo", "foo", Some(""));
171 
172         assert_diff_paths("../foo/bar/baz", "../foo/bar/baz", Some("".into()));
173         assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
174     }
175 
176     #[test]
test_subset()177     fn test_subset() {
178         assert_diff_paths("foo", "fo", Some("../foo"));
179         assert_diff_paths("fo", "foo", Some("../fo"));
180     }
181 
182     #[test]
test_empty()183     fn test_empty() {
184         assert_diff_paths("", "", Some(""));
185         assert_diff_paths("foo", "", Some("foo"));
186         assert_diff_paths("", "foo", Some(".."));
187     }
188 
189     #[test]
test_relative()190     fn test_relative() {
191         assert_diff_paths("../foo", "../bar", Some("../foo"));
192         assert_diff_paths("../foo", "../foo/bar/baz", Some("../.."));
193         assert_diff_paths("../foo/bar/baz", "../foo", Some("bar/baz"));
194 
195         assert_diff_paths("foo/bar/baz", "foo", Some("bar/baz"));
196         assert_diff_paths("foo/bar/baz", "foo/bar", Some("baz"));
197         assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
198         assert_diff_paths("foo/bar/baz", "foo/bar/baz/", Some(""));
199 
200         assert_diff_paths("foo/bar/baz/", "foo", Some("bar/baz"));
201         assert_diff_paths("foo/bar/baz/", "foo/bar", Some("baz"));
202         assert_diff_paths("foo/bar/baz/", "foo/bar/baz", Some(""));
203         assert_diff_paths("foo/bar/baz/", "foo/bar/baz/", Some(""));
204 
205         assert_diff_paths("foo/bar/baz", "foo/", Some("bar/baz"));
206         assert_diff_paths("foo/bar/baz", "foo/bar/", Some("baz"));
207         assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some(""));
208     }
209 
210     #[test]
test_current_directory()211     fn test_current_directory() {
212         assert_diff_paths(".", "foo", Some("../."));
213         assert_diff_paths("foo", ".", Some("foo"));
214         assert_diff_paths("/foo", "/.", Some("foo"));
215     }
216 
assert_diff_paths(path: &str, base: &str, expected: Option<&str>)217     fn assert_diff_paths(path: &str, base: &str, expected: Option<&str>) {
218         assert_eq!(diff_paths(path, base), expected.map(|s| s.into()));
219         #[cfg(feature = "camino")]
220         assert_eq!(diff_utf8_paths(path, base), expected.map(|s| s.into()));
221     }
222 }
223