diff options
Diffstat (limited to 'crates/vfs/src/vfs_path.rs')
-rw-r--r-- | crates/vfs/src/vfs_path.rs | 94 |
1 files changed, 90 insertions, 4 deletions
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index bd14911c9..2b3d7fd84 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs | |||
@@ -3,25 +3,37 @@ use std::fmt; | |||
3 | 3 | ||
4 | use paths::{AbsPath, AbsPathBuf}; | 4 | use paths::{AbsPath, AbsPathBuf}; |
5 | 5 | ||
6 | /// Path in [`Vfs`]. | ||
7 | /// | ||
6 | /// Long-term, we want to support files which do not reside in the file-system, | 8 | /// Long-term, we want to support files which do not reside in the file-system, |
7 | /// so we treat VfsPaths as opaque identifiers. | 9 | /// so we treat `VfsPath`s as opaque identifiers. |
10 | /// | ||
11 | /// [`Vfs`]: crate::Vfs | ||
8 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 12 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
9 | pub struct VfsPath(VfsPathRepr); | 13 | pub struct VfsPath(VfsPathRepr); |
10 | 14 | ||
11 | impl VfsPath { | 15 | impl VfsPath { |
12 | /// Creates an "in-memory" path from `/`-separates string. | 16 | /// Creates an "in-memory" path from `/`-separated string. |
17 | /// | ||
13 | /// This is most useful for testing, to avoid windows/linux differences | 18 | /// This is most useful for testing, to avoid windows/linux differences |
19 | /// | ||
20 | /// # Panics | ||
21 | /// | ||
22 | /// Panics if `path` does not start with `'/'`. | ||
14 | pub fn new_virtual_path(path: String) -> VfsPath { | 23 | pub fn new_virtual_path(path: String) -> VfsPath { |
15 | assert!(path.starts_with('/')); | 24 | assert!(path.starts_with('/')); |
16 | VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path))) | 25 | VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path))) |
17 | } | 26 | } |
18 | 27 | ||
28 | /// Returns the `AbsPath` representation of `self` if `self` is on the file system. | ||
19 | pub fn as_path(&self) -> Option<&AbsPath> { | 29 | pub fn as_path(&self) -> Option<&AbsPath> { |
20 | match &self.0 { | 30 | match &self.0 { |
21 | VfsPathRepr::PathBuf(it) => Some(it.as_path()), | 31 | VfsPathRepr::PathBuf(it) => Some(it.as_path()), |
22 | VfsPathRepr::VirtualPath(_) => None, | 32 | VfsPathRepr::VirtualPath(_) => None, |
23 | } | 33 | } |
24 | } | 34 | } |
35 | |||
36 | /// Creates a new `VfsPath` with `path` adjoined to `self`. | ||
25 | pub fn join(&self, path: &str) -> Option<VfsPath> { | 37 | pub fn join(&self, path: &str) -> Option<VfsPath> { |
26 | match &self.0 { | 38 | match &self.0 { |
27 | VfsPathRepr::PathBuf(it) => { | 39 | VfsPathRepr::PathBuf(it) => { |
@@ -34,12 +46,30 @@ impl VfsPath { | |||
34 | } | 46 | } |
35 | } | 47 | } |
36 | } | 48 | } |
49 | |||
50 | /// Remove the last component of `self` if there is one. | ||
51 | /// | ||
52 | /// If `self` has no component, returns `false`; else returns `true`. | ||
53 | /// | ||
54 | /// # Example | ||
55 | /// | ||
56 | /// ``` | ||
57 | /// # use vfs::{AbsPathBuf, VfsPath}; | ||
58 | /// let mut path = VfsPath::from(AbsPathBuf::assert("/foo/bar".into())); | ||
59 | /// assert!(path.pop()); | ||
60 | /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/foo".into()))); | ||
61 | /// assert!(path.pop()); | ||
62 | /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/".into()))); | ||
63 | /// assert!(!path.pop()); | ||
64 | /// ``` | ||
37 | pub fn pop(&mut self) -> bool { | 65 | pub fn pop(&mut self) -> bool { |
38 | match &mut self.0 { | 66 | match &mut self.0 { |
39 | VfsPathRepr::PathBuf(it) => it.pop(), | 67 | VfsPathRepr::PathBuf(it) => it.pop(), |
40 | VfsPathRepr::VirtualPath(it) => it.pop(), | 68 | VfsPathRepr::VirtualPath(it) => it.pop(), |
41 | } | 69 | } |
42 | } | 70 | } |
71 | |||
72 | /// Returns `true` if `other` is a prefix of `self`. | ||
43 | pub fn starts_with(&self, other: &VfsPath) -> bool { | 73 | pub fn starts_with(&self, other: &VfsPath) -> bool { |
44 | match (&self.0, &other.0) { | 74 | match (&self.0, &other.0) { |
45 | (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs), | 75 | (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs), |
@@ -48,6 +78,10 @@ impl VfsPath { | |||
48 | (VfsPathRepr::VirtualPath(_), _) => false, | 78 | (VfsPathRepr::VirtualPath(_), _) => false, |
49 | } | 79 | } |
50 | } | 80 | } |
81 | |||
82 | /// Returns the `VfsPath` without its final component, if there is one. | ||
83 | /// | ||
84 | /// Returns [`None`] if the path is a root or prefix. | ||
51 | pub fn parent(&self) -> Option<VfsPath> { | 85 | pub fn parent(&self) -> Option<VfsPath> { |
52 | let mut parent = self.clone(); | 86 | let mut parent = self.clone(); |
53 | if parent.pop() { | 87 | if parent.pop() { |
@@ -57,6 +91,7 @@ impl VfsPath { | |||
57 | } | 91 | } |
58 | } | 92 | } |
59 | 93 | ||
94 | /// Returns `self`'s base name and file extension. | ||
60 | pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | 95 | pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { |
61 | match &self.0 { | 96 | match &self.0 { |
62 | VfsPathRepr::PathBuf(p) => Some(( | 97 | VfsPathRepr::PathBuf(p) => Some(( |
@@ -67,7 +102,14 @@ impl VfsPath { | |||
67 | } | 102 | } |
68 | } | 103 | } |
69 | 104 | ||
70 | // Don't make this `pub` | 105 | /// **Don't make this `pub`** |
106 | /// | ||
107 | /// Encode the path in the given buffer. | ||
108 | /// | ||
109 | /// The encoding will be `0` if [`AbsPathBuf`], `1` if [`VirtualPath`], followed | ||
110 | /// by `self`'s representation. | ||
111 | /// | ||
112 | /// Note that this encoding is dependent on the operating system. | ||
71 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { | 113 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { |
72 | let tag = match &self.0 { | 114 | let tag = match &self.0 { |
73 | VfsPathRepr::PathBuf(_) => 0, | 115 | VfsPathRepr::PathBuf(_) => 0, |
@@ -224,6 +266,7 @@ mod windows_paths { | |||
224 | } | 266 | } |
225 | } | 267 | } |
226 | 268 | ||
269 | /// Internal, private representation of [`VfsPath`]. | ||
227 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 270 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
228 | enum VfsPathRepr { | 271 | enum VfsPathRepr { |
229 | PathBuf(AbsPathBuf), | 272 | PathBuf(AbsPathBuf), |
@@ -260,13 +303,34 @@ impl fmt::Debug for VfsPathRepr { | |||
260 | } | 303 | } |
261 | } | 304 | } |
262 | 305 | ||
306 | /// `/`-separated virtual path. | ||
307 | /// | ||
308 | /// This is used to describe files that do not reside on the file system. | ||
263 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 309 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
264 | struct VirtualPath(String); | 310 | struct VirtualPath(String); |
265 | 311 | ||
266 | impl VirtualPath { | 312 | impl VirtualPath { |
313 | /// Returns `true` if `other` is a prefix of `self` (as strings). | ||
267 | fn starts_with(&self, other: &VirtualPath) -> bool { | 314 | fn starts_with(&self, other: &VirtualPath) -> bool { |
268 | self.0.starts_with(&other.0) | 315 | self.0.starts_with(&other.0) |
269 | } | 316 | } |
317 | |||
318 | /// Remove the last component of `self`. | ||
319 | /// | ||
320 | /// This will find the last `'/'` in `self`, and remove everything after it, | ||
321 | /// including the `'/'`. | ||
322 | /// | ||
323 | /// If `self` contains no `'/'`, returns `false`; else returns `true`. | ||
324 | /// | ||
325 | /// # Example | ||
326 | /// | ||
327 | /// ```rust,ignore | ||
328 | /// let mut path = VirtualPath("/foo/bar".to_string()); | ||
329 | /// path.pop(); | ||
330 | /// assert_eq!(path.0, "/foo"); | ||
331 | /// path.pop(); | ||
332 | /// assert_eq!(path.0, ""); | ||
333 | /// ``` | ||
270 | fn pop(&mut self) -> bool { | 334 | fn pop(&mut self) -> bool { |
271 | let pos = match self.0.rfind('/') { | 335 | let pos = match self.0.rfind('/') { |
272 | Some(pos) => pos, | 336 | Some(pos) => pos, |
@@ -275,6 +339,17 @@ impl VirtualPath { | |||
275 | self.0 = self.0[..pos].to_string(); | 339 | self.0 = self.0[..pos].to_string(); |
276 | true | 340 | true |
277 | } | 341 | } |
342 | |||
343 | /// Append the given *relative* path `path` to `self`. | ||
344 | /// | ||
345 | /// This will resolve any leading `"../"` in `path` before appending it. | ||
346 | /// | ||
347 | /// Returns [`None`] if `path` has more leading `"../"` than the number of | ||
348 | /// components in `self`. | ||
349 | /// | ||
350 | /// # Notes | ||
351 | /// | ||
352 | /// In practice, appending here means `self/path` as strings. | ||
278 | fn join(&self, mut path: &str) -> Option<VirtualPath> { | 353 | fn join(&self, mut path: &str) -> Option<VirtualPath> { |
279 | let mut res = self.clone(); | 354 | let mut res = self.clone(); |
280 | while path.starts_with("../") { | 355 | while path.starts_with("../") { |
@@ -287,7 +362,18 @@ impl VirtualPath { | |||
287 | Some(res) | 362 | Some(res) |
288 | } | 363 | } |
289 | 364 | ||
290 | pub(crate) fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | 365 | /// Returns `self`'s base name and file extension. |
366 | /// | ||
367 | /// # Returns | ||
368 | /// - `None` if `self` ends with `"//"`. | ||
369 | /// - `Some((name, None))` if `self`'s base contains no `.`, or only one `.` at | ||
370 | /// the start. | ||
371 | /// - `Some((name, Some(extension))` else. | ||
372 | /// | ||
373 | /// # Note | ||
374 | /// The extension will not contains `.`. This means `"/foo/bar.baz.rs"` will | ||
375 | /// return `Some(("bar.baz", Some("rs"))`. | ||
376 | fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | ||
291 | let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 }; | 377 | let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 }; |
292 | let file_name = match file_path.rfind('/') { | 378 | let file_name = match file_path.rfind('/') { |
293 | Some(position) => &file_path[position + 1..], | 379 | Some(position) => &file_path[position + 1..], |