mod catalog;
mod error;
mod name;
mod render;
use std::path::Path;
pub use catalog::{Database, Stack, TemplateFile};
pub use error::TemplateError;
pub use name::ProjectName;
use catalog::files;
use render::render;
const ARCATURE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn generate(target: &Path, stack: Stack, database: Database) -> Result<(), TemplateError> {
if target.exists() {
return Err(TemplateError::ExistingTarget {
path: target.to_path_buf(),
});
}
let name = ProjectName::parse(target.file_name().and_then(|n| n.to_str()).ok_or(
TemplateError::InvalidDestination {
reason: "destination has no final path component".into(),
},
)?)?;
let parent = target.parent().ok_or(TemplateError::InvalidDestination {
reason: "destination has no parent directory".into(),
})?;
if !parent.exists() {
std::fs::create_dir_all(parent).map_err(|e| TemplateError::Io {
path: parent.into(),
source: e,
})?;
}
let staging_name = format!(".{}.arcature-staging", name.rust_identifier());
let staging = parent.join(&staging_name);
if staging.exists() {
return Err(TemplateError::InvalidDestination {
reason: format!("staging directory already exists: {}", staging.display()),
});
}
for file in files(stack, database) {
let rendered = render(file.content, &name, ARCATURE_VERSION, stack, database);
let dest = staging.join(file.path);
let dir = dest.parent().ok_or(TemplateError::InvalidDestination {
reason: "file has no parent directory".into(),
})?;
std::fs::create_dir_all(dir).map_err(|e| TemplateError::Io {
path: dir.into(),
source: e,
})?;
std::fs::write(&dest, &rendered).map_err(|e| TemplateError::Io {
path: dest.clone(),
source: e,
})?;
}
std::fs::rename(&staging, target).map_err(|e| {
let _ = std::fs::remove_dir_all(&staging);
TemplateError::Rename {
staging: staging.clone(),
target: target.to_path_buf(),
source: e,
}
})?;
Ok(())
}