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 into_abs_path(self) -> Option<AbsPathBuf> {
43 match self.0 {
44 VfsPathRepr::PathBuf(it) => Some(it),
45 VfsPathRepr::VirtualPath(_) => None,
46 }
47 }
48
49 pub fn join(&self, path: &str) -> Option<VfsPath> {
51 match &self.0 {
52 VfsPathRepr::PathBuf(it) => {
53 let res = it.join(path).normalize();
54 Some(VfsPath(VfsPathRepr::PathBuf(res)))
55 }
56 VfsPathRepr::VirtualPath(it) => {
57 let res = it.join(path)?;
58 Some(VfsPath(VfsPathRepr::VirtualPath(res)))
59 }
60 }
61 }
62
63 pub fn pop(&mut self) -> bool {
79 match &mut self.0 {
80 VfsPathRepr::PathBuf(it) => it.pop(),
81 VfsPathRepr::VirtualPath(it) => it.pop(),
82 }
83 }
84
85 pub fn starts_with(&self, other: &VfsPath) -> bool {
87 match (&self.0, &other.0) {
88 (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
89 (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
90 (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => false,
91 }
92 }
93
94 pub fn strip_prefix(&self, other: &VfsPath) -> Option<&RelPath> {
95 match (&self.0, &other.0) {
96 (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.strip_prefix(rhs),
97 (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.strip_prefix(rhs),
98 (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => None,
99 }
100 }
101
102 pub fn parent(&self) -> Option<VfsPath> {
106 let mut parent = self.clone();
107 if parent.pop() { Some(parent) } else { None }
108 }
109
110 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
112 match &self.0 {
113 VfsPathRepr::PathBuf(p) => p.name_and_extension(),
114 VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
115 }
116 }
117
118 pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
127 let tag = match &self.0 {
128 VfsPathRepr::PathBuf(_) => 0,
129 VfsPathRepr::VirtualPath(_) => 1,
130 };
131 buf.push(tag);
132 match &self.0 {
133 VfsPathRepr::PathBuf(path) => {
134 #[cfg(windows)]
135 {
136 use windows_paths::Encode;
137 let path: &std::path::Path = path.as_ref();
138 let components = path.components();
139 let mut add_sep = false;
140 for component in components {
141 if add_sep {
142 windows_paths::SEP.encode(buf);
143 }
144 let len_before = buf.len();
145 match component {
146 std::path::Component::Prefix(prefix) => {
147 prefix.kind().encode(buf);
149 }
150 std::path::Component::RootDir => {
151 if !add_sep {
152 component.as_os_str().encode(buf);
153 }
154 }
155 _ => component.as_os_str().encode(buf),
156 }
157
158 add_sep = len_before != buf.len();
160 }
161 }
162 #[cfg(unix)]
163 {
164 use std::os::unix::ffi::OsStrExt;
165 buf.extend(path.as_os_str().as_bytes());
166 }
167 #[cfg(not(any(windows, unix)))]
168 {
169 buf.extend(path.as_os_str().to_string_lossy().as_bytes());
170 }
171 }
172 VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()),
173 }
174 }
175}
176
177#[cfg(windows)]
178mod windows_paths {
179 pub(crate) trait Encode {
180 fn encode(&self, buf: &mut Vec<u8>);
181 }
182
183 impl Encode for std::ffi::OsStr {
184 fn encode(&self, buf: &mut Vec<u8>) {
185 use std::os::windows::ffi::OsStrExt;
186 for wchar in self.encode_wide() {
187 buf.extend(wchar.to_le_bytes().iter().copied());
188 }
189 }
190 }
191
192 impl Encode for u8 {
193 fn encode(&self, buf: &mut Vec<u8>) {
194 let wide = *self as u16;
195 buf.extend(wide.to_le_bytes().iter().copied())
196 }
197 }
198
199 impl Encode for &str {
200 fn encode(&self, buf: &mut Vec<u8>) {
201 debug_assert!(self.is_ascii());
202 for b in self.as_bytes() {
203 b.encode(buf)
204 }
205 }
206 }
207
208 pub(crate) const SEP: &str = "\\";
209 const VERBATIM: &str = "\\\\?\\";
210 const UNC: &str = "UNC";
211 const DEVICE: &str = "\\\\.\\";
212 const COLON: &str = ":";
213
214 impl Encode for std::path::Prefix<'_> {
215 fn encode(&self, buf: &mut Vec<u8>) {
216 match self {
217 std::path::Prefix::Verbatim(c) => {
218 VERBATIM.encode(buf);
219 c.encode(buf);
220 }
221 std::path::Prefix::VerbatimUNC(server, share) => {
222 VERBATIM.encode(buf);
223 UNC.encode(buf);
224 SEP.encode(buf);
225 server.encode(buf);
226 SEP.encode(buf);
227 share.encode(buf);
228 }
229 std::path::Prefix::VerbatimDisk(d) => {
230 VERBATIM.encode(buf);
231 d.encode(buf);
232 COLON.encode(buf);
233 }
234 std::path::Prefix::DeviceNS(device) => {
235 DEVICE.encode(buf);
236 device.encode(buf);
237 }
238 std::path::Prefix::UNC(server, share) => {
239 SEP.encode(buf);
240 SEP.encode(buf);
241 server.encode(buf);
242 SEP.encode(buf);
243 share.encode(buf);
244 }
245 std::path::Prefix::Disk(d) => {
246 d.encode(buf);
247 COLON.encode(buf);
248 }
249 }
250 }
251 }
252 #[test]
253 fn paths_encoding() {
254 test_eq("C:/x.rs", "c:/x.rs");
256 test_eq("C:/x/y.rs", "C:\\x\\y.rs");
258
259 fn test_eq(a: &str, b: &str) {
260 let mut b1 = Vec::new();
261 let mut b2 = Vec::new();
262 vfs(a).encode(&mut b1);
263 vfs(b).encode(&mut b2);
264 assert_eq!(b1, b2);
265 }
266 }
267
268 #[test]
269 fn test_sep_root_dir_encoding() {
270 let mut buf = Vec::new();
271 vfs("C:/x/y").encode(&mut buf);
272 assert_eq!(&buf, &[0, 67, 0, 58, 0, 92, 0, 120, 0, 92, 0, 121, 0])
273 }
274
275 #[cfg(test)]
276 fn vfs(str: &str) -> super::VfsPath {
277 use super::{AbsPathBuf, VfsPath};
278 VfsPath::from(AbsPathBuf::try_from(str).unwrap())
279 }
280}
281
282#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
284enum VfsPathRepr {
285 PathBuf(AbsPathBuf),
286 VirtualPath(VirtualPath),
287}
288
289impl From<AbsPathBuf> for VfsPath {
290 fn from(v: AbsPathBuf) -> Self {
291 VfsPath(VfsPathRepr::PathBuf(v.normalize()))
292 }
293}
294
295impl fmt::Display for VfsPath {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 match &self.0 {
298 VfsPathRepr::PathBuf(it) => it.fmt(f),
299 VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
300 }
301 }
302}
303
304impl fmt::Debug for VfsPath {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 fmt::Debug::fmt(&self.0, f)
307 }
308}
309
310impl fmt::Debug for VfsPathRepr {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 match &self {
313 VfsPathRepr::PathBuf(it) => it.fmt(f),
314 VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
315 }
316 }
317}
318
319impl PartialEq<AbsPath> for VfsPath {
320 fn eq(&self, other: &AbsPath) -> bool {
321 match &self.0 {
322 VfsPathRepr::PathBuf(lhs) => lhs == other,
323 VfsPathRepr::VirtualPath(_) => false,
324 }
325 }
326}
327impl PartialEq<VfsPath> for AbsPath {
328 fn eq(&self, other: &VfsPath) -> bool {
329 other == self
330 }
331}
332
333#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
337struct VirtualPath(String);
338
339impl VirtualPath {
340 fn starts_with(&self, other: &VirtualPath) -> bool {
342 self.0.starts_with(&other.0)
343 }
344
345 fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> {
346 <_ as AsRef<paths::Utf8Path>>::as_ref(&self.0)
347 .strip_prefix(&base.0)
348 .ok()
349 .map(RelPath::new_unchecked)
350 }
351
352 fn pop(&mut self) -> bool {
369 let pos = match self.0.rfind('/') {
370 Some(pos) => pos,
371 None => return false,
372 };
373 self.0 = self.0[..pos].to_string();
374 true
375 }
376
377 fn join(&self, mut path: &str) -> Option<VirtualPath> {
388 let mut res = self.clone();
389 while path.starts_with("../") {
390 if !res.pop() {
391 return None;
392 }
393 path = &path["../".len()..];
394 }
395 path = path.trim_start_matches("./");
396 res.0 = format!("{}/{path}", res.0);
397 Some(res)
398 }
399
400 fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
411 let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
412 let file_name = match file_path.rfind('/') {
413 Some(position) => &file_path[position + 1..],
414 None => file_path,
415 };
416
417 if file_name.is_empty() {
418 None
419 } else {
420 let mut file_stem_and_extension = file_name.rsplitn(2, '.');
421 let extension = file_stem_and_extension.next();
422 let file_stem = file_stem_and_extension.next();
423
424 match (file_stem, extension) {
425 (None, None) => None,
426 (None | Some(""), Some(_)) => Some((file_name, None)),
427 (Some(file_stem), extension) => Some((file_stem, extension)),
428 }
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests;