Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / io.rs
9.8 kB 334 lines
1pub mod memory; 2 3use crate::error::{Error, FileIoAction, FileKind, Result}; 4use async_trait::async_trait; 5use debug_ignore::DebugIgnore; 6use flate2::read::GzDecoder; 7use std::{collections::HashMap, fmt::Debug, io, time::SystemTime, vec::IntoIter}; 8use tar::{Archive, Entry}; 9 10use camino::{Utf8Path, Utf8PathBuf}; 11 12/// Takes in a source path and a target path and determines a relative path 13/// from source -> target. 14/// If given a relative target path, no calculation occurs. 15/// # Panics 16/// The provided source path should be absolute, otherwise will panic. 17pub fn make_relative(source_path: &Utf8Path, target_path: &Utf8Path) -> Utf8PathBuf { 18 assert!(source_path.is_absolute()); 19 // Input target will always be canonicalised whereas source will not 20 // This causes problems with diffing on windows since canonicalised paths have a special root 21 // As such we are attempting to strip the target path 22 // Based on https://github.com/rust-lang/rust/issues/42869#issuecomment-1712317081 23 #[cfg(target_family = "windows")] 24 let binding = target_path.to_string(); 25 #[cfg(target_family = "windows")] 26 let target_path = Utf8Path::new(binding.trim_start_matches(r"\\?\")); 27 28 match target_path.is_absolute() { 29 true => pathdiff::diff_utf8_paths(target_path, source_path) 30 .expect("Should not fail on two absolute paths"), 31 32 false => target_path.into(), 33 } 34} 35 36pub trait Reader: std::io::Read { 37 /// A wrapper around `std::io::Read` that has Gleam's error handling. 38 fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<usize> { 39 self.read(buffer).map_err(|e| self.convert_err(e)) 40 } 41 42 fn convert_err<E: std::error::Error>(&self, error: E) -> Error; 43} 44 45pub trait Utf8Writer: std::fmt::Write { 46 /// A wrapper around `fmt::Write` that has Gleam's error handling. 47 fn str_write(&mut self, str: &str) -> Result<()> { 48 self.write_str(str).map_err(|e| self.convert_err(e)) 49 } 50 51 fn convert_err<E: std::error::Error>(&self, err: E) -> Error; 52} 53 54impl Utf8Writer for String { 55 fn convert_err<E: std::error::Error>(&self, error: E) -> Error { 56 Error::FileIo { 57 action: FileIoAction::WriteTo, 58 kind: FileKind::File, 59 path: Utf8PathBuf::from("<in memory>"), 60 err: Some(error.to_string()), 61 } 62 } 63} 64 65pub trait Writer: std::io::Write + Utf8Writer { 66 /// A wrapper around `io::Write` that has Gleam's error handling. 67 fn write(&mut self, bytes: &[u8]) -> Result<(), Error> { 68 std::io::Write::write(self, bytes) 69 .map(|_| ()) 70 .map_err(|e| self.convert_err(e)) 71 } 72} 73 74#[derive(Debug, PartialEq, Eq, Clone)] 75pub enum Content { 76 Binary(Vec<u8>), 77 Text(String), 78} 79 80impl Content { 81 pub fn as_bytes(&self) -> &[u8] { 82 match self { 83 Content::Binary(data) => data, 84 Content::Text(data) => data.as_bytes(), 85 } 86 } 87 88 pub fn text(&self) -> Option<&str> { 89 match self { 90 Content::Binary(_) => None, 91 Content::Text(s) => Some(s), 92 } 93 } 94} 95 96impl From<Vec<u8>> for Content { 97 fn from(bytes: Vec<u8>) -> Self { 98 Content::Binary(bytes) 99 } 100} 101 102impl From<&[u8]> for Content { 103 fn from(bytes: &[u8]) -> Self { 104 Content::Binary(bytes.to_vec()) 105 } 106} 107 108impl From<String> for Content { 109 fn from(text: String) -> Self { 110 Content::Text(text) 111 } 112} 113 114impl From<&str> for Content { 115 fn from(text: &str) -> Self { 116 Content::Text(text.to_string()) 117 } 118} 119 120#[derive(Debug, PartialEq, Eq, Clone)] 121pub struct OutputFile { 122 pub content: Content, 123 pub path: Utf8PathBuf, 124} 125 126#[derive(Debug)] 127pub struct ReadDir { 128 entries: Vec<io::Result<DirEntry>>, 129} 130 131impl FromIterator<io::Result<DirEntry>> for ReadDir { 132 fn from_iter<I: IntoIterator<Item = io::Result<DirEntry>>>(iter: I) -> Self { 133 ReadDir { 134 entries: iter.into_iter().collect(), 135 } 136 } 137} 138 139impl ReadDir { 140 pub fn extend(mut self, other: ReadDir) -> Self { 141 self.entries.extend(other); 142 143 ReadDir { 144 entries: self.entries, 145 } 146 } 147} 148 149impl IntoIterator for ReadDir { 150 type Item = io::Result<DirEntry>; 151 type IntoIter = IntoIter<Self::Item>; 152 153 fn into_iter(self) -> Self::IntoIter { 154 self.entries.into_iter() 155 } 156} 157 158#[derive(Debug, Clone)] 159pub struct DirEntry { 160 pub pathbuf: Utf8PathBuf, 161} 162 163impl DirEntry { 164 pub fn from_path<P: AsRef<Utf8Path>>(path: P) -> DirEntry { 165 DirEntry { 166 pathbuf: path.as_ref().to_path_buf(), 167 } 168 } 169 170 pub fn from_pathbuf(pathbuf: Utf8PathBuf) -> DirEntry { 171 DirEntry { pathbuf } 172 } 173 174 pub fn as_path(&self) -> &Utf8Path { 175 self.pathbuf.as_path() 176 } 177 178 pub fn into_path(self) -> Utf8PathBuf { 179 self.pathbuf 180 } 181} 182 183/// A trait used to read files. 184/// Typically we use an implementation that reads from the file system, 185/// but in tests and in other places other implementations may be used. 186pub trait FileSystemReader { 187 fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>; 188 fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>; 189 fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir>; 190 fn read(&self, path: &Utf8Path) -> Result<String, Error>; 191 fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error>; 192 fn reader(&self, path: &Utf8Path) -> Result<WrappedReader, Error>; 193 fn is_file(&self, path: &Utf8Path) -> bool; 194 fn is_directory(&self, path: &Utf8Path) -> bool; 195 fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime, Error>; 196 fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error>; 197} 198 199/// A trait used to run other programs. 200pub trait CommandExecutor { 201 fn exec( 202 &self, 203 program: &str, 204 args: &[String], 205 env: &[(&str, String)], 206 cwd: Option<&Utf8Path>, 207 stdio: Stdio, 208 ) -> Result<i32, Error>; 209} 210 211#[derive(Clone, Copy, Debug, PartialEq, Eq)] 212pub enum Stdio { 213 Inherit, 214 Null, 215} 216 217impl Stdio { 218 pub fn get_process_stdio(&self) -> std::process::Stdio { 219 match self { 220 Stdio::Inherit => std::process::Stdio::inherit(), 221 Stdio::Null => std::process::Stdio::null(), 222 } 223 } 224} 225 226/// A trait used to write files. 227/// Typically we use an implementation that writes to the file system, 228/// but in tests and in other places other implementations may be used. 229pub trait FileSystemWriter { 230 fn mkdir(&self, path: &Utf8Path) -> Result<(), Error>; 231 fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error>; 232 fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error>; 233 fn delete_directory(&self, path: &Utf8Path) -> Result<(), Error>; 234 fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 235 fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 236 fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 237 fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; 238 fn delete_file(&self, path: &Utf8Path) -> Result<(), Error>; 239} 240 241#[derive(Debug)] 242/// A wrapper around a Read implementing object that has Gleam's error handling. 243pub struct WrappedReader { 244 path: Utf8PathBuf, 245 inner: DebugIgnore<Box<dyn std::io::Read>>, 246} 247 248impl WrappedReader { 249 pub fn new(path: &Utf8Path, inner: Box<dyn std::io::Read>) -> Self { 250 Self { 251 path: path.to_path_buf(), 252 inner: DebugIgnore(inner), 253 } 254 } 255 256 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> { 257 self.inner.read(buffer) 258 } 259} 260 261impl std::io::Read for WrappedReader { 262 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> { 263 self.read(buffer) 264 } 265} 266 267impl Reader for WrappedReader { 268 fn convert_err<E: std::error::Error>(&self, err: E) -> Error { 269 Error::FileIo { 270 kind: FileKind::File, 271 action: FileIoAction::Read, 272 path: self.path.clone(), 273 err: Some(err.to_string()), 274 } 275 } 276} 277 278#[async_trait] 279pub trait HttpClient { 280 async fn send(&self, request: http::Request<Vec<u8>>) 281 -> Result<http::Response<Vec<u8>>, Error>; 282} 283 284pub trait TarUnpacker { 285 // FIXME: The reader types are restrictive here. We should be more generic 286 // than this. 287 fn io_result_entries<'a>( 288 &self, 289 archive: &'a mut Archive<WrappedReader>, 290 ) -> io::Result<tar::Entries<'a, WrappedReader>>; 291 292 fn entries<'a>( 293 &self, 294 archive: &'a mut Archive<WrappedReader>, 295 ) -> Result<tar::Entries<'a, WrappedReader>> { 296 tracing::debug!("iterating through tar archive"); 297 self.io_result_entries(archive) 298 .map_err(|e| Error::ExpandTar { 299 error: e.to_string(), 300 }) 301 } 302 303 fn io_result_unpack( 304 &self, 305 path: &Utf8Path, 306 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>, 307 ) -> io::Result<()>; 308 309 fn unpack( 310 &self, 311 path: &Utf8Path, 312 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>, 313 ) -> Result<()> { 314 tracing::debug!(path = ?path, "unpacking tar archive"); 315 self.io_result_unpack(path, archive) 316 .map_err(|e| Error::FileIo { 317 action: FileIoAction::WriteTo, 318 kind: FileKind::Directory, 319 path: path.to_path_buf(), 320 err: Some(e.to_string()), 321 }) 322 } 323} 324 325pub fn ordered_map<S, K, V>(value: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error> 326where 327 S: serde::Serializer, 328 K: serde::Serialize + Ord, 329 V: serde::Serialize, 330{ 331 use serde::Serialize; 332 let ordered: std::collections::BTreeMap<_, _> = value.iter().collect(); 333 ordered.serialize(serializer) 334}