clap_verbosity_flag2/
lib.rs1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
59
60pub use log::Level;
61pub use log::LevelFilter;
62
63#[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 pub fn new(verbose: u8, quiet: u8) -> Self {
94 Verbosity {
95 verbose,
96 quiet,
97 phantom: std::marker::PhantomData,
98 }
99 }
100
101 pub fn log_level(&self) -> Option<log::Level> {
105 level_enum(self.verbosity())
106 }
107
108 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 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
155pub 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#[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#[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#[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#[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#[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}