diff --git a/src/main.rs b/src/main.rs index addc9a9..8afc93b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ fn main() { specs::compile(&args.devices, &args.env.spec_path, dbg); for spec in specifications { - let result = tmpls::process_templates(&spec, dbg).ok().unwrap(); + let result = RenderedConfig::from_spec(&spec, dbg).ok().unwrap(); output_rendered_configs(result, &args.env.out_path, dbg) } } diff --git a/src/tmpls.rs b/src/tmpls.rs index 2f8362a..e9a18ed 100644 --- a/src/tmpls.rs +++ b/src/tmpls.rs @@ -21,88 +21,87 @@ pub struct Configuration { pub data: String, } -pub fn process_templates( - spec: &Specification, - dbg: LogLevel, -) -> Result> { - // Create Tera context from spec.compiled - let mut context = Context::new(); - if let Value::Object(map) = &spec.compiled { - if let Some(Value::Object(data_map)) = map.get("data") { - for (key, value) in data_map { - context.insert(key, value); +impl RenderedConfig { + pub fn from_spec( + spec: &Specification, + dbg: LogLevel, + ) -> Result> { + let mut context = Context::new(); + + // Flatten "data" map into Tera context + if let Some(data_map) = spec.compiled.get("data").and_then(|v| v.as_object()) { + for (key, val) in data_map { + context.insert(key, val); } } - } - let device_name = match context.get("hostname") { - Some(value) => value.as_str().unwrap_or("default_hostname"), - None => "default_hostname", - }; + let hostname = context + .get("hostname") + .and_then(|v| v.as_str()) + .unwrap_or("default_hostname") + .to_string(); - // Get the base directory path - let base_dir = format!("./tmpl/{}", spec.get_layer()); + let base_dir = std::path::Path::new("./tmpl").join(spec.get_layer()); - // Read structure.yaml - let structure_path = format!("{}/structure.yaml", base_dir); - let structure_content: String = match fs::read_to_string(&structure_path) { - Ok(content) => content, - Err(e) => { - eprintln!( - "while attempting to read {}: {}, skipping", - &structure_path, e - ); - String::new() - } - }; - let config: TemplateConfig = yaml_serde::from_str(&structure_content)?; + // Read structure.yaml + let structure_path = base_dir.join("structure.yaml"); + let structure_data = fs::read_to_string(&structure_path) + .map_err(|e| format!("Failed to read {}: {}", structure_path.display(), e))?; - // Initialize Tera with the specific directory - let mut tera = Tera::default(); + let config_meta: TemplateConfig = yaml_serde::from_str(&structure_data)?; - // Process each template - info!(dbg, "Rendering {}", &device_name); - let mut configs = Vec::new(); - for template_name in config.files { - // Read the template file directly - let template_path = format!("{}/{}.tera", base_dir, template_name); - verb!(dbg, " | {}", &template_path); - let template_content = match fs::read_to_string(&template_path) { - Ok(content) => content, - Err(e) => { - eprintln!( - "while attempting to read {}: {}, skipping", - &template_path, e - ); - String::new() - } - }; + let mut tera = Tera::default(); + let mut configs = Vec::new(); - // Add this specific template to Tera - tera.add_raw_template(&template_name, &template_content)?; + info!(dbg, "Rendering {}", hostname); - // Render the template - let rendered = tera - .render(&template_name, &context) - .inspect_err(|e| { - let mut chain = format!("{}", e); - let mut next_source = std::error::Error::source(e); - while let Some(source) = next_source { - chain.push_str(&format!(" -> {}", source)); - next_source = source.source(); + let template_data: Vec<(String, String)> = config_meta + .files + .iter() + .filter_map(|name| { + let path = base_dir.join(format!("{}.tera", name)); + match fs::read_to_string(&path) { + Ok(content) => Some((name.clone(), content)), + Err(e) => { + eprintln!("Skipping {}: {}", path.display(), e); + None + } } - eprintln!("[tera] {}", chain); }) - .unwrap_or_default(); - configs.push(Configuration { - name: String::from(&template_name), - data: rendered, - }); + .collect(); + + tera.add_raw_templates(template_data)?; + + for template_name in &config_meta.files { + verb!(dbg, " | {}", &template_name); + if !tera.get_template_names().any(|n| n == template_name) { + continue; + } + + match tera.render(template_name, &context) { + Ok(rendered) => configs.push(Configuration { + name: template_name.clone(), + data: rendered, + }), + Err(e) => Self::log_tera_error(template_name, &e), + } + } + + Ok(Self { + hostname, + configs, + spec: spec.compiled.clone(), + }) } - Ok(RenderedConfig { - hostname: String::from(device_name), - configs, - spec: spec.compiled.clone(), - }) + /// walk the error chain + fn log_tera_error(name: &str, e: &tera::Error) { + let mut chain = e.to_string(); + let mut source = std::error::Error::source(e); + while let Some(s) = source { + chain.push_str(&format!(" -> {}", s)); + source = s.source(); + } + eprintln!("[tera] {}: {}", name, chain); + } }