1 //! Abstract-ish representation of paths for VFS.
4 use paths::{AbsPath, AbsPathBuf};
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.
11 /// [`Vfs`]: crate::Vfs
12 #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
13 pub struct VfsPath(VfsPathRepr);
16 /// Creates an "in-memory" path from `/`-separated string.
18 /// This is most useful for testing, to avoid windows/linux differences
22 /// Panics if `path` does not start with `'/'`.
23 pub fn new_virtual_path(path: String) -> VfsPath {
24 assert!(path.starts_with('/'));
25 VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path)))
28 /// Returns the `AbsPath` representation of `self` if `self` is on the file system.
29 pub fn as_path(&self) -> Option<&AbsPath> {
31 VfsPathRepr::PathBuf(it) => Some(it.as_path()),
32 VfsPathRepr::VirtualPath(_) => None,
36 /// Creates a new `VfsPath` with `path` adjoined to `self`.
37 pub fn join(&self, path: &str) -> Option<VfsPath> {
39 VfsPathRepr::PathBuf(it) => {
40 let res = it.join(path).normalize();
41 Some(VfsPath(VfsPathRepr::PathBuf(res)))
43 VfsPathRepr::VirtualPath(it) => {
44 let res = it.join(path)?;
45 Some(VfsPath(VfsPathRepr::VirtualPath(res)))
50 /// Remove the last component of `self` if there is one.
52 /// If `self` has no component, returns `false`; else returns `true`.
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());
65 pub fn pop(&mut self) -> bool {
67 VfsPathRepr::PathBuf(it) => it.pop(),
68 VfsPathRepr::VirtualPath(it) => it.pop(),
72 /// Returns `true` if `other` is a prefix of `self`.
73 pub fn starts_with(&self, other: &VfsPath) -> bool {
74 match (&self.0, &other.0) {
75 (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
76 (VfsPathRepr::PathBuf(_), _) => false,
77 (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
78 (VfsPathRepr::VirtualPath(_), _) => false,
82 /// Returns the `VfsPath` without its final component, if there is one.
84 /// Returns [`None`] if the path is a root or prefix.
85 pub fn parent(&self) -> Option<VfsPath> {
86 let mut parent = self.clone();
94 /// Returns `self`'s base name and file extension.
95 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
97 VfsPathRepr::PathBuf(p) => Some((
98 p.file_stem()?.to_str()?,
99 p.extension().and_then(|extension| extension.to_str()),
101 VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
105 /// **Don't make this `pub`**
107 /// Encode the path in the given buffer.
109 /// The encoding will be `0` if [`AbsPathBuf`], `1` if [`VirtualPath`], followed
110 /// by `self`'s representation.
112 /// Note that this encoding is dependent on the operating system.
113 pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
114 let tag = match &self.0 {
115 VfsPathRepr::PathBuf(_) => 0,
116 VfsPathRepr::VirtualPath(_) => 1,
120 VfsPathRepr::PathBuf(path) => {
123 use windows_paths::Encode;
124 let path: &std::path::Path = path.as_ref();
125 let components = path.components();
126 let mut add_sep = false;
127 for component in components {
129 windows_paths::SEP.encode(buf);
131 let len_before = buf.len();
133 std::path::Component::Prefix(prefix) => {
134 // kind() returns a normalized and comparable path prefix.
135 prefix.kind().encode(buf);
137 std::path::Component::RootDir => {
139 component.as_os_str().encode(buf);
142 _ => component.as_os_str().encode(buf),
145 // some components may be encoded empty
146 add_sep = len_before != buf.len();
151 use std::os::unix::ffi::OsStrExt;
152 buf.extend(path.as_os_str().as_bytes());
154 #[cfg(not(any(windows, unix)))]
156 buf.extend(path.as_os_str().to_string_lossy().as_bytes());
159 VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()),
166 pub(crate) trait Encode {
167 fn encode(&self, buf: &mut Vec<u8>);
170 impl Encode for std::ffi::OsStr {
171 fn encode(&self, buf: &mut Vec<u8>) {
172 use std::os::windows::ffi::OsStrExt;
173 for wchar in self.encode_wide() {
174 buf.extend(wchar.to_le_bytes().iter().copied());
180 fn encode(&self, buf: &mut Vec<u8>) {
181 let wide = *self as u16;
182 buf.extend(wide.to_le_bytes().iter().copied())
186 impl Encode for &str {
187 fn encode(&self, buf: &mut Vec<u8>) {
188 debug_assert!(self.is_ascii());
189 for b in self.as_bytes() {
195 pub(crate) const SEP: &str = "\\";
196 const VERBATIM: &str = "\\\\?\\";
197 const UNC: &str = "UNC";
198 const DEVICE: &str = "\\\\.\\";
199 const COLON: &str = ":";
201 impl Encode for std::path::Prefix<'_> {
202 fn encode(&self, buf: &mut Vec<u8>) {
204 std::path::Prefix::Verbatim(c) => {
205 VERBATIM.encode(buf);
208 std::path::Prefix::VerbatimUNC(server, share) => {
209 VERBATIM.encode(buf);
216 std::path::Prefix::VerbatimDisk(d) => {
217 VERBATIM.encode(buf);
221 std::path::Prefix::DeviceNS(device) => {
225 std::path::Prefix::UNC(server, share) => {
232 std::path::Prefix::Disk(d) => {
240 fn paths_encoding() {
241 // drive letter casing agnostic
242 test_eq("C:/x.rs", "c:/x.rs");
243 // separator agnostic
244 test_eq("C:/x/y.rs", "C:\\x\\y.rs");
246 fn test_eq(a: &str, b: &str) {
247 let mut b1 = Vec::new();
248 let mut b2 = Vec::new();
249 vfs(a).encode(&mut b1);
250 vfs(b).encode(&mut b2);
256 fn test_sep_root_dir_encoding() {
257 let mut buf = Vec::new();
258 vfs("C:/x/y").encode(&mut buf);
259 assert_eq!(&buf, &[0, 67, 0, 58, 0, 92, 0, 120, 0, 92, 0, 121, 0])
263 fn vfs(str: &str) -> super::VfsPath {
264 use super::{AbsPathBuf, VfsPath};
265 use std::convert::TryFrom;
266 VfsPath::from(AbsPathBuf::try_from(str).unwrap())
270 /// Internal, private representation of [`VfsPath`].
271 #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
274 VirtualPath(VirtualPath),
277 impl From<AbsPathBuf> for VfsPath {
278 fn from(v: AbsPathBuf) -> Self {
279 VfsPath(VfsPathRepr::PathBuf(v.normalize()))
283 impl fmt::Display for VfsPath {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f),
287 VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Display::fmt(it, f),
292 impl fmt::Debug for VfsPath {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 fmt::Debug::fmt(&self.0, f)
298 impl fmt::Debug for VfsPathRepr {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 VfsPathRepr::PathBuf(it) => fmt::Debug::fmt(&it.display(), f),
302 VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Debug::fmt(&it, f),
307 /// `/`-separated virtual path.
309 /// This is used to describe files that do not reside on the file system.
310 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
311 struct VirtualPath(String);
314 /// Returns `true` if `other` is a prefix of `self` (as strings).
315 fn starts_with(&self, other: &VirtualPath) -> bool {
316 self.0.starts_with(&other.0)
319 /// Remove the last component of `self`.
321 /// This will find the last `'/'` in `self`, and remove everything after it,
322 /// including the `'/'`.
324 /// If `self` contains no `'/'`, returns `false`; else returns `true`.
329 /// let mut path = VirtualPath("/foo/bar".to_string());
331 /// assert_eq!(path.0, "/foo");
333 /// assert_eq!(path.0, "");
335 fn pop(&mut self) -> bool {
336 let pos = match self.0.rfind('/') {
338 None => return false,
340 self.0 = self.0[..pos].to_string();
344 /// Append the given *relative* path `path` to `self`.
346 /// This will resolve any leading `"../"` in `path` before appending it.
348 /// Returns [`None`] if `path` has more leading `"../"` than the number of
349 /// components in `self`.
353 /// In practice, appending here means `self/path` as strings.
354 fn join(&self, mut path: &str) -> Option<VirtualPath> {
355 let mut res = self.clone();
356 while path.starts_with("../") {
360 path = &path["../".len()..]
362 res.0 = format!("{}/{}", res.0, path);
366 /// Returns `self`'s base name and file extension.
369 /// - `None` if `self` ends with `"//"`.
370 /// - `Some((name, None))` if `self`'s base contains no `.`, or only one `.` at
372 /// - `Some((name, Some(extension))` else.
375 /// The extension will not contains `.`. This means `"/foo/bar.baz.rs"` will
376 /// return `Some(("bar.baz", Some("rs"))`.
377 fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
378 let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
379 let file_name = match file_path.rfind('/') {
380 Some(position) => &file_path[position + 1..],
384 if file_name.is_empty() {
387 let mut file_stem_and_extension = file_name.rsplitn(2, '.');
388 let extension = file_stem_and_extension.next();
389 let file_stem = file_stem_and_extension.next();
391 match (file_stem, extension) {
392 (None, None) => None,
393 (None | Some(""), Some(_)) => Some((file_name, None)),
394 (Some(file_stem), extension) => Some((file_stem, extension)),