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