/ src / util.rs
util.rs
  1  //! Utility functions.
  2  
  3  use std::{
  4      fs::{copy, create_dir_all, metadata, read, set_permissions, write, File},
  5      os::unix::fs::PermissionsExt,
  6      path::{Path, PathBuf},
  7      time::SystemTime,
  8  };
  9  
 10  use clingwrap::runner::CommandError;
 11  use time::{macros::format_description, OffsetDateTime};
 12  
 13  /// Create an empty file.
 14  pub fn create_file(filename: &Path) -> Result<PathBuf, UtilError> {
 15      File::create(filename).map_err(|e| UtilError::CreateFile(filename.into(), e))?;
 16      Ok(filename.into())
 17  }
 18  
 19  /// Create a directory.
 20  pub fn mkdir(dirname: &Path) -> Result<(), UtilError> {
 21      create_dir_all(dirname).map_err(|e| UtilError::CreateDir(dirname.into(), e))?;
 22      Ok(())
 23  }
 24  
 25  /// Create a subdirectory.
 26  pub fn mkdir_child(parent: &Path, subdir: &str) -> Result<PathBuf, UtilError> {
 27      let pathname = parent.join(subdir);
 28      create_dir_all(&pathname).map_err(|e| UtilError::CreateDir(pathname.clone(), e))?;
 29      Ok(pathname)
 30  }
 31  
 32  /// Make sure a directory exists and is empty.
 33  pub fn recreate_dir(dirname: &Path) -> Result<(), UtilError> {
 34      if dirname.exists() {
 35          std::fs::remove_dir_all(dirname).map_err(|e| UtilError::RemoveDir(dirname.into(), e))?;
 36      }
 37      mkdir(dirname)?;
 38      Ok(())
 39  }
 40  
 41  /// Read a text file.
 42  pub fn cat_text_file(filename: &Path) -> Result<String, UtilError> {
 43      let data = read(filename).map_err(|err| UtilError::Read(filename.into(), err))?;
 44      let text = String::from_utf8(data).map_err(|err| UtilError::Utf8(filename.into(), err))?;
 45      Ok(text)
 46  }
 47  
 48  /// Write a file.
 49  pub fn write_file(filename: &Path, data: &[u8]) -> Result<(), UtilError> {
 50      write(filename, data).map_err(|err| UtilError::WriteFile(filename.into(), err))
 51  }
 52  
 53  /// Copy a file.
 54  pub fn copy_file(src: &Path, dst: &Path) -> Result<(), UtilError> {
 55      copy(src, dst).map_err(|err| UtilError::Copy(src.into(), dst.into(), err))?;
 56      Ok(())
 57  }
 58  
 59  /// Copy a file, make sure user has read and write permission to the copy.
 60  pub fn copy_file_rw(src: &Path, dst: &Path) -> Result<(), UtilError> {
 61      copy_file(src, dst)?;
 62      let mut perms = std::fs::metadata(dst)
 63          .map_err(|err| UtilError::GetMetadata(dst.into(), err))?
 64          .permissions();
 65      perms.set_mode(0o644);
 66      std::fs::set_permissions(dst, perms)
 67          .map_err(|err| UtilError::SetPermissions(dst.into(), err))?;
 68      Ok(())
 69  }
 70  
 71  /// Write a file, make it executable.
 72  pub fn write_executable(filename: &Path, data: &[u8]) -> Result<(), UtilError> {
 73      // Unix mode bits for an executable file: read/write/exec for
 74      // owner, read/exec for group and others
 75      const EXECUTABLE: u32 = 0o755;
 76  
 77      write(filename, data).map_err(|err| UtilError::WriteFile(filename.into(), err))?;
 78      let meta = metadata(filename).map_err(|err| UtilError::GetMetadata(filename.into(), err))?;
 79      let mut perm = meta.permissions();
 80      perm.set_mode(EXECUTABLE);
 81      set_permissions(filename, perm).map_err(|err| UtilError::MakeExec(filename.into(), err))?;
 82      Ok(())
 83  }
 84  
 85  /// Current time as a string.
 86  pub fn now() -> Result<String, UtilError> {
 87      let fmt = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]Z");
 88      OffsetDateTime::now_utc()
 89          .format(fmt)
 90          .map_err(UtilError::TimeFormat)
 91  }
 92  
 93  /// Format a time stamp.
 94  pub fn format_timestamp(time: SystemTime) -> Result<String, UtilError> {
 95      let fmt = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]Z");
 96      OffsetDateTime::from(time)
 97          .format(fmt)
 98          .map_err(UtilError::TimeFormat)
 99  }
100  
101  /// Errors from utility functions.
102  #[derive(Debug, thiserror::Error)]
103  pub enum UtilError {
104      /// Can't create directory.
105      #[error("failed to create directory {0}")]
106      CreateDir(PathBuf, #[source] std::io::Error),
107  
108      /// Can't remove directory.
109      #[error("failed to remove directory {0}")]
110      RemoveDir(PathBuf, #[source] std::io::Error),
111  
112      /// Can't write file.
113      #[error("failed to write file {0}")]
114      WriteFile(PathBuf, #[source] std::io::Error),
115  
116      /// Can't get file metadata.
117      #[error("failed to get metadata for file: {0}")]
118      GetMetadata(PathBuf, #[source] std::io::Error),
119  
120      /// Can't make executable.
121      #[error("failed to make a file executable: {0}")]
122      MakeExec(PathBuf, #[source] std::io::Error),
123  
124      /// Can't copy file.
125      #[error("failed to copy file {0} to {1}")]
126      Copy(PathBuf, PathBuf, #[source] std::io::Error),
127  
128      /// Can't set file permissions.
129      #[error("failed to set permissions for file {0}")]
130      SetPermissions(PathBuf, #[source] std::io::Error),
131  
132      /// Can't read file.
133      #[error("failed to read file {0}")]
134      Read(PathBuf, #[source] std::io::Error),
135  
136      /// Can't read file as UTF-8.
137      #[error("failed to understand file {0} into UTF8")]
138      Utf8(PathBuf, #[source] std::string::FromUtf8Error),
139  
140      /// Can't format time as string.
141      #[error("failed to format time stamp")]
142      TimeFormat(#[source] time::error::Format),
143  
144      /// Can't create file.
145      #[error("failed to create file {0}")]
146      CreateFile(PathBuf, #[source] std::io::Error),
147  
148      /// Can't run program.
149      #[error("failed to run program {0}")]
150      Execute(&'static str, #[source] CommandError),
151  }