use std::{
collections::{BTreeMap, HashMap},
io::{BufWriter, Write},
path::{Path, PathBuf},
sync::Mutex,
};
use thiserror::Error;
#[derive(Debug)]
pub struct ConstructStore<F: FilesFormatter>(Mutex<InnerConstructStore<F>>);
impl<F: FilesFormatter> Default for ConstructStore<F> {
fn default() -> Self {
Self(Mutex::new(Default::default()))
}
}
#[derive(Debug)]
struct InnerConstructStore<F: FilesFormatter> {
file_constructs: HashMap<PathBuf, FileConstructs<F>>,
all_constructs: FileConstructs<F>,
}
impl<F: FilesFormatter> Default for InnerConstructStore<F> {
fn default() -> Self {
Self {
file_constructs: Default::default(),
all_constructs: Default::default(),
}
}
}
#[derive(Debug, Clone)]
struct FileConstructs<F: FilesFormatter>(BTreeMap<F::Key, String>);
impl<F: FilesFormatter> Default for FileConstructs<F> {
fn default() -> Self {
Self(BTreeMap::new())
}
}
#[derive(Debug, Error)]
pub enum ConstructWriteError {
#[error("The output file already exists: {0}")]
AlreadyExist(PathBuf),
#[error("IO Error")]
IO(#[from] std::io::Error),
}
pub trait FilesFormatter
where
Self: Sized,
{
type Key: Ord + std::fmt::Debug + Clone;
fn get_construct_store(&self) -> &ConstructStore<Self>;
fn add_requirement_string<P: AsRef<Path>>(&self, file: P, key: Self::Key, requirement: String) {
let store = self.get_construct_store();
let mut store = store.0.lock().unwrap();
if !store.file_constructs.contains_key(file.as_ref()) {
store
.file_constructs
.insert(file.as_ref().to_owned(), FileConstructs::default());
}
let file = store.file_constructs.get_mut(file.as_ref()).unwrap();
file.0.entry(key).or_insert(requirement);
}
fn add_requirement_string_all(&self, key: Self::Key, requirement: String) {
let store = self.get_construct_store();
let mut store = store.0.lock().unwrap();
store.all_constructs.0.entry(key).or_insert(requirement);
}
fn add_requirement<R: Requirement<Self>>(&self, requirement: R) {
let key = requirement.key();
let file = requirement.file(self);
let string = requirement.format(self);
self.add_requirement_string(file, key, string);
}
fn overwrite(&self) -> bool {
false
}
fn generate_files(self) -> Result<(), ConstructWriteError> {
let mut store = self.get_construct_store().0.lock().unwrap();
let InnerConstructStore {
file_constructs,
all_constructs,
} = &mut *store;
for (path, mut content) in file_constructs.drain() {
content.0.extend(all_constructs.0.clone());
if path.exists() && !self.overwrite() {
return Err(ConstructWriteError::AlreadyExist(path));
}
let file = std::fs::File::create(path)?;
let writer = BufWriter::new(file);
self.write_file(writer, content.0.into_values())?;
}
Ok(())
}
fn write_file<W: Write>(
&self,
mut w: W,
constructs: impl Iterator<Item = String>,
) -> Result<(), ConstructWriteError> {
let mut is_first = true;
for construct in constructs {
if !is_first {
writeln!(w)?;
}
write!(w, "{construct}")?;
is_first = false;
}
Ok(())
}
}
pub trait Requirement<F: FilesFormatter> {
fn key(&self) -> F::Key;
fn file(&self, formatter: &F) -> PathBuf;
fn format(self, formatter: &F) -> String;
}