From 451c3a790443de7514550fd36137a666ce3661e5 Mon Sep 17 00:00:00 2001 From: Enigbe Ochekliye Date: Mon, 11 Mar 2024 10:41:17 +0100 Subject: [PATCH] refactor: move Cli-related functionality to cli.rs - additionally reduces the LOC by implementing the overwrite_field!(...) macro and from_config_field(...) generic function. - makes the requirement of a configuration file optional, defaulting to default Cli values if no config file is provided --- sim-cli/src/cli.rs | 310 ++++++++++++++++++++++++++++++ sim-cli/src/main.rs | 446 +------------------------------------------- 2 files changed, 319 insertions(+), 437 deletions(-) create mode 100644 sim-cli/src/cli.rs diff --git a/sim-cli/src/cli.rs b/sim-cli/src/cli.rs new file mode 100644 index 00000000..833e259b --- /dev/null +++ b/sim-cli/src/cli.rs @@ -0,0 +1,310 @@ +use std::{collections::HashMap, fmt::Debug, path::PathBuf, str::FromStr}; + +use clap::{builder::TypedValueParser, Parser}; +use config::{Config, File}; +use log::LevelFilter; +use serde::Deserialize; +use sim_lib::SimulationConfig; + +/// The default directory where the simulation files are stored and where the results will be written to. +pub const DEFAULT_DATA_DIR: &str = "."; + +/// The default simulation file to be used by the simulator. +pub const DEFAULT_SIM_FILE: &str = "sim.json"; + +/// The default expected payment amount for the simulation, around ~$10 at the time of writing. +pub const EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000; + +/// The number of times over each node in the network sends its total deployed capacity in a calendar month. +pub const ACTIVITY_MULTIPLIER: f64 = 2.0; + +/// Default batch size to flush result data to disk +pub const DEFAULT_PRINT_BATCH_SIZE: u32 = 500; + +/// Default configuration file +pub const DEFAULT_CONFIGURATION_FILE: &str = "conf.ini"; + +/// Default total time +pub const DEFAULT_TOTAL_TIME: Option = None; + +/// Default log level +pub const DEFAULT_LOG_LEVEL: &str = "info"; + +/// Default no results +pub const DEFAULT_NO_RESULTS: bool = false; + +/// Default log interval +pub const DEFAULT_LOG_INTERVAL: u32 = 60; + +/// Configuration header +pub const CONFIG_HEADER: &str = "simln.conf"; + +/// Workspace root directory +pub const WORKSPACE_ROOT_DIR: &str = "."; + +/// Deserializes a f64 as long as it is positive and greater than 0. +fn deserialize_f64_greater_than_zero(x: String) -> Result { + match x.parse::() { + Ok(x) => { + if x > 0.0 { + Ok(x) + } else { + Err(format!("capacity_multiplier must be higher than 0. {x} received.")) + } + }, + Err(e) => Err(e.to_string()), + } +} + +/// Custom deserialization function for `LevelFilter`. Required because `LevelFilter` does +/// not implement deserialize +pub fn deserialize_log_level<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let level_filter_str = String::deserialize(deserializer)?; + + let level_filter = LevelFilter::from_str(&level_filter_str) + .map_err(|e| serde::de::Error::custom(format!("Failed to deserialize LevelFilter from &str: {e}")))?; + + Ok(level_filter) +} + +/// Custom deserialization function for total time. This method is required because +/// the `config` crate is unable to parse null values in `.ini` files to an `Option::None` +pub fn deserialize_total_time<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let total_time_str = String::deserialize(deserializer)?; + + if total_time_str.is_empty() { + return Ok(None); + } + + // Parse string value to u32 + let total_time = total_time_str + .parse::() + .map_err(|e| serde::de::Error::custom(format!("Failed to parse u32 from &str: {e}")))?; + + Ok(Some(total_time)) +} + +#[derive(Parser, Deserialize)] +#[command(version, about)] +pub struct Cli { + /// Path to a directory containing simulation files, and where simulation results will be stored + #[clap(long, short, default_value = DEFAULT_DATA_DIR)] + data_dir: PathBuf, + /// Path to the simulation file to be used by the simulator + /// This can either be an absolute path, or relative path with respect to data_dir + #[clap(long, short, default_value = DEFAULT_SIM_FILE)] + sim_file: PathBuf, + /// Total time the simulator will be running + #[clap(long, short)] + #[serde(deserialize_with = "deserialize_total_time")] + total_time: Option, + /// Number of activity results to batch together before printing to csv file [min: 1] + #[clap( + long, + short, + default_value_t = DEFAULT_PRINT_BATCH_SIZE, + value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64) + )] + print_batch_size: u32, + /// Level of verbosity of the messages displayed by the simulator. + /// Possible values: [off, error, warn, info, debug, trace] + #[clap(long, short, verbatim_doc_comment, default_value = DEFAULT_LOG_LEVEL)] + #[serde(deserialize_with = "deserialize_log_level")] + log_level: LevelFilter, + /// Expected payment amount for the random activity generator + #[clap( + long, + short, + default_value_t = EXPECTED_PAYMENT_AMOUNT, + value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u64::MAX) + )] + expected_pmt_amt: u64, + /// Multiplier of the overall network capacity used by the random activity generator + #[clap( + long, + short, + default_value_t = ACTIVITY_MULTIPLIER, + value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero) + )] + capacity_multiplier: f64, + /// Do not create an output file containing the simulations results + #[clap(long, default_value_t = DEFAULT_NO_RESULTS)] + no_results: bool, + /// Sets a custom configuration (INI) file + #[clap(long, short = 'C', value_name = "CONFIG_FILE", default_value = DEFAULT_CONFIGURATION_FILE)] + config: PathBuf, + #[clap( + long, + short = 'L', + default_value_t = DEFAULT_LOG_INTERVAL, + value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64) + )] + log_interval: u32, +} + +/// Implementation of Cli with default values +impl Default for Cli { + fn default() -> Self { + Cli { + data_dir: PathBuf::from(DEFAULT_DATA_DIR), + sim_file: PathBuf::from(DEFAULT_SIM_FILE), + total_time: DEFAULT_TOTAL_TIME, + print_batch_size: DEFAULT_PRINT_BATCH_SIZE, + log_level: LevelFilter::Info, + expected_pmt_amt: EXPECTED_PAYMENT_AMOUNT, + capacity_multiplier: ACTIVITY_MULTIPLIER, + no_results: DEFAULT_NO_RESULTS, + config: DEFAULT_CONFIGURATION_FILE.into(), + log_interval: DEFAULT_LOG_INTERVAL, + } + } +} + +impl Cli { + /// Creates Cli from a configuration if provided, else it defaults to + /// CLI default values + fn from_config_file(file_path: PathBuf) -> anyhow::Result { + let default_cli = Cli::default(); + + if file_path == PathBuf::from(DEFAULT_CONFIGURATION_FILE) { + let config_path_res = default_config_path(WORKSPACE_ROOT_DIR.into(), file_path.clone()); + if config_path_res.is_err() { + log::info!("Default configuration file: {file_path:?} (not found, skipping)."); + return anyhow::Ok(default_cli); + } + } + + let simln_conf = Config::builder() + .add_source(File::with_name(file_path.as_os_str().to_string_lossy().as_ref())) + .build()? + .get_table(CONFIG_HEADER)?; + + let config_cli = Cli { + data_dir: from_config_field(&simln_conf, "data_dir", DEFAULT_DATA_DIR.into()), + sim_file: from_config_field(&simln_conf, "sim_file", DEFAULT_SIM_FILE.into()), + total_time: from_config_field(&simln_conf, "total_time", DEFAULT_TOTAL_TIME), + print_batch_size: from_config_field(&simln_conf, "print_batch_size", DEFAULT_PRINT_BATCH_SIZE), + log_level: LevelFilter::from_str(&from_config_field::( + &simln_conf, + "log_level", + DEFAULT_LOG_LEVEL.to_string(), + ))?, + expected_pmt_amt: from_config_field(&simln_conf, "expected_pmt_amt", EXPECTED_PAYMENT_AMOUNT), + capacity_multiplier: from_config_field(&simln_conf, "capacity_multiplier", ACTIVITY_MULTIPLIER), + no_results: from_config_field(&simln_conf, "no_results", DEFAULT_NO_RESULTS), + log_interval: from_config_field(&simln_conf, "log_interval", DEFAULT_LOG_INTERVAL), + config: default_cli.config, + }; + + anyhow::Ok(config_cli) + } + + /// Converts into simulation config + pub fn to_simulation_config(&self) -> SimulationConfig { + SimulationConfig { + log_level: self.log_level, + total_time: self.total_time, + expected_pmt_amt: self.expected_pmt_amt, + capacity_multiplier: self.capacity_multiplier, + no_results: self.no_results, + print_batch_size: self.print_batch_size, + data_dir: self.data_dir.to_path_buf(), + sim_file: self.sim_file.to_path_buf(), + log_interval: self.log_interval, + } + } + + /// Overwrites Cli with another + fn overwrite_with(&mut self, cli: &Cli) { + macro_rules! overwrite_field { + ($cli:ident, $field:ident, $default:expr) => { + if $cli.$field != $default { + log::info!("Command line arg: {}={:?}.", stringify!($field), $cli.$field); + self.$field = $cli.$field.clone(); + } + }; + } + + overwrite_field!(cli, data_dir, PathBuf::from(DEFAULT_DATA_DIR)); + overwrite_field!(cli, sim_file, PathBuf::from(DEFAULT_SIM_FILE)); + overwrite_field!(cli, total_time, DEFAULT_TOTAL_TIME); + overwrite_field!(cli, print_batch_size, DEFAULT_PRINT_BATCH_SIZE); + overwrite_field!(cli, log_level, LevelFilter::Info); + overwrite_field!(cli, expected_pmt_amt, EXPECTED_PAYMENT_AMOUNT); + overwrite_field!(cli, capacity_multiplier, ACTIVITY_MULTIPLIER); + overwrite_field!(cli, no_results, DEFAULT_NO_RESULTS); + overwrite_field!(cli, log_interval, DEFAULT_LOG_INTERVAL); + overwrite_field!(cli, config, PathBuf::from(DEFAULT_CONFIGURATION_FILE)); + } +} + +/// Helper function to parse field_name from the config map. Defaults to the +/// field_name's default if deserialization fails +fn from_config_field<'de, T>(config_map: &HashMap, field_name: &str, default_value: T) -> T +where + T: Clone + Deserialize<'de> + Debug, +{ + config_map.get(field_name).map_or(default_value.clone(), |value| { + let res = value.clone().try_deserialize(); + match res { + Ok(result) => { + log::info!("Configuration file arg: {field_name}={result:?}."); + result + }, + Err(e) => { + log::error!("Failed to parse {field_name}. Error => {e:?}."); + log::info!("Default value {:?} used.", default_value); + default_value + }, + } + }) +} + +/// Merge command line and configuration value `Cli`s +pub fn merge_cli() -> anyhow::Result { + let cli = Cli::parse(); + log::info!("Configuration file: {:?}.", cli.config.display()); + let mut cli_from_config = Cli::from_config_file(cli.config.clone())?; + cli_from_config.overwrite_with(&cli); + + anyhow::Ok(cli_from_config) +} + +fn default_config_path(root_dir: PathBuf, config_file: PathBuf) -> anyhow::Result { + let conf_path = if config_file.is_relative() { + root_dir.join(config_file) + } else { + config_file + }; + + if conf_path.exists() { + Ok(conf_path) + } else { + anyhow::bail!("Default configuration file not found.") + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::cli::from_config_field; + + #[test] + fn test_from_config_field() { + let mut config_map = HashMap::new(); + config_map.insert("key".to_string(), config::Value::new(None, "value")); + + let value = from_config_field(&config_map, "key", "default_value".to_string()); + let default_value = from_config_field(&config_map, "no_key", "default_value"); + + assert_eq!(value, "value"); + assert_eq!(default_value, "default_value"); + } +} diff --git a/sim-cli/src/main.rs b/sim-cli/src/main.rs index 5eeaf5e8..ccf3b994 100644 --- a/sim-cli/src/main.rs +++ b/sim-cli/src/main.rs @@ -1,455 +1,27 @@ use bitcoin::secp256k1::PublicKey; -use config::{Config, File}; use flexi_logger::{LogSpecBuilder, Logger}; -use serde::Deserialize; +use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; -use std::{collections::HashMap, str::FromStr}; use tokio::sync::Mutex; use anyhow::anyhow; -use clap::builder::TypedValueParser; -use clap::Parser; use log::LevelFilter; use sim_lib::{ - cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection, - NodeId, SimParams, Simulation, SimulationConfig, WriteResults, + cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection, NodeId, SimParams, + Simulation, WriteResults, }; -/// The default directory where the simulation files are stored and where the results will be written to. -pub const DEFAULT_DATA_DIR: &str = "."; +mod cli; +use cli::{merge_cli, DEFAULT_LOG_LEVEL}; -/// The default simulation file to be used by the simulator. -pub const DEFAULT_SIM_FILE: &str = "sim.json"; - -/// The default expected payment amount for the simulation, around ~$10 at the time of writing. -pub const EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000; - -/// The number of times over each node in the network sends its total deployed capacity in a calendar month. -pub const ACTIVITY_MULTIPLIER: f64 = 2.0; - -/// Default batch size to flush result data to disk -const DEFAULT_PRINT_BATCH_SIZE: u32 = 500; - -/// Default configuration file -const DEFAULT_CONFIGURATION_FILE: &str = "conf.ini"; - -/// Default total time -const DEFAULT_TOTAL_TIME: Option = None; - -/// Default log level -const DEFAULT_LOG_LEVEL: &str = "info"; - -/// Default no results -const DEFAULT_NO_RESULTS: bool = false; - -/// Default log interval -const DEFAULT_LOG_INTERVAL: u32 = 60; - -/// Deserializes a f64 as long as it is positive and greater than 0. -fn deserialize_f64_greater_than_zero(x: String) -> Result { - match x.parse::() { - Ok(x) => { - if x > 0.0 { - Ok(x) - } else { - Err(format!( - "capacity_multiplier must be higher than 0. {x} received." - )) - } - }, - Err(e) => Err(e.to_string()), - } -} - -/// Custom deserialization function for `LevelFilter`. Required because `LevelFilter` does -/// not implement deserialize -pub fn deserialize_log_level<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let level_filter_str = String::deserialize(deserializer)?; - - let level_filter = LevelFilter::from_str(&level_filter_str).map_err(|e| { - serde::de::Error::custom(format!("Failed to deserialize LevelFilter from &str: {e}")) - })?; - - Ok(level_filter) -} - -/// Custom deserialization function for total time. This method is required because -/// the `config` crate is unable to parse null values in `.ini` files to an `Option::None` -pub fn deserialize_total_time<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let total_time_str = String::deserialize(deserializer)?; - - if total_time_str.is_empty() { - return Ok(None); - } - - // Parse string value to u32 - let total_time = total_time_str - .parse::() - .map_err(|e| serde::de::Error::custom(format!("Failed to parse u32 from &str: {e}")))?; - - Ok(Some(total_time)) -} - -#[derive(Parser, Deserialize)] -#[command(version, about)] -struct Cli { - /// Path to a directory containing simulation files, and where simulation results will be stored - #[clap(long, short, default_value = DEFAULT_DATA_DIR)] - data_dir: PathBuf, - /// Path to the simulation file to be used by the simulator - /// This can either be an absolute path, or relative path with respect to data_dir - #[clap(long, short, default_value = DEFAULT_SIM_FILE)] - sim_file: PathBuf, - /// Total time the simulator will be running - #[clap(long, short)] - total_time: Option, - /// Number of activity results to batch together before printing to csv file [min: 1] - #[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64))] - print_batch_size: u32, - /// Level of verbosity of the messages displayed by the simulator. - /// Possible values: [off, error, warn, info, debug, trace] - #[clap(long, short, verbatim_doc_comment, default_value = DEFAULT_LOG_LEVEL)] - #[serde(deserialize_with = "deserialize_log_level")] - log_level: LevelFilter, - /// Expected payment amount for the random activity generator - #[clap(long, short, default_value_t = EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u64::MAX))] - expected_pmt_amt: u64, - /// Multiplier of the overall network capacity used by the random activity generator - #[clap(long, short, default_value_t = ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))] - capacity_multiplier: f64, - /// Do not create an output file containing the simulations results - #[clap(long, default_value_t = DEFAULT_NO_RESULTS)] - no_results: bool, - /// Sets a custom configuration (INI) file - #[clap(long, short = 'C', value_name = "CONFIG_FILE", default_value = DEFAULT_CONFIGURATION_FILE)] - config: PathBuf, - #[clap(long, short = 'L', default_value_t = DEFAULT_LOG_INTERVAL, value_parser = clap::builder::RangedU64ValueParser::::new().range(1..u32::MAX as u64))] - log_interval: u32, -} - -/// Implementation of Cli with default values -impl Default for Cli { - fn default() -> Self { - Cli { - data_dir: PathBuf::from(DEFAULT_DATA_DIR), - sim_file: PathBuf::from(DEFAULT_SIM_FILE), - total_time: DEFAULT_TOTAL_TIME, - print_batch_size: DEFAULT_PRINT_BATCH_SIZE, - log_level: LevelFilter::Info, - expected_pmt_amt: EXPECTED_PAYMENT_AMOUNT, - capacity_multiplier: ACTIVITY_MULTIPLIER, - no_results: DEFAULT_NO_RESULTS, - config: PathBuf::from(DEFAULT_CONFIGURATION_FILE), - log_interval: DEFAULT_LOG_INTERVAL, - } - } -} - -impl Cli { - /// Creates Cli from a configuration if provided, else it defaults to - /// CLI default values - fn from_config_file(file_path: PathBuf) -> anyhow::Result { - let default_cli = Cli::default(); - - let config = Config::builder() - .add_source(File::with_name( - file_path.as_os_str().to_string_lossy().as_ref(), - )) - .build()?; - - let simln_conf = config.get_table("simln.conf")?; - - let config_data_dir = - simln_conf - .get("data_dir") - .map_or(default_cli.data_dir.clone(), |val| { - let data_dir_res = val.clone().try_deserialize::(); - match data_dir_res { - Ok(data_dir) => { - log::info!("Configuration file arg: data_dir={:?}.", data_dir); - PathBuf::from(data_dir) - }, - Err(e) => { - log::error!( - "Failed to parse data_dir. Default value used. Error: {e:?}." - ); - default_cli.data_dir.clone() - }, - } - }); - - let config_sim_file = - simln_conf - .get("sim_file") - .map_or(default_cli.sim_file.clone(), |val| { - let sim_file_res = val.clone().try_deserialize::(); - match sim_file_res { - Ok(sim_file) => { - log::info!("Configuration file arg: sim_file={:?}.", sim_file); - PathBuf::from(sim_file) - }, - Err(e) => { - log::error!( - "Failed to parse sim_file. Default value used. Error: {e:?}.", - ); - default_cli.sim_file - }, - } - }); - - let config_total_time = - simln_conf - .get("total_time") - .map_or(default_cli.total_time, |val| { - let total_time_res = val.clone().try_deserialize::>(); - match total_time_res { - Ok(total_time) => { - log::info!("Configuration file arg: total_time={:?}.", total_time); - total_time - }, - Err(e) => { - log::error!( - "Failed to parse total_time. Default value used. Error: {e:?}." - ); - default_cli.total_time - }, - } - }); - - let config_print_batch_size = - simln_conf - .get("print_batch_size") - .map_or(default_cli.print_batch_size, |val| { - let print_batch_size_res = val.clone().try_deserialize::(); - match print_batch_size_res { - Ok(print_batch_size) => { - log::info!( - "Configuration file arg: print_batch_size={:?}.", - print_batch_size - ); - print_batch_size - }, - Err(e) => { - log::error!( - "Failed to parse print_batch_size. Default value used. Error: {e:?}." - ); - default_cli.print_batch_size - }, - } - }); - - let config_log_level = - simln_conf - .get("log_level") - .map_or(DEFAULT_LOG_LEVEL.to_string(), |val| { - let log_level_res = val.clone().try_deserialize::(); - match log_level_res { - Ok(log_level) => { - log::info!("Configuration file arg: log_level={:?}.", log_level); - log_level - }, - Err(e) => { - log::error!( - "Failed to parse log_level. Default value used. Error: {e:?}." - ); - DEFAULT_LOG_LEVEL.to_string() - }, - } - }); - - let config_expected_pmt_amt = - simln_conf - .get("expected_pmt_amt") - .map_or(default_cli.expected_pmt_amt, |val| { - let pmt_amt_res = val.clone().try_deserialize::(); - match pmt_amt_res { - Ok(pmt_amt) => { - log::info!("Configuration file arg: expected_pmt_amt={:?}.", pmt_amt); - pmt_amt - }, - Err(e) => { - log::error!( - "Failed to parse expected_pmt_amt. Default value used. Error: {e:?}." - ); - default_cli.expected_pmt_amt - }, - } - }); - - let config_capacity_multiplier = - simln_conf - .get("capacity_multiplier") - .map_or(default_cli.capacity_multiplier, |val| { - let capacity_multiplier_res = val.clone().try_deserialize::(); - match capacity_multiplier_res { - Ok(capacity_multiplier) => { - log::info!( - "Configuration file arg: capacity_multiplier={:?}.", - capacity_multiplier - ); - capacity_multiplier - }, - Err(e) => { - log::error!( - "Failed to parse capacity_multiplier. Default value used. Error: {e:?}." - ); - default_cli.capacity_multiplier - }, - } - }); - - let config_no_results = - simln_conf - .get("no_results") - .map_or(default_cli.no_results, |val| { - let no_results_res = val.clone().try_deserialize::(); - match no_results_res { - Ok(no_results) => { - log::info!("Configuration file arg: no_results={:?}.", no_results); - no_results - }, - Err(e) => { - log::error!( - "Failed to parse no_results. Default value used. Error: {e:?}." - ); - default_cli.no_results - }, - } - }); - - let config_log_interval = - simln_conf - .get("log_interval") - .map_or(default_cli.log_interval, |val| { - let log_interval_res = val.clone().try_deserialize::(); - match log_interval_res { - Ok(log_interval) => { - log::info!("Configuration file arg: log_interval={:?}.", log_interval); - log_interval - }, - Err(e) => { - log::error!( - "Failed to parse log_interval. Default value used. Error: {e:?}." - ); - default_cli.log_interval - }, - } - }); - - let config_cli = Cli { - data_dir: config_data_dir, - sim_file: config_sim_file, - total_time: config_total_time, - print_batch_size: config_print_batch_size, - log_level: LevelFilter::from_str(&config_log_level)?, - expected_pmt_amt: config_expected_pmt_amt, - capacity_multiplier: config_capacity_multiplier, - no_results: config_no_results, - config: default_cli.config, - log_interval: config_log_interval, - }; - - Ok(config_cli) - } - - /// Converts into simulation config - fn to_simulation_config(&self) -> SimulationConfig { - SimulationConfig { - log_level: self.log_level, - total_time: self.total_time, - expected_pmt_amt: self.expected_pmt_amt, - capacity_multiplier: self.capacity_multiplier, - no_results: self.no_results, - print_batch_size: self.print_batch_size, - data_dir: self.data_dir.to_path_buf(), - sim_file: self.sim_file.to_path_buf(), - log_interval: self.log_interval, - } - } -} - -/// Merge command line and configuration value `Cli`s -fn merge_cli() -> anyhow::Result { - let cli = Cli::parse(); - log::info!( - "Configuration file: {:?}.", - cli.config.canonicalize()?.display() - ); - - let mut cli_from_config = Cli::from_config_file(cli.config.to_path_buf())?; - - if cli.data_dir != PathBuf::from(DEFAULT_DATA_DIR) { - log::info!("Command line arg: data_dir={:?}.", cli.data_dir); - cli_from_config.data_dir = cli.data_dir - } - - if cli.sim_file != PathBuf::from(DEFAULT_SIM_FILE) { - log::info!("Command line arg: sim_file={:?}.", cli.sim_file); - cli_from_config.sim_file = cli.sim_file - } - - if cli.total_time != DEFAULT_TOTAL_TIME { - log::info!("Command line arg: total_time={:?}.", cli.total_time); - cli_from_config.total_time = cli.total_time - } - - if cli.print_batch_size != DEFAULT_PRINT_BATCH_SIZE { - log::info!( - "Command line arg: print_batch_size={:?}.", - cli.print_batch_size - ); - cli_from_config.print_batch_size = cli.print_batch_size - } - - if cli.log_level.as_str() != DEFAULT_LOG_LEVEL { - log::info!("Command line arg: log_level={:?}.", cli.log_level); - cli_from_config.log_level = cli.log_level - } - - if cli.expected_pmt_amt != EXPECTED_PAYMENT_AMOUNT { - log::info!( - "Command line arg: expected_pmt_amt={:?}.", - cli.expected_pmt_amt - ); - cli_from_config.expected_pmt_amt = cli.expected_pmt_amt - } - - if cli.capacity_multiplier != ACTIVITY_MULTIPLIER { - log::info!( - "Command line arg: capacity_multiplier={:?}.", - cli.capacity_multiplier - ); - cli_from_config.capacity_multiplier = cli.capacity_multiplier - } - - if cli.no_results != DEFAULT_NO_RESULTS { - log::info!("Command line arg: no_results={:?}.", cli.no_results); - cli_from_config.no_results = cli.no_results - } - - if cli.log_interval != DEFAULT_LOG_INTERVAL { - log::info!("Command line arg: log_interval={:?}.", cli.log_interval); - cli_from_config.log_interval = cli.log_interval; - } - - if cli.config != PathBuf::from(DEFAULT_CONFIGURATION_FILE) { - log::info!("Command line arg: config={:?}.", cli.config); - } - - anyhow::Ok(cli_from_config) -} +/// Colour pallette of the logger +const COLOUR_PALETTE: &str = "b1;3;2;4;6"; #[tokio::main] async fn main() -> anyhow::Result<()> { - let logger_handle = Logger::try_with_str("info")? - .set_palette("b1;3;2;4;6".to_string()) + let logger_handle = Logger::try_with_str(DEFAULT_LOG_LEVEL)? + .set_palette(COLOUR_PALETTE.to_string()) .start()?; let cli = merge_cli()?;