aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs/src/vfs_path.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs/src/vfs_path.rs')
-rw-r--r--crates/vfs/src/vfs_path.rs94
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
4use paths::{AbsPath, AbsPathBuf}; 4use 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)]
9pub struct VfsPath(VfsPathRepr); 13pub struct VfsPath(VfsPathRepr);
10 14
11impl VfsPath { 15impl 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)]
228enum VfsPathRepr { 271enum 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)]
264struct VirtualPath(String); 310struct VirtualPath(String);
265 311
266impl VirtualPath { 312impl 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..],