1use std::path::*;
15
16pub fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf>
35where
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")]
80mod utf8_paths {
81 use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
82
83 #[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")]
150pub use crate::utf8_paths::*;
151
152#[cfg(test)]
153mod 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}