use std::path::Path;
use std::{fs::File, io::BufWriter};
use thiserror::Error;
use crate::Anvil;
use crate::Forge;
pub struct Generate<A: Anvil> {
template: A,
}
#[derive(Error, Debug)]
pub enum GenerateError {
#[error("failed to perform file I/O while generating file: {0}")]
StdIo(#[from] std::io::Error),
#[error("failed to render template during file generation: {0}")]
Template(#[from] Box<dyn std::error::Error>),
}
impl<A: Anvil> Forge for Generate<A> {
type Error = GenerateError;
fn forge(&self, into: impl AsRef<Path>) -> Result<(), Self::Error> {
let path = into.as_ref();
let prefix = path.parent().expect("no parent directory");
std::fs::create_dir_all(prefix).map_err(GenerateError::StdIo)?;
let file = File::create_new(path).map_err(GenerateError::StdIo)?;
let mut writer = BufWriter::new(file);
self.template
.anvil(&mut writer)
.map_err(|e| GenerateError::Template(Box::new(e)))?;
Ok(())
}
}
impl<A: Anvil> Generate<A> {
pub fn new(template: A) -> Self {
Self { template }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Read;
use tempfile::tempdir;
struct MockAnvil {
content: String,
}
impl Anvil for MockAnvil {
type Error = std::io::Error;
fn anvil(&self, writer: &mut (impl std::io::Write + Sized)) -> Result<(), Self::Error> {
writer.write_all(self.content.as_bytes())?;
Ok(())
}
}
#[test]
fn test_generate_creates_new_file() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("generated_file.txt");
let expected_content = "Generated content";
let template = MockAnvil {
content: expected_content.to_string(),
};
let generate = Generate::new(template);
let result = generate.forge(&file_path);
assert!(result.is_ok());
assert!(file_path.exists());
let mut file = File::open(&file_path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
assert_eq!(content, expected_content);
}
#[test]
fn test_generate_creates_parent_directories() {
let temp_dir = tempdir().unwrap();
let nested_path = temp_dir.path().join("nested/directories/file.txt");
let expected_content = "Content in nested directories";
let template = MockAnvil {
content: expected_content.to_string(),
};
let generate = Generate::new(template);
let result = generate.forge(&nested_path);
assert!(result.is_ok());
assert!(nested_path.exists());
assert!(nested_path.parent().unwrap().exists());
let mut file = File::open(&nested_path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
assert_eq!(content, expected_content);
}
#[test]
fn test_generate_handles_template_error() {
struct FailingAnvil;
impl Anvil for FailingAnvil {
type Error = std::io::Error;
fn anvil(
&self,
_writer: &mut (impl std::io::Write + Sized),
) -> Result<(), Self::Error> {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Template error",
))
}
}
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("should_not_be_created.txt");
let generate = Generate::new(FailingAnvil);
let result = generate.forge(&file_path);
assert!(result.is_err());
match result {
Err(GenerateError::Template(err)) => assert_eq!(err.to_string(), "Template error"),
other => unreachable!("Expected Template error but got: {:?}", other),
}
}
}