1use std::fmt;
3
4use paths::{AbsPath, AbsPathBuf, RelPath};
5
6#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
13pub struct VfsPath(VfsPathRepr);
14
15impl VfsPath {
16 pub fn new_virtual_path(path: String) -> VfsPath {
24 assert!(path.starts_with('/'));
25 VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path)))
26 }
27
28 pub fn new_real_path(path: String) -> VfsPath {
31 VfsPath::from(AbsPathBuf::assert(path.into()))
32 }
33
34 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 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 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 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
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 pub fn parent(&self) -> Option<VfsPath> {
99 let mut parent = self.clone();
100 if parent.pop() { Some(parent) } else { None }
101 }
102
103 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
105 match &self.0 {
106 VfsPathRepr::PathBuf(p) => p.name_and_extension(),
107 VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
108 }
109 }
110
111 pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
120 let tag = match &self.0 {
121 VfsPathRepr::PathBuf(_) => 0,
122 VfsPathRepr::VirtualPath(_) => 1,
123 };
124 buf.push(tag);
125 match &self.0 {
126 VfsPathRepr::PathBuf(path) => {
127 #[cfg(windows)]
128 {
129 use windows_paths::Encode;
130 let path: &std::path::Path = path.as_ref();
131 let components = path.components();
132 let mut add_sep = false;
133 for component in components {
134 if add_sep {
135 windows_paths::SEP.encode(buf);
136 }
137 let len_before = buf.len();
138 match component {
139 std::path::Component::Prefix(prefix) => {
140 prefix.kind().encode(buf);
142 }
143 std::path::Component::RootDir => {
144 if !add_sep {
145 component.as_os_str().encode(buf);
146 }
147 }
148 _ => component.as_os_str().encode(buf),
149 }
150
151 add_sep = len_before != buf.len();
153 }
154 }
155 #[cfg(unix)]
156 {
157 use std::os::unix::ffi::OsStrExt;
158 buf.extend(path.as_os_str().as_bytes());
159 }
160 #[cfg(not(any(windows, unix)))]
161 {
162 buf.extend(path.as_os_str().to_string_lossy().as_bytes());
163 }
164 }
165 VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()),
166 }
167 }
168}
169
170#[cfg(windows)]
171mod windows_paths {
172 pub(crate) trait Encode {
173 fn encode(&self, buf: &mut Vec<u8>);
174 }
175
176 impl Encode for std::ffi::OsStr {
177 fn encode(&self, buf: &mut Vec<u8>) {
178 use std::os::windows::ffi::OsStrExt;
179 for wchar in self.encode_wide() {
180 buf.extend(wchar.to_le_bytes().iter().copied());
181 }
182 }
183 }
184
185 impl Encode for u8 {
186 fn encode(&self, buf: &mut Vec<u8>) {
187 let wide = *self as u16;
188 buf.extend(wide.to_le_bytes().iter().copied())
189 }
190 }
191
192 impl Encode for &str {
193 fn encode(&self, buf: &mut Vec<u8>) {
194 debug_assert!(self.is_ascii());
195 for b in self.as_bytes() {
196 b.encode(buf)
197 }
198 }
199 }
200
201 pub(crate) const SEP: &str = "\\";
202 const VERBATIM: &str = "\\\\?\\";
203 const UNC: &str = "UNC";
204 const DEVICE: &str = "\\\\.\\";
205 const COLON: &str = ":";
206
207 impl Encode for std::path::Prefix<'_> {
208 fn encode(&self, buf: &mut Vec<u8>) {
209 match self {
210 std::path::Prefix::Verbatim(c) => {
211 VERBATIM.encode(buf);
212 c.encode(buf);
213 }
214 std::path::Prefix::VerbatimUNC(server, share) => {
215 VERBATIM.encode(buf);
216 UNC.encode(buf);
217 SEP.encode(buf);
218 server.encode(buf);
219 SEP.encode(buf);
220 share.encode(buf);
221 }
222 std::path::Prefix::VerbatimDisk(d) => {
223 VERBATIM.encode(buf);
224 d.encode(buf);
225 COLON.encode(buf);
226 }
227 std::path::Prefix::DeviceNS(device) => {
228 DEVICE.encode(buf);
229 device.encode(buf);
230 }
231 std::path::Prefix::UNC(server, share) => {
232 SEP.encode(buf);
233 SEP.encode(buf);
234 server.encode(buf);
235 SEP.encode(buf);
236 share.encode(buf);
237 }
238 std::path::Prefix::Disk(d) => {
239 d.encode(buf);
240 COLON.encode(buf);
241 }
242 }
243 }
244 }
245 #[test]
246 fn paths_encoding() {
247 test_eq("C:/x.rs", "c:/x.rs");
249 test_eq("C:/x/y.rs", "C:\\x\\y.rs");
251
252 fn test_eq(a: &str, b: &str) {
253 let mut b1 = Vec::new();
254 let mut b2 = Vec::new();
255 vfs(a).encode(&mut b1);
256 vfs(b).encode(&mut b2);
257 assert_eq!(b1, b2);
258 }
259 }
260
261 #[test]
262 fn test_sep_root_dir_encoding() {
263 let mut buf = Vec::new();
264 vfs("C:/x/y").encode(&mut buf);
265 assert_eq!(&buf, &[0, 67, 0, 58, 0, 92, 0, 120, 0, 92, 0, 121, 0])
266 }
267
268 #[cfg(test)]
269 fn vfs(str: &str) -> super::VfsPath {
270 use super::{AbsPathBuf, VfsPath};
271 VfsPath::from(AbsPathBuf::try_from(str).unwrap())
272 }
273}
274
275#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
277enum VfsPathRepr {
278 PathBuf(AbsPathBuf),
279 VirtualPath(VirtualPath),
280}
281
282impl From<AbsPathBuf> for VfsPath {
283 fn from(v: AbsPathBuf) -> Self {
284 VfsPath(VfsPathRepr::PathBuf(v.normalize()))
285 }
286}
287
288impl fmt::Display for VfsPath {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match &self.0 {
291 VfsPathRepr::PathBuf(it) => it.fmt(f),
292 VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
293 }
294 }
295}
296
297impl fmt::Debug for VfsPath {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 fmt::Debug::fmt(&self.0, f)
300 }
301}
302
303impl fmt::Debug for VfsPathRepr {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 match &self {
306 VfsPathRepr::PathBuf(it) => it.fmt(f),
307 VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
308 }
309 }
310}
311
312impl PartialEq<AbsPath> for VfsPath {
313 fn eq(&self, other: &AbsPath) -> bool {
314 match &self.0 {
315 VfsPathRepr::PathBuf(lhs) => lhs == other,
316 VfsPathRepr::VirtualPath(_) => false,
317 }
318 }
319}
320impl PartialEq<VfsPath> for AbsPath {
321 fn eq(&self, other: &VfsPath) -> bool {
322 other == self
323 }
324}
325
326#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
330struct VirtualPath(String);
331
332impl VirtualPath {
333 fn starts_with(&self, other: &VirtualPath) -> bool {
335 self.0.starts_with(&other.0)
336 }
337
338 fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> {
339 <_ as AsRef<paths::Utf8Path>>::as_ref(&self.0)
340 .strip_prefix(&base.0)
341 .ok()
342 .map(RelPath::new_unchecked)
343 }
344
345 fn pop(&mut self) -> bool {
362 let pos = match self.0.rfind('/') {
363 Some(pos) => pos,
364 None => return false,
365 };
366 self.0 = self.0[..pos].to_string();
367 true
368 }
369
370 fn join(&self, mut path: &str) -> Option<VirtualPath> {
381 let mut res = self.clone();
382 while path.starts_with("../") {
383 if !res.pop() {
384 return None;
385 }
386 path = &path["../".len()..];
387 }
388 path = path.trim_start_matches("./");
389 res.0 = format!("{}/{path}", res.0);
390 Some(res)
391 }
392
393 fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
404 let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
405 let file_name = match file_path.rfind('/') {
406 Some(position) => &file_path[position + 1..],
407 None => file_path,
408 };
409
410 if file_name.is_empty() {
411 None
412 } else {
413 let mut file_stem_and_extension = file_name.rsplitn(2, '.');
414 let extension = file_stem_and_extension.next();
415 let file_stem = file_stem_and_extension.next();
416
417 match (file_stem, extension) {
418 (None, None) => None,
419 (None | Some(""), Some(_)) => Some((file_name, None)),
420 (Some(file_stem), extension) => Some((file_stem, extension)),
421 }
422 }
423 }
424}
425
426#[cfg(test)]
427mod tests;