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 | /// ``` |
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" )))] |
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 ] |
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 ] |
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 ] |
177 | fn test_subset() { |
178 | assert_diff_paths("foo" , "fo" , Some("../foo" )); |
179 | assert_diff_paths("fo" , "foo" , Some("../fo" )); |
180 | } |
181 | |
182 | #[test ] |
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 ] |
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 ] |
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 | |
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 | |