use std::fmt::Debug;
use std::fs::{File, OpenOptions};
use std::path::{Component, Path, PathBuf};
pub use crate::prelude::*;
#[derive(Default)]
pub struct Datadir {
inner: Option<DatadirInner>,
}
#[derive(Debug)]
pub struct DatadirInner {
base: tempfile::TempDir,
}
impl Debug for Datadir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Some(inner) => (inner as &dyn Debug).fmt(f),
None => f
.debug_struct("Datadir")
.field("inner", &self.inner)
.finish(),
}
}
}
impl ContextElement for Datadir {
fn created(&mut self, scenario: &Scenario) {
assert!(self.inner.is_none());
self.inner = DatadirInner::build(scenario.title());
}
}
impl DatadirInner {
fn build(title: &str) -> Option<Self> {
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)")
}
pub fn base_path(&self) -> &Path {
self.inner().base.path()
}
#[throws(StepError)]
pub fn canonicalise_filename<S: AsRef<Path>>(&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
}
#[throws(StepError)]
pub fn open_write<S: AsRef<Path>>(&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 open_read<S: AsRef<Path>>(&self, subpath: S) -> File {
let full_path = self.canonicalise_filename(subpath)?;
OpenOptions::new()
.create(false)
.write(false)
.read(true)
.open(full_path)?
}
#[throws(StepError)]
pub fn create_dir_all<S: AsRef<Path>>(&self, subpath: S) {
let full_path = self.canonicalise_filename(subpath)?;
std::fs::create_dir_all(full_path)?;
}
#[throws(StepError)]
pub fn remove_file<S: AsRef<Path>>(&self, subpath: S) {
let full_path = self.canonicalise_filename(subpath)?;
std::fs::remove_file(full_path)?;
}
}
#[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} bytes, but only {available} were available",
));
}
}
#[step]
pub fn datadir_has_enough_space_megabytes(context: &ScenarioContext, megabytes: u64) {
datadir_has_enough_space::call(context, megabytes * 1024 * 1024)?;
}