• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Abstract-ish representation of paths for VFS.
2 use std::fmt;
3 
4 use paths::{AbsPath, AbsPathBuf, RelPath};
5 
6 /// Path in [`Vfs`].
7 ///
8 /// Long-term, we want to support files which do not reside in the file-system,
9 /// so we treat `VfsPath`s as opaque identifiers.
10 ///
11 /// [`Vfs`]: crate::Vfs
12 #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
13 pub struct VfsPath(VfsPathRepr);
14 
15 impl VfsPath {
16     /// Creates an "in-memory" path from `/`-separated string.
17     ///
18     /// This is most useful for testing, to avoid windows/linux differences
19     ///
20     /// # Panics
21     ///
22     /// Panics if `path` does not start with `'/'`.
new_virtual_path(path: String) -> VfsPath23     pub fn new_virtual_path(path: String) -> VfsPath {
24         assert!(path.starts_with('/'));
25         VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path)))
26     }
27 
28     /// Create a path from string. Input should be a string representation of
29     /// an absolute path inside filesystem
new_real_path(path: String) -> VfsPath30     pub fn new_real_path(path: String) -> VfsPath {
31         VfsPath::from(AbsPathBuf::assert(path.into()))
32     }
33 
34     /// Returns the `AbsPath` representation of `self` if `self` is on the file system.
as_path(&self) -> Option<&AbsPath>35     pub fn as_path(&self) -> Option<&AbsPath> {
36         match &self.0 {
37             VfsPathRepr::PathBuf(it) => Some(it.as_path()),
38             VfsPathRepr::VirtualPath(_) => None,
39         }
40     }
41 
42     /// Creates a new `VfsPath` with `path` adjoined to `self`.
join(&self, path: &str) -> Option<VfsPath>43     pub fn join(&self, path: &str) -> Option<VfsPath> {
44         match &self.0 {
45             VfsPathRepr::PathBuf(it) => {
46                 let res = it.join(path).normalize();
47                 Some(VfsPath(VfsPathRepr::PathBuf(res)))
48             }
49             VfsPathRepr::VirtualPath(it) => {
50                 let res = it.join(path)?;
51                 Some(VfsPath(VfsPathRepr::VirtualPath(res)))
52             }
53         }
54     }
55 
56     /// Remove the last component of `self` if there is one.
57     ///
58     /// If `self` has no component, returns `false`; else returns `true`.
59     ///
60     /// # Example
61     ///
62     /// ```
63     /// # use vfs::{AbsPathBuf, VfsPath};
64     /// let mut path = VfsPath::from(AbsPathBuf::assert("/foo/bar".into()));
65     /// assert!(path.pop());
66     /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/foo".into())));
67     /// assert!(path.pop());
68     /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/".into())));
69     /// assert!(!path.pop());
70     /// ```
pop(&mut self) -> bool71     pub fn pop(&mut self) -> bool {
72         match &mut self.0 {
73             VfsPathRepr::PathBuf(it) => it.pop(),
74             VfsPathRepr::VirtualPath(it) => it.pop(),
75         }
76     }
77 
78     /// Returns `true` if `other` is a prefix of `self`.
starts_with(&self, other: &VfsPath) -> bool79     pub fn starts_with(&self, other: &VfsPath) -> bool {
80         match (&self.0, &other.0) {
81             (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
82             (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
83             (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => false,
84         }
85     }
86 
strip_prefix(&self, other: &VfsPath) -> Option<&RelPath>87     pub fn strip_prefix(&self, other: &VfsPath) -> Option<&RelPath> {
88         match (&self.0, &other.0) {
89             (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.strip_prefix(rhs),
90             (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.strip_prefix(rhs),
91             (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => None,
92         }
93     }
94 
95     /// Returns the `VfsPath` without its final component, if there is one.
96     ///
97     /// Returns [`None`] if the path is a root or prefix.
parent(&self) -> Option<VfsPath>98     pub fn parent(&self) -> Option<VfsPath> {
99         let mut parent = self.clone();
100         if parent.pop() {
101             Some(parent)
102         } else {
103             None
104         }
105     }
106 
107     /// Returns `self`'s base name and file extension.
name_and_extension(&self) -> Option<(&str, Option<&str>)>108     pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
109         match &self.0 {
110             VfsPathRepr::PathBuf(p) => p.name_and_extension(),
111             VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
112         }
113     }
114 
115     /// **Don't make this `pub`**
116     ///
117     /// Encode the path in the given buffer.
118     ///
119     /// The encoding will be `0` if [`AbsPathBuf`], `1` if [`VirtualPath`], followed
120     /// by `self`'s representation.
121     ///
122     /// Note that this encoding is dependent on the operating system.
encode(&self, buf: &mut Vec<u8>)123     pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
124         let tag = match &self.0 {
125             VfsPathRepr::PathBuf(_) => 0,
126             VfsPathRepr::VirtualPath(_) => 1,
127         };
128         buf.push(tag);
129         match &self.0 {
130             VfsPathRepr::PathBuf(path) => {
131                 #[cfg(windows)]
132                 {
133                     use windows_paths::Encode;
134                     let path: &std::path::Path = path.as_ref();
135                     let components = path.components();
136                     let mut add_sep = false;
137                     for component in components {
138                         if add_sep {
139                             windows_paths::SEP.encode(buf);
140                         }
141                         let len_before = buf.len();
142                         match component {
143                             std::path::Component::Prefix(prefix) => {
144                                 // kind() returns a normalized and comparable path prefix.
145                                 prefix.kind().encode(buf);
146                             }
147                             std::path::Component::RootDir => {
148                                 if !add_sep {
149                                     component.as_os_str().encode(buf);
150                                 }
151                             }
152                             _ => component.as_os_str().encode(buf),
153                         }
154 
155                         // some components may be encoded empty
156                         add_sep = len_before != buf.len();
157                     }
158                 }
159                 #[cfg(unix)]
160                 {
161                     use std::os::unix::ffi::OsStrExt;
162                     buf.extend(path.as_os_str().as_bytes());
163                 }
164                 #[cfg(not(any(windows, unix)))]
165                 {
166                     buf.extend(path.as_os_str().to_string_lossy().as_bytes());
167                 }
168             }
169             VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()),
170         }
171     }
172 }
173 
174 #[cfg(windows)]
175 mod windows_paths {
176     pub(crate) trait Encode {
encode(&self, buf: &mut Vec<u8>)177         fn encode(&self, buf: &mut Vec<u8>);
178     }
179 
180     impl Encode for std::ffi::OsStr {
encode(&self, buf: &mut Vec<u8>)181         fn encode(&self, buf: &mut Vec<u8>) {
182             use std::os::windows::ffi::OsStrExt;
183             for wchar in self.encode_wide() {
184                 buf.extend(wchar.to_le_bytes().iter().copied());
185             }
186         }
187     }
188 
189     impl Encode for u8 {
encode(&self, buf: &mut Vec<u8>)190         fn encode(&self, buf: &mut Vec<u8>) {
191             let wide = *self as u16;
192             buf.extend(wide.to_le_bytes().iter().copied())
193         }
194     }
195 
196     impl Encode for &str {
encode(&self, buf: &mut Vec<u8>)197         fn encode(&self, buf: &mut Vec<u8>) {
198             debug_assert!(self.is_ascii());
199             for b in self.as_bytes() {
200                 b.encode(buf)
201             }
202         }
203     }
204 
205     pub(crate) const SEP: &str = "\\";
206     const VERBATIM: &str = "\\\\?\\";
207     const UNC: &str = "UNC";
208     const DEVICE: &str = "\\\\.\\";
209     const COLON: &str = ":";
210 
211     impl Encode for std::path::Prefix<'_> {
212         fn encode(&self, buf: &mut Vec<u8>) {
213             match self {
214                 std::path::Prefix::Verbatim(c) => {
215                     VERBATIM.encode(buf);
216                     c.encode(buf);
217                 }
218                 std::path::Prefix::VerbatimUNC(server, share) => {
219                     VERBATIM.encode(buf);
220                     UNC.encode(buf);
221                     SEP.encode(buf);
222                     server.encode(buf);
223                     SEP.encode(buf);
224                     share.encode(buf);
225                 }
226                 std::path::Prefix::VerbatimDisk(d) => {
227                     VERBATIM.encode(buf);
228                     d.encode(buf);
229                     COLON.encode(buf);
230                 }
231                 std::path::Prefix::DeviceNS(device) => {
232                     DEVICE.encode(buf);
233                     device.encode(buf);
234                 }
235                 std::path::Prefix::UNC(server, share) => {
236                     SEP.encode(buf);
237                     SEP.encode(buf);
238                     server.encode(buf);
239                     SEP.encode(buf);
240                     share.encode(buf);
241                 }
242                 std::path::Prefix::Disk(d) => {
243                     d.encode(buf);
244                     COLON.encode(buf);
245                 }
246             }
247         }
248     }
249     #[test]
250     fn paths_encoding() {
251         // drive letter casing agnostic
252         test_eq("C:/x.rs", "c:/x.rs");
253         // separator agnostic
254         test_eq("C:/x/y.rs", "C:\\x\\y.rs");
255 
256         fn test_eq(a: &str, b: &str) {
257             let mut b1 = Vec::new();
258             let mut b2 = Vec::new();
259             vfs(a).encode(&mut b1);
260             vfs(b).encode(&mut b2);
261             assert_eq!(b1, b2);
262         }
263     }
264 
265     #[test]
266     fn test_sep_root_dir_encoding() {
267         let mut buf = Vec::new();
268         vfs("C:/x/y").encode(&mut buf);
269         assert_eq!(&buf, &[0, 67, 0, 58, 0, 92, 0, 120, 0, 92, 0, 121, 0])
270     }
271 
272     #[cfg(test)]
273     fn vfs(str: &str) -> super::VfsPath {
274         use super::{AbsPathBuf, VfsPath};
275         VfsPath::from(AbsPathBuf::try_from(str).unwrap())
276     }
277 }
278 
279 /// Internal, private representation of [`VfsPath`].
280 #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
281 enum VfsPathRepr {
282     PathBuf(AbsPathBuf),
283     VirtualPath(VirtualPath),
284 }
285 
286 impl From<AbsPathBuf> for VfsPath {
287     fn from(v: AbsPathBuf) -> Self {
288         VfsPath(VfsPathRepr::PathBuf(v.normalize()))
289     }
290 }
291 
292 impl fmt::Display for VfsPath {
293     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294         match &self.0 {
295             VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f),
296             VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Display::fmt(it, f),
297         }
298     }
299 }
300 
301 impl fmt::Debug for VfsPath {
302     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303         fmt::Debug::fmt(&self.0, f)
304     }
305 }
306 
307 impl fmt::Debug for VfsPathRepr {
308     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309         match &self {
310             VfsPathRepr::PathBuf(it) => fmt::Debug::fmt(&it.display(), f),
311             VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Debug::fmt(&it, f),
312         }
313     }
314 }
315 
316 /// `/`-separated virtual path.
317 ///
318 /// This is used to describe files that do not reside on the file system.
319 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
320 struct VirtualPath(String);
321 
322 impl VirtualPath {
323     /// Returns `true` if `other` is a prefix of `self` (as strings).
324     fn starts_with(&self, other: &VirtualPath) -> bool {
325         self.0.starts_with(&other.0)
326     }
327 
328     fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> {
329         <_ as AsRef<std::path::Path>>::as_ref(&self.0)
330             .strip_prefix(&base.0)
331             .ok()
332             .map(RelPath::new_unchecked)
333     }
334 
335     /// Remove the last component of `self`.
336     ///
337     /// This will find the last `'/'` in `self`, and remove everything after it,
338     /// including the `'/'`.
339     ///
340     /// If `self` contains no `'/'`, returns `false`; else returns `true`.
341     ///
342     /// # Example
343     ///
344     /// ```rust,ignore
345     /// let mut path = VirtualPath("/foo/bar".to_string());
346     /// path.pop();
347     /// assert_eq!(path.0, "/foo");
348     /// path.pop();
349     /// assert_eq!(path.0, "");
350     /// ```
351     fn pop(&mut self) -> bool {
352         let pos = match self.0.rfind('/') {
353             Some(pos) => pos,
354             None => return false,
355         };
356         self.0 = self.0[..pos].to_string();
357         true
358     }
359 
360     /// Append the given *relative* path `path` to `self`.
361     ///
362     /// This will resolve any leading `"../"` in `path` before appending it.
363     ///
364     /// Returns [`None`] if `path` has more leading `"../"` than the number of
365     /// components in `self`.
366     ///
367     /// # Notes
368     ///
369     /// In practice, appending here means `self/path` as strings.
370     fn join(&self, mut path: &str) -> Option<VirtualPath> {
371         let mut res = self.clone();
372         while path.starts_with("../") {
373             if !res.pop() {
374                 return None;
375             }
376             path = &path["../".len()..];
377         }
378         path = path.trim_start_matches("./");
379         res.0 = format!("{}/{path}", res.0);
380         Some(res)
381     }
382 
383     /// Returns `self`'s base name and file extension.
384     ///
385     /// # Returns
386     /// - `None` if `self` ends with `"//"`.
387     /// - `Some((name, None))` if `self`'s base contains no `.`, or only one `.` at
388     /// the start.
389     /// - `Some((name, Some(extension))` else.
390     ///
391     /// # Note
392     /// The extension will not contains `.`. This means `"/foo/bar.baz.rs"` will
393     /// return `Some(("bar.baz", Some("rs"))`.
name_and_extension(&self) -> Option<(&str, Option<&str>)>394     fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
395         let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
396         let file_name = match file_path.rfind('/') {
397             Some(position) => &file_path[position + 1..],
398             None => file_path,
399         };
400 
401         if file_name.is_empty() {
402             None
403         } else {
404             let mut file_stem_and_extension = file_name.rsplitn(2, '.');
405             let extension = file_stem_and_extension.next();
406             let file_stem = file_stem_and_extension.next();
407 
408             match (file_stem, extension) {
409                 (None, None) => None,
410                 (None | Some(""), Some(_)) => Some((file_name, None)),
411                 (Some(file_stem), extension) => Some((file_stem, extension)),
412             }
413         }
414     }
415 }
416 
417 #[cfg(test)]
418 mod tests;
419