Newer
Older
Eduardo Trujillo
committed
use log::LevelFilter;
use serde::{de::DeserializeOwned, Serialize};
use crate::config::{self, ConfigFileFormat};
// Exit codes as defined in sysexits.h.
pub const CONFIGURATION_ERROR_EXIT_CODE: i32 = 78;
#[derive(Error, Debug)]
pub enum CliError {
#[error(transparent)]
ArgParse(#[from] clap::Error),
#[error(transparent)]
Config(#[from] config::ConfigError),
}
Eduardo Trujillo
committed
let result = Self::try_parse().map_err(CliError::ArgParse);
Eduardo Trujillo
committed
match &result {
Ok(result) => {
try_init_pretty_logger(
result.get_log_environment_variable_name(),
result.get_log_level_filter(),
)
.unwrap();
}
Err(_) => {
// Initialize the logger with defaults.
pretty_env_logger::init();
}
}
result
}
fn init() -> Self {
match Self::try_init() {
Ok(args) => args,
Err(CliError::ArgParse(err)) => err.exit(),
Err(CliError::Config(err)) => {
log::error!("{}", err);
safe_exit(CONFIGURATION_ERROR_EXIT_CODE);
Eduardo Trujillo
committed
fn get_log_level_filter(&self) -> Option<LevelFilter> {
None
}
fn get_log_environment_variable_name(&self) -> Option<&str> {
None
pub trait ConfigurableAppOpts<C: DeserializeOwned + Default + Serialize>: AppOpts {
fn try_init_with_config() -> Result<(Self, C), CliError> {
let opts = Self::try_init()?;
let app = <Self as IntoApp>::command();
let conf = config::from_defaults(
app.get_name(),
&opts.get_additional_config_paths(),
opts.get_config_file_format(),
)
.map_err(CliError::Config)?;
fn init_with_config() -> (Self, C) {
match Self::try_init_with_config() {
Ok(args_and_config) => args_and_config,
Err(CliError::ArgParse(err)) => err.exit(),
Err(CliError::Config(err)) => {
log::error!("{}", err);
safe_exit(CONFIGURATION_ERROR_EXIT_CODE);
fn get_config_file_format(&self) -> ConfigFileFormat {
ConfigFileFormat::Toml
}
fn get_additional_config_paths(&self) -> Vec<(PathBuf, Option<ConfigFileFormat>)>;
Eduardo Trujillo
committed
fn try_init_pretty_logger(
environment_variable_name: Option<&str>,
level_filter_override: Option<LevelFilter>,
) -> Result<(), log::SetLoggerError> {
let mut builder = pretty_env_logger::formatted_builder();
if let Some(level_filter_override) = level_filter_override {
builder.filter(None, level_filter_override);
}
if let Ok(s) = ::std::env::var(environment_variable_name.unwrap_or("RUST_LOG")) {
builder.parse_filters(&s);
}
builder.try_init()
}
/// Converts a integer verbosity value into a `LevelFilter`.
///
/// Examples:
///
/// ```
/// use collective::cli::get_log_level_filter_from_verbosity;
///
/// assert_eq!(
/// get_log_level_filter_from_verbosity(1),
/// Some(log::LevelFilter::Info)
/// );
///
/// assert_eq!(get_log_level_filter_from_verbosity(15), None);
/// ```
pub fn get_log_level_filter_from_verbosity(verbosity: u8) -> Option<log::LevelFilter> {
match verbosity {
3 => Some(log::LevelFilter::Trace),
2 => Some(log::LevelFilter::Debug),
1 => Some(log::LevelFilter::Info),
_ => None,
}
}
/// Flushes stdout/stderr and terminates the current process.
/// This is used internally in the library to handle non-critical CLI errors.
pub fn safe_exit(code: i32) -> ! {
use std::io::Write;
let _ = std::io::stdout().lock().flush();
let _ = std::io::stderr().lock().flush();
std::process::exit(code)