1use crate::paths::{PathStyle, is_absolute};
2use anyhow::{Context as _, Result, anyhow, bail};
3use serde::{Deserialize, Serialize};
4use std::{
5 borrow::Cow,
6 ffi::OsStr,
7 fmt,
8 ops::Deref,
9 path::{Path, PathBuf},
10 sync::Arc,
11};
12
13#[repr(transparent)]
14#[derive(PartialEq, Eq, Hash, Serialize)]
15pub struct RelPath(str);
16
17#[derive(Clone, Serialize, Deserialize)]
18pub struct RelPathBuf(String);
19
20impl RelPath {
21 pub fn empty() -> &'static Self {
22 unsafe { Self::new_unchecked("") }
23 }
24
25 #[track_caller]
26 pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> anyhow::Result<&Self> {
27 let this = unsafe { Self::new_unchecked(s) };
28 if this.0.starts_with("/")
29 || this.0.ends_with("/")
30 || this
31 .components()
32 .any(|component| component == ".." || component == "." || component.is_empty())
33 {
34 bail!("invalid relative path: {:?}", &this.0);
35 }
36 Ok(this)
37 }
38
39 #[track_caller]
40 pub fn from_std_path(path: &Path, path_style: PathStyle) -> Result<Arc<Self>> {
41 let path = path.to_str().context("non utf-8 path")?;
42 let mut string = Cow::Borrowed(path);
43
44 if is_absolute(&string, path_style) {
45 return Err(anyhow!("absolute path not allowed: {path:?}"));
46 }
47
48 if path_style == PathStyle::Windows {
49 string = Cow::Owned(string.as_ref().replace('\\', "/"))
50 }
51
52 let mut this = RelPathBuf::new();
53 for component in unsafe { Self::new_unchecked(string.as_ref()) }.components() {
54 match component {
55 "" => {}
56 "." => {}
57 ".." => {
58 if !this.pop() {
59 return Err(anyhow!("path is not relative: {string:?}"));
60 }
61 }
62 other => this.push(RelPath::new(other)?),
63 }
64 }
65
66 Ok(this.into())
67 }
68
69 pub unsafe fn new_unchecked<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
70 unsafe { &*(s.as_ref() as *const str as *const Self) }
71 }
72
73 pub fn is_empty(&self) -> bool {
74 self.0.is_empty()
75 }
76
77 pub fn components(&self) -> RelPathComponents<'_> {
78 RelPathComponents(&self.0)
79 }
80
81 pub fn ancestors(&self) -> RelPathAncestors<'_> {
82 RelPathAncestors(Some(&self.0))
83 }
84
85 pub fn file_name(&self) -> Option<&str> {
86 self.components().next_back()
87 }
88
89 pub fn file_stem(&self) -> Option<&str> {
90 Some(self.as_std_path().file_stem()?.to_str().unwrap())
91 }
92
93 pub fn extension(&self) -> Option<&str> {
94 Some(self.as_std_path().extension()?.to_str().unwrap())
95 }
96
97 pub fn parent(&self) -> Option<&Self> {
98 let mut components = self.components();
99 components.next_back()?;
100 Some(components.rest())
101 }
102
103 pub fn starts_with(&self, other: &Self) -> bool {
104 self.strip_prefix(other).is_ok()
105 }
106
107 pub fn ends_with(&self, other: &Self) -> bool {
108 if let Some(suffix) = self.0.strip_suffix(&other.0) {
109 if suffix.ends_with('/') {
110 return true;
111 } else if suffix.is_empty() {
112 return true;
113 }
114 }
115 false
116 }
117
118 pub fn strip_prefix(&self, other: &Self) -> Result<&Self> {
119 if other.is_empty() {
120 return Ok(self);
121 }
122 if let Some(suffix) = self.0.strip_prefix(&other.0) {
123 if let Some(suffix) = suffix.strip_prefix('/') {
124 return Ok(unsafe { Self::new_unchecked(suffix) });
125 } else if suffix.is_empty() {
126 return Ok(Self::empty());
127 }
128 }
129 Err(anyhow!("failed to strip prefix: {other:?} from {self:?}"))
130 }
131
132 pub fn len(&self) -> usize {
133 self.0.matches('/').count() + 1
134 }
135
136 pub fn last_n_components(&self, count: usize) -> Option<&Self> {
137 let len = self.len();
138 if len >= count {
139 let mut components = self.components();
140 for _ in 0..(len - count) {
141 components.next()?;
142 }
143 Some(components.rest())
144 } else {
145 None
146 }
147 }
148
149 pub fn push(&self, component: &str) -> Result<Arc<Self>> {
150 if component.is_empty() {
151 bail!("pushed component is empty");
152 } else if component.contains('/') {
153 bail!("pushed component contains a separator: {component:?}");
154 }
155 let path = format!(
156 "{}{}{}",
157 &self.0,
158 if self.is_empty() { "" } else { "/" },
159 component
160 );
161 Ok(Arc::from(unsafe { Self::new_unchecked(&path) }))
162 }
163
164 pub fn join(&self, other: &Self) -> Arc<Self> {
165 let result = if self.0.is_empty() {
166 Cow::Borrowed(&other.0)
167 } else if other.0.is_empty() {
168 Cow::Borrowed(&self.0)
169 } else {
170 Cow::Owned(format!("{}/{}", &self.0, &other.0))
171 };
172 Arc::from(unsafe { Self::new_unchecked(result.as_ref()) })
173 }
174
175 pub fn to_proto(&self) -> String {
176 self.0.to_owned()
177 }
178
179 pub fn to_rel_path_buf(&self) -> RelPathBuf {
180 RelPathBuf(self.0.to_string())
181 }
182
183 pub fn from_proto(path: &str) -> Result<Arc<Self>> {
184 Ok(Arc::from(Self::new(path)?))
185 }
186
187 pub fn as_str(&self) -> &str {
188 &self.0
189 }
190
191 pub fn display(&self, style: PathStyle) -> Cow<'_, str> {
192 match style {
193 PathStyle::Posix => Cow::Borrowed(&self.0),
194 PathStyle::Windows => Cow::Owned(self.0.replace('/', "\\")),
195 }
196 }
197
198 pub fn as_bytes(&self) -> &[u8] {
199 &self.0.as_bytes()
200 }
201
202 pub fn as_os_str(&self) -> &OsStr {
203 self.0.as_ref()
204 }
205
206 pub fn as_std_path(&self) -> &Path {
207 Path::new(&self.0)
208 }
209}
210
211impl PartialOrd for RelPath {
212 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
213 Some(self.cmp(other))
214 }
215}
216
217impl Ord for RelPath {
218 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
219 self.components().cmp(other.components())
220 }
221}
222
223impl fmt::Debug for RelPath {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 fmt::Debug::fmt(&self.0, f)
226 }
227}
228
229impl fmt::Debug for RelPathBuf {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 fmt::Debug::fmt(&self.0, f)
232 }
233}
234
235impl RelPathBuf {
236 pub fn new() -> Self {
237 Self(String::new())
238 }
239
240 pub fn pop(&mut self) -> bool {
241 if let Some(ix) = self.0.rfind('/') {
242 self.0.truncate(ix);
243 true
244 } else if !self.is_empty() {
245 self.0.clear();
246 true
247 } else {
248 false
249 }
250 }
251
252 pub fn push(&mut self, path: &RelPath) {
253 if !self.is_empty() {
254 self.0.push('/');
255 }
256 self.0.push_str(&path.0);
257 }
258
259 pub fn as_rel_path(&self) -> &RelPath {
260 unsafe { RelPath::new_unchecked(self.0.as_str()) }
261 }
262
263 pub fn set_extension(&mut self, extension: &str) -> bool {
264 if let Some(filename) = self.file_name() {
265 let mut filename = PathBuf::from(filename);
266 filename.set_extension(extension);
267 self.pop();
268 self.0.push_str(filename.to_str().unwrap());
269 true
270 } else {
271 false
272 }
273 }
274}
275
276impl Into<Arc<RelPath>> for RelPathBuf {
277 fn into(self) -> Arc<RelPath> {
278 Arc::from(self.as_rel_path())
279 }
280}
281
282impl AsRef<RelPath> for RelPathBuf {
283 fn as_ref(&self) -> &RelPath {
284 self.as_rel_path()
285 }
286}
287
288impl Deref for RelPathBuf {
289 type Target = RelPath;
290
291 fn deref(&self) -> &Self::Target {
292 self.as_ref()
293 }
294}
295
296impl AsRef<Path> for RelPath {
297 fn as_ref(&self) -> &Path {
298 Path::new(&self.0)
299 }
300}
301
302impl From<&RelPath> for Arc<RelPath> {
303 fn from(rel_path: &RelPath) -> Self {
304 let bytes: Arc<str> = Arc::from(&rel_path.0);
305 unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
306 }
307}
308
309#[cfg(any(test, feature = "test-support"))]
310#[track_caller]
311pub fn rel_path(path: &str) -> &RelPath {
312 RelPath::new(path).unwrap()
313}
314
315impl PartialEq<str> for RelPath {
316 fn eq(&self, other: &str) -> bool {
317 self.0 == *other
318 }
319}
320
321pub struct RelPathComponents<'a>(&'a str);
322
323pub struct RelPathAncestors<'a>(Option<&'a str>);
324
325const SEPARATOR: char = '/';
326
327impl<'a> RelPathComponents<'a> {
328 pub fn rest(&self) -> &'a RelPath {
329 unsafe { RelPath::new_unchecked(self.0) }
330 }
331}
332
333impl<'a> Iterator for RelPathComponents<'a> {
334 type Item = &'a str;
335
336 fn next(&mut self) -> Option<Self::Item> {
337 if let Some(sep_ix) = self.0.find(SEPARATOR) {
338 let (head, tail) = self.0.split_at(sep_ix);
339 self.0 = &tail[1..];
340 Some(head)
341 } else if self.0.is_empty() {
342 None
343 } else {
344 let result = self.0;
345 self.0 = "";
346 Some(result)
347 }
348 }
349}
350
351impl<'a> Iterator for RelPathAncestors<'a> {
352 type Item = &'a RelPath;
353
354 fn next(&mut self) -> Option<Self::Item> {
355 let result = self.0?;
356 if let Some(sep_ix) = result.rfind(SEPARATOR) {
357 self.0 = Some(&result[..sep_ix]);
358 } else if !result.is_empty() {
359 self.0 = Some("");
360 } else {
361 self.0 = None;
362 }
363 Some(unsafe { RelPath::new_unchecked(result) })
364 }
365}
366
367impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
368 fn next_back(&mut self) -> Option<Self::Item> {
369 if let Some(sep_ix) = self.0.rfind(SEPARATOR) {
370 let (head, tail) = self.0.split_at(sep_ix);
371 self.0 = head;
372 Some(&tail[1..])
373 } else if self.0.is_empty() {
374 None
375 } else {
376 let result = self.0;
377 self.0 = "";
378 Some(result)
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use itertools::Itertools;
387 use std::path::PathBuf;
388
389 #[test]
390 fn test_path_construction() {
391 assert!(RelPath::new("/").is_err());
392 assert!(RelPath::new("/foo").is_err());
393 assert!(RelPath::new("foo/").is_err());
394 assert!(RelPath::new("foo//bar").is_err());
395 assert!(RelPath::new("foo/../bar").is_err());
396 assert!(RelPath::new("./foo/bar").is_err());
397 assert!(RelPath::new("..").is_err());
398
399 assert!(RelPath::from_std_path(Path::new("/"), PathStyle::local()).is_err());
400 assert!(RelPath::from_std_path(Path::new("//"), PathStyle::local()).is_err());
401 assert!(RelPath::from_std_path(Path::new("/foo/"), PathStyle::local()).is_err());
402 assert_eq!(
403 RelPath::from_std_path(&PathBuf::from_iter(["foo", ""]), PathStyle::local()).unwrap(),
404 Arc::from(rel_path("foo"))
405 );
406 }
407
408 #[test]
409 fn test_rel_path_from_std_path() {
410 assert_eq!(
411 RelPath::from_std_path(Path::new("foo/bar/../baz/./quux/"), PathStyle::local())
412 .unwrap()
413 .as_ref(),
414 rel_path("foo/baz/quux")
415 );
416 }
417
418 #[test]
419 fn test_rel_path_components() {
420 let path = rel_path("foo/bar/baz");
421 assert_eq!(
422 path.components().collect::<Vec<_>>(),
423 vec!["foo", "bar", "baz"]
424 );
425 assert_eq!(
426 path.components().rev().collect::<Vec<_>>(),
427 vec!["baz", "bar", "foo"]
428 );
429
430 let path = rel_path("");
431 let mut components = path.components();
432 assert_eq!(components.next(), None);
433 }
434
435 #[test]
436 fn test_rel_path_ancestors() {
437 let path = rel_path("foo/bar/baz");
438 let mut ancestors = path.ancestors();
439 assert_eq!(ancestors.next(), Some(rel_path("foo/bar/baz")));
440 assert_eq!(ancestors.next(), Some(rel_path("foo/bar")));
441 assert_eq!(ancestors.next(), Some(rel_path("foo")));
442 assert_eq!(ancestors.next(), Some(rel_path("")));
443 assert_eq!(ancestors.next(), None);
444
445 let path = rel_path("foo");
446 let mut ancestors = path.ancestors();
447 assert_eq!(ancestors.next(), Some(rel_path("foo")));
448 assert_eq!(ancestors.next(), Some(RelPath::empty()));
449 assert_eq!(ancestors.next(), None);
450
451 let path = RelPath::empty();
452 let mut ancestors = path.ancestors();
453 assert_eq!(ancestors.next(), Some(RelPath::empty()));
454 assert_eq!(ancestors.next(), None);
455 }
456
457 #[test]
458 fn test_rel_path_parent() {
459 assert_eq!(
460 rel_path("foo/bar/baz").parent(),
461 Some(RelPath::new("foo/bar").unwrap())
462 );
463 assert_eq!(rel_path("foo").parent(), Some(RelPath::empty()));
464 assert_eq!(rel_path("").parent(), None);
465 }
466
467 #[test]
468 fn test_rel_path_partial_ord_is_compatible_with_std() {
469 let test_cases = ["a/b/c", "relative/path/with/dot.", "relative/path/with.dot"];
470 for [lhs, rhs] in test_cases.iter().array_combinations::<2>() {
471 assert_eq!(
472 Path::new(lhs).cmp(Path::new(rhs)),
473 RelPath::new(lhs).unwrap().cmp(RelPath::new(rhs).unwrap())
474 );
475 }
476 }
477
478 #[test]
479 fn test_strip_prefix() {
480 let parent = rel_path("");
481 let child = rel_path(".foo");
482
483 assert!(child.starts_with(parent));
484 assert_eq!(child.strip_prefix(parent).unwrap(), child);
485 }
486
487 #[test]
488 fn test_rel_path_constructors_absolute_path() {
489 assert!(RelPath::from_std_path(Path::new("/a/b"), PathStyle::Windows).is_err());
490 assert!(RelPath::from_std_path(Path::new("\\a\\b"), PathStyle::Windows).is_err());
491 assert!(RelPath::from_std_path(Path::new("/a/b"), PathStyle::Posix).is_err());
492 assert!(RelPath::from_std_path(Path::new("C:/a/b"), PathStyle::Windows).is_err());
493 assert!(RelPath::from_std_path(Path::new("C:\\a\\b"), PathStyle::Windows).is_err());
494 assert!(RelPath::from_std_path(Path::new("C:/a/b"), PathStyle::Posix).is_ok());
495 }
496
497 #[test]
498 fn test_push() {
499 assert_eq!(rel_path("a/b").push("c").unwrap().as_str(), "a/b/c");
500 assert_eq!(rel_path("").push("c").unwrap().as_str(), "c");
501 assert!(rel_path("a/b").push("").is_err());
502 assert!(rel_path("a/b").push("c/d").is_err());
503 }
504
505 #[test]
506 fn test_pop() {
507 let mut path = rel_path("a/b").to_rel_path_buf();
508 path.pop();
509 assert_eq!(path.as_rel_path().as_str(), "a");
510 path.pop();
511 assert_eq!(path.as_rel_path().as_str(), "");
512 path.pop();
513 assert_eq!(path.as_rel_path().as_str(), "");
514 }
515}