use std::fs;
use std::marker::PhantomData;
use std::path::{Component, Path, PathBuf};
use tempfile::{Builder, TempDir};
pub struct Suite {
name: String,
root: Root<'static>,
}
impl Suite {
#[must_use]
#[track_caller]
pub fn new(name: &str) -> Self {
let dir = Builder::new()
.prefix(&format!("{name}-"))
.tempdir()
.unwrap_or_else(|error| panic!("create a temporary directory for {name}: {error}"));
Self {
name: name.to_owned(),
root: Root {
dir,
suite: PhantomData,
},
}
}
#[must_use]
#[track_caller]
pub fn scenario(&self, name: &str) -> Root<'_> {
let dir = Builder::new()
.prefix(&format!("{name}-"))
.tempdir_in(self.root.path())
.unwrap_or_else(|error| panic!("create {} scenario {name}: {error}", self.name));
Root {
dir,
suite: PhantomData,
}
}
#[must_use]
pub fn root(&self) -> &Root<'static> {
&self.root
}
}
pub struct Root<'suite> {
dir: TempDir,
suite: PhantomData<&'suite ()>,
}
impl Root<'_> {
#[must_use]
pub fn path(&self) -> &Path {
self.dir.path()
}
#[track_caller]
pub fn file(&self, relative: impl AsRef<Path>, body: impl AsRef<[u8]>) -> PathBuf {
let path = self.inside(relative.as_ref());
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.unwrap_or_else(|error| panic!("create {}: {error}", parent.display()));
}
fs::write(&path, body).unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
path
}
#[must_use]
#[track_caller]
pub fn read(&self, relative: impl AsRef<Path>) -> String {
let path = self.inside(relative.as_ref());
fs::read_to_string(&path).unwrap_or_else(|error| panic!("read {}: {error}", path.display()))
}
#[track_caller]
fn inside(&self, relative: &Path) -> PathBuf {
assert!(
relative
.components()
.all(|part| matches!(part, Component::Normal(_) | Component::CurDir)),
"{} must stay inside the fixture root",
relative.display()
);
self.path().join(relative)
}
}