ruff_db/system/path.rs
1use camino::{Utf8Path, Utf8PathBuf};
2use std::borrow::Borrow;
3use std::fmt::Formatter;
4use std::ops::Deref;
5use std::path::{Path, PathBuf, StripPrefixError};
6
7/// A slice of a path on [`System`](super::System) (akin to [`str`]).
8///
9/// The path is guaranteed to be valid UTF-8.
10#[repr(transparent)]
11#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
12pub struct SystemPath(Utf8Path);
13
14impl SystemPath {
15 pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
16 let path = path.as_ref();
17 // SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
18 // *const Utf8Path to a *const FsPath is valid.
19 unsafe { &*(path as *const Utf8Path as *const SystemPath) }
20 }
21
22 /// Takes any path, and when possible, converts Windows UNC paths to regular paths.
23 /// If the path can't be converted, it's returned unmodified.
24 ///
25 /// On non-Windows this is no-op.
26 ///
27 /// `\\?\C:\Windows` will be converted to `C:\Windows`,
28 /// but `\\?\C:\COM` will be left as-is (due to a reserved filename).
29 ///
30 /// Use this to pass arbitrary paths to programs that may not be UNC-aware.
31 ///
32 /// It's generally safe to pass UNC paths to legacy programs, because
33 /// these paths contain a reserved prefix, so will gracefully fail
34 /// if used with legacy APIs that don't support UNC.
35 ///
36 /// This function does not perform any I/O.
37 ///
38 /// Currently paths with unpaired surrogates aren't converted even if they
39 /// could be, due to limitations of Rust's `OsStr` API.
40 ///
41 /// To check if a path remained as UNC, use `path.as_os_str().as_encoded_bytes().starts_with(b"\\\\")`.
42 #[inline]
43 pub fn simplified(&self) -> &SystemPath {
44 // SAFETY: simplified only trims the path, that means the returned path must be a valid UTF-8 path.
45 SystemPath::from_std_path(dunce::simplified(self.as_std_path())).unwrap()
46 }
47
48 /// Returns `true` if the `SystemPath` is absolute, i.e., if it is independent of
49 /// the current directory.
50 ///
51 /// * On Unix, a path is absolute if it starts with the root, so
52 /// `is_absolute` and [`has_root`] are equivalent.
53 ///
54 /// * On Windows, a path is absolute if it has a prefix and starts with the
55 /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
56 ///
57 /// # Examples
58 ///
59 /// ```
60 /// use ruff_db::system::SystemPath;
61 ///
62 /// assert!(!SystemPath::new("foo.txt").is_absolute());
63 /// ```
64 ///
65 /// [`has_root`]: Utf8Path::has_root
66 #[inline]
67 #[must_use]
68 pub fn is_absolute(&self) -> bool {
69 self.0.is_absolute()
70 }
71
72 /// Extracts the file extension, if possible.
73 ///
74 /// The extension is:
75 ///
76 /// * [`None`], if there is no file name;
77 /// * [`None`], if there is no embedded `.`;
78 /// * [`None`], if the file name begins with `.` and has no other `.`s within;
79 /// * Otherwise, the portion of the file name after the final `.`
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use ruff_db::system::SystemPath;
85 ///
86 /// assert_eq!("rs", SystemPath::new("foo.rs").extension().unwrap());
87 /// assert_eq!("gz", SystemPath::new("foo.tar.gz").extension().unwrap());
88 /// ```
89 ///
90 /// See [`Path::extension`] for more details.
91 #[inline]
92 #[must_use]
93 pub fn extension(&self) -> Option<&str> {
94 self.0.extension()
95 }
96
97 /// Determines whether `base` is a prefix of `self`.
98 ///
99 /// Only considers whole path components to match.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use ruff_db::system::SystemPath;
105 ///
106 /// let path = SystemPath::new("/etc/passwd");
107 ///
108 /// assert!(path.starts_with("/etc"));
109 /// assert!(path.starts_with("/etc/"));
110 /// assert!(path.starts_with("/etc/passwd"));
111 /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay
112 /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay
113 ///
114 /// assert!(!path.starts_with("/e"));
115 /// assert!(!path.starts_with("/etc/passwd.txt"));
116 ///
117 /// assert!(!SystemPath::new("/etc/foo.rs").starts_with("/etc/foo"));
118 /// ```
119 #[inline]
120 #[must_use]
121 pub fn starts_with(&self, base: impl AsRef<SystemPath>) -> bool {
122 self.0.starts_with(base.as_ref())
123 }
124
125 /// Determines whether `child` is a suffix of `self`.
126 ///
127 /// Only considers whole path components to match.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use ruff_db::system::SystemPath;
133 ///
134 /// let path = SystemPath::new("/etc/resolv.conf");
135 ///
136 /// assert!(path.ends_with("resolv.conf"));
137 /// assert!(path.ends_with("etc/resolv.conf"));
138 /// assert!(path.ends_with("/etc/resolv.conf"));
139 ///
140 /// assert!(!path.ends_with("/resolv.conf"));
141 /// assert!(!path.ends_with("conf")); // use .extension() instead
142 /// ```
143 #[inline]
144 #[must_use]
145 pub fn ends_with(&self, child: impl AsRef<SystemPath>) -> bool {
146 self.0.ends_with(child.as_ref())
147 }
148
149 /// Returns the `FileSystemPath` without its final component, if there is one.
150 ///
151 /// Returns [`None`] if the path terminates in a root or prefix.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use ruff_db::system::SystemPath;
157 ///
158 /// let path = SystemPath::new("/foo/bar");
159 /// let parent = path.parent().unwrap();
160 /// assert_eq!(parent, SystemPath::new("/foo"));
161 ///
162 /// let grand_parent = parent.parent().unwrap();
163 /// assert_eq!(grand_parent, SystemPath::new("/"));
164 /// assert_eq!(grand_parent.parent(), None);
165 /// ```
166 #[inline]
167 #[must_use]
168 pub fn parent(&self) -> Option<&SystemPath> {
169 self.0.parent().map(SystemPath::new)
170 }
171
172 /// Produces an iterator over `SystemPath` and its ancestors.
173 ///
174 /// The iterator will yield the `SystemPath` that is returned if the [`parent`] method is used zero
175 /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`,
176 /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns
177 /// [`None`], the iterator will do likewise. The iterator will always yield at least one value,
178 /// namely `&self`.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use ruff_db::system::SystemPath;
184 ///
185 /// let mut ancestors = SystemPath::new("/foo/bar").ancestors();
186 /// assert_eq!(ancestors.next(), Some(SystemPath::new("/foo/bar")));
187 /// assert_eq!(ancestors.next(), Some(SystemPath::new("/foo")));
188 /// assert_eq!(ancestors.next(), Some(SystemPath::new("/")));
189 /// assert_eq!(ancestors.next(), None);
190 ///
191 /// let mut ancestors = SystemPath::new("../foo/bar").ancestors();
192 /// assert_eq!(ancestors.next(), Some(SystemPath::new("../foo/bar")));
193 /// assert_eq!(ancestors.next(), Some(SystemPath::new("../foo")));
194 /// assert_eq!(ancestors.next(), Some(SystemPath::new("..")));
195 /// assert_eq!(ancestors.next(), Some(SystemPath::new("")));
196 /// assert_eq!(ancestors.next(), None);
197 /// ```
198 ///
199 /// [`parent`]: SystemPath::parent
200 #[inline]
201 pub fn ancestors(&self) -> impl Iterator<Item = &SystemPath> {
202 self.0.ancestors().map(SystemPath::new)
203 }
204
205 /// Produces an iterator over the [`camino::Utf8Component`]s of the path.
206 ///
207 /// When parsing the path, there is a small amount of normalization:
208 ///
209 /// * Repeated separators are ignored, so `a/b` and `a//b` both have
210 /// `a` and `b` as components.
211 ///
212 /// * Occurrences of `.` are normalized away, except if they are at the
213 /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and
214 /// `a/b` all have `a` and `b` as components, but `./a/b` starts with
215 /// an additional [`CurDir`] component.
216 ///
217 /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
218 ///
219 /// Note that no other normalization takes place; in particular, `a/c`
220 /// and `a/b/../c` are distinct, to account for the possibility that `b`
221 /// is a symbolic link (so its parent isn't `a`).
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use camino::{Utf8Component};
227 /// use ruff_db::system::SystemPath;
228 ///
229 /// let mut components = SystemPath::new("/tmp/foo.txt").components();
230 ///
231 /// assert_eq!(components.next(), Some(Utf8Component::RootDir));
232 /// assert_eq!(components.next(), Some(Utf8Component::Normal("tmp")));
233 /// assert_eq!(components.next(), Some(Utf8Component::Normal("foo.txt")));
234 /// assert_eq!(components.next(), None)
235 /// ```
236 ///
237 /// [`CurDir`]: camino::Utf8Component::CurDir
238 #[inline]
239 pub fn components(&self) -> camino::Utf8Components<'_> {
240 self.0.components()
241 }
242
243 /// Returns the final component of the `FileSystemPath`, if there is one.
244 ///
245 /// If the path is a normal file, this is the file name. If it's the path of a directory, this
246 /// is the directory name.
247 ///
248 /// Returns [`None`] if the path terminates in `..`.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use camino::Utf8Path;
254 /// use ruff_db::system::SystemPath;
255 ///
256 /// assert_eq!(Some("bin"), SystemPath::new("/usr/bin/").file_name());
257 /// assert_eq!(Some("foo.txt"), SystemPath::new("tmp/foo.txt").file_name());
258 /// assert_eq!(Some("foo.txt"), SystemPath::new("foo.txt/.").file_name());
259 /// assert_eq!(Some("foo.txt"), SystemPath::new("foo.txt/.//").file_name());
260 /// assert_eq!(None, SystemPath::new("foo.txt/..").file_name());
261 /// assert_eq!(None, SystemPath::new("/").file_name());
262 /// ```
263 #[inline]
264 #[must_use]
265 pub fn file_name(&self) -> Option<&str> {
266 self.0.file_name()
267 }
268
269 /// Extracts the stem (non-extension) portion of [`self.file_name`].
270 ///
271 /// [`self.file_name`]: SystemPath::file_name
272 ///
273 /// The stem is:
274 ///
275 /// * [`None`], if there is no file name;
276 /// * The entire file name if there is no embedded `.`;
277 /// * The entire file name if the file name begins with `.` and has no other `.`s within;
278 /// * Otherwise, the portion of the file name before the final `.`
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// use ruff_db::system::SystemPath;
284 ///
285 /// assert_eq!("foo", SystemPath::new("foo.rs").file_stem().unwrap());
286 /// assert_eq!("foo.tar", SystemPath::new("foo.tar.gz").file_stem().unwrap());
287 /// ```
288 #[inline]
289 #[must_use]
290 pub fn file_stem(&self) -> Option<&str> {
291 self.0.file_stem()
292 }
293
294 /// Returns a path that, when joined onto `base`, yields `self`.
295 ///
296 /// # Errors
297 ///
298 /// If `base` is not a prefix of `self` (i.e., [`starts_with`]
299 /// returns `false`), returns [`Err`].
300 ///
301 /// [`starts_with`]: SystemPath::starts_with
302 ///
303 /// # Examples
304 ///
305 /// ```
306 /// use ruff_db::system::{SystemPath, SystemPathBuf};
307 ///
308 /// let path = SystemPath::new("/test/haha/foo.txt");
309 ///
310 /// assert_eq!(path.strip_prefix("/"), Ok(SystemPath::new("test/haha/foo.txt")));
311 /// assert_eq!(path.strip_prefix("/test"), Ok(SystemPath::new("haha/foo.txt")));
312 /// assert_eq!(path.strip_prefix("/test/"), Ok(SystemPath::new("haha/foo.txt")));
313 /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(SystemPath::new("")));
314 /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(SystemPath::new("")));
315 ///
316 /// assert!(path.strip_prefix("test").is_err());
317 /// assert!(path.strip_prefix("/haha").is_err());
318 ///
319 /// let prefix = SystemPathBuf::from("/test/");
320 /// assert_eq!(path.strip_prefix(prefix), Ok(SystemPath::new("haha/foo.txt")));
321 /// ```
322 #[inline]
323 pub fn strip_prefix(
324 &self,
325 base: impl AsRef<SystemPath>,
326 ) -> std::result::Result<&SystemPath, StripPrefixError> {
327 self.0.strip_prefix(base.as_ref()).map(SystemPath::new)
328 }
329
330 /// Creates an owned [`SystemPathBuf`] with `path` adjoined to `self`.
331 ///
332 /// See [`std::path::PathBuf::push`] for more details on what it means to adjoin a path.
333 ///
334 /// # Examples
335 ///
336 /// ```
337 /// use ruff_db::system::{SystemPath, SystemPathBuf};
338 ///
339 /// assert_eq!(SystemPath::new("/etc").join("passwd"), SystemPathBuf::from("/etc/passwd"));
340 /// ```
341 #[inline]
342 #[must_use]
343 pub fn join(&self, path: impl AsRef<SystemPath>) -> SystemPathBuf {
344 SystemPathBuf::from_utf8_path_buf(self.0.join(&path.as_ref().0))
345 }
346
347 /// Creates an owned [`SystemPathBuf`] like `self` but with the given extension.
348 ///
349 /// See [`std::path::PathBuf::set_extension`] for more details.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use ruff_db::system::{SystemPath, SystemPathBuf};
355 ///
356 /// let path = SystemPath::new("foo.rs");
357 /// assert_eq!(path.with_extension("txt"), SystemPathBuf::from("foo.txt"));
358 ///
359 /// let path = SystemPath::new("foo.tar.gz");
360 /// assert_eq!(path.with_extension(""), SystemPathBuf::from("foo.tar"));
361 /// assert_eq!(path.with_extension("xz"), SystemPathBuf::from("foo.tar.xz"));
362 /// assert_eq!(path.with_extension("").with_extension("txt"), SystemPathBuf::from("foo.txt"));
363 /// ```
364 #[inline]
365 pub fn with_extension(&self, extension: &str) -> SystemPathBuf {
366 SystemPathBuf::from_utf8_path_buf(self.0.with_extension(extension))
367 }
368
369 /// Converts the path to an owned [`SystemPathBuf`].
370 pub fn to_path_buf(&self) -> SystemPathBuf {
371 SystemPathBuf(self.0.to_path_buf())
372 }
373
374 /// Returns the path as a string slice.
375 #[inline]
376 pub fn as_str(&self) -> &str {
377 self.0.as_str()
378 }
379
380 /// Returns the std path for the file.
381 #[inline]
382 pub fn as_std_path(&self) -> &Path {
383 self.0.as_std_path()
384 }
385
386 /// Returns the [`Utf8Path`] for the file.
387 #[inline]
388 pub fn as_utf8_path(&self) -> &Utf8Path {
389 &self.0
390 }
391
392 pub fn from_std_path(path: &Path) -> Option<&SystemPath> {
393 Some(SystemPath::new(Utf8Path::from_path(path)?))
394 }
395
396 /// Makes a path absolute and normalizes it without accessing the file system.
397 ///
398 /// Adapted from [cargo](https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61)
399 ///
400 /// # Examples
401 ///
402 /// ## Posix paths
403 ///
404 /// ```
405 /// # #[cfg(unix)]
406 /// # fn main() {
407 /// use ruff_db::system::{SystemPath, SystemPathBuf};
408 ///
409 /// // Relative to absolute
410 /// let absolute = SystemPath::absolute("foo/./bar", "/tmp");
411 /// assert_eq!(absolute, SystemPathBuf::from("/tmp/foo/bar"));
412 ///
413 /// // Path's going past the root are normalized to the root
414 /// let absolute = SystemPath::absolute("../../../", "/tmp");
415 /// assert_eq!(absolute, SystemPathBuf::from("/"));
416 ///
417 /// // Absolute to absolute
418 /// let absolute = SystemPath::absolute("/foo//test/.././bar.rs", "/tmp");
419 /// assert_eq!(absolute, SystemPathBuf::from("/foo/bar.rs"));
420 /// # }
421 /// # #[cfg(not(unix))]
422 /// # fn main() {}
423 /// ```
424 ///
425 /// ## Windows paths
426 ///
427 /// ```
428 /// # #[cfg(windows)]
429 /// # fn main() {
430 /// use ruff_db::system::{SystemPath, SystemPathBuf};
431 ///
432 /// // Relative to absolute
433 /// let absolute = SystemPath::absolute(r"foo\.\bar", r"C:\tmp");
434 /// assert_eq!(absolute, SystemPathBuf::from(r"C:\tmp\foo\bar"));
435 ///
436 /// // Path's going past the root are normalized to the root
437 /// let absolute = SystemPath::absolute(r"..\..\..\", r"C:\tmp");
438 /// assert_eq!(absolute, SystemPathBuf::from(r"C:\"));
439 ///
440 /// // Absolute to absolute
441 /// let absolute = SystemPath::absolute(r"C:\foo//test\..\./bar.rs", r"C:\tmp");
442 /// assert_eq!(absolute, SystemPathBuf::from(r"C:\foo\bar.rs"));
443 /// # }
444 /// # #[cfg(not(windows))]
445 /// # fn main() {}
446 /// ```
447 pub fn absolute(path: impl AsRef<SystemPath>, cwd: impl AsRef<SystemPath>) -> SystemPathBuf {
448 fn absolute(path: &SystemPath, cwd: &SystemPath) -> SystemPathBuf {
449 let path = &path.0;
450
451 let mut components = path.components().peekable();
452 let mut ret = if let Some(
453 c @ (camino::Utf8Component::Prefix(..) | camino::Utf8Component::RootDir),
454 ) = components.peek().cloned()
455 {
456 components.next();
457 Utf8PathBuf::from(c.as_str())
458 } else {
459 cwd.0.to_path_buf()
460 };
461
462 for component in components {
463 match component {
464 camino::Utf8Component::Prefix(..) => unreachable!(),
465 camino::Utf8Component::RootDir => {
466 ret.push(component);
467 }
468 camino::Utf8Component::CurDir => {}
469 camino::Utf8Component::ParentDir => {
470 ret.pop();
471 }
472 camino::Utf8Component::Normal(c) => {
473 ret.push(c);
474 }
475 }
476 }
477
478 SystemPathBuf::from_utf8_path_buf(ret)
479 }
480
481 absolute(path.as_ref(), cwd.as_ref())
482 }
483}
484
485impl ToOwned for SystemPath {
486 type Owned = SystemPathBuf;
487
488 fn to_owned(&self) -> Self::Owned {
489 self.to_path_buf()
490 }
491}
492
493/// An owned, mutable path on [`System`](`super::System`) (akin to [`String`]).
494///
495/// The path is guaranteed to be valid UTF-8.
496#[repr(transparent)]
497#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
498#[cfg_attr(
499 feature = "serde",
500 derive(serde::Serialize, serde::Deserialize),
501 serde(transparent)
502)]
503#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
504pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
505
506impl get_size2::GetSize for SystemPathBuf {
507 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
508 (self.0.capacity(), tracker)
509 }
510}
511
512impl SystemPathBuf {
513 pub fn new() -> Self {
514 Self(Utf8PathBuf::new())
515 }
516
517 pub fn from_utf8_path_buf(path: Utf8PathBuf) -> Self {
518 Self(path)
519 }
520
521 pub fn from_path_buf(
522 path: std::path::PathBuf,
523 ) -> std::result::Result<Self, std::path::PathBuf> {
524 Utf8PathBuf::from_path_buf(path).map(Self)
525 }
526
527 /// Try to convert from `path` directly, falling back to the lossy string representation on
528 /// error.
529 pub fn from_path_buf_lossy(path: std::path::PathBuf) -> Self {
530 Self::from_path_buf(path)
531 .unwrap_or_else(|path| Self::from(path.to_string_lossy().to_string()))
532 }
533
534 /// Extends `self` with `path`.
535 ///
536 /// If `path` is absolute, it replaces the current path.
537 ///
538 /// On Windows:
539 ///
540 /// * if `path` has a root but no prefix (e.g., `\windows`), it
541 /// replaces everything except for the prefix (if any) of `self`.
542 /// * if `path` has a prefix but no root, it replaces `self`.
543 ///
544 /// # Examples
545 ///
546 /// Pushing a relative path extends the existing path:
547 ///
548 /// ```
549 /// use ruff_db::system::SystemPathBuf;
550 ///
551 /// let mut path = SystemPathBuf::from("/tmp");
552 /// path.push("file.bk");
553 /// assert_eq!(path, SystemPathBuf::from("/tmp/file.bk"));
554 /// ```
555 ///
556 /// Pushing an absolute path replaces the existing path:
557 ///
558 /// ```
559 ///
560 /// use ruff_db::system::SystemPathBuf;
561 ///
562 /// let mut path = SystemPathBuf::from("/tmp");
563 /// path.push("/etc");
564 /// assert_eq!(path, SystemPathBuf::from("/etc"));
565 /// ```
566 pub fn push(&mut self, path: impl AsRef<SystemPath>) {
567 self.0.push(&path.as_ref().0);
568 }
569
570 pub fn into_utf8_path_buf(self) -> Utf8PathBuf {
571 self.0
572 }
573
574 pub fn into_std_path_buf(self) -> PathBuf {
575 self.0.into_std_path_buf()
576 }
577
578 pub fn into_string(self) -> String {
579 self.0.into_string()
580 }
581
582 #[inline]
583 pub fn as_path(&self) -> &SystemPath {
584 SystemPath::new(&self.0)
585 }
586}
587
588impl From<&SystemPath> for Box<SystemPath> {
589 fn from(path: &SystemPath) -> Self {
590 Box::from(path.to_path_buf())
591 }
592}
593
594impl From<SystemPathBuf> for Box<SystemPath> {
595 fn from(path: SystemPathBuf) -> Self {
596 let path = Box::into_raw(path.0.into_boxed_path()) as *mut SystemPath;
597 // SAFETY: SystemPath is marked as #[repr(transparent)] so the conversion from a
598 // *mut Utf8Path to a *mut SystemPath is valid.
599 unsafe { Box::from_raw(path) }
600 }
601}
602
603impl Clone for Box<SystemPath> {
604 fn clone(&self) -> Self {
605 Box::from(&**self)
606 }
607}
608
609impl get_size2::GetSize for Box<SystemPath> {
610 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
611 (std::mem::size_of_val(&**self), tracker)
612 }
613}
614
615impl Borrow<SystemPath> for SystemPathBuf {
616 fn borrow(&self) -> &SystemPath {
617 self.as_path()
618 }
619}
620
621impl From<&str> for SystemPathBuf {
622 fn from(value: &str) -> Self {
623 SystemPathBuf::from_utf8_path_buf(Utf8PathBuf::from(value))
624 }
625}
626
627impl From<String> for SystemPathBuf {
628 fn from(value: String) -> Self {
629 SystemPathBuf::from_utf8_path_buf(Utf8PathBuf::from(value))
630 }
631}
632
633impl Default for SystemPathBuf {
634 fn default() -> Self {
635 Self::new()
636 }
637}
638
639impl AsRef<SystemPath> for SystemPathBuf {
640 #[inline]
641 fn as_ref(&self) -> &SystemPath {
642 self.as_path()
643 }
644}
645
646impl AsRef<SystemPath> for SystemPath {
647 #[inline]
648 fn as_ref(&self) -> &SystemPath {
649 self
650 }
651}
652
653impl AsRef<SystemPath> for Utf8Path {
654 #[inline]
655 fn as_ref(&self) -> &SystemPath {
656 SystemPath::new(self)
657 }
658}
659
660impl AsRef<SystemPath> for Utf8PathBuf {
661 #[inline]
662 fn as_ref(&self) -> &SystemPath {
663 SystemPath::new(self.as_path())
664 }
665}
666
667impl AsRef<SystemPath> for camino::Utf8Component<'_> {
668 #[inline]
669 fn as_ref(&self) -> &SystemPath {
670 SystemPath::new(self.as_str())
671 }
672}
673
674impl AsRef<SystemPath> for str {
675 #[inline]
676 fn as_ref(&self) -> &SystemPath {
677 SystemPath::new(self)
678 }
679}
680
681impl AsRef<SystemPath> for String {
682 #[inline]
683 fn as_ref(&self) -> &SystemPath {
684 SystemPath::new(self)
685 }
686}
687
688impl AsRef<Path> for SystemPath {
689 #[inline]
690 fn as_ref(&self) -> &Path {
691 self.0.as_std_path()
692 }
693}
694
695impl Deref for SystemPathBuf {
696 type Target = SystemPath;
697
698 #[inline]
699 fn deref(&self) -> &Self::Target {
700 self.as_path()
701 }
702}
703
704impl AsRef<Path> for SystemPathBuf {
705 #[inline]
706 fn as_ref(&self) -> &Path {
707 self.0.as_std_path()
708 }
709}
710
711impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
712 fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
713 let mut buf = SystemPathBuf::new();
714 buf.extend(iter);
715 buf
716 }
717}
718
719impl<P: AsRef<SystemPath>> Extend<P> for SystemPathBuf {
720 fn extend<I: IntoIterator<Item = P>>(&mut self, iter: I) {
721 for path in iter {
722 self.push(path);
723 }
724 }
725}
726
727impl std::fmt::Debug for SystemPath {
728 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729 self.0.fmt(f)
730 }
731}
732
733impl std::fmt::Display for SystemPath {
734 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
735 self.0.fmt(f)
736 }
737}
738
739impl std::fmt::Debug for SystemPathBuf {
740 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
741 self.0.fmt(f)
742 }
743}
744
745impl std::fmt::Display for SystemPathBuf {
746 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
747 self.0.fmt(f)
748 }
749}
750
751#[cfg(feature = "cache")]
752impl ruff_cache::CacheKey for SystemPath {
753 fn cache_key(&self, hasher: &mut ruff_cache::CacheKeyHasher) {
754 self.0.as_str().cache_key(hasher);
755 }
756}
757
758#[cfg(feature = "cache")]
759impl ruff_cache::CacheKey for SystemPathBuf {
760 fn cache_key(&self, hasher: &mut ruff_cache::CacheKeyHasher) {
761 self.as_path().cache_key(hasher);
762 }
763}
764
765/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
766#[repr(transparent)]
767#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
768pub struct SystemVirtualPath(str);
769
770impl SystemVirtualPath {
771 pub const fn new(path: &str) -> &SystemVirtualPath {
772 // SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
773 // *const str to a *const SystemVirtualPath is valid.
774 unsafe { &*(path as *const str as *const SystemVirtualPath) }
775 }
776
777 /// Converts the path to an owned [`SystemVirtualPathBuf`].
778 pub fn to_path_buf(&self) -> SystemVirtualPathBuf {
779 SystemVirtualPathBuf(self.0.to_string())
780 }
781
782 /// Extracts the file extension, if possible.
783 ///
784 /// # Examples
785 ///
786 /// ```
787 /// use ruff_db::system::SystemVirtualPath;
788 ///
789 /// assert_eq!(None, SystemVirtualPath::new("untitled:Untitled-1").extension());
790 /// assert_eq!("ipynb", SystemVirtualPath::new("untitled:Untitled-1.ipynb").extension().unwrap());
791 /// assert_eq!("ipynb", SystemVirtualPath::new("vscode-notebook-cell:Untitled-1.ipynb").extension().unwrap());
792 /// ```
793 ///
794 /// See [`Path::extension`] for more details.
795 pub fn extension(&self) -> Option<&str> {
796 Path::new(&self.0).extension().and_then(|ext| ext.to_str())
797 }
798
799 /// Returns the path as a string slice.
800 #[inline]
801 pub fn as_str(&self) -> &str {
802 &self.0
803 }
804}
805
806/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
807#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord, get_size2::GetSize)]
808pub struct SystemVirtualPathBuf(String);
809
810impl SystemVirtualPathBuf {
811 #[inline]
812 pub const fn as_path(&self) -> &SystemVirtualPath {
813 SystemVirtualPath::new(self.0.as_str())
814 }
815}
816
817impl From<&SystemVirtualPath> for Box<SystemVirtualPath> {
818 fn from(path: &SystemVirtualPath) -> Self {
819 Box::from(path.to_path_buf())
820 }
821}
822
823impl From<SystemVirtualPathBuf> for Box<SystemVirtualPath> {
824 fn from(path: SystemVirtualPathBuf) -> Self {
825 let path = Box::into_raw(path.0.into_boxed_str()) as *mut SystemVirtualPath;
826 // SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
827 // *mut str to a *mut SystemVirtualPath is valid.
828 unsafe { Box::from_raw(path) }
829 }
830}
831
832impl Clone for Box<SystemVirtualPath> {
833 fn clone(&self) -> Self {
834 Box::from(&**self)
835 }
836}
837
838impl get_size2::GetSize for Box<SystemVirtualPath> {
839 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
840 (std::mem::size_of_val(&**self), tracker)
841 }
842}
843
844impl From<String> for SystemVirtualPathBuf {
845 fn from(value: String) -> Self {
846 SystemVirtualPathBuf(value)
847 }
848}
849
850impl AsRef<SystemVirtualPath> for SystemVirtualPathBuf {
851 #[inline]
852 fn as_ref(&self) -> &SystemVirtualPath {
853 self.as_path()
854 }
855}
856
857impl AsRef<SystemVirtualPath> for SystemVirtualPath {
858 #[inline]
859 fn as_ref(&self) -> &SystemVirtualPath {
860 self
861 }
862}
863
864impl AsRef<SystemVirtualPath> for str {
865 #[inline]
866 fn as_ref(&self) -> &SystemVirtualPath {
867 SystemVirtualPath::new(self)
868 }
869}
870
871impl AsRef<SystemVirtualPath> for String {
872 #[inline]
873 fn as_ref(&self) -> &SystemVirtualPath {
874 SystemVirtualPath::new(self)
875 }
876}
877
878impl Deref for SystemVirtualPathBuf {
879 type Target = SystemVirtualPath;
880
881 fn deref(&self) -> &Self::Target {
882 self.as_path()
883 }
884}
885
886impl std::fmt::Debug for SystemVirtualPath {
887 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
888 self.0.fmt(f)
889 }
890}
891
892impl std::fmt::Display for SystemVirtualPath {
893 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
894 self.0.fmt(f)
895 }
896}
897
898impl std::fmt::Debug for SystemVirtualPathBuf {
899 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
900 self.0.fmt(f)
901 }
902}
903
904impl std::fmt::Display for SystemVirtualPathBuf {
905 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
906 self.0.fmt(f)
907 }
908}
909
910#[cfg(feature = "cache")]
911impl ruff_cache::CacheKey for SystemVirtualPath {
912 fn cache_key(&self, hasher: &mut ruff_cache::CacheKeyHasher) {
913 self.as_str().cache_key(hasher);
914 }
915}
916
917#[cfg(feature = "cache")]
918impl ruff_cache::CacheKey for SystemVirtualPathBuf {
919 fn cache_key(&self, hasher: &mut ruff_cache::CacheKeyHasher) {
920 self.as_path().cache_key(hasher);
921 }
922}
923
924impl Borrow<SystemVirtualPath> for SystemVirtualPathBuf {
925 fn borrow(&self) -> &SystemVirtualPath {
926 self.as_path()
927 }
928}
929
930/// Deduplicates identical paths and removes nested paths.
931///
932/// # Examples
933/// ```rust
934/// use ruff_db::system::{SystemPath, deduplicate_nested_paths};///
935///
936/// let paths = vec![SystemPath::new("/a/b/c"), SystemPath::new("/a/b"), SystemPath::new("/a/beta"), SystemPath::new("/a/b/c")];
937/// assert_eq!(deduplicate_nested_paths(paths).collect::<Vec<_>>(), &[SystemPath::new("/a/b"), SystemPath::new("/a/beta")]);
938/// ```
939pub fn deduplicate_nested_paths<P, I>(paths: I) -> DeduplicatedNestedPathsIter<P>
940where
941 I: IntoIterator<Item = P>,
942 P: AsRef<SystemPath>,
943{
944 DeduplicatedNestedPathsIter::new(paths)
945}
946
947pub struct DeduplicatedNestedPathsIter<P> {
948 inner: std::vec::IntoIter<P>,
949 next: Option<P>,
950}
951
952impl<P> DeduplicatedNestedPathsIter<P>
953where
954 P: AsRef<SystemPath>,
955{
956 fn new<I>(paths: I) -> Self
957 where
958 I: IntoIterator<Item = P>,
959 {
960 let mut paths = paths.into_iter().collect::<Vec<_>>();
961 // Sort the path to ensure that e.g. `/a/b/c`, comes right after `/a/b`.
962 paths.sort_unstable_by(|left, right| left.as_ref().cmp(right.as_ref()));
963
964 let mut iter = paths.into_iter();
965
966 Self {
967 next: iter.next(),
968 inner: iter,
969 }
970 }
971}
972
973impl<P> Iterator for DeduplicatedNestedPathsIter<P>
974where
975 P: AsRef<SystemPath>,
976{
977 type Item = P;
978
979 fn next(&mut self) -> Option<Self::Item> {
980 let current = self.next.take()?;
981
982 for next in self.inner.by_ref() {
983 // Skip all paths that have the same prefix as the current path
984 if !next.as_ref().starts_with(current.as_ref()) {
985 self.next = Some(next);
986 break;
987 }
988 }
989
990 Some(current)
991 }
992}