1use std::{borrow::Cow, iter};
2
3fn split_path(path: &str) -> Vec<&str> {
4 let mut last_idx = 0;
5 let mut rv = vec![];
6 for (idx, _) in path.match_indices(&['/', '\\'][..]) {
7 rv.push(&path[last_idx..idx]);
8 last_idx = idx;
9 }
10 if last_idx < path.len() {
11 rv.push(&path[last_idx..]);
12 }
13 rv
14}
15
16fn is_abs_path(s: &str) -> bool {
17 if s.starts_with('/') {
18 return true;
19 } else if s.len() > 3 {
20 let b = s.as_bytes();
21 if b[1] == b':'
22 && (b[2] == b'/' || b[2] == b'\\')
23 && ((b[0] >= b'a' && b[0] <= b'z') || (b[0] >= b'A' && b[0] <= b'Z'))
24 {
25 return true;
26 }
27 }
28 false
29}
30
31fn find_common_prefix_of_sorted_vec<'a>(items: &'a [Cow<'a, [&'a str]>]) -> Option<&'a [&'a str]> {
32 if items.is_empty() {
33 return None;
34 }
35
36 let shortest = &items[0];
37 let mut max_idx = None;
38 for seq in items.iter() {
39 let mut seq_max_idx = None;
40 for (idx, &comp) in shortest.iter().enumerate() {
41 if seq.get(idx) != Some(&comp) {
42 break;
43 }
44 seq_max_idx = Some(idx);
45 }
46 if max_idx.is_none() || seq_max_idx < max_idx {
47 max_idx = seq_max_idx;
48 }
49 }
50
51 if let Some(max_idx) = max_idx {
52 Some(&shortest[..=max_idx])
53 } else {
54 None
55 }
56}
57
58pub fn find_common_prefix<'a, I: Iterator<Item = &'a str>>(iter: I) -> Option<String> {
59 let mut items: Vec<Cow<'_, [&str]>> = iter
60 .filter(|x| is_abs_path(x))
61 .map(|x| Cow::Owned(split_path(x)))
62 .collect();
63 items.sort_by_key(|x| x.len());
64
65 if let Some(slice) = find_common_prefix_of_sorted_vec(&items) {
66 let rv = slice.join("");
67 if !rv.is_empty() && &rv != "/" {
68 return Some(rv);
69 }
70 }
71
72 None
73}
74
75pub fn make_relative_path(base: &str, target: &str) -> String {
88 let target_path: Vec<_> = target
89 .split(&['/', '\\'][..])
90 .filter(|x| !x.is_empty())
91 .collect();
92 let mut base_path: Vec<_> = base
93 .split(&['/', '\\'][..])
94 .filter(|x| !x.is_empty())
95 .collect();
96 base_path.pop();
97
98 let mut items = vec![
99 Cow::Borrowed(target_path.as_slice()),
100 Cow::Borrowed(base_path.as_slice()),
101 ];
102 items.sort_by_key(|x| x.len());
103
104 let prefix = find_common_prefix_of_sorted_vec(&items)
105 .map(|x| x.len())
106 .unwrap_or(0);
107 let mut rel_list: Vec<_> = iter::repeat("../").take(base_path.len() - prefix).collect();
108 rel_list.extend_from_slice(&target_path[prefix..]);
109 if rel_list.is_empty() {
110 ".".into()
111 } else {
112 rel_list.join("")
113 }
114}
115
116pub fn greatest_lower_bound<'a, T, K: Ord, F: Fn(&'a T) -> K>(
117 slice: &'a [T],
118 key: &K,
119 map: F,
120) -> Option<(usize, &'a T)> {
121 let mut idx = match slice.binary_search_by_key(key, &map) {
122 Ok(index) => index,
123 Err(index) => {
124 return slice.get(index.checked_sub(1)?).map(|res| (index, res));
128 }
129 };
130
131 for i in (0..idx).rev() {
136 if map(&slice[i]) == *key {
137 idx = i;
138 } else {
139 break;
140 }
141 }
142 slice.get(idx).map(|res| (idx, res))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_is_abs_path() {
151 assert!(is_abs_path("C:\\foo.txt"));
152 assert!(is_abs_path("d:/foo.txt"));
153 assert!(!is_abs_path("foo.txt"));
154 assert!(is_abs_path("/foo.txt"));
155 assert!(is_abs_path("/"));
156 }
157
158 #[test]
159 fn test_split_path() {
160 assert_eq!(split_path("/foo/bar/baz"), &["", "/foo", "/bar", "/baz"]);
161 }
162
163 #[test]
164 fn test_find_common_prefix() {
165 let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah"].into_iter());
166 assert_eq!(rv, Some("/foo/bar/baz".into()));
167
168 let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/meh"].into_iter());
169 assert_eq!(rv, None);
170
171 let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/foo"].into_iter());
172 assert_eq!(rv, Some("/foo".into()));
173
174 let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "foo"].into_iter());
175 assert_eq!(rv, Some("/foo/bar/baz".into()));
176
177 let rv = find_common_prefix(
178 vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter(),
179 );
180 assert_eq!(rv, None);
181
182 let rv = find_common_prefix(
183 vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter(),
184 );
185 assert_eq!(rv, None);
186 }
187
188 #[test]
189 fn test_make_relative_path() {
190 assert_eq!(
191 &make_relative_path("/foo/bar/baz.js", "/foo/bar/baz.map"),
192 "baz.map"
193 );
194 assert_eq!(
195 &make_relative_path("/foo/bar/.", "/foo/bar/baz.map"),
196 "baz.map"
197 );
198 assert_eq!(
199 &make_relative_path("/foo/bar/baz.js", "/foo/baz.map"),
200 "../baz.map"
201 );
202 assert_eq!(&make_relative_path("foo.txt", "foo.js"), "foo.js");
203 assert_eq!(&make_relative_path("blah/foo.txt", "foo.js"), "../foo.js");
204 }
205
206 #[test]
207 fn test_greatest_lower_bound() {
208 let cmp = |&(i, _id)| i;
209
210 let haystack = vec![(1, 1)];
211 assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1));
212 assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 1));
213 assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
214
215 let haystack = vec![(1, 1), (1, 2)];
216 assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1));
217 assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 2));
218 assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
219
220 let haystack = vec![(1, 1), (1, 2), (1, 3)];
221 assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1));
222 assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 3));
223 assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
224
225 let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4)];
226 assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1));
227 assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 4));
228 assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
229
230 let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)];
231 assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1));
232 assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 5));
233 assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
234 }
235}