From 96499615803b1adb64d266a299a4355b30f552ca Mon Sep 17 00:00:00 2001 From: lost Date: Sun, 22 Feb 2026 05:43:26 -0700 Subject: [PATCH] refactor logger --- src/cli.rs | 26 +++++++------ src/lib.rs | 2 +- src/log.rs | 38 ++++++++----------- src/render.rs | 101 +++++++++++++++++++++++++++----------------------- src/spec.rs | 58 ++++++++++++++--------------- src/tmpl.rs | 17 ++++----- 6 files changed, 121 insertions(+), 121 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0d71a7a..fb876dd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use crate::log::LogLevel; +use crate::log::{LOG_LEVEL, LogLevel}; use clap::{Arg, ArgAction, ArgGroup, Command}; use regex::Regex; use std::path::PathBuf; @@ -22,17 +22,21 @@ 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(); + + let devices = Regex::new(matches.get_one::("devices").unwrap()) + .expect("Invalid regex pattern provided for `devices`"); + + let mut loglevel = LogLevel::Info; + if matches.get_flag("debug") { + loglevel = LogLevel::Debug; + } else if matches.get_flag("verbose") { + loglevel = LogLevel::Verbose; + } + LOG_LEVEL.set(loglevel).ok(); + 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 - }, + devices, + loglevel, env: EnvVars::parse(), } } diff --git a/src/lib.rs b/src/lib.rs index 29cb019..8cdac83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,6 @@ pub mod spec; pub mod tmpl; pub use cli::Args; -pub use log::LogLevel; +pub use log::{LogLevel, log_level}; pub use render::DeviceConfigBundle; pub use spec::Specification; diff --git a/src/log.rs b/src/log.rs index bb6d24e..c844cfb 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] +use std::sync::OnceLock; -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] pub enum LogLevel { Debug, Verbose, @@ -10,17 +11,10 @@ pub enum LogLevel { None, } -impl LogLevel { - pub fn value(&self) -> u8 { - match self { - LogLevel::Debug => u8::MIN, - LogLevel::Verbose => u8::from(30), - LogLevel::Info => u8::from(60), - LogLevel::Warning => u8::from(90), - LogLevel::Critical => u8::from(120), - LogLevel::None => u8::MAX, - } - } +pub static LOG_LEVEL: OnceLock = OnceLock::new(); + +pub fn log_level() -> LogLevel { + *LOG_LEVEL.get().unwrap_or(&LogLevel::Info) } impl std::fmt::Display for LogLevel { @@ -31,8 +25,8 @@ impl std::fmt::Display for LogLevel { #[macro_export] macro_rules! info { - ($current_level:expr, $($msg:expr),*) => { - if LogLevel::Info.value() >= $current_level.value() { + ($($msg:expr),*) => { + if LogLevel::Info >= $crate::log_level() { println!($($msg),*); } }; @@ -40,8 +34,8 @@ macro_rules! info { #[macro_export] macro_rules! verb { - ($current_level:expr, $($msg:expr),*) => { - if LogLevel::Verbose.value() >= $current_level.value() { + ($($msg:expr),*) => { + if LogLevel::Verbose >= $crate::log_level() { println!($($msg),*); } }; @@ -49,8 +43,8 @@ macro_rules! verb { #[macro_export] macro_rules! dbug { - ($current_level:expr, $($msg:expr),*) => { - if LogLevel::Debug.value() >= $current_level.value() { + ($($msg:expr),*) => { + if LogLevel::Debug >= $crate::log_level() { println!($($msg),*); } }; @@ -58,8 +52,8 @@ macro_rules! dbug { #[macro_export] macro_rules! warn { - ($current_level:expr, $($msg:expr),*) => { - if LogLevel::Warning.value() >= $current_level.value() { + ($($msg:expr),*) => { + if LogLevel::Warning >= $crate::log_level() { eprintln!($($msg),*); } }; @@ -67,8 +61,8 @@ macro_rules! warn { #[macro_export] macro_rules! crit { - ($current_level:expr, $($msg:expr),*) => { - if LogLevel::Critical.value() >= $current_level.value() { + ($($msg:expr),*) => { + if LogLevel::Critical >= $crate::log_level() { eprintln!($($msg),*); } }; diff --git a/src/render.rs b/src/render.rs index 88c1e75..0131892 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,7 +1,8 @@ use crate::log::LogLevel; use crate::spec::Specification; use crate::tmpl; -use crate::{dbug, info, verb}; +use crate::{crit, dbug, info, verb}; +use std::error::Error; use std::io::Write; pub struct DeviceConfigBundle { @@ -23,50 +24,49 @@ pub struct Configuration { impl DeviceConfigBundle { pub fn from_spec(spec: Specification) -> Result> { - let mut context = Self::get_context(&spec); + let mut context = Self::merge_context(&spec); let hostname = Self::get_hostname(&context, &spec); - info!(&spec.loglevel, "Rendering {}", hostname); - let base_dir = std::path::Path::new("./tmpl").join(spec.get_layer()); + info!("Loading templates for '{hostname}':"); + let structure_path = base_dir.join("structure.yaml"); let structure = tmpl::Structure::from_file(&structure_path) - .map_err(|e| format!("Failed to parse {}: {}", structure_path.display(), e))?; + .map_err(|e| format!("Failed to parse {}: {e}", structure_path.display()))?; let mut renderer = tera::Tera::default(); - renderer.add_raw_templates(structure.load_template_data(&base_dir, spec.loglevel))?; - dbug!(&spec.loglevel, "Processing templates for {}", hostname); + renderer.add_raw_templates(structure.load_template_data(base_dir))?; + dbug!("Processing templates for {hostname}"); - let mut config_variants = Vec::new(); + let mut failures = false; + let mut configs = Vec::new(); for variant in &structure.variations { context.insert(variant, &true); - let mut configs = Vec::new(); + let mut cfgs = 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; - } + dbug!(" | {template_name}.{variant}"); 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), + Ok(data) => cfgs.push(Configuration::new(template_name, data)), + Err(e) => { + crit!("[!] {}", Error::source(&e).unwrap_or_else(|| &e)); + renderer.add_raw_template(template_name, "")?; + failures = true; + } } } - config_variants.push(ConfigVariant { - name: String::from(variant), - configs, - }); + configs.push(ConfigVariant::new(variant, cfgs)); context.remove(variant); } - Ok(Self { - hostname, - configs: config_variants, - spec, - platform: structure.platform, - }) + match failures { + true => Err(Box::::from("Rendering failed.")), + false => Ok(Self { + hostname, + configs, + spec, + platform: structure.platform, + }), + } } fn get_hostname(context: &tera::Context, spec: &Specification) -> String { @@ -77,7 +77,7 @@ impl DeviceConfigBundle { .unwrap_or_else(|| spec.components.get_hostname()) } - fn get_context(spec: &Specification) -> tera::Context { + fn merge_context(spec: &Specification) -> tera::Context { let mut context = tera::Context::new(); if let Some(data_map) = spec.compiled.get("data").and_then(|v| v.as_object()) { for (key, val) in data_map { @@ -87,18 +87,8 @@ impl DeviceConfigBundle { context } - 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); - } - pub fn output_artifacts(self: Self) { - info!(&self.spec.loglevel, "Writing Output:"); + info!("Writing Output:"); let device_outpath = std::path::Path::new(&self.spec.outpath).join(self.hostname); if device_outpath.exists() { @@ -106,12 +96,12 @@ impl DeviceConfigBundle { } std::fs::create_dir_all(&device_outpath).ok(); - for variant_configs in &self.configs { + for variant_configs in self.configs { let out_path = device_outpath.join(&variant_configs.name); std::fs::create_dir_all(&out_path).ok(); let merged_config_path = - device_outpath.join(format!("all.{}.{}", variant_configs.name, &self.platform)); + 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) @@ -119,17 +109,16 @@ impl DeviceConfigBundle { .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()); + let config_outpath = out_path.join(format!("{}.{}", config.name, self.platform)); + verb!(" | {}", &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()); + info!(" | {} ", &merged_config_path.display()); } let compiled_spec_path = device_outpath.join("context.yaml"); - verb!(&self.spec.loglevel, " | {}", &compiled_spec_path.display()); + verb!(" | {}", &compiled_spec_path.display()); std::fs::write( &compiled_spec_path, yaml_serde::to_string(&self.spec.compiled).unwrap(), @@ -137,3 +126,21 @@ impl DeviceConfigBundle { .ok(); } } + +impl ConfigVariant { + pub fn new(name: &str, configs: Vec) -> Self { + Self { + name: String::from(name), + configs, + } + } +} + +impl Configuration { + pub fn new(name: &str, data: String) -> Self { + Self { + name: String::from(name), + data, + } + } +} diff --git a/src/spec.rs b/src/spec.rs index c46cae8..c7625df 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -10,10 +10,20 @@ use yaml_serde; pub struct Specification { pub compiled: Value, pub components: Components, - pub loglevel: LogLevel, pub outpath: PathBuf, } +trait Pipe: Sized { + fn tap(self, f: F) -> Self { + f(&self); + self + } + fn pipe U>(self, f: F) -> U { + f(self) + } +} +impl Pipe for T {} + impl Specification { pub fn build( partitional: PathBuf, @@ -21,7 +31,6 @@ impl Specification { common: PathBuf, zonal: PathBuf, device: PathBuf, - loglevel: LogLevel, outpath: PathBuf, ) -> Result> { let json_values: Vec = [&partitional, ®ional, &common, &zonal, &device] @@ -48,7 +57,6 @@ impl Specification { zonal, device, }, - loglevel, outpath, }) } @@ -68,30 +76,26 @@ impl Specification { /// 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 + Self::get_specs(&args.env.spec_path) + .pipe(|p| { + info!(" Skyforge found {} renderable devices", p.len()); + Self::filter_specs(&args.devices, p) + }) + .tap(|p| info!("Matched {} devices against '{}'", p.len(), &args.devices)) .into_iter() .map(|spec| { - verb!(&args.loglevel, " | {}", spec.display()); + verb!(" | {}", spec.display()); + let common = Self::get_common(&spec); + let regional = Self::get_regional(&spec); Self::build( - Self::get_partitional(&Self::get_common(&spec), &Self::get_regional(&spec)), - Self::get_regional(&spec), - Self::get_common(&spec), + Self::get_partitional(&common, ®ional), + regional, + common, Self::get_zonal(&spec), spec, - args.loglevel, - std::path::PathBuf::from(&args.env.out_path), + args.env.out_path.clone(), ) - .unwrap_or_else(|e| panic!("failed to build Specification: {}", e)) + .expect("failed to build Specification") }) .collect() } @@ -156,7 +160,7 @@ impl Specification { .collect() } - fn get_specs(spec_path: &PathBuf, dbg: LogLevel) -> Vec { + fn get_specs(spec_path: &PathBuf) -> 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()) { @@ -167,12 +171,6 @@ impl Specification { } } } - info!( - dbg, - "Skyforge found {} renderable devices in {}", - result.len(), - std::env::current_dir().unwrap().display() - ); result } } @@ -181,8 +179,8 @@ impl std::fmt::Display for Specification { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "Compiled Specification: {}\nLogLevel: {}\nComponents: {}", - self.compiled, self.loglevel, self.components, + "Compiled Specification: {}\nComponents: {}", + self.compiled, self.components, ) } } diff --git a/src/tmpl.rs b/src/tmpl.rs index 16f3a00..e8f6136 100644 --- a/src/tmpl.rs +++ b/src/tmpl.rs @@ -1,5 +1,7 @@ use crate::{LogLevel, crit, info}; use serde::Deserialize; +use std::error::Error; +use std::path::PathBuf; use yaml_serde; #[derive(Deserialize)] @@ -10,28 +12,23 @@ pub struct Structure { } impl Structure { - pub fn from_file(path: &std::path::Path) -> Result> { - let data = - std::fs::read_to_string(path).map_err(|e| format!("{}: {}", path.display(), e))?; + pub fn from_file(path: &PathBuf) -> Result> { + let data = std::fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?; Ok(yaml_serde::from_str(&data)?) } - pub fn load_template_data( - &self, - tmpl_dir: &std::path::Path, - dbg: LogLevel, - ) -> Vec<(String, String)> { + pub fn load_template_data(&self, tmpl_dir: PathBuf) -> Vec<(String, String)> { self.files .iter() .filter_map(|name| { let template = tmpl_dir.join(format!("{}.tera", name)); match std::fs::read_to_string(&template) { Ok(content) => { - info!(dbg, " | {}", template.display()); + info!(" | {}", template.display()); Some((name.clone(), content)) } Err(e) => { - crit!(dbg, "Skipping {}: {}", template.display(), e); + crit!("[!] {}: {e}", template.display()); None } }