use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::num::NonZeroU8;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use anyhow::{Context, Error, Result};
use crate::{NumberedDir, KEEP_DEFAULT, ROOT_DEFAULT};
#[derive(Clone)]
pub struct NumberedDirBuilder {
parent: PathBuf,
base: String,
count: NonZeroU8,
#[allow(clippy::type_complexity)]
reuse_fn: Option<Arc<Box<dyn Fn(&Path) -> bool + Send + Sync>>>,
}
impl fmt::Debug for NumberedDirBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NumberedDirBuilder")
.field("parent", &self.parent)
.field("base", &self.base)
.field("count", &self.count)
.field("reusefn", &"<Fn(&Path) -> bool>")
.finish()
}
}
impl NumberedDirBuilder {
pub fn new(base: String) -> Self {
if base.contains('/') || base.contains('\\') {
panic!("base must not contain path separators");
}
let root = format!("{}-of-{}", ROOT_DEFAULT, whoami::username());
Self {
parent: std::env::temp_dir().join(root),
base,
count: KEEP_DEFAULT.unwrap(),
reuse_fn: None,
}
}
pub fn base(&mut self, base: String) -> &mut Self {
self.base = base;
self
}
pub fn root(&mut self, root: impl Into<String>) -> &mut Self {
self.parent.set_file_name(root.into());
self
}
pub fn user_root(&mut self, prefix: &str) -> &mut Self {
let root = format!("{}{}", prefix, whoami::username());
self.parent.set_file_name(root);
self
}
pub fn tmpdir_provider(&mut self, provider: impl FnOnce() -> PathBuf) -> &mut Self {
let default_root = OsString::from_str(ROOT_DEFAULT).unwrap();
let root = self.parent.file_name().unwrap_or(&default_root);
self.parent = provider().join(root);
self
}
pub fn set_parent(&mut self, path: PathBuf) -> &mut Self {
if path.file_name().and_then(|name| name.to_str()).is_none() {
panic!("Last component of parent is not UTF-8");
}
self.parent = path;
self
}
pub fn count(&mut self, count: NonZeroU8) -> &mut Self {
self.count = count;
self
}
pub fn reusefn<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&Path) -> bool + Send + Sync + 'static,
{
self.reuse_fn = Some(Arc::new(Box::new(f)));
self
}
pub fn disable_reuse(&mut self) -> &mut Self {
self.reuse_fn = None;
self
}
pub fn create(&self) -> Result<NumberedDir> {
if !self.parent.exists() {
fs::create_dir_all(&self.parent).context("Failed to create root directory")?;
}
if !self.parent.is_dir() {
return Err(Error::msg("Path for root is not a directory"));
}
if let Some(ref reuse_fn) = self.reuse_fn {
for numdir in NumberedDir::iterate(&self.parent, &self.base)? {
if reuse_fn(numdir.path()) {
return Ok(numdir);
}
}
}
NumberedDir::create(&self.parent, &self.base, self.count)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_create() {
let parent = tempfile::tempdir().unwrap();
let dir = NumberedDirBuilder::new(String::from("base"))
.tmpdir_provider(|| parent.path().to_path_buf())
.create()
.unwrap();
assert!(dir.path().is_dir());
let root = dir
.path()
.parent()
.unwrap()
.file_name()
.unwrap()
.to_string_lossy();
assert!(root.starts_with("testdir-of-"));
}
#[test]
fn test_builder_root() {
let parent = tempfile::tempdir().unwrap();
let dir = NumberedDirBuilder::new(String::from("base"))
.tmpdir_provider(|| parent.path().to_path_buf())
.root("myroot")
.create()
.unwrap();
assert!(dir.path().is_dir());
let root = parent.path().join("myroot");
assert_eq!(dir.path(), root.join("base-0"));
}
#[test]
fn test_builder_user_root() {
let parent = tempfile::tempdir().unwrap();
let dir = NumberedDirBuilder::new(String::from("base"))
.tmpdir_provider(|| parent.path().to_path_buf())
.root("myroot-")
.create()
.unwrap();
assert!(dir.path().is_dir());
let root = dir
.path()
.parent()
.unwrap()
.file_name()
.unwrap()
.to_string_lossy();
assert!(root.starts_with("myroot-"));
}
#[test]
fn test_builder_set_parent() {
let temp = tempfile::tempdir().unwrap();
let parent = temp.path().join("myparent");
let dir = NumberedDirBuilder::new(String::from("base"))
.set_parent(parent.clone())
.create()
.unwrap();
assert!(dir.path().is_dir());
assert_eq!(dir.path(), parent.join("base-0"));
}
#[test]
fn test_builder_count() {
let temp = tempfile::tempdir().unwrap();
let parent = temp.path();
let mut builder = NumberedDirBuilder::new(String::from("base"));
builder.tmpdir_provider(|| parent.to_path_buf());
builder.count(NonZeroU8::new(1).unwrap());
let dir0 = builder.create().unwrap();
assert!(dir0.path().is_dir());
let dir1 = builder.create().unwrap();
assert!(!dir0.path().is_dir());
assert!(dir1.path().is_dir());
}
}