fs.rs (6696B)
1 use std::{ 2 ffi::OsStr, 3 fmt::{self, Display}, 4 fs, 5 ops::Deref, 6 path::{Path, PathBuf, StripPrefixError}, 7 }; 8 9 use miette::{ensure, Context, IntoDiagnostic}; 10 11 #[derive(Debug)] 12 pub(crate) struct FileRoot { 13 nickname: &'static str, 14 path: PathBuf, 15 } 16 17 impl Eq for FileRoot {} 18 19 impl PartialEq for FileRoot { 20 fn eq(&self, other: &Self) -> bool { 21 self.path == other.path 22 } 23 } 24 25 impl Ord for FileRoot { 26 fn cmp(&self, other: &Self) -> std::cmp::Ordering { 27 self.path.cmp(&other.path) 28 } 29 } 30 31 impl PartialOrd for FileRoot { 32 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 33 Some(self.cmp(other)) 34 } 35 } 36 37 impl FileRoot { 38 pub(crate) fn new<P>(nickname: &'static str, path: P) -> miette::Result<Self> 39 where 40 P: AsRef<Path>, 41 { 42 let path = path.as_ref(); 43 Ok(Self { 44 nickname, 45 path: dunce::canonicalize(path) 46 .map_err(miette::Report::msg) 47 .wrap_err_with(|| format!("failed to canonicalize {path:?}"))?, 48 }) 49 } 50 51 pub(crate) fn nickname(&self) -> &str { 52 self.nickname 53 } 54 55 pub(crate) fn try_child<P>(&self, path: P) -> Result<Child<'_>, StripPrefixError> 56 where 57 P: AsRef<Path>, 58 { 59 let path = path.as_ref(); 60 if path.is_absolute() { 61 path.strip_prefix(&self.path)?; 62 } 63 Ok(Child { 64 root: self, 65 path: self.path.join(path), 66 }) 67 } 68 69 #[track_caller] 70 pub(crate) fn child<P>(&self, path: P) -> Child<'_> 71 where 72 P: AsRef<Path>, 73 { 74 self.try_child(path) 75 .into_diagnostic() 76 .wrap_err("invariant violation: `path` is absolute and not a child of this file root") 77 .unwrap() 78 } 79 80 fn removed_dir<P>(&self, path: P) -> miette::Result<Child<'_>> 81 where 82 P: AsRef<Path>, 83 { 84 let path = path.as_ref(); 85 let child = self.child(path); 86 if child.exists() { 87 log::info!("removing old contents of {child}…",); 88 log::trace!("removing directory {:?}", &*child); 89 fs::remove_dir_all(&*child) 90 .map_err(miette::Report::msg) 91 .wrap_err_with(|| format!("failed to remove old contents of {child}"))?; 92 } 93 Ok(child) 94 } 95 96 pub(crate) fn regen_dir<P>( 97 &self, 98 path: P, 99 gen: impl FnOnce(&Child<'_>) -> miette::Result<()>, 100 ) -> miette::Result<Child<'_>> 101 where 102 P: AsRef<Path>, 103 { 104 let child = self.removed_dir(path)?; 105 gen(&child)?; 106 ensure!( 107 child.is_dir(), 108 "{} was not regenerated for an unknown reason", 109 child, 110 ); 111 Ok(child) 112 } 113 } 114 115 impl Deref for FileRoot { 116 type Target = Path; 117 118 fn deref(&self) -> &Self::Target { 119 &self.path 120 } 121 } 122 123 impl AsRef<Path> for FileRoot { 124 fn as_ref(&self) -> &Path { 125 &self.path 126 } 127 } 128 129 impl AsRef<OsStr> for FileRoot { 130 fn as_ref(&self) -> &OsStr { 131 self.path.as_os_str() 132 } 133 } 134 135 impl Display for FileRoot { 136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 137 let Self { nickname, path } = self; 138 write!(f, "`{}` (AKA `<{nickname}>`)", path.display()) 139 } 140 } 141 142 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] 143 pub(crate) struct Child<'a> { 144 root: &'a FileRoot, 145 /// NOTE: This is always an absolute path that is a child of the `root`. 146 path: PathBuf, 147 } 148 149 impl Child<'_> { 150 pub(crate) fn relative_path(&self) -> &Path { 151 let Self { root, path } = self; 152 path.strip_prefix(root).unwrap() 153 } 154 155 pub(crate) fn try_child<P>(&self, path: P) -> Result<Self, StripPrefixError> 156 where 157 P: AsRef<Path>, 158 { 159 let child_path = path.as_ref(); 160 let Self { root, path } = self; 161 162 if child_path.is_absolute() { 163 child_path.strip_prefix(path)?; 164 } 165 Ok(Child { 166 root, 167 path: path.join(child_path), 168 }) 169 } 170 171 #[track_caller] 172 pub(crate) fn child<P>(&self, path: P) -> Self 173 where 174 P: AsRef<Path>, 175 { 176 self.try_child(path) 177 .into_diagnostic() 178 .wrap_err("invariant violation: `path` is absolute and not a child of this child") 179 .unwrap() 180 } 181 } 182 183 impl Deref for Child<'_> { 184 type Target = Path; 185 186 fn deref(&self) -> &Self::Target { 187 &self.path 188 } 189 } 190 191 impl AsRef<Path> for Child<'_> { 192 fn as_ref(&self) -> &Path { 193 &self.path 194 } 195 } 196 197 impl AsRef<OsStr> for Child<'_> { 198 fn as_ref(&self) -> &OsStr { 199 self.path.as_os_str() 200 } 201 } 202 203 impl Display for Child<'_> { 204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 205 write!( 206 f, 207 "`<{}>{}{}`", 208 self.root.nickname(), 209 std::path::MAIN_SEPARATOR, 210 self.relative_path().display() 211 ) 212 } 213 } 214 215 pub(crate) fn read_to_string<P>(path: P) -> miette::Result<String> 216 where 217 P: AsRef<Path>, 218 { 219 fs::read_to_string(&path) 220 .into_diagnostic() 221 .wrap_err_with(|| { 222 format!( 223 "failed to read UTF-8 string from path {}", 224 path.as_ref().display() 225 ) 226 }) 227 } 228 229 pub(crate) fn rename<P1, P2>(from: P1, to: P2) -> miette::Result<()> 230 where 231 P1: AsRef<Path>, 232 P2: AsRef<Path>, 233 { 234 fs::rename(&from, &to).into_diagnostic().wrap_err_with(|| { 235 format!( 236 "failed to rename {} to {}", 237 from.as_ref().display(), 238 to.as_ref().display() 239 ) 240 }) 241 } 242 243 pub(crate) fn try_exists<P>(path: P) -> miette::Result<bool> 244 where 245 P: AsRef<Path>, 246 { 247 let path = path.as_ref(); 248 path.try_exists() 249 .into_diagnostic() 250 .wrap_err_with(|| format!("failed to check if path exists: {}", path.display())) 251 } 252 253 pub(crate) fn create_dir_all<P>(path: P) -> miette::Result<()> 254 where 255 P: AsRef<Path>, 256 { 257 fs::create_dir_all(&path) 258 .into_diagnostic() 259 .wrap_err_with(|| { 260 format!( 261 "failed to create directories leading up to {}", 262 path.as_ref().display() 263 ) 264 }) 265 } 266 267 pub(crate) fn remove_file<P>(path: P) -> miette::Result<()> 268 where 269 P: AsRef<Path>, 270 { 271 fs::remove_file(&path) 272 .into_diagnostic() 273 .wrap_err_with(|| format!("failed to remove file at path {}", path.as_ref().display())) 274 } 275 276 pub(crate) fn write<P, C>(path: P, contents: C) -> miette::Result<()> 277 where 278 P: AsRef<Path>, 279 C: AsRef<[u8]>, 280 { 281 fs::write(&path, &contents) 282 .into_diagnostic() 283 .wrap_err_with(|| format!("failed to write to path {}", path.as_ref().display())) 284 }