From 8877c61aed3e32e1317fe289c5a37df5544258fb Mon Sep 17 00:00:00 2001 From: lost Date: Wed, 18 Feb 2026 03:39:29 -0700 Subject: [PATCH] refactor: modularize codebase and improve type safety --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 8 +- .../spec/xyz/ex-core-r/xyz2-ex-core-r102.yaml | 2 +- src/cli.rs | 196 ++++++------ src/lib.rs | 10 + src/main.rs | 30 +- src/render.rs | 129 ++++---- src/spec.rs | 286 ++++++++++-------- src/tmpl.rs | 20 +- 10 files changed, 358 insertions(+), 327 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8acdc02..fbad5ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skyforge" -version = "0.1.0" +version = "0.1.112" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 5624b6b..a474f59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "skyforge" -version = "0.1.0" +version = "0.1.112" edition = "2024" [dependencies] diff --git a/README.md b/README.md index e628b1b..9ad9338 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ Options: -V, --version Print version Environment: - SF_SPEC_PATH Path to the directory containing templates. Defaults to "./spec". - SF_TMPL_PATH Path to the directory containing specifications. Defaults to "./tmpl". - SF_OUT_PATH Path to the directory for command output. Defaults to "./out". - SF_LOG_PATH Path to the directory for log output. Defaults to "./log". + SF_SPEC_PATH Directory containing templates + SF_TMPL_PATH Directory containing specifications + SF_OUT_PATH Directory for command output + SF_LOG_PATH Directory for log output ``` ### Standard diff --git a/demo/spec/xyz/ex-core-r/xyz2-ex-core-r102.yaml b/demo/spec/xyz/ex-core-r/xyz2-ex-core-r102.yaml index 63858d2..744d400 100644 --- a/demo/spec/xyz/ex-core-r/xyz2-ex-core-r102.yaml +++ b/demo/spec/xyz/ex-core-r/xyz2-ex-core-r102.yaml @@ -1,2 +1,2 @@ device: - hostname: xyz2-ex-core-r202 + hostname: xyz2-ex-core-r102 diff --git a/src/cli.rs b/src/cli.rs index d3febbc..0d71a7a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,81 +1,7 @@ use crate::log::LogLevel; use clap::{Arg, ArgAction, ArgGroup, Command}; use regex::Regex; -use std::fmt; - -#[derive(Debug)] -pub struct Args { - pub devices: Regex, - pub loglevel: LogLevel, - pub env: EnvVars, -} - -impl fmt::Display for Args { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "devices: {}, loglevel: {}, env: {}", - self.devices, self.loglevel, self.env - ) - } -} - -#[derive(Debug)] -pub struct EnvVars { - pub spec_path: String, - pub tmpl_path: String, - pub out_path: String, - pub log_path: String, -} - -impl fmt::Display for EnvVars { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "spec_path: {}, tmpl_path: {}, out_path: {}, log_path: {}", - self.spec_path, self.tmpl_path, self.out_path, self.log_path - ) - } -} - -/// loads `command line arguments` and `environment variables` using custom `clap` -pub fn parse_args() -> Args { - let matches: clap::ArgMatches = build().get_matches(); - let raw_devices = matches.get_one::("devices").unwrap(); - Args { - devices: Regex::new(&raw_devices).expect("Invalid regex pattern provided for `devices`"), - loglevel: if matches.get_flag("debug") { - LogLevel::Debug - } else if matches.get_flag("verbose") { - LogLevel::Verbose - } else { - LogLevel::Info - }, - env: parse_env(), - } -} - -/// loads environment variables -pub fn parse_env() -> EnvVars { - EnvVars { - spec_path: match std::env::var("SF_SPEC_PATH") { - Ok(path) => path.trim_end_matches('/').to_string(), - Err(_) => String::from("./spec"), - }, - tmpl_path: match std::env::var("SF_TMPL_PATH") { - Ok(path) => path.trim_end_matches('/').to_string(), - Err(_) => String::from("./tmpl"), - }, - out_path: match std::env::var("SF_OUT_PATH") { - Ok(path) => path.trim_end_matches('/').to_string(), - Err(_) => String::from("./out"), - }, - log_path: match std::env::var("SF_LOG_PATH") { - Ok(path) => path.trim_end_matches('/').to_string(), - Err(_) => String::from("./log"), - }, - } -} +use std::path::PathBuf; const ABOUT_MSG: &str = r#"Skyforge Config Generation Engine"#; const ENV_MSG: &str = r#"Environment: @@ -85,38 +11,90 @@ const ENV_MSG: &str = r#"Environment: SF_LOG_PATH Directory for log output "#; -/// builds a custom command line argument parser -fn build() -> Command { - Command::new("app") - .about(ABOUT_MSG) - .version(env!("CARGO_PKG_VERSION")) - .author("rskntroot") - .arg( - Arg::new("devices") - .long("devices") - .short('d') - .help("A regular expression pattern") - .required(true), - ) - .arg( - Arg::new("debug") - .long("debug") - .help("Print debug information") - .action(ArgAction::SetTrue) - .required(false), - ) - .arg( - Arg::new("verbose") - .short('v') - .long("verbose") - .help("Print verbose information") - .action(ArgAction::SetTrue) - .required(false), - ) - .group( - ArgGroup::new("loglevel") - .args(&["debug", "verbose"]) - .required(false), - ) - .after_help(ENV_MSG) +#[derive(Debug)] +pub struct Args { + pub devices: Regex, + pub loglevel: LogLevel, + pub env: EnvVars, +} + +impl Args { + /// loads command line arguments and environment variables + pub fn parse() -> Args { + let matches: clap::ArgMatches = Self::parser().get_matches(); + let raw_devices = matches.get_one::("devices").unwrap(); + Args { + devices: Regex::new(&raw_devices) + .expect("Invalid regex pattern provided for `devices`"), + loglevel: if matches.get_flag("debug") { + LogLevel::Debug + } else if matches.get_flag("verbose") { + LogLevel::Verbose + } else { + LogLevel::Info + }, + env: EnvVars::parse(), + } + } + fn parser() -> Command { + Command::new("skyforge") + .about(ABOUT_MSG) + .version(env!("CARGO_PKG_VERSION")) + .author("rskntroot") + .arg( + Arg::new("devices") + .long("devices") + .short('d') + .help("A regular expression pattern") + .required(true), + ) + .arg( + Arg::new("debug") + .long("debug") + .help("Print debug information") + .action(ArgAction::SetTrue) + .required(false), + ) + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Print verbose information") + .action(ArgAction::SetTrue) + .required(false), + ) + .group( + ArgGroup::new("loglevel") + .args(&["debug", "verbose"]) + .required(false), + ) + .after_help(ENV_MSG) + } +} + +#[derive(Debug)] +pub struct EnvVars { + pub spec_path: PathBuf, + pub tmpl_path: PathBuf, + pub out_path: PathBuf, + pub log_path: PathBuf, +} + +impl EnvVars { + pub fn parse() -> Self { + Self { + spec_path: std::env::var("SF_SPEC_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("./spec")), + tmpl_path: std::env::var("SF_TMPL_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("./tmpl")), + out_path: std::env::var("SF_OUT_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("./out")), + log_path: std::env::var("SF_LOG_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("./log")), + } + } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..29cb019 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +pub mod cli; +pub mod log; +pub mod render; +pub mod spec; +pub mod tmpl; + +pub use cli::Args; +pub use log::LogLevel; +pub use render::DeviceConfigBundle; +pub use spec::Specification; diff --git a/src/main.rs b/src/main.rs index d2799d7..17688fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,11 @@ -mod cli; -mod log; -mod render; -mod spec; -mod tmpl; +use skyforge::{Args, DeviceConfigBundle, Specification}; -use log::LogLevel; -use render::RenderedConfig; +fn main() -> Result<(), Box> { + let args = Args::parse(); -fn main() { - let args: cli::Args = cli::parse_args(); - let dbg: LogLevel = args.loglevel; - - dbug!(dbg, "{:#?}", &args); - - let specifications: Vec = - spec::compile(&args.devices, &args.env.spec_path, dbg); - - for spec in specifications { - RenderedConfig::from_spec(&spec, dbg) - .unwrap_or_else(|e| { - eprintln!("{}", e); - std::process::exit(1); - }) - .output(&args.env.out_path, dbg); + for spec in Specification::compile(&args) { + DeviceConfigBundle::from_spec(spec)?.output_artifacts(); } + + Ok(()) } diff --git a/src/render.rs b/src/render.rs index e696b7f..88c1e75 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,13 +1,19 @@ +use crate::log::LogLevel; use crate::spec::Specification; use crate::tmpl; -use crate::{LogLevel, info, verb}; -use serde_json::Value; +use crate::{dbug, info, verb}; use std::io::Write; -pub struct RenderedConfig { +pub struct DeviceConfigBundle { pub hostname: String, + pub configs: Vec, + pub spec: Specification, + pub platform: String, +} + +pub struct ConfigVariant { + pub name: String, pub configs: Vec, - pub spec: Value, } pub struct Configuration { @@ -15,15 +21,12 @@ pub struct Configuration { pub data: String, } -impl RenderedConfig { - pub fn from_spec( - spec: &Specification, - dbg: LogLevel, - ) -> Result> { - let context = Self::get_context(&spec); +impl DeviceConfigBundle { + pub fn from_spec(spec: Specification) -> Result> { + let mut context = Self::get_context(&spec); let hostname = Self::get_hostname(&context, &spec); - info!(dbg, "Rendering {}", hostname); + info!(&spec.loglevel, "Rendering {}", hostname); let base_dir = std::path::Path::new("./tmpl").join(spec.get_layer()); let structure_path = base_dir.join("structure.yaml"); @@ -31,28 +34,38 @@ impl RenderedConfig { .map_err(|e| format!("Failed to parse {}: {}", structure_path.display(), e))?; let mut renderer = tera::Tera::default(); - renderer.add_raw_templates(structure.load_template_data(&base_dir))?; + renderer.add_raw_templates(structure.load_template_data(&base_dir, spec.loglevel))?; + dbug!(&spec.loglevel, "Processing templates for {}", hostname); - let mut configs = Vec::new(); - for template_name in &structure.files { - verb!(dbg, " | {}", &template_name); - if !renderer.get_template_names().any(|n| n == template_name) { - continue; - } - - match renderer.render(template_name, &context) { - Ok(rendered) => configs.push(Configuration { - name: template_name.clone(), - data: rendered, - }), - Err(e) => Self::log_tera_error(template_name, &e), + let mut config_variants = Vec::new(); + for variant in &structure.variations { + context.insert(variant, &true); + let mut configs = Vec::new(); + for template_name in &structure.files { + dbug!(&spec.loglevel, " | {}.{}", &template_name, variant); + if !renderer.get_template_names().any(|n| n == template_name) { + continue; + } + match renderer.render(template_name, &context) { + Ok(data) => configs.push(Configuration { + name: template_name.clone(), + data, + }), + Err(e) => Self::log_tera_error(template_name, &e), + } } + config_variants.push(ConfigVariant { + name: String::from(variant), + configs, + }); + context.remove(variant); } Ok(Self { hostname, - configs, - spec: spec.compiled.clone(), + configs: config_variants, + spec, + platform: structure.platform, }) } @@ -61,13 +74,7 @@ impl RenderedConfig { .get("hostname") .and_then(|v| v.as_str()) .map(|s| s.to_string()) - .unwrap_or_else(|| { - spec.device - .file_stem() - .and_then(|n| n.to_str()) - .unwrap_or("unknown") - .to_string() - }) + .unwrap_or_else(|| spec.components.get_hostname()) } fn get_context(spec: &Specification) -> tera::Context { @@ -90,33 +97,43 @@ impl RenderedConfig { eprintln!("[tera] {}: {}", name, chain); } - pub fn output(self: Self, outdir: &str, dbg: LogLevel) { - info!(dbg, "Writing Output:"); + pub fn output_artifacts(self: Self) { + info!(&self.spec.loglevel, "Writing Output:"); - let out_path = std::path::Path::new(outdir).join(self.hostname); - if out_path.exists() { - std::fs::remove_dir_all(&out_path).ok(); + let device_outpath = std::path::Path::new(&self.spec.outpath).join(self.hostname); + if device_outpath.exists() { + std::fs::remove_dir_all(&device_outpath).ok(); } - std::fs::create_dir_all(&out_path).ok(); + std::fs::create_dir_all(&device_outpath).ok(); - let merged_config_path = out_path.join("all.conf"); - let mut all_file: std::fs::File = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&merged_config_path) - .expect("unable to open"); + for variant_configs in &self.configs { + let out_path = device_outpath.join(&variant_configs.name); + std::fs::create_dir_all(&out_path).ok(); - for config in &self.configs { - let template_path = out_path.join(format!("{}.tera", &config.name)); - verb!(dbg, " | {}", &template_path.display()); - std::fs::write(&template_path, &config.data).ok(); - all_file.write_all(&config.data.as_bytes()).ok(); + let merged_config_path = + device_outpath.join(format!("all.{}.{}", variant_configs.name, &self.platform)); + let mut all_file: std::fs::File = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&merged_config_path) + .expect("unable to open"); + + for config in &variant_configs.configs { + let config_outpath = out_path.join(format!("{}.{}", &config.name, &self.platform)); + std::fs::create_dir_all(&config_outpath).ok(); + verb!(&self.spec.loglevel, " | {}", &config_outpath.display()); + std::fs::write(&config_outpath, &config.data).ok(); + all_file.write_all(&config.data.as_bytes()).ok(); + } + info!(&self.spec.loglevel, " | {} ", &merged_config_path.display()); } - let spec: String = yaml_serde::to_string(&self.spec).unwrap(); - let compiled_spec_path = out_path.join("compiled-spec.yaml"); - verb!(dbg, " | {}", &compiled_spec_path.display()); - std::fs::write(&compiled_spec_path, spec).ok(); - info!(dbg, " | {} ", &merged_config_path.display()); + let compiled_spec_path = device_outpath.join("context.yaml"); + verb!(&self.spec.loglevel, " | {}", &compiled_spec_path.display()); + std::fs::write( + &compiled_spec_path, + yaml_serde::to_string(&self.spec.compiled).unwrap(), + ) + .ok(); } } diff --git a/src/spec.rs b/src/spec.rs index 8feff88..c46cae8 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,19 +1,17 @@ +use crate::cli::Args; use crate::{LogLevel, info, verb}; use regex::Regex; use serde_json::{Map, Value, json}; -use std::path::{Path, PathBuf}; -use std::{env, fmt, fs}; +use std::path::PathBuf; use walkdir::WalkDir; use yaml_serde; #[derive(Debug)] pub struct Specification { - pub partitional: PathBuf, - pub regional: PathBuf, - pub common: PathBuf, - pub zonal: PathBuf, - pub device: PathBuf, pub compiled: Value, + pub components: Components, + pub loglevel: LogLevel, + pub outpath: PathBuf, } impl Specification { @@ -23,10 +21,12 @@ impl Specification { common: PathBuf, zonal: PathBuf, device: PathBuf, + loglevel: LogLevel, + outpath: PathBuf, ) -> Result> { let json_values: Vec = [&partitional, ®ional, &common, &zonal, &device] .iter() - .filter_map(|path| fs::read_to_string(path).ok()) + .filter_map(|path| std::fs::read_to_string(path).ok()) .map(|content| yaml_serde::from_str::(&content)) .collect::, _>>()?; @@ -40,17 +40,22 @@ impl Specification { let compiled = json!({"data": merged_map}); Ok(Self { - partitional, - regional, - common, - zonal, - device, compiled, + components: Components { + partitional, + regional, + common, + zonal, + device, + }, + loglevel, + outpath, }) } pub fn get_layer(&self) -> String { - self.device + self.components + .device .parent() .unwrap() .file_name() @@ -60,126 +65,157 @@ impl Specification { .filter(|c| !c.is_numeric()) .collect::() } + + /// compiles specifications from regex and paths provided by Args + pub fn compile(args: &Args) -> Vec { + let spec_list: Vec = Self::filter_specs( + &args.devices, + Self::get_specs(&args.env.spec_path, args.loglevel), + ); + info!( + &args.loglevel, + "Matched {} devices against '{}'", + &spec_list.len(), + &args.devices + ); + spec_list + .into_iter() + .map(|spec| { + verb!(&args.loglevel, " | {}", spec.display()); + Self::build( + Self::get_partitional(&Self::get_common(&spec), &Self::get_regional(&spec)), + Self::get_regional(&spec), + Self::get_common(&spec), + Self::get_zonal(&spec), + spec, + args.loglevel, + std::path::PathBuf::from(&args.env.out_path), + ) + .unwrap_or_else(|e| panic!("failed to build Specification: {}", e)) + }) + .collect() + } + + fn get_partitional(common: &PathBuf, regional: &PathBuf) -> PathBuf { + let re = Regex::new(r"partition: ([^\n\r$]+)").expect("failed"); + + [common, regional] + .iter() + .find_map(|path| { + std::fs::read_to_string(path).ok().and_then(|contents| { + re.captures(&contents).and_then(|captures| { + captures.get(1).map(|matched| { + PathBuf::from(format!("./spec/common/{}.yaml", matched.as_str().trim())) + }) + }) + }) + }) + .unwrap_or_else(|| PathBuf::from("./spec/common/default.yaml")) + } + + fn get_regional(path: &PathBuf) -> PathBuf { + let dir_name = path + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(); + + path.parent() + .unwrap() + .parent() + .unwrap() + .join("common") + .join(format!("{}.yaml", &dir_name[0..2])) + } + + fn get_common(spec: &PathBuf) -> PathBuf { + spec.with_file_name("common.yaml") + } + + fn get_zonal(spec: &PathBuf) -> PathBuf { + let zone = spec + .file_name() + .and_then(|f| f.to_str()) + .and_then(|f| f.split_once('-')) + .map(|(zone, _)| spec.with_file_name(zone)) + .unwrap_or_else(|| spec.with_file_name("common.yaml")); + + PathBuf::from(format!("{}.common.yaml", zone.to_str().unwrap())) + } + + fn filter_specs(pattern: &Regex, spec_list: Vec) -> Vec { + spec_list + .into_iter() + .filter(|spec| { + spec.file_name() + .and_then(|name| name.to_str()) + .map_or(false, |name| pattern.is_match(name)) + }) + .collect() + } + + fn get_specs(spec_path: &PathBuf, dbg: LogLevel) -> Vec { + let mut result = Vec::new(); + let root = spec_path.as_path(); + for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) { + if let Ok(path) = entry.path().strip_prefix(root) { + let path_str = path.to_string_lossy().to_string(); + if !path_str.contains("common") && path_str.ends_with("yaml") { + result.push(root.join(path)); + } + } + } + info!( + dbg, + "Skyforge found {} renderable devices in {}", + result.len(), + std::env::current_dir().unwrap().display() + ); + result + } } -impl fmt::Display for Specification { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl std::fmt::Display for Specification { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "Specification:\nPartitional: {}\nRegional: {}\nCommon: {}\nZonal: {}\nDevice: {}\nCompiled:\n{}", + "Compiled Specification: {}\nLogLevel: {}\nComponents: {}", + self.compiled, self.loglevel, self.components, + ) + } +} + +#[derive(Debug)] +pub struct Components { + partitional: PathBuf, + regional: PathBuf, + common: PathBuf, + zonal: PathBuf, + device: PathBuf, +} + +impl Components { + pub fn get_hostname(self: &Self) -> String { + self.device + .file_stem() + .and_then(|n| n.to_str()) + .unwrap_or("unknown") + .to_string() + } +} + +impl std::fmt::Display for Components { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + " | Partitional: {}\n | Regional: {}\n | Common: {}\n | Zonal: {}\n | Device: {}\n", self.partitional.display(), self.regional.display(), self.common.display(), self.zonal.display(), self.device.display(), - self.compiled ) } } - -pub fn compile(pattern: &Regex, spec_path: &String, dbg: LogLevel) -> Vec { - let spec_list: Vec = filter_specs(pattern, get_specs(spec_path, dbg)); - info!( - dbg, - "Matched {} devices against '{}'", - &spec_list.len(), - &pattern - ); - for spec in &spec_list { - verb!(dbg, " | {}", spec.display()); - } - let mut specifications: Vec = Vec::new(); - for spec in spec_list { - let zonal = get_zonal(&spec); - let common = get_common(&spec); - let regional = get_regional(&spec); - let partitional = get_partitional(&common, ®ional); - specifications.push( - match Specification::build(partitional, regional, common, zonal, spec) { - Ok(compiled_spec) => compiled_spec, - Err(e) => panic!("failed to build Specification: {}", e), - }, - ) - } - specifications -} - -fn get_partitional(common: &PathBuf, regional: &PathBuf) -> PathBuf { - let re = Regex::new(r"partition: ([^\n\r$]+)").expect("failed"); - - [common, regional] - .iter() - .find_map(|path| { - fs::read_to_string(path).ok().and_then(|contents| { - re.captures(&contents).and_then(|captures| { - captures.get(1).map(|matched| { - PathBuf::from(format!("./spec/common/{}.yaml", matched.as_str().trim())) - }) - }) - }) - }) - .unwrap_or_else(|| PathBuf::from("./spec/common/default.yaml")) -} - -fn get_regional(path: &PathBuf) -> PathBuf { - let dir_name = path - .parent() - .unwrap() - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(); - - path.parent() - .unwrap() - .parent() - .unwrap() - .join("common") - .join(format!("{}.yaml", &dir_name[0..2])) -} - -fn get_common(spec: &PathBuf) -> PathBuf { - spec.with_file_name("common.yaml") -} - -fn get_zonal(spec: &PathBuf) -> PathBuf { - let zone = spec - .file_name() - .and_then(|f| f.to_str()) - .and_then(|f| f.split_once('-')) - .map(|(zone, _)| spec.with_file_name(zone)) - .unwrap_or_else(|| spec.with_file_name("common.yaml")); - - PathBuf::from(format!("{}.common.yaml", zone.to_str().unwrap())) -} - -fn filter_specs(pattern: &Regex, spec_list: Vec) -> Vec { - spec_list - .into_iter() - .filter(|spec| { - spec.file_name() - .and_then(|name| name.to_str()) - .map_or(false, |name| pattern.is_match(name)) - }) - .collect() -} - -fn get_specs(spec_path: &String, dbg: LogLevel) -> Vec { - let mut result = Vec::new(); - let root = Path::new(&spec_path); - for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) { - if let Ok(path) = entry.path().strip_prefix(root) { - let path_str = path.to_string_lossy().to_string(); - if !path_str.contains("common") && path_str.ends_with("yaml") { - result.push(root.join(path)); - } - } - } - info!( - dbg, - "Skyforge found {} renderable devices in {}", - result.len(), - env::current_dir().unwrap().display() - ); - result -} diff --git a/src/tmpl.rs b/src/tmpl.rs index 6f9455f..16f3a00 100644 --- a/src/tmpl.rs +++ b/src/tmpl.rs @@ -1,12 +1,11 @@ +use crate::{LogLevel, crit, info}; use serde::Deserialize; use yaml_serde; #[derive(Deserialize)] pub struct Structure { pub files: Vec, - #[allow(dead_code)] //todo pub platform: String, - #[allow(dead_code)] //todo pub variations: Vec, } @@ -17,15 +16,22 @@ impl Structure { Ok(yaml_serde::from_str(&data)?) } - pub fn load_template_data(&self, base_dir: &std::path::Path) -> Vec<(String, String)> { + pub fn load_template_data( + &self, + tmpl_dir: &std::path::Path, + dbg: LogLevel, + ) -> Vec<(String, String)> { self.files .iter() .filter_map(|name| { - let path = base_dir.join(format!("{}.tera", name)); - match std::fs::read_to_string(&path) { - Ok(content) => Some((name.clone(), content)), + let template = tmpl_dir.join(format!("{}.tera", name)); + match std::fs::read_to_string(&template) { + Ok(content) => { + info!(dbg, " | {}", template.display()); + Some((name.clone(), content)) + } Err(e) => { - eprintln!("Skipping {}: {}", path.display(), e); + crit!(dbg, "Skipping {}: {}", template.display(), e); None } }