1use std::fmt::{self, Debug, Display, Formatter};
2use std::path::{Component, Path, PathBuf};
3
4#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
6pub struct VirtualPath(PathBuf);
7
8impl VirtualPath {
9 pub fn new(path: impl AsRef<Path>) -> Self {
14 Self::new_impl(path.as_ref())
15 }
16
17 fn new_impl(path: &Path) -> Self {
19 let mut out = Path::new(&Component::RootDir).to_path_buf();
20 for component in path.components() {
21 match component {
22 Component::Prefix(_) | Component::RootDir => {}
23 Component::CurDir => {}
24 Component::ParentDir => match out.components().next_back() {
25 Some(Component::Normal(_)) => {
26 out.pop();
27 }
28 _ => out.push(component),
29 },
30 Component::Normal(_) => out.push(component),
31 }
32 }
33 Self(out)
34 }
35
36 pub fn within_root(path: &Path, root: &Path) -> Option<Self> {
42 path.strip_prefix(root).ok().map(Self::new)
43 }
44
45 pub fn as_rooted_path(&self) -> &Path {
47 &self.0
48 }
49
50 pub fn as_rootless_path(&self) -> &Path {
52 self.0.strip_prefix(Component::RootDir).unwrap_or(&self.0)
53 }
54
55 pub fn resolve(&self, root: &Path) -> Option<PathBuf> {
61 let root_len = root.as_os_str().len();
62 let mut out = root.to_path_buf();
63 for component in self.0.components() {
64 match component {
65 Component::Prefix(_) => {}
66 Component::RootDir => {}
67 Component::CurDir => {}
68 Component::ParentDir => {
69 out.pop();
70 if out.as_os_str().len() < root_len {
71 return None;
72 }
73 }
74 Component::Normal(_) => out.push(component),
75 }
76 }
77 Some(out)
78 }
79
80 pub fn join(&self, path: impl AsRef<Path>) -> Self {
82 if let Some(parent) = self.0.parent() {
83 Self::new(parent.join(path))
84 } else {
85 Self::new(path)
86 }
87 }
88
89 pub fn with_extension(&self, extension: &str) -> Self {
91 Self(self.0.with_extension(extension))
92 }
93}
94
95impl Debug for VirtualPath {
96 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
97 Display::fmt(&self.0.display(), f)
98 }
99}