use ggen_core::{FileTreeTemplate, GenerationResult, TemplateContext, TemplateParser};
use ggen_utils::error::Result;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub fn generate_file_tree(
template_path: &Path, output_dir: &Path, variables: &HashMap<String, String>, force: bool,
) -> Result<GenerationResult> {
let template = TemplateParser::parse_file(template_path)
.map_err(|e| ggen_utils::error::Error::new(&format!("Failed to parse template: {}", e)))?;
let var_map: std::collections::BTreeMap<String, String> = variables
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let context = TemplateContext::from_map(var_map)
.map_err(|e| ggen_utils::error::Error::new(&format!("Failed to create context: {}", e)))?;
if let Err(e) = context.validate_required(template.required_variables()) {
return Err(ggen_utils::error::Error::new(&format!(
"Validation failed: {}",
e
)));
}
if !force && would_overwrite(&template, output_dir, &context)? {
return Err(ggen_utils::error::Error::new(
"Files would be overwritten. Use --force to overwrite or choose different output directory",
));
}
let result = ggen_core::templates::generate_file_tree(template, context, output_dir)
.map_err(|e| ggen_utils::error::Error::new(&format!("Generation failed: {}", e)))?;
Ok(result)
}
fn would_overwrite(
template: &FileTreeTemplate, output: &Path, context: &TemplateContext,
) -> Result<bool> {
fn check_nodes(
nodes: &[ggen_core::FileTreeNode], current_path: &Path, context: &TemplateContext,
) -> Result<bool> {
for node in nodes {
let rendered_name = context
.render_string(&node.name)
.map_err(|e| ggen_utils::error::Error::new(&format!("Failed to render: {}", e)))?;
let node_path = current_path.join(&rendered_name);
match node.node_type {
ggen_core::NodeType::Directory => {
if check_nodes(&node.children, &node_path, context)? {
return Ok(true);
}
}
ggen_core::NodeType::File => {
if node_path.exists() {
return Ok(true);
}
}
}
}
Ok(false)
}
check_nodes(template.nodes(), output, context)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_would_overwrite_detects_existing_files() {
let temp_dir = TempDir::new().unwrap();
let template_content = r#"
name: "test-project"
nodes:
- type: directory
name: "src"
children:
- type: file
name: "main.rs"
content: "fn main() {}"
"#;
let template_path = temp_dir.path().join("template.yaml");
fs::write(&template_path, template_content).unwrap();
let output_dir = temp_dir.path().join("output");
fs::create_dir_all(output_dir.join("src")).unwrap();
fs::write(output_dir.join("src/main.rs"), "existing").unwrap();
assert!(output_dir.join("src/main.rs").exists());
}
#[test]
fn test_variables_conversion() {
let mut vars = HashMap::new();
vars.insert("name".to_string(), "test".to_string());
vars.insert("author".to_string(), "Alice".to_string());
let btree_map: std::collections::BTreeMap<String, String> =
vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
assert_eq!(btree_map.get("name").unwrap(), "test");
assert_eq!(btree_map.get("author").unwrap(), "Alice");
}
}
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct GenerateTreeInput {
pub template: PathBuf,
pub output: PathBuf,
pub var: Vec<String>,
pub force: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct GenerateTreeOutput {
pub files_generated: usize,
pub directories_created: usize,
pub output_path: String,
}
pub async fn execute_generate_tree(input: GenerateTreeInput) -> Result<GenerateTreeOutput> {
let mut variables = HashMap::new();
for var_str in &input.var {
let parts: Vec<&str> = var_str.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(ggen_utils::error::Error::new(&format!(
"Invalid variable format: '{}'. Expected 'key=value'",
var_str
)));
}
variables.insert(parts[0].to_string(), parts[1].to_string());
}
let result = generate_file_tree(&input.template, &input.output, &variables, input.force)?;
Ok(GenerateTreeOutput {
files_generated: result.files().len(),
directories_created: result.directories().len(),
output_path: input.output.display().to_string(),
})
}
pub fn run(args: &GenerateTreeInput) -> Result<()> {
let runtime = tokio::runtime::Runtime::new()
.map_err(|e| ggen_utils::error::Error::new(&format!("Failed to create runtime: {}", e)))?;
let output = runtime.block_on(execute_generate_tree(args.clone()))?;
ggen_utils::alert_success!("Generated file tree from: {}", args.template.display());
ggen_utils::alert_info!("📁 Output directory: {}", output.output_path);
ggen_utils::alert_info!("📄 Files created: {}", output.files_generated);
ggen_utils::alert_info!("📂 Directories created: {}", output.directories_created);
Ok(())
}