oxedyne/fe2o3/fe2o3_core/src/path.rs
4.3 KiB, 3 runs
created by r1870400018:114, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use crate::prelude::*; |
| 2 | |
| 3 | use std::{ |
| 4 | ops::Deref, |
| 5 | path::{ |
| 6 | Component, |
| 7 | Path, |
| 8 | PathBuf, |
| 9 | }, |
| 10 | }; |
| 11 | |
| 12 | |
| 13 | /// Check that the string contains no path components and is just a filename. |
| 14 | pub fn is_filename(name: &str) -> bool { |
| 15 | !name.contains(std::path::MAIN_SEPARATOR) && |
| 16 | cfg!(not(windows)) || !name.contains('/') |
| 17 | } |
| 18 | |
| 19 | /// Normalise a relative path without reference to the state of the file system by: |
| 20 | /// - Ensure path begins with "." or "..", e.g. /a/b -> ./a/b, ./../a -> ../a |
| 21 | /// - Eliminate redundant path components, e.g. a/b/../c -> ./a/c |
| 22 | /// - Eliminate trailing separator, e.g. a/b/ -> ./a/b |
| 23 | /// The result will always begin with either "." or a sequence of "..". Neither will appear |
| 24 | /// elsewhere in the path. |
| 25 | pub trait NormalPath { |
| 26 | fn normalise(&self) -> NormPathBuf; |
| 27 | } |
| 28 | |
| 29 | new_type_priv!(NormPathBuf, PathBuf, Clone, Debug); |
| 30 | |
| 31 | /// A `NormPathBuf` wraps a `PathBuf` with some extra methods. Making the inner object private |
| 32 | /// ensures that a `NormPathBuf` can generally only be created by normalising a `Path`. |
| 33 | impl NormPathBuf { |
| 34 | /// Test whether the normalised `PathBuf` escapes upwards out of the current directory. |
| 35 | pub fn escapes(&self) -> bool { |
| 36 | match self.components().next() { |
| 37 | Some(std::path::Component::ParentDir) => true, |
| 38 | _ => false, |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | /// Remove leading "." and ".." components, and trailing separator. We know that `self` is |
| 43 | /// normalised, so all of these components will be at the front of the path. |
| 44 | pub fn remove_relative(self) -> Self { |
| 45 | let mut components = self.0.components(); |
| 46 | let mut pbuf = PathBuf::new(); |
| 47 | loop { |
| 48 | let component = components.next(); |
| 49 | //debug!(" component = {:?}", component); |
| 50 | match component { |
| 51 | Some(Component::CurDir) | Some(Component::ParentDir) => continue, |
| 52 | _ => { |
| 53 | pbuf.extend(component); |
| 54 | pbuf.extend(components); |
| 55 | break |
| 56 | }, |
| 57 | } |
| 58 | } |
| 59 | Self(pbuf) |
| 60 | } |
| 61 | |
| 62 | /// Ensure the path begins with "/". after removing relative components. |
| 63 | pub fn absolute(self) -> Self { |
| 64 | let path = self.remove_relative(); |
| 65 | let mut components = path.0.components(); |
| 66 | match components.next() { |
| 67 | Some(Component::RootDir) => path, |
| 68 | component => { |
| 69 | let mut pbuf = PathBuf::from("/"); |
| 70 | pbuf.extend(component); |
| 71 | pbuf.extend(components); |
| 72 | Self(pbuf) |
| 73 | }, |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | pub fn join(mut self, rel_path: Self) -> Self { |
| 78 | self.0.push(rel_path.0); |
| 79 | Self(self.0) |
| 80 | } |
| 81 | |
| 82 | pub fn as_pathbuf(self) -> PathBuf { self.0 } |
| 83 | } |
| 84 | |
| 85 | impl<T: ?Sized> AsRef<T> for NormPathBuf where <NormPathBuf as Deref>::Target: AsRef<T> { |
| 86 | fn as_ref(&self) -> &T { |
| 87 | self.deref().as_ref() |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | impl NormalPath for Path { |
| 92 | |
| 93 | fn normalise(&self) -> NormPathBuf { |
| 94 | let mut normalised = PathBuf::new(); |
| 95 | |
| 96 | let mut first = true; |
| 97 | for component in self.components() { |
| 98 | //debug!(">> {:?} first={}", component, first); |
| 99 | match component { |
| 100 | Component::RootDir => { |
| 101 | normalised.push("."); |
| 102 | }, |
| 103 | Component::ParentDir => { |
| 104 | if normalised.as_os_str().is_empty() || normalised == Path::new("..") |
| 105 | { |
| 106 | normalised.push(component.as_os_str()); |
| 107 | } else if normalised == Path::new(".") { |
| 108 | // Replace "." with ".." |
| 109 | normalised.pop(); |
| 110 | normalised.push(component.as_os_str()); |
| 111 | } else { |
| 112 | normalised.pop(); |
| 113 | } |
| 114 | }, |
| 115 | Component::CurDir => { |
| 116 | if first { |
| 117 | normalised.push(component.as_os_str()); |
| 118 | } |
| 119 | }, |
| 120 | _ => { |
| 121 | if first { |
| 122 | normalised.push("."); |
| 123 | } |
| 124 | normalised.push(component.as_os_str()); |
| 125 | }, |
| 126 | } |
| 127 | //debug!(" > {:?}", normalised); |
| 128 | if first { |
| 129 | first = false; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | NormPathBuf(normalised) |
| 134 | } |
| 135 | } |