repo_description: A fast and powerful log viewer and processor that converts JSON logs or logfmt logs into a clear human-readable format.
repo_topics: cli command-line-tool hl human json json-log json-logging json-logs log log-viewer logfmt logging logs rust structured-logging translates-json-logs viewer
code: // stdlib imports
  use std::{
      fs,
      io::{self, Read, Seek, Write},
      path::{Path, PathBuf},
  };
  
  // third-party imports
  #[cfg(test)]
  use mockall::mock;
  
  // ---
  
  pub trait FileSystem {
      type Metadata: 'static;
  
      fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
      fn metadata(&self, path: &Path) -> io::Result<Self::Metadata>;
      fn exists(&self, path: &Path) -> io::Result<bool>;
      fn open(&self, path: &Path) -> io::Result<Box<dyn FileRead<Metadata = Self::Metadata> + Send + Sync>>;
      fn create(&self, path: &Path) -> io::Result<Box<dyn FileReadWrite<Metadata = Self::Metadata> + Send + Sync>>;
  }
  
  #[cfg(test)]
  mock! {
      pub FileSystem<M> {}
  
      impl<M:'static> FileSystem for FileSystem<M> {
          type Metadata = M;
  
          fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
          fn metadata(&self, path: &Path) -> io::Result<M>;
          fn exists(&self, path: &Path) -> io::Result<bool>;
          fn open(&self, path: &Path) -> io::Result<Box<dyn FileRead<Metadata=M> + Send + Sync>>;
          fn create(&self, path: &Path) -> io::Result<Box<dyn FileReadWrite<Metadata=M> + Send + Sync>>;
      }
  }
  
  macro_rules! delegate_fs_methods {
      () => {
          #[inline]
          fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
              (**self).canonicalize(path)
          }
  
          #[inline]
          fn metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
              (**self).metadata(path)
          }
  
          #[inline]
          fn exists(&self, path: &Path) -> io::Result<bool> {
              (**self).exists(path)
          }
  
          #[inline]
          fn open(&self, path: &Path) -> io::Result<Box<dyn FileRead<Metadata = Self::Metadata> + Send + Sync>> {
              (**self).open(path)
          }
  
          #[inline]
          fn create(&self, path: &Path) -> io::Result<Box<dyn FileReadWrite<Metadata = Self::Metadata> + Send + Sync>> {
              (**self).create(path)
          }
      };
  }
  
  impl<T> FileSystem for &T
  where
      T: FileSystem,
  {
      type Metadata = T::Metadata;
      delegate_fs_methods!();
  }
  
  impl<T> FileSystem for std::sync::Arc<T>
  where
      T: FileSystem,
  {
      type Metadata = T::Metadata;
      delegate_fs_methods!();
  }
  
  // ---
  
  pub trait Meta {
      type Metadata;
  
      fn metadata(&self) -> io::Result<Self::Metadata>;
  }
  
  impl Meta for fs::File {
      type Metadata = fs::Metadata;
  
      #[inline]
      fn metadata(&self) -> io::Result<Self::Metadata> {
          self.metadata()
      }
  }
  
  // ---
  
  pub trait FileRead: Read + Seek + Meta {}
  
  impl<T: Read + Seek + Meta> FileRead for T {}
  
  #[cfg(test)]
  mock! {
      pub FileRead<M> {}
  
      impl<M> Read for FileRead<M> {
          fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
      }
  
      impl<M> Seek for FileRead<M> {
          fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>;
      }
  
      impl<M> Meta for FileRead<M> {
          type Metadata = M;
  
          fn metadata(&self) -> io::Result<M>;
      }
  }
  
  // ---
  
  pub trait FileReadWrite: FileRead + Write {}
  
  impl<T: FileRead + Write> FileReadWrite for T {}
  
  #[cfg(test)]
  mock! {
      pub FileReadWrite<M> {}
  
      impl<M> Read for FileReadWrite<M> {
          fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
      }
  
      impl<M> Seek for FileReadWrite<M> {
          fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>;
      }
  
      impl<M> Meta for FileReadWrite<M> {
          type Metadata = M;
  
          fn metadata(&self) -> io::Result<M>;
      }
  
      impl<M> Write for FileReadWrite<M> {
          fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
          fn flush(&mut self) -> io::Result<()>;
      }
  }
  
  // ---
  
  #[derive(Default)]
  pub struct LocalFileSystem;
  
  impl FileSystem for LocalFileSystem {
      type Metadata = fs::Metadata;
  
      #[inline]
      #[cfg(not(target_os = "linux"))]
      fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
          fs::canonicalize(path)
      }
  
      #[inline]
      #[cfg(target_os = "linux")]
      fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
          use std::os::unix::fs::FileTypeExt;
          let meta = fs::metadata(path)?;
          if meta.file_type().is_fifo()
              || meta.file_type().is_socket()
              || meta.file_type().is_block_device()
              || meta.file_type().is_char_device()
          {
              return std::path::absolute(path);
          }
          fs::canonicalize(path)
      }
  
      #[inline]
      fn metadata(&self, path: &Path) -> io::Result<fs::Metadata> {
          fs::metadata(path)
      }
  
      #[inline]
      fn exists(&self, path: &Path) -> io::Result<bool> {
          fs::exists(path)
      }
  
      #[inline]
      fn open(&self, path: &Path) -> io::Result<Box<dyn FileRead<Metadata = Self::Metadata> + Send + Sync>> {
          Ok(Box::new(fs::File::open(path)?))
      }
  
      #[inline]
      fn create(&self, path: &Path) -> io::Result<Box<dyn FileReadWrite<Metadata = Self::Metadata> + Send + Sync>> {
          Ok(Box::new(fs::File::create(path)?))
      }
  }
  
  // ---
  
  #[cfg(test)]
  pub mod mem {
      use super::{FileRead, Meta};
  
      use std::{
          collections::HashMap,
          io::{self, Read, Seek, Write},
          path::{Path, PathBuf},
          sync::{Arc, RwLock},
          time::SystemTime,
      };
  
      use clean_path::Clean;
  
      // ---
  
      #[derive(Copy, Clone, Debug)]
      #[allow(dead_code)]
      pub struct Metadata {
          pub len: usize,
          pub created: SystemTime,
          pub modified: SystemTime,
      }
  
      #[derive(Copy, Clone)]
      struct InternalMetadata {
          pub created: SystemTime,
          pub modified: SystemTime,
      }
  
      impl InternalMetadata {
          #[inline]
          fn new() -> Self {
              let now = SystemTime::now();
  
              Self {
                  created: now,
                  modified: now,
              }
          }
      }
  
      impl From<(usize, InternalMetadata)> for Metadata {
          #[inline]
          fn from(meta: (usize, InternalMetadata)) -> Self {
              Self {
                  len: meta.0,
                  created: meta.1.created,
                  modified: meta.1.modified,
              }
          }
      }
  
      struct File {
          data: Vec<u8>,
          meta: InternalMetadata,
      }
  
      impl File {
          #[inline]
          fn new(data: Vec<u8>) -> Self {
              Self {
                  data,
                  meta: InternalMetadata::new(),
              }
          }
  
          #[inline]
          fn metadata(&self) -> Metadata {
              (self.data.len(), self.meta).into()
          }
      }
  
      // ---
  
      struct FileCursor {
          file: Arc<RwLock<File>>,
          pos: usize,
      }
  
      impl FileCursor {
          #[inline]
          fn new(file: Arc<RwLock<File>>) -> Self {
              Self { file, pos: 0 }
          }
      }
  
      impl Read for FileCursor {
          fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
              let data = &self.file.read().unwrap().data;
              let len = buf.len().min(data.len() - self.pos);
              buf[..len].copy_from_slice(&data[self.pos..self.pos + len]);
              self.pos += len;
              Ok(len)
          }
      }
  
      impl Write for FileCursor {
          fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
              let file = &mut self.file.write().unwrap();
              let data = &mut file.data;
              if self.pos + buf.len() > data.len() {
                  data.resize(self.pos + buf.len(), 0);
              }
              data[self.pos..self.pos + buf.len()].copy_from_slice(buf);
              file.meta.modified = SystemTime::now();
              self.pos += buf.len();
              Ok(buf.len())
          }
  
          #[inline]
          fn flush(&mut self) -> io::Result<()> {
              Ok(())
          }
      }
  
      impl Seek for FileCursor {
          fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
              let new_pos = match pos {
                  io::SeekFrom::Start(offset) => offset as usize,
                  io::SeekFrom::Current(offset) => (self.pos as i64 + offset) as usize,
                  io::SeekFrom::End(offset) => (self.file.read().unwrap().data.len() as i64 + offset) as usize,
              };
              self.pos = new_pos;
              Ok(new_pos as u64)
          }
      }
  
      impl Meta for FileCursor {
          type Metadata = Metadata;
  
          #[inline]
          fn metadata(&self) -> io::Result<Self::Metadata> {
              Ok(self.file.read().unwrap().metadata())
          }
      }
  
      // ---
  
      #[derive(Default)]
      pub struct FileSystem {
          files: RwLock<HashMap<PathBuf, Arc<RwLock<File>>>>,
      }
  
      impl FileSystem {
          pub fn new() -> Self {
              FileSystem {
                  files: RwLock::new(HashMap::new()),
              }
          }
      }
  
      impl super::FileSystem for FileSystem {
          type Metadata = Metadata;
  
          fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
              Ok(PathBuf::from("/tmp").join(path).clean())
          }
  
          fn metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
              let path = self.canonicalize(path)?;
              let files = self.files.read().unwrap();
              if let Some(file) = files.get(&path) {
                  Ok(file.read().unwrap().metadata())
              } else {
                  Err(io::Error::new(io::ErrorKind::NotFound, "file not found"))
              }
          }
  
          fn exists(&self, path: &Path) -> io::Result<bool> {
              let path = self.canonicalize(path)?;
              let files = self.files.read().unwrap();
              Ok(files.contains_key(&path))
          }
  
          fn open(&self, path: &Path) -> io::Result<Box<dyn FileRead<Metadata = Self::Metadata> + Send + Sync>> {
              let path = self.canonicalize(path)?;
              let files = self.files.read().unwrap();
              if let Some(file) = files.get(&path) {
                  Ok(Box::new(FileCursor::new(file.clone())))
              } else {
                  Err(io::Error::new(io::ErrorKind::NotFound, "file not found"))
              }
          }
  
          fn create(
              &self,
              path: &Path,
          ) -> io::Result<Box<dyn super::FileReadWrite<Metadata = Self::Metadata> + Send + Sync>> {
              let path = self.canonicalize(path)?;
              let mut files = self.files.write().unwrap();
              if files.contains_key(&path) {
                  return Err(io::Error::new(io::ErrorKind::AlreadyExists, "file already exists"));
              }
              let file = Arc::new(RwLock::new(File::new(Vec::new())));
              files.insert(path.clone(), file.clone());
              Ok(Box::new(FileCursor::new(file)))
          }
      }
  }
  
  #[cfg(test)]
  mod tests;
  