clap_verbosity_flag2/
lib.rs

1//! Control `log` level with a `--verbose` flag for your CLI
2//!
3//! # Examples
4//!
5//! To get `--quiet` and `--verbose` flags through your entire program, just `flatten`
6//! [`Verbosity`]:
7//! ```rust,no_run
8//! # use clap::Parser;
9//! # use clap_verbosity_flag::Verbosity;
10//! #
11//! # /// Le CLI
12//! # #[derive(Debug, Parser)]
13//! # struct Cli {
14//! #[command(flatten)]
15//! verbose: Verbosity,
16//! # }
17//! ```
18//!
19//! You can then use this to configure your logger:
20//! ```rust,no_run
21//! # use clap::Parser;
22//! # use clap_verbosity_flag::Verbosity;
23//! #
24//! # /// Le CLI
25//! # #[derive(Debug, Parser)]
26//! # struct Cli {
27//! #     #[command(flatten)]
28//! #     verbose: Verbosity,
29//! # }
30//! let cli = Cli::parse();
31//! env_logger::Builder::new()
32//!     .filter_level(cli.verbose.log_level_filter())
33//!     .init();
34//! ```
35//!
36//! By default, this will only report errors.
37//! - `-q` silences output
38//! - `-v` show warnings
39//! - `-vv` show info
40//! - `-vvv` show debug
41//! - `-vvvv` show trace
42//!
43//! You can also customize the default logging level:
44//! ```rust,no_run
45//! # use clap::Parser;
46//! use clap_verbosity_flag::{Verbosity, InfoLevel};
47//!
48//! /// Le CLI
49//! #[derive(Debug, Parser)]
50//! struct Cli {
51//!     #[command(flatten)]
52//!     verbose: Verbosity<InfoLevel>,
53//! }
54//! ```
55//!
56//! Or implement our [`LogLevel`] trait to customize the default log level and help output.
57
58#![cfg_attr(docsrs, feature(doc_auto_cfg))]
59
60pub use log::Level;
61pub use log::LevelFilter;
62
63/// Logging flags to `#[command(flatte)]` into your CLI
64#[derive(clap::Args, Debug, Clone, Default)]
65pub struct Verbosity<L: LogLevel = ErrorLevel> {
66    #[arg(
67        long,
68        short = 'v',
69        action = clap::ArgAction::Count,
70        global = true,
71        help = L::verbose_help(),
72        long_help = L::verbose_long_help(),
73    )]
74    verbose: u8,
75
76    #[arg(
77        long,
78        short = 'q',
79        action = clap::ArgAction::Count,
80        global = true,
81        help = L::quiet_help(),
82        long_help = L::quiet_long_help(),
83        conflicts_with = "verbose",
84    )]
85    quiet: u8,
86
87    #[arg(skip)]
88    phantom: std::marker::PhantomData<L>,
89}
90
91impl<L: LogLevel> Verbosity<L> {
92    /// Create a new verbosity instance by explicitly setting the values
93    pub fn new(verbose: u8, quiet: u8) -> Self {
94        Verbosity {
95            verbose,
96            quiet,
97            phantom: std::marker::PhantomData,
98        }
99    }
100
101    /// Get the log level.
102    ///
103    /// `None` means all output is disabled.
104    pub fn log_level(&self) -> Option<log::Level> {
105        level_enum(self.verbosity())
106    }
107
108    /// Get the log level filter.
109    pub fn log_level_filter(&self) -> log::LevelFilter {
110        level_enum(self.verbosity())
111            .map(|l| l.to_level_filter())
112            .unwrap_or(log::LevelFilter::Off)
113    }
114
115    /// If the user requested complete silence (i.e. not just no-logging).
116    pub fn is_silent(&self) -> bool {
117        self.log_level().is_none()
118    }
119
120    fn verbosity(&self) -> i8 {
121        level_value(L::default()) - (self.quiet as i8) + (self.verbose as i8)
122    }
123}
124
125fn level_value(level: Option<log::Level>) -> i8 {
126    match level {
127        None => -1,
128        Some(log::Level::Error) => 0,
129        Some(log::Level::Warn) => 1,
130        Some(log::Level::Info) => 2,
131        Some(log::Level::Debug) => 3,
132        Some(log::Level::Trace) => 4,
133    }
134}
135
136fn level_enum(verbosity: i8) -> Option<log::Level> {
137    match verbosity {
138        std::i8::MIN..=-1 => None,
139        0 => Some(log::Level::Error),
140        1 => Some(log::Level::Warn),
141        2 => Some(log::Level::Info),
142        3 => Some(log::Level::Debug),
143        4..=std::i8::MAX => Some(log::Level::Trace),
144    }
145}
146
147use std::fmt;
148
149impl<L: LogLevel> fmt::Display for Verbosity<L> {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.verbosity())
152    }
153}
154
155/// Customize the default log-level and associated help
156pub trait LogLevel {
157    fn default() -> Option<log::Level>;
158
159    fn verbose_help() -> Option<&'static str> {
160        Some("Increase logging verbosity")
161    }
162
163    fn verbose_long_help() -> Option<&'static str> {
164        None
165    }
166
167    fn quiet_help() -> Option<&'static str> {
168        Some("Decrease logging verbosity")
169    }
170
171    fn quiet_long_help() -> Option<&'static str> {
172        None
173    }
174}
175
176/// Default to [`log::Level::Error`]
177#[derive(Copy, Clone, Debug, Default)]
178pub struct ErrorLevel;
179
180impl LogLevel for ErrorLevel {
181    fn default() -> Option<log::Level> {
182        Some(log::Level::Error)
183    }
184}
185
186/// Default to [`log::Level::Warn`]
187#[derive(Copy, Clone, Debug, Default)]
188pub struct WarnLevel;
189
190impl LogLevel for WarnLevel {
191    fn default() -> Option<log::Level> {
192        Some(log::Level::Warn)
193    }
194}
195
196/// Default to [`log::Level::Info`]
197#[derive(Copy, Clone, Debug, Default)]
198pub struct InfoLevel;
199
200impl LogLevel for InfoLevel {
201    fn default() -> Option<log::Level> {
202        Some(log::Level::Info)
203    }
204}
205
206/// Default to [`log::Level::Debug`]
207#[derive(Copy, Clone, Debug, Default)]
208pub struct DebugLevel;
209
210impl LogLevel for DebugLevel {
211    fn default() -> Option<log::Level> {
212        Some(log::Level::Debug)
213    }
214}
215
216/// Default to [`log::Level::Trace`]
217#[derive(Copy, Clone, Debug, Default)]
218pub struct TraceLevel;
219
220impl LogLevel for TraceLevel {
221    fn default() -> Option<log::Level> {
222        Some(log::Level::Trace)
223    }
224}
225
226#[cfg(test)]
227mod test {
228    use super::*;
229
230    #[test]
231    fn verify_app() {
232        #[derive(Debug, clap::Parser)]
233        struct Cli {
234            #[command(flatten)]
235            verbose: Verbosity,
236        }
237
238        use clap::CommandFactory;
239        Cli::command().debug_assert()
240    }
241}