From 2c66dcdf37d836627c98daeec27aa7c2ca4d2630 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 26 Aug 2017 15:27:13 +0200 Subject: [PATCH 01/10] Rewrite logging: Types This is the first part of a series of patches that reimplements the logging backend to be more powerful and configurable. This first patch adds types and infrastructure to be able to implement a powerful logging abstraction. It does not implement much functionality, except for a check whether a module is enabled or not when logging (if configured in the config file). --- lib/core/libimagrt/src/error.rs | 8 +- lib/core/libimagrt/src/lib.rs | 1 + lib/core/libimagrt/src/logger.rs | 338 +++++++++++++++++++++++------- lib/core/libimagrt/src/runtime.rs | 68 +++--- 4 files changed, 307 insertions(+), 108 deletions(-) diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs index 388335af..ff992291 100644 --- a/lib/core/libimagrt/src/error.rs +++ b/lib/core/libimagrt/src/error.rs @@ -23,7 +23,13 @@ use std::io::Error as IOError; generate_error_types!(RuntimeError, RuntimeErrorKind, Instantiate => "Could not instantiate", IOError => "IO Error", - ProcessExitFailure => "Process exited with failure" + IOLogFileOpenError => "IO Error: Could not open logfile", + ProcessExitFailure => "Process exited with failure", + ConfigReadError => "Error while reading the configuration", + ConfigTypeError => "Error while reading the configuration: Type Error", + GlobalLogLevelConfigMissing => "Global config 'imag.logging.level' missing", + InvalidLogLevelSpec => "Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed", + TomlReadError => "Error while reading in TOML document" ); impl From for RuntimeError { diff --git a/lib/core/libimagrt/src/lib.rs b/lib/core/libimagrt/src/lib.rs index ce0e08c1..addce7b0 100644 --- a/lib/core/libimagrt/src/lib.rs +++ b/lib/core/libimagrt/src/lib.rs @@ -38,6 +38,7 @@ extern crate itertools; #[cfg(unix)] extern crate xdg_basedir; extern crate env_logger; extern crate ansi_term; +extern crate handlebars; extern crate clap; extern crate toml; diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index 992a9d94..e80a4c7b 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -19,67 +19,75 @@ use std::io::Write; use std::io::stderr; +use std::collections::BTreeMap; +use configuration::Configuration; +use error::RuntimeErrorKind as EK; +use error::RuntimeError; +use error::MapErrInto; +use runtime::Runtime; + +use libimagerror::into::IntoError; + +use clap::ArgMatches; use log::{Log, LogLevel, LogRecord, LogMetadata}; +use toml::Value; +use toml_query::read::TomlValueReadExt; -use ansi_term::Style; -use ansi_term::Colour; -use ansi_term::ANSIString; +type ModuleName = String; +type Result = ::std::result::Result; + +enum LogDestination { + Stderr, + File(::std::fs::File), +} + +impl Default for LogDestination { + fn default() -> LogDestination { + LogDestination::Stderr + } +} + +struct ModuleSettings { + enabled: bool, + level: LogLevel, + + #[allow(unused)] + destinations: Vec, +} /// Logger implementation for `log` crate. pub struct ImagLogger { - prefix: String, - dbg_fileline: bool, - lvl: LogLevel, - color_enabled: bool, + global_loglevel : LogLevel, + + #[allow(unused)] + global_destinations : Vec, + // global_format_trace : , + // global_format_debug : , + // global_format_info : , + // global_format_warn : , + // global_format_error : , + module_settings : BTreeMap, } impl ImagLogger { /// Create a new ImagLogger object with a certain level - pub fn new(lvl: LogLevel) -> ImagLogger { - ImagLogger { - prefix: "[imag]".to_owned(), - dbg_fileline: true, - lvl: lvl, - color_enabled: true - } + pub fn new(matches: &ArgMatches, config: Option<&Configuration>) -> Result { + Ok(ImagLogger { + global_loglevel : try!(aggregate_global_loglevel(matches, config)), + global_destinations : try!(aggregate_global_destinations(matches, config)), + // global_format_trace : try!(aggregate_global_format_trace(matches, config)), + // global_format_debug : try!(aggregate_global_format_debug(matches, config)), + // global_format_info : try!(aggregate_global_format_info(matches, config)), + // global_format_warn : try!(aggregate_global_format_warn(matches, config)), + // global_format_error : try!(aggregate_global_format_error(matches, config)), + module_settings : try!(aggregate_module_settings(matches, config)), + }) } - /// Set debugging to include file and line - pub fn with_dbg_file_and_line(mut self, b: bool) -> ImagLogger { - self.dbg_fileline = b; - self - } - - /// Set debugging to include prefix - pub fn with_prefix(mut self, pref: String) -> ImagLogger { - self.prefix = pref; - self - } - - /// Set debugging to have color - pub fn with_color(mut self, b: bool) -> ImagLogger { - self.color_enabled = b; - self - } - - /// Helper function to colorize a string with a certain Style - fn style_or_not(&self, c: Style, s: String) -> ANSIString { - if self.color_enabled { - c.paint(s) - } else { - ANSIString::from(s) - } - } - - /// Helper function to colorize a string with a certain Color - fn color_or_not(&self, c: Colour, s: String) -> ANSIString { - if self.color_enabled { - c.paint(s) - } else { - ANSIString::from(s) - } + pub fn global_loglevel(&self) -> LogLevel { + self.global_loglevel } } @@ -87,47 +95,217 @@ impl ImagLogger { impl Log for ImagLogger { fn enabled(&self, metadata: &LogMetadata) -> bool { - metadata.level() <= self.lvl + metadata.level() <= self.global_loglevel } fn log(&self, record: &LogRecord) { - use ansi_term::Colour::Red; - use ansi_term::Colour::Yellow; - use ansi_term::Colour::Cyan; + let log_level = record.level(); + let log_location = record.location(); + let log_target = record.target(); - if self.enabled(record.metadata()) { - // TODO: This is just simple logging. Maybe we can enhance this lateron - let loc = record.location(); - match record.metadata().level() { - LogLevel::Debug => { - let lvl = self.color_or_not(Cyan, format!("{}", record.level())); - let args = self.color_or_not(Cyan, format!("{}", record.args())); - if self.dbg_fileline { - let file = self.color_or_not(Cyan, format!("{}", loc.file())); - let ln = self.color_or_not(Cyan, format!("{}", loc.line())); + self.module_settings + .get(log_target) + .map(|module_setting| { + if module_setting.enabled && module_setting.level >= log_level { + write!(stderr(), "[imag][{}]: {}", log_level, record.args()).ok(); + } + }) + .unwrap_or_else(|| { + if self.global_loglevel >= log_level { + // Yes, we log + write!(stderr(), "[imag][{}]: {}", log_level, record.args()).ok(); + } + }); + } +} - writeln!(stderr(), "{}[{: <5}][{}][{: >5}]: {}", self.prefix, lvl, file, ln, args).ok(); - } else { - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - } - }, - LogLevel::Warn | LogLevel::Error => { - let lvl = self.style_or_not(Red.blink(), format!("{}", record.level())); - let args = self.color_or_not(Red, format!("{}", record.args())); +fn match_log_level_str(s: &str) -> Result { + match s { + "trace" => Ok(LogLevel::Trace), + "debug" => Ok(LogLevel::Debug), + "info" => Ok(LogLevel::Info), + "warn" => Ok(LogLevel::Warn), + "error" => Ok(LogLevel::Error), + _ => return Err(EK::InvalidLogLevelSpec.into_error()), + } +} - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - }, - LogLevel::Info => { - let lvl = self.color_or_not(Yellow, format!("{}", record.level())); - let args = self.color_or_not(Yellow, format!("{}", record.args())); +fn aggregate_global_loglevel(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + match config { + Some(cfg) => match cfg + .read("imag.logging.level") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::String(ref s))) => match_log_level_str(s), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e) + }, + None => { + if matches.is_present(Runtime::arg_debugging_name()) { + return Ok(LogLevel::Debug) + } - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - }, - _ => { - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, record.level(), record.args()).ok(); - }, + matches + .value_of(Runtime::arg_verbosity_name()) + .map(match_log_level_str) + .unwrap_or(Ok(LogLevel::Info)) + } + } +} + +fn translate_destination(raw: &str) -> Result { + use std::fs::OpenOptions; + + match raw { + "-" => Ok(LogDestination::Stderr), + other => { + OpenOptions::new() + .append(true) + .create(true) + .open(other) + .map(LogDestination::File) + .map_err_into(EK::IOLogFileOpenError) + } + } +} + + +fn translate_destinations(raw: &Vec) -> Result> { + raw.iter() + .fold(Ok(vec![]), |acc, val| { + acc.and_then(|mut v| { + let dest = match *val { + Value::String(ref s) => try!(translate_destination(s)), + _ => return Err(EK::ConfigTypeError.into_error()), + }; + v.push(dest); + Ok(v) + }) + }) +} + +fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configuration>) + -> Result> +{ + + match config { + Some(cfg) => match cfg + .read("imag.logging.destinations") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::Array(ref a))) => translate_destinations(a), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e) + }, + None => { + if let Some(values) = matches.value_of(Runtime::arg_logdest_name()) { + // parse logdest specification from commandline + + values.split(",") + .fold(Ok(vec![]), move |acc, dest| { + acc.and_then(|mut v| { + v.push(try!(translate_destination(dest))); + Ok(v) + }) + }) + } else { + Ok(vec![ LogDestination::default() ]) } } } } +// fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>) +// -> +// { +// unimplemented!() +// } +// +// fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>) +// -> +// { +// unimplemented!() +// } +// +// fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>) +// -> +// { +// unimplemented!() +// } +// +// fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>) +// -> +// { +// unimplemented!() +// } +// +// fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>) +// -> +// { +// unimplemented!() +// } + +fn aggregate_module_settings(matches: &ArgMatches, config: Option<&Configuration>) + -> Result> +{ + match config { + Some(cfg) => match cfg + .read("imag.logging.modules") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::Table(ref t))) => { + // translate the module settings from the table `t` + let mut settings = BTreeMap::new(); + + for (module_name, v) in t { + let destinations = try!(match v.read("destinations") { + Ok(Some(&Value::Array(ref a))) => translate_destinations(a), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let level = try!(match v.read("level") { + Ok(Some(&Value::String(ref s))) => match_log_level_str(s), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let enabled = try!(match v.read("enabled") { + Ok(Some(&Value::Boolean(b))) => Ok(b), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let module_settings = ModuleSettings { + enabled: enabled, + level: level, + destinations: destinations, + }; + + // We don't care whether there was a value, we override it. + let _ = settings.insert(module_name.to_owned(), module_settings); + } + + Ok(settings) + }, + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e), + }, + None => { + write!(stderr(), "No Configuration.").ok(); + write!(stderr(), "cannot find module-settings for logging.").ok(); + write!(stderr(), "Will use global defaults").ok(); + + Ok(BTreeMap::new()) + } + } +} + diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs index d30ca3aa..d4f29f1c 100644 --- a/lib/core/libimagrt/src/runtime.rs +++ b/lib/core/libimagrt/src/runtime.rs @@ -20,14 +20,12 @@ use std::path::PathBuf; use std::process::Command; use std::env; -use std::io::stderr; -use std::io::Write; +use std::process::exit; pub use clap::App; use clap::{Arg, ArgMatches}; use log; -use log::LogLevelFilter; use configuration::{Configuration, InternalConfiguration}; use error::RuntimeError; @@ -35,6 +33,7 @@ use error::RuntimeErrorKind; use error::MapErrInto; use logger::ImagLogger; +use libimagerror::trace::*; use libimagstore::store::Store; use libimagstore::file_abstraction::InMemoryFileAbstraction; use spec::CliSpec; @@ -112,16 +111,10 @@ impl<'a> Runtime<'a> { where C: Clone + CliSpec<'a> + InternalConfiguration { use std::io::stdout; - use clap::Shell; - let is_debugging = matches.is_present(Runtime::arg_debugging_name()); - if cli_app.enable_logging() { - let is_verbose = matches.is_present(Runtime::arg_verbosity_name()); - let colored = !matches.is_present(Runtime::arg_no_color_output_name()); - - Runtime::init_logger(is_debugging, is_verbose, colored); + Runtime::init_logger(&matches, config.as_ref()) } match matches.value_of(Runtime::arg_generate_compl()) { @@ -151,9 +144,9 @@ impl<'a> Runtime<'a> { None => None, }; - if is_debugging { - write!(stderr(), "Config: {:?}\n", config).ok(); - write!(stderr(), "Store-config: {:?}\n", store_config).ok(); + if matches.is_present(Runtime::arg_debugging_name()) { + debug!("Config: {:?}\n", config); + debug!("Store-config: {:?}\n", store_config); } let store_result = if cli_app.use_inmemory_fs() { @@ -200,9 +193,11 @@ impl<'a> Runtime<'a> { .arg(Arg::with_name(Runtime::arg_verbosity_name()) .short("v") .long("verbose") - .help("Enables verbosity") + .help("Enables verbosity, can be used to set log level to one of 'trace', 'debug', 'info', 'warn' or 'error'") .required(false) - .takes_value(false)) + .takes_value(true) + .possible_values(&["trace", "debug", "info", "warn", "error"]) + .value_name("LOGLEVEL")) .arg(Arg::with_name(Runtime::arg_debugging_name()) .long("debug") @@ -254,6 +249,21 @@ impl<'a> Runtime<'a> { .value_name("SHELL") .possible_values(&["bash", "fish", "zsh"])) + .arg(Arg::with_name(Runtime::arg_logdest_name()) + .long(Runtime::arg_logdest_name()) + .help("Override the logging destinations from the configuration: values can be seperated by ',', a value of '-' marks the stderr output, everything else is expected to be a path") + .required(false) + .takes_value(true) + .value_name("LOGDESTS")) + + .arg(Arg::with_name(Runtime::arg_override_module_logging_setting_name()) + .long(Runtime::arg_override_module_logging_setting_name()) + .help("Override a module logging setting for one module. Format: ==, whereas is either 'enabled', 'level' or 'destinations' - This commandline argument is CURRENTLY NOT IMPLEMENTED") + .multiple(true) + .required(false) + .takes_value(true) + .value_name("SPEC")) + } /// Get the argument names of the Runtime which are available @@ -338,26 +348,30 @@ impl<'a> Runtime<'a> { self } + /// Get the argument name for the logging destination + pub fn arg_logdest_name() -> &'static str { + "logging-destinations" + } + + pub fn arg_override_module_logging_setting_name() -> &'static str { + "override-module-log-setting" + } + /// Initialize the internal logger - fn init_logger(is_debugging: bool, is_verbose: bool, colored: bool) { + fn init_logger(matches: &ArgMatches, config: Option<&Configuration>) { use std::env::var as env_var; use env_logger; if env_var("IMAG_LOG_ENV").is_ok() { env_logger::init().unwrap(); } else { - let lvl = if is_debugging { - LogLevelFilter::Debug - } else if is_verbose { - LogLevelFilter::Info - } else { - LogLevelFilter::Warn - }; - log::set_logger(|max_log_lvl| { - max_log_lvl.set(lvl); - debug!("Init logger with {}", lvl); - Box::new(ImagLogger::new(lvl.to_log_level().unwrap()).with_color(colored)) + let logger = ImagLogger::new(matches, config) + .map_err_trace() + .unwrap_or_else(|_| exit(1)); + max_log_lvl.set(logger.global_loglevel().to_log_level_filter()); + debug!("Init logger with {}", logger.global_loglevel()); + Box::new(logger) }) .map_err(|e| panic!("Could not setup logger: {:?}", e)) .ok(); From ae249540204d0e0107d20eb381823427f7a4b160 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 26 Aug 2017 22:49:30 +0200 Subject: [PATCH 02/10] Implement templating --- lib/core/libimagrt/Cargo.toml | 1 + lib/core/libimagrt/src/error.rs | 3 +- lib/core/libimagrt/src/logger.rs | 207 +++++++++++++++++++++++++------ 3 files changed, 174 insertions(+), 37 deletions(-) diff --git a/lib/core/libimagrt/Cargo.toml b/lib/core/libimagrt/Cargo.toml index 25e95ff0..e557ecd4 100644 --- a/lib/core/libimagrt/Cargo.toml +++ b/lib/core/libimagrt/Cargo.toml @@ -23,6 +23,7 @@ itertools = "0.5" ansi_term = "0.9" is-match = "0.1" toml-query = "0.3.0" +handlebars = "0.29.0" libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" } diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs index ff992291..8427f0b6 100644 --- a/lib/core/libimagrt/src/error.rs +++ b/lib/core/libimagrt/src/error.rs @@ -29,7 +29,8 @@ generate_error_types!(RuntimeError, RuntimeErrorKind, ConfigTypeError => "Error while reading the configuration: Type Error", GlobalLogLevelConfigMissing => "Global config 'imag.logging.level' missing", InvalidLogLevelSpec => "Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed", - TomlReadError => "Error while reading in TOML document" + TomlReadError => "Error while reading in TOML document", + TemplateStringRegistrationError => "Error while registering logging template string" ); impl From for RuntimeError { diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index e80a4c7b..c82bac3b 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -33,6 +33,7 @@ use clap::ArgMatches; use log::{Log, LogLevel, LogRecord, LogMetadata}; use toml::Value; use toml_query::read::TomlValueReadExt; +use handlebars::Handlebars; type ModuleName = String; type Result = ::std::result::Result; @@ -68,21 +69,56 @@ pub struct ImagLogger { // global_format_warn : , // global_format_error : , module_settings : BTreeMap, + + handlebars: Handlebars, } impl ImagLogger { /// Create a new ImagLogger object with a certain level pub fn new(matches: &ArgMatches, config: Option<&Configuration>) -> Result { + let mut handlebars = Handlebars::new(); + + handlebars.register_helper("black" , Box::new(self::template_helpers::ColorizeBlackHelper)); + handlebars.register_helper("blue" , Box::new(self::template_helpers::ColorizeBlueHelper)); + handlebars.register_helper("cyan" , Box::new(self::template_helpers::ColorizeCyanHelper)); + handlebars.register_helper("green" , Box::new(self::template_helpers::ColorizeGreenHelper)); + handlebars.register_helper("purple" , Box::new(self::template_helpers::ColorizePurpleHelper)); + handlebars.register_helper("red" , Box::new(self::template_helpers::ColorizeRedHelper)); + handlebars.register_helper("white" , Box::new(self::template_helpers::ColorizeWhiteHelper)); + handlebars.register_helper("yellow" , Box::new(self::template_helpers::ColorizeYellowHelper)); + + { + let fmt = try!(aggregate_global_format_trace(matches, config)); + try!(handlebars.register_template_string("TRACE", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_debug(matches, config)); + try!(handlebars.register_template_string("DEBUG", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_info(matches, config)); + try!(handlebars.register_template_string("INFO", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_warn(matches, config)); + try!(handlebars.register_template_string("WARN", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_error(matches, config)); + try!(handlebars.register_template_string("ERROR", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + Ok(ImagLogger { global_loglevel : try!(aggregate_global_loglevel(matches, config)), global_destinations : try!(aggregate_global_destinations(matches, config)), - // global_format_trace : try!(aggregate_global_format_trace(matches, config)), - // global_format_debug : try!(aggregate_global_format_debug(matches, config)), - // global_format_info : try!(aggregate_global_format_info(matches, config)), - // global_format_warn : try!(aggregate_global_format_warn(matches, config)), - // global_format_error : try!(aggregate_global_format_error(matches, config)), module_settings : try!(aggregate_module_settings(matches, config)), + handlebars : handlebars, }) } @@ -103,17 +139,31 @@ impl Log for ImagLogger { let log_location = record.location(); let log_target = record.target(); + let mut data = BTreeMap::new(); + { + data.insert("level", format!("{}", log_level)); + data.insert("module_path", String::from(log_location.module_path())); + data.insert("file", String::from(log_location.file())); + data.insert("line", format!("{}", log_location.line())); + data.insert("target", String::from(log_target)); + data.insert("message", format!("{}", record.args())); + } + let logtext = self + .handlebars + .render(&format!("{}", log_level), &data) + .unwrap_or_else(|e| format!("Failed rendering logging data: {:?}\n", e)); + self.module_settings .get(log_target) .map(|module_setting| { if module_setting.enabled && module_setting.level >= log_level { - write!(stderr(), "[imag][{}]: {}", log_level, record.args()).ok(); + let _ = write!(stderr(), "{}\n", logtext); } }) .unwrap_or_else(|| { if self.global_loglevel >= log_level { // Yes, we log - write!(stderr(), "[imag][{}]: {}", log_level, record.args()).ok(); + let _ = write!(stderr(), "{}\n", logtext); } }); } @@ -219,35 +269,35 @@ fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configura } } -// fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>) -// -> -// { -// unimplemented!() -// } -// -// fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>) -// -> -// { -// unimplemented!() -// } -// -// fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>) -// -> -// { -// unimplemented!() -// } -// -// fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>) -// -> -// { -// unimplemented!() -// } -// -// fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>) -// -> -// { -// unimplemented!() -// } +fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + unimplemented!() +} + +fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + unimplemented!() +} + +fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + unimplemented!() +} + +fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + unimplemented!() +} + +fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + unimplemented!() +} fn aggregate_module_settings(matches: &ArgMatches, config: Option<&Configuration>) -> Result> @@ -309,3 +359,88 @@ fn aggregate_module_settings(matches: &ArgMatches, config: Option<&Configuration } } +mod template_helpers { + use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper}; + use ansi_term::Colour; + + #[derive(Clone, Copy)] + pub struct ColorizeBlackHelper; + + impl HelperDef for ColorizeBlackHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Black, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeBlueHelper; + + impl HelperDef for ColorizeBlueHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Blue, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeCyanHelper; + + impl HelperDef for ColorizeCyanHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Cyan, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeGreenHelper; + + impl HelperDef for ColorizeGreenHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Green, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizePurpleHelper; + + impl HelperDef for ColorizePurpleHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Purple, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeRedHelper; + + impl HelperDef for ColorizeRedHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Red, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeWhiteHelper; + + impl HelperDef for ColorizeWhiteHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::White, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeYellowHelper; + + impl HelperDef for ColorizeYellowHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Yellow, h, hb, rc) + } + } + + fn colorize(color: Colour, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + use handlebars::JsonRender; + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + + try!(write!(rc.writer(), "{}", color.paint(p.value().render()))); + Ok(()) + } +} + From 5ec1cd48a04ccb56527c47288012256feb22ad83 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 26 Aug 2017 23:00:48 +0200 Subject: [PATCH 03/10] Implement format-fetching from config/cli --- lib/core/libimagrt/src/error.rs | 7 +++- lib/core/libimagrt/src/logger.rs | 59 ++++++++++++++++++++++++++---- lib/core/libimagrt/src/runtime.rs | 60 +++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 7 deletions(-) diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs index 8427f0b6..9db8666f 100644 --- a/lib/core/libimagrt/src/error.rs +++ b/lib/core/libimagrt/src/error.rs @@ -30,7 +30,12 @@ generate_error_types!(RuntimeError, RuntimeErrorKind, GlobalLogLevelConfigMissing => "Global config 'imag.logging.level' missing", InvalidLogLevelSpec => "Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed", TomlReadError => "Error while reading in TOML document", - TemplateStringRegistrationError => "Error while registering logging template string" + TemplateStringRegistrationError => "Error while registering logging template string", + ConfigMissingLoggingFormatTrace => "Missing config for logging format for trace logging", + ConfigMissingLoggingFormatDebug => "Missing config for logging format for debug logging", + ConfigMissingLoggingFormatInfo => "Missing config for logging format for info logging", + ConfigMissingLoggingFormatWarn => "Missing config for logging format for warn logging", + ConfigMissingLoggingFormatError => "Missing config for logging format for error logging" ); impl From for RuntimeError { diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index c82bac3b..788af9cc 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -269,37 +269,84 @@ fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configura } } +#[inline] +fn aggregate_global_format( + read_str: &str, + cli_match_name: &str, + error_kind_if_missing: EK, + matches: &ArgMatches, + config: Option<&Configuration> + ) +-> Result +{ + match config { + Some(cfg) => match cfg + .read(read_str) + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::String(ref s))) => Ok(s.clone()), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e) + }, + None => match matches.value_of(cli_match_name).map(String::from) { + Some(s) => Ok(s), + None => Err(error_kind_if_missing.into_error()) + } + } +} + fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>) -> Result { - unimplemented!() + aggregate_global_format("imag.logging.format.trace", + Runtime::arg_override_trace_logging_format(), + EK::ConfigMissingLoggingFormatTrace, + matches, + config) } fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>) -> Result { - unimplemented!() + aggregate_global_format("imag.logging.format.debug", + Runtime::arg_override_debug_logging_format(), + EK::ConfigMissingLoggingFormatDebug, + matches, + config) } fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>) -> Result { - unimplemented!() + aggregate_global_format("imag.logging.format.info", + Runtime::arg_override_info_logging_format(), + EK::ConfigMissingLoggingFormatInfo, + matches, + config) } fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>) -> Result { - unimplemented!() + aggregate_global_format("imag.logging.format.warn", + Runtime::arg_override_warn_logging_format(), + EK::ConfigMissingLoggingFormatWarn, + matches, + config) } fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>) -> Result { - unimplemented!() + aggregate_global_format("imag.logging.format.error", + Runtime::arg_override_error_logging_format(), + EK::ConfigMissingLoggingFormatError, + matches, + config) } -fn aggregate_module_settings(matches: &ArgMatches, config: Option<&Configuration>) +fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuration>) -> Result> { match config { diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs index d4f29f1c..815e8e67 100644 --- a/lib/core/libimagrt/src/runtime.rs +++ b/lib/core/libimagrt/src/runtime.rs @@ -264,6 +264,46 @@ impl<'a> Runtime<'a> { .takes_value(true) .value_name("SPEC")) + .arg(Arg::with_name(Runtime::arg_override_trace_logging_format()) + .long(Runtime::arg_override_trace_logging_format()) + .help("Override the logging format for the trace logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_debug_logging_format()) + .long(Runtime::arg_override_debug_logging_format()) + .help("Override the logging format for the debug logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_info_logging_format()) + .long(Runtime::arg_override_info_logging_format()) + .help("Override the logging format for the info logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_warn_logging_format()) + .long(Runtime::arg_override_warn_logging_format()) + .help("Override the logging format for the warn logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_error_logging_format()) + .long(Runtime::arg_override_error_logging_format()) + .help("Override the logging format for the error logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + } /// Get the argument names of the Runtime which are available @@ -357,6 +397,26 @@ impl<'a> Runtime<'a> { "override-module-log-setting" } + pub fn arg_override_trace_logging_format() -> &'static str { + "override-logging-format-trace" + } + + pub fn arg_override_debug_logging_format() -> &'static str { + "override-logging-format-debug" + } + + pub fn arg_override_info_logging_format() -> &'static str { + "override-logging-format-info" + } + + pub fn arg_override_warn_logging_format() -> &'static str { + "override-logging-format-warn" + } + + pub fn arg_override_error_logging_format() -> &'static str { + "override-logging-format-error" + } + /// Initialize the internal logger fn init_logger(matches: &ArgMatches, config: Option<&Configuration>) { use std::env::var as env_var; From 482377abb9a9d4a5cbfc10110330c19be2aa1354 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 27 Aug 2017 14:13:43 +0200 Subject: [PATCH 04/10] Actually print error here (as logging isnt initialized at this point) --- lib/core/libimagrt/src/runtime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs index 815e8e67..43c12904 100644 --- a/lib/core/libimagrt/src/runtime.rs +++ b/lib/core/libimagrt/src/runtime.rs @@ -77,8 +77,8 @@ impl<'a> Runtime<'a> { Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound { return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e))); } else { - warn!("No config file found."); - warn!("Continuing without configuration file"); + println!("No config file found."); + println!("Continuing without configuration file"); None }, From df51736c8718f1974ad31724858e6bb870fe5a2e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 27 Aug 2017 14:34:10 +0200 Subject: [PATCH 05/10] Add imag logging configuration --- imagrc.toml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/imagrc.toml b/imagrc.toml index 5c5598b5..cd7ef72a 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -1,6 +1,35 @@ # This is a example configuration file for the imag suite. # It is written in TOML +[imag.logging] +level = "debug" +destinations = [ "-" ] + +# Valid variables for logging: +# * "level" +# * "module_path" +# * "file" +# * "line" +# * "target" +# * "message" +# +# Valid functions to be applied: +# * "black" +# * "blue" +# * "cyan" +# * "green" +# * "purple" +# * "red" +# * "white" +# * "yellow" + +[imag.logging.format] +trace = "[imag][{{red level}}][{{module_path}}]: {{message}}" +debug = "[imag][{{cyan level}}]: {{message}}" +info = "[imag]: {{message}}" +warn = "[imag][{{cyan level}}]: {{yellow message}}" +error = "[imag][{{red level}}]: {{red message}}" + # # Configuration options for the user interface # From 64f96092cd3b61d1b104fa764499670b80439ab2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 28 Aug 2017 11:40:50 +0200 Subject: [PATCH 06/10] Fix aggregation --- lib/core/libimagrt/src/error.rs | 1 + lib/core/libimagrt/src/logger.rs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs index 9db8666f..ba2a34bc 100644 --- a/lib/core/libimagrt/src/error.rs +++ b/lib/core/libimagrt/src/error.rs @@ -28,6 +28,7 @@ generate_error_types!(RuntimeError, RuntimeErrorKind, ConfigReadError => "Error while reading the configuration", ConfigTypeError => "Error while reading the configuration: Type Error", GlobalLogLevelConfigMissing => "Global config 'imag.logging.level' missing", + GlobalDestinationConfigMissing => "Global config 'imag.logging.destinations' missing", InvalidLogLevelSpec => "Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed", TomlReadError => "Error while reading in TOML document", TemplateStringRegistrationError => "Error while registering logging template string", diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index 788af9cc..a29faf46 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -50,11 +50,11 @@ impl Default for LogDestination { } struct ModuleSettings { - enabled: bool, - level: LogLevel, + enabled: bool, + level: Option, #[allow(unused)] - destinations: Vec, + destinations: Option>, } /// Logger implementation for `log` crate. @@ -156,7 +156,7 @@ impl Log for ImagLogger { self.module_settings .get(log_target) .map(|module_setting| { - if module_setting.enabled && module_setting.level >= log_level { + if module_setting.enabled && module_setting.level.unwrap_or(self.global_loglevel) >= log_level { let _ = write!(stderr(), "{}\n", logtext); } }) @@ -248,7 +248,7 @@ fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configura { Ok(Some(&Value::Array(ref a))) => translate_destinations(a), Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Ok(None) => Err(EK::GlobalDestinationConfigMissing.into_error()), Err(e) => Err(e) }, None => { @@ -286,7 +286,7 @@ fn aggregate_global_format( { Ok(Some(&Value::String(ref s))) => Ok(s.clone()), Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Ok(None) => Err(error_kind_if_missing.into_error()), Err(e) => Err(e) }, None => match matches.value_of(cli_match_name).map(String::from) { @@ -360,23 +360,23 @@ fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuratio for (module_name, v) in t { let destinations = try!(match v.read("destinations") { - Ok(Some(&Value::Array(ref a))) => translate_destinations(a), + Ok(Some(&Value::Array(ref a))) => translate_destinations(a).map(Some), + Ok(None) => Ok(None), Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), Err(e) => Err(e).map_err_into(EK::TomlReadError), }); let level = try!(match v.read("level") { - Ok(Some(&Value::String(ref s))) => match_log_level_str(s), + Ok(Some(&Value::String(ref s))) => match_log_level_str(s).map(Some), + Ok(None) => Ok(None), Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), Err(e) => Err(e).map_err_into(EK::TomlReadError), }); let enabled = try!(match v.read("enabled") { Ok(Some(&Value::Boolean(b))) => Ok(b), + Ok(None) => Ok(false), Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), Err(e) => Err(e).map_err_into(EK::TomlReadError), }); @@ -393,7 +393,10 @@ fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuratio Ok(settings) }, Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), - Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Ok(None) => { + // No modules configured. This is okay! + Ok(BTreeMap::new()) + }, Err(e) => Err(e), }, None => { From 93444be41217d785f0b0ee7cbfe4e016a7810834 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 28 Aug 2017 12:09:23 +0200 Subject: [PATCH 07/10] Light code cleanup --- lib/core/libimagrt/src/logger.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index a29faf46..ec32b616 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -135,33 +135,34 @@ impl Log for ImagLogger { } fn log(&self, record: &LogRecord) { - let log_level = record.level(); - let log_location = record.location(); - let log_target = record.target(); - let mut data = BTreeMap::new(); + { - data.insert("level", format!("{}", log_level)); - data.insert("module_path", String::from(log_location.module_path())); - data.insert("file", String::from(log_location.file())); - data.insert("line", format!("{}", log_location.line())); - data.insert("target", String::from(log_target)); - data.insert("message", format!("{}", record.args())); + data.insert("level", format!("{}", record.level())); + data.insert("module_path", String::from(record.location().module_path())); + data.insert("file", String::from(record.location().file())); + data.insert("line", format!("{}", record.location().line())); + data.insert("target", String::from(record.target())); + data.insert("message", format!("{}", record.args())); } + let logtext = self .handlebars - .render(&format!("{}", log_level), &data) + .render(&format!("{}", record.level()), &data) .unwrap_or_else(|e| format!("Failed rendering logging data: {:?}\n", e)); self.module_settings - .get(log_target) + .get(record.target()) .map(|module_setting| { - if module_setting.enabled && module_setting.level.unwrap_or(self.global_loglevel) >= log_level { + let set = module_setting.enabled && + module_setting.level.unwrap_or(self.global_loglevel) >= record.level(); + + if set { let _ = write!(stderr(), "{}\n", logtext); } }) .unwrap_or_else(|| { - if self.global_loglevel >= log_level { + if self.global_loglevel >= record.level() { // Yes, we log let _ = write!(stderr(), "{}\n", logtext); } From 0683bf198bb716732927ca3ac74eafb784d9cb41 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 30 Aug 2017 20:56:24 +0200 Subject: [PATCH 08/10] Fix recursion problem --- lib/core/libimagrt/src/logger.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index ec32b616..a1e4de83 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -135,6 +135,18 @@ impl Log for ImagLogger { } fn log(&self, record: &LogRecord) { + if record.location().module_path().starts_with("handlebars") { + // This is a ugly, yet necessary hack. When logging, we use handlebars for templating. + // But as the handlebars library itselfs logs via a normal logging macro ("debug!()"), + // we have a recursion in our chain. + // + // To prevent this recursion, we return here. + // + // (As of handlebars 0.29.0 - please check whether you can update handlebars if you see + // this. Hopefully the next version has a compiletime flag to disable logging) + return; + } + let mut data = BTreeMap::new(); { From 4908cc6aaa02d4fdacd7639b557b91d9daf6ceb2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 30 Aug 2017 21:16:31 +0200 Subject: [PATCH 09/10] Add more helpers --- imagrc.toml | 2 +- lib/core/libimagrt/src/logger.rs | 62 ++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/imagrc.toml b/imagrc.toml index cd7ef72a..955f5238 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -27,7 +27,7 @@ destinations = [ "-" ] trace = "[imag][{{red level}}][{{module_path}}]: {{message}}" debug = "[imag][{{cyan level}}]: {{message}}" info = "[imag]: {{message}}" -warn = "[imag][{{cyan level}}]: {{yellow message}}" +warn = "[imag][{{bold level}}]: {{yellow message}}" error = "[imag][{{red level}}]: {{red message}}" # diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index a1e4de83..2dc361d8 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -88,6 +88,11 @@ impl ImagLogger { handlebars.register_helper("white" , Box::new(self::template_helpers::ColorizeWhiteHelper)); handlebars.register_helper("yellow" , Box::new(self::template_helpers::ColorizeYellowHelper)); + handlebars.register_helper("underline" , Box::new(self::template_helpers::UnderlineHelper)); + handlebars.register_helper("bold" , Box::new(self::template_helpers::BoldHelper)); + handlebars.register_helper("blink" , Box::new(self::template_helpers::BlinkHelper)); + handlebars.register_helper("strikethrough" , Box::new(self::template_helpers::StrikethroughHelper)); + { let fmt = try!(aggregate_global_format_trace(matches, config)); try!(handlebars.register_template_string("TRACE", fmt) // name must be uppercase @@ -423,8 +428,9 @@ fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuratio } mod template_helpers { - use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper}; + use handlebars::{Handlebars, HelperDef, JsonRender, RenderError, RenderContext, Helper}; use ansi_term::Colour; + use ansi_term::Style; #[derive(Clone, Copy)] pub struct ColorizeBlackHelper; @@ -499,11 +505,63 @@ mod template_helpers { } fn colorize(color: Colour, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { - use handlebars::JsonRender; let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); try!(write!(rc.writer(), "{}", color.paint(p.value().render()))); Ok(()) } + + #[derive(Clone, Copy)] + pub struct UnderlineHelper; + + impl HelperDef for UnderlineHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().underline(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct BoldHelper; + + impl HelperDef for BoldHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().bold(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct BlinkHelper; + + impl HelperDef for BlinkHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().blink(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct StrikethroughHelper; + + impl HelperDef for StrikethroughHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().strikethrough(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + } From 336e0fb9187d0441c8b3ef59e05a66a3c28140f9 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 3 Sep 2017 13:48:36 +0200 Subject: [PATCH 10/10] Add changelog --- doc/src/09020-changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/src/09020-changelog.md b/doc/src/09020-changelog.md index e7445989..f0105f4b 100644 --- a/doc/src/09020-changelog.md +++ b/doc/src/09020-changelog.md @@ -25,6 +25,7 @@ This section contains the changelog from the last release to the next release. * The codebase was moved to a more tree-ish approach, where several subdirectories were introduced for different types of crates * The documentation got a major overhaul and was partly rewritten + * The logger is now configurable via the config file. * New * `libimagentrygps` was introduced * Fixed bugs