diff --git a/Cargo.lock b/Cargo.lock index 1339b8a30..0b7b56200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,6 @@ version = "2.0.0-beta.6" dependencies = [ "assert2", "clap 3.1.18", - "clap-verbosity-flag", "clap_complete", "clap_complete_fig", "color-eyre", diff --git a/packages_rs/nextclade-cli/Cargo.toml b/packages_rs/nextclade-cli/Cargo.toml index 66820ecb2..d15a6ee2f 100644 --- a/packages_rs/nextclade-cli/Cargo.toml +++ b/packages_rs/nextclade-cli/Cargo.toml @@ -12,7 +12,6 @@ publish = false [dependencies] assert2 = "0.3.6" clap = { version = "3.1.8", features = ["derive"] } -clap-verbosity-flag = "1.0.0" clap_complete = "3.1.1" clap_complete_fig = "3.1.4" color-eyre = "0.6.1" diff --git a/packages_rs/nextclade-cli/src/cli/mod.rs b/packages_rs/nextclade-cli/src/cli/mod.rs index 9d65c1ddd..01d8e1970 100644 --- a/packages_rs/nextclade-cli/src/cli/mod.rs +++ b/packages_rs/nextclade-cli/src/cli/mod.rs @@ -7,3 +7,4 @@ pub mod nextclade_dataset_get; pub mod nextclade_dataset_list; pub mod nextclade_loop; pub mod nextclade_ordered_writer; +pub mod verbosity; diff --git a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs index 3e94e229c..407d1caac 100644 --- a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs +++ b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs @@ -1,12 +1,11 @@ use crate::cli::common::get_fasta_basename; +use crate::cli::verbosity::{Verbosity, WarnLevel}; use clap::{AppSettings, ArgEnum, CommandFactory, Parser, Subcommand, ValueHint}; use clap_complete::{generate, Generator, Shell}; use clap_complete_fig::Fig; -use clap_verbosity_flag::{Verbosity, WarnLevel}; use eyre::{eyre, ContextCompat, Report, WrapErr}; use itertools::Itertools; use lazy_static::lazy_static; -use log::LevelFilter; use nextclade::align::params::AlignPairwiseParamsOptional; use nextclade::make_error; use nextclade::utils::global_init::setup_logger; @@ -18,7 +17,6 @@ use strum_macros::EnumIter; lazy_static! { static ref SHELLS: &'static [&'static str] = &["bash", "elvish", "fish", "fig", "powershell", "zsh"]; - static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"]; } #[derive(Parser, Debug)] @@ -37,17 +35,9 @@ pub struct NextalignArgs { #[clap(subcommand)] pub command: NextalignCommands, - /// Set verbosity level [default: warn] - #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "silent", possible_values(VERBOSITIES.iter()))] - pub verbosity: Option<LevelFilter>, - - /// Disable all console output. Same as --verbosity=off - #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "verbosity")] - pub silent: bool, - /// Make output more quiet or more verbose #[clap(flatten)] - pub verbose: Verbosity<WarnLevel>, + pub verbosity: Verbosity<WarnLevel>, } #[derive(Subcommand, Debug)] @@ -426,17 +416,7 @@ pub fn nextalign_check_removed_args(run_args: &mut NextalignRunArgs) -> Result<( pub fn nextalign_parse_cli_args() -> Result<NextalignArgs, Report> { let mut args = NextalignArgs::parse(); - // --verbosity=<level> and --silent take priority over -v and -q - let filter_level = if args.silent { - LevelFilter::Off - } else { - match args.verbosity { - None => args.verbose.log_level_filter(), - Some(verbosity) => verbosity, - } - }; - - setup_logger(filter_level); + setup_logger(args.verbosity.get_filter_level()); match &mut args.command { NextalignCommands::Completions { shell } => { diff --git a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs index acb1afffb..a8faae72b 100644 --- a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs +++ b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs @@ -1,15 +1,13 @@ use crate::cli::common::get_fasta_basename; +use crate::cli::verbosity::{Verbosity, WarnLevel}; use crate::io::http_client::ProxyConfig; use clap::{AppSettings, ArgEnum, ArgGroup, CommandFactory, Parser, Subcommand, ValueHint}; use clap_complete::{generate, Generator, Shell}; use clap_complete_fig::Fig; -use clap_verbosity_flag::{Verbosity, WarnLevel}; use eyre::{eyre, ContextCompat, Report, WrapErr}; use itertools::Itertools; use lazy_static::lazy_static; -use log::LevelFilter; use nextclade::align::params::AlignPairwiseParamsOptional; -use nextclade::io::fs::{basename_maybe, extension}; use nextclade::utils::global_init::setup_logger; use nextclade::{getenv, make_error}; use std::fmt::Debug; @@ -24,7 +22,6 @@ const DATA_FULL_DOMAIN: &str = getenv!("DATA_FULL_DOMAIN"); lazy_static! { static ref SHELLS: &'static [&'static str] = &["bash", "elvish", "fish", "fig", "powershell", "zsh"]; - static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"]; } #[derive(Parser, Debug)] @@ -43,17 +40,9 @@ pub struct NextcladeArgs { #[clap(subcommand)] pub command: NextcladeCommands, - /// Set verbosity level [default: warn] - #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "silent", possible_values(VERBOSITIES.iter()))] - pub verbosity: Option<LevelFilter>, - - /// Disable all console output. Same as --verbosity=off - #[clap(long, global = true, conflicts_with = "verbose", conflicts_with = "verbosity")] - pub silent: bool, - /// Make output more quiet or more verbose #[clap(flatten)] - pub verbose: Verbosity<WarnLevel>, + pub verbosity: Verbosity<WarnLevel>, } #[derive(Subcommand, Debug)] @@ -733,17 +722,7 @@ pub fn nextclade_check_removed_args(run_args: &mut NextcladeRunArgs) -> Result<( pub fn nextclade_parse_cli_args() -> Result<NextcladeArgs, Report> { let mut args = NextcladeArgs::parse(); - // --verbosity=<level> and --silent take priority over -v and -q - let filter_level = if args.silent { - LevelFilter::Off - } else { - match args.verbosity { - None => args.verbose.log_level_filter(), - Some(verbosity) => verbosity, - } - }; - - setup_logger(filter_level); + setup_logger(args.verbosity.get_filter_level()); match &mut args.command { NextcladeCommands::Completions { shell } => { diff --git a/packages_rs/nextclade-cli/src/cli/verbosity.rs b/packages_rs/nextclade-cli/src/cli/verbosity.rs new file mode 100644 index 000000000..5483c85fb --- /dev/null +++ b/packages_rs/nextclade-cli/src/cli/verbosity.rs @@ -0,0 +1,135 @@ +//! Inspired by clap-verbosity-flag: +//! https://github.com/rust-cli/clap-verbosity-flag +use clap::Args; +use lazy_static::lazy_static; +use log::{Level, LevelFilter}; +use std::fmt::{Display, Formatter, Result}; +use std::marker::PhantomData; + +lazy_static! { + static ref VERBOSITIES: &'static [&'static str] = &["off", "error", "warn", "info", "debug", "trace"]; +} + +#[derive(Args, Debug, Clone)] +pub struct Verbosity<L: LogLevel = ErrorLevel> { + /// Set verbosity level of console output [default: warn] + #[clap(long, global = true, possible_values(VERBOSITIES.iter()))] + #[clap(conflicts_with = "quiet", conflicts_with = "verbose", conflicts_with = "silent")] + #[clap(display_order = 900)] + pub verbosity: Option<LevelFilter>, + + /// Disable all console output. Same as `--verbosity=off` + #[clap(long, global = true)] + #[clap(conflicts_with = "quiet", conflicts_with = "verbose", conflicts_with = "verbosity")] + #[clap(display_order = 901)] + pub silent: bool, + + /// Make console output more verbose. Add multiple occurrences to increase verbosity further. + #[clap(long, short = 'v', parse(from_occurrences), global = true)] + #[clap(conflicts_with = "quiet", conflicts_with = "verbosity", conflicts_with = "silent")] + #[clap(display_order = 902)] + verbose: i8, + + /// Make console output more quiet. Add multiple occurrences to make output even more quiet. + #[clap(long, short = 'q', parse(from_occurrences), global = true)] + #[clap(conflicts_with = "verbose", conflicts_with = "verbosity")] + #[clap(display_order = 903)] + quiet: i8, + + #[clap(skip)] + phantom: PhantomData<L>, +} + +impl<L: LogLevel> Verbosity<L> { + pub fn get_filter_level(&self) -> LevelFilter { + // --verbosity=<level> and --silent take priority over -v and -q + if self.silent { + LevelFilter::Off + } else { + match self.verbosity { + Some(verbosity) => verbosity, + None => self.log_level_filter(), + } + } + } + + /// Get the log level. + /// + /// `None` means all output is disabled. + pub fn log_level(&self) -> Option<Level> { + level_enum(self.verbosity()) + } + + /// Get the log level filter. + pub fn log_level_filter(&self) -> LevelFilter { + level_enum(self.verbosity()).map_or(LevelFilter::Off, |l| l.to_level_filter()) + } + + /// If the user requested complete silence (i.e. not just no-logging). + pub fn is_silent(&self) -> bool { + self.log_level().is_none() + } + + fn verbosity(&self) -> i8 { + level_value(L::default()) - self.quiet + self.verbose + } +} + +const fn level_value(level: Option<Level>) -> i8 { + match level { + None => -1, + Some(Level::Error) => 0, + Some(Level::Warn) => 1, + Some(Level::Info) => 2, + Some(Level::Debug) => 3, + Some(Level::Trace) => 4, + } +} + +const fn level_enum(verbosity: i8) -> Option<Level> { + match verbosity { + i8::MIN..=-1 => None, + 0 => Some(Level::Error), + 1 => Some(Level::Warn), + 2 => Some(Level::Info), + 3 => Some(Level::Debug), + 4..=i8::MAX => Some(Level::Trace), + } +} + +impl<L: LogLevel> Display for Verbosity<L> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", self.verbosity()) + } +} + +pub trait LogLevel { + fn default() -> Option<Level>; +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct ErrorLevel; + +impl LogLevel for ErrorLevel { + fn default() -> Option<Level> { + Some(Level::Error) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct WarnLevel; + +impl LogLevel for WarnLevel { + fn default() -> Option<Level> { + Some(Level::Warn) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct InfoLevel; + +impl LogLevel for InfoLevel { + fn default() -> Option<Level> { + Some(Level::Info) + } +}