From a7e3fbe029105ade85fba47333364693486fc3e7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 9 Feb 2022 09:51:57 -0600 Subject: [PATCH] fix!: Give control over help text A runtime default means we can't change our help text off of it. We could accept the default as a const generics and match off of that for help text but the user might want more control and, for now, we'd have to expose all of that as integers. So we're offering a trait with zero-sized types to control the default level and different clap settings, like help text. --- src/lib.rs | 151 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 42 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6ba8f3d..35cb16f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,85 +2,105 @@ //! //! # Examples //! +//! To get `--quiet` and `--verbose` flags through your entire program, just `flatten` +//! [`Verbosity`]: //! ```rust,no_run -//! use clap::Parser; -//! use clap_verbosity_flag::Verbosity; -//! -//! /// Le CLI -//! #[derive(Debug, Parser)] -//! struct Cli { -//! #[clap(flatten)] -//! verbose: Verbosity, -//! } +//! # use clap::Parser; +//! # use clap_verbosity_flag::Verbosity; +//! # +//! # /// Le CLI +//! # #[derive(Debug, Parser)] +//! # struct Cli { +//! #[clap(flatten)] +//! verbose: Verbosity, +//! # } +//! ``` //! +//! You can then use this to configure your logger: +//! ```rust,no_run +//! # use clap::Parser; +//! # use clap_verbosity_flag::Verbosity; +//! # +//! # /// Le CLI +//! # #[derive(Debug, Parser)] +//! # struct Cli { +//! # #[clap(flatten)] +//! # verbose: Verbosity, +//! # } //! let cli = Cli::parse(); //! env_logger::Builder::new() //! .filter_level(cli.verbose.log_level_filter()) //! .init(); //! ``` //! -//! This will only report errors. +//! By default, this will only report errors. //! - `-q` silences output //! - `-v` show warnings //! - `-vv` show info //! - `-vvv` show debug //! - `-vvvv` show trace - -use log::Level; -use log::LevelFilter; +//! +//! You can also customize the default logging level: +//! ```rust,no_run +//! # use clap::Parser; +//! use clap_verbosity_flag::{Verbosity, InfoLevel}; +//! +//! /// Le CLI +//! #[derive(Debug, Parser)] +//! struct Cli { +//! #[clap(flatten)] +//! verbose: Verbosity, +//! } +//! ``` +//! +//! Or implement [`LogLevel`] yourself for more control. #[derive(clap::Args, Debug, Clone)] -pub struct Verbosity { - /// Pass many times for more log output - /// - /// By default, it'll only report errors. Passing `-v` one time also prints - /// warnings, `-vv` enables info logging, `-vvv` debug, and `-vvvv` trace. - #[clap(long, short = 'v', parse(from_occurrences), global = true)] +pub struct Verbosity { + #[clap( + long, + short = 'v', + parse(from_occurrences), + global = true, + help = L::verbose_help(), + long_help = L::verbose_long_help(), + )] verbose: i8, - /// Pass many times for less log output #[clap( long, short = 'q', parse(from_occurrences), global = true, - conflicts_with = "verbose" + help = L::quiet_help(), + long_help = L::quiet_long_help(), + conflicts_with = "verbose", )] quiet: i8, #[clap(skip)] - default: i8, + phantom: std::marker::PhantomData, } -impl Verbosity { +impl Verbosity { /// Create a new verbosity instance by explicitly setting the values - pub fn new(verbose: i8, quiet: i8, default: i8) -> Verbosity { + pub fn new(verbose: i8, quiet: i8) -> Self { Verbosity { verbose, quiet, - default, + phantom: std::marker::PhantomData, } } - /// Change the default level. - /// - /// When the level is lower than `log::Error` (the default), multiple `-q`s will be needed for - /// complete silence - /// - /// `None` means all output is disabled. - pub fn set_default(&mut self, level: Option) { - self.default = level_value(level); - } - /// Get the log level. /// /// `None` means all output is disabled. - pub fn log_level(&self) -> Option { + pub fn log_level(&self) -> Option { level_enum(self.verbosity()) } /// Get the log level filter. - pub fn log_level_filter(&self) -> LevelFilter { + pub fn log_level_filter(&self) -> log::LevelFilter { level_enum(self.verbosity()) .map(|l| l.to_level_filter()) .unwrap_or(log::LevelFilter::Off) @@ -92,11 +112,11 @@ impl Verbosity { } fn verbosity(&self) -> i8 { - self.default - self.quiet + self.verbose + level_value(L::default()) - self.quiet + self.verbose } } -fn level_value(level: Option) -> i8 { +fn level_value(level: Option) -> i8 { match level { None => -1, Some(log::Level::Error) => 0, @@ -107,7 +127,7 @@ fn level_value(level: Option) -> i8 { } } -fn level_enum(verbosity: i8) -> Option { +fn level_enum(verbosity: i8) -> Option { match verbosity { std::i8::MIN..=-1 => None, 0 => Some(log::Level::Error), @@ -120,19 +140,66 @@ fn level_enum(verbosity: i8) -> Option { use std::fmt; -impl fmt::Display for Verbosity { +impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.verbosity()) } } +pub trait LogLevel { + fn default() -> Option; + + fn verbose_help() -> Option<&'static str> { + Some("More output per occurrence") + } + + fn verbose_long_help() -> Option<&'static str> { + None + } + + fn quiet_help() -> Option<&'static str> { + Some("Less output per occurrence") + } + + fn quiet_long_help() -> Option<&'static str> { + None + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct ErrorLevel; + +impl LogLevel for ErrorLevel { + fn default() -> Option { + Some(log::Level::Error) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct WarnLevel; + +impl LogLevel for WarnLevel { + fn default() -> Option { + Some(log::Level::Warn) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct InfoLevel; + +impl LogLevel for InfoLevel { + fn default() -> Option { + Some(log::Level::Info) + } +} + #[cfg(test)] mod test { use super::*; #[test] fn verify_app() { - #[derive(Debug, clap::StructOpt)] + #[derive(Debug, clap::Parser)] struct Cli { #[clap(flatten)] verbose: Verbosity,