//! The datadir step library provides a small number of steps which can be used //! in your scenarios, though its primary use is to provide the Datadir context //! object which other libraries will use to provide filesystem access. //! //! If you want to create files, run commands, etc. from your scenarios, you //! will likely be using them from within the datadir. use std::fs::{File, OpenOptions}; use std::path::{Component, Path, PathBuf}; pub use crate::prelude::*; #[derive(Default)] pub struct Datadir { inner: Option, } pub struct DatadirInner { base: tempfile::TempDir, } impl ContextElement for Datadir { fn created(&mut self, scenario: &Scenario) { assert!(self.inner.is_none()); self.inner = DatadirInner::build(scenario.title()); } // TODO: Support saving datadir between steps } impl DatadirInner { fn build(title: &str) -> Option { let safe_title: String = title .chars() .map(|c| match c { 'a'..='z' | 'A'..='Z' | '0'..='9' => c, _ => '-', }) .collect(); let base = tempfile::Builder::new() .prefix("subplot") .suffix(&safe_title) .rand_bytes(5) .tempdir() .ok()?; Some(Self { base }) } } impl Datadir { fn inner(&self) -> &DatadirInner { self.inner .as_ref() .expect("Attempted to access Datadir too early (or late)") } /// Retrieve the base data directory path which can be used to store /// files etc. for this step. pub fn base_path(&self) -> &Path { self.inner().base.path() } /// Canonicalise a subpath into this dir #[throws(StepError)] pub fn canonicalise_filename>(&self, subpath: S) -> PathBuf { let mut ret = self.base_path().to_path_buf(); for component in subpath.as_ref().components() { match component { Component::CurDir => {} Component::ParentDir => { throw!("embedded filenames may not contain .."); } Component::RootDir | Component::Prefix(_) => { throw!("embedded filenames must be relative"); } c => ret.push(c), } } ret } /// Open a file for writing #[throws(StepError)] pub fn open_write>(&self, subpath: S) -> File { let full_path = self.canonicalise_filename(subpath)?; OpenOptions::new() .create(true) .write(true) .truncate(true) .open(full_path)? } #[throws(StepError)] pub fn create_dir_all>(&self, subpath: S) { let full_path = self.canonicalise_filename(subpath)?; std::fs::create_dir_all(full_path)?; } } /// A simple check for enough disk space in the data dir #[step] pub fn datadir_has_enough_space(datadir: &Datadir, bytes: u64) { let available = fs2::available_space(datadir.base_path())?; if available < bytes { throw!(format!( "Available space check failed, wanted {} bytes, but only {} were available", bytes, available )); } } /// A convenience step for enough disk space in the data dir in megabytes #[step] pub fn datadir_has_enough_space_megabytes(context: &ScenarioContext, megabytes: u64) { datadir_has_enough_space::call(context, megabytes * 1024 * 1024) }