1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
61#![warn(clippy::print_stderr)]
62#![warn(clippy::print_stdout)]
63
64pub use log::Level;
65pub use log::LevelFilter;
66
67#[derive(clap::Args, Debug, Clone, Default)]
69#[command(about = None, long_about = None)]
70pub struct Verbosity<L: LogLevel = ErrorLevel> {
71 #[arg(
72 long,
73 short = 'v',
74 action = clap::ArgAction::Count,
75 global = true,
76 help = L::verbose_help(),
77 long_help = L::verbose_long_help(),
78 )]
79 verbose: u8,
80
81 #[arg(
82 long,
83 short = 'q',
84 action = clap::ArgAction::Count,
85 global = true,
86 help = L::quiet_help(),
87 long_help = L::quiet_long_help(),
88 conflicts_with = "verbose",
89 )]
90 quiet: u8,
91
92 #[arg(skip)]
93 phantom: std::marker::PhantomData<L>,
94}
95
96impl<L: LogLevel> Verbosity<L> {
97 pub fn new(verbose: u8, quiet: u8) -> Self {
99 Verbosity {
100 verbose,
101 quiet,
102 phantom: std::marker::PhantomData,
103 }
104 }
105
106 pub fn is_present(&self) -> bool {
109 self.verbose != 0 || self.quiet != 0
110 }
111
112 pub fn log_level(&self) -> Option<Level> {
116 level_enum(self.verbosity())
117 }
118
119 pub fn log_level_filter(&self) -> LevelFilter {
121 level_enum(self.verbosity())
122 .map(|l| l.to_level_filter())
123 .unwrap_or(LevelFilter::Off)
124 }
125
126 pub fn is_silent(&self) -> bool {
128 self.log_level().is_none()
129 }
130
131 fn verbosity(&self) -> u8 {
132 let default_verbosity = level_value(L::default());
133 let verbosity = default_verbosity as i16 - self.quiet as i16 + self.verbose as i16;
134 verbosity.clamp(0, u8::MAX as i16) as u8
135 }
136}
137
138fn level_value(level: Option<Level>) -> u8 {
139 match level {
140 None => 0,
141 Some(Level::Error) => 1,
142 Some(Level::Warn) => 2,
143 Some(Level::Info) => 3,
144 Some(Level::Debug) => 4,
145 Some(Level::Trace) => 5,
146 }
147}
148
149fn level_enum(verbosity: u8) -> Option<Level> {
150 match verbosity {
151 0 => None,
152 1 => Some(Level::Error),
153 2 => Some(Level::Warn),
154 3 => Some(Level::Info),
155 4 => Some(Level::Debug),
156 5..=u8::MAX => Some(Level::Trace),
157 }
158}
159
160use std::fmt;
161
162impl<L: LogLevel> fmt::Display for Verbosity<L> {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "{}", self.verbosity())
165 }
166}
167
168pub trait LogLevel {
170 fn default() -> Option<Level>;
172
173 fn verbose_help() -> Option<&'static str> {
175 Some("Increase logging verbosity")
176 }
177
178 fn verbose_long_help() -> Option<&'static str> {
180 None
181 }
182
183 fn quiet_help() -> Option<&'static str> {
185 Some("Decrease logging verbosity")
186 }
187
188 fn quiet_long_help() -> Option<&'static str> {
190 None
191 }
192}
193
194#[derive(Copy, Clone, Debug, Default)]
196pub struct ErrorLevel;
197
198impl LogLevel for ErrorLevel {
199 fn default() -> Option<Level> {
200 Some(Level::Error)
201 }
202}
203
204#[derive(Copy, Clone, Debug, Default)]
206pub struct WarnLevel;
207
208impl LogLevel for WarnLevel {
209 fn default() -> Option<Level> {
210 Some(Level::Warn)
211 }
212}
213
214#[derive(Copy, Clone, Debug, Default)]
216pub struct InfoLevel;
217
218impl LogLevel for InfoLevel {
219 fn default() -> Option<Level> {
220 Some(Level::Info)
221 }
222}
223
224#[derive(Copy, Clone, Debug, Default)]
226pub struct DebugLevel;
227
228impl LogLevel for DebugLevel {
229 fn default() -> Option<Level> {
230 Some(Level::Debug)
231 }
232}
233
234#[derive(Copy, Clone, Debug, Default)]
236pub struct TraceLevel;
237
238impl LogLevel for TraceLevel {
239 fn default() -> Option<Level> {
240 Some(Level::Trace)
241 }
242}
243
244#[derive(Copy, Clone, Debug, Default)]
246pub struct OffLevel;
247
248impl LogLevel for OffLevel {
249 fn default() -> Option<Level> {
250 None
251 }
252}
253
254#[cfg(test)]
255mod test {
256 use super::*;
257
258 #[test]
259 fn verify_app() {
260 #[derive(Debug, clap::Parser)]
261 struct Cli {
262 #[command(flatten)]
263 verbose: Verbosity,
264 }
265
266 use clap::CommandFactory;
267 Cli::command().debug_assert();
268 }
269
270 #[test]
271 fn verbosity_off_level() {
272 let tests = [
273 (0, 0, None, LevelFilter::Off),
275 (1, 0, Some(Level::Error), LevelFilter::Error),
276 (2, 0, Some(Level::Warn), LevelFilter::Warn),
277 (3, 0, Some(Level::Info), LevelFilter::Info),
278 (4, 0, Some(Level::Debug), LevelFilter::Debug),
279 (5, 0, Some(Level::Trace), LevelFilter::Trace),
280 (6, 0, Some(Level::Trace), LevelFilter::Trace),
281 (255, 0, Some(Level::Trace), LevelFilter::Trace),
282 (0, 1, None, LevelFilter::Off),
283 (0, 255, None, LevelFilter::Off),
284 (255, 255, None, LevelFilter::Off),
285 ];
286
287 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
288 let v = Verbosity::<OffLevel>::new(*verbose, *quiet);
289 assert_eq!(
290 v.log_level(),
291 *expected_level,
292 "verbose = {verbose}, quiet = {quiet}"
293 );
294 assert_eq!(
295 v.log_level_filter(),
296 *expected_filter,
297 "verbose = {verbose}, quiet = {quiet}"
298 );
299 }
300 }
301
302 #[test]
303 fn verbosity_error_level() {
304 let tests = [
305 (0, 0, Some(Level::Error), LevelFilter::Error),
307 (1, 0, Some(Level::Warn), LevelFilter::Warn),
308 (2, 0, Some(Level::Info), LevelFilter::Info),
309 (3, 0, Some(Level::Debug), LevelFilter::Debug),
310 (4, 0, Some(Level::Trace), LevelFilter::Trace),
311 (5, 0, Some(Level::Trace), LevelFilter::Trace),
312 (255, 0, Some(Level::Trace), LevelFilter::Trace),
313 (0, 1, None, LevelFilter::Off),
314 (0, 2, None, LevelFilter::Off),
315 (0, 255, None, LevelFilter::Off),
316 (255, 255, Some(Level::Error), LevelFilter::Error),
317 ];
318
319 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
320 let v = Verbosity::<ErrorLevel>::new(*verbose, *quiet);
321 assert_eq!(
322 v.log_level(),
323 *expected_level,
324 "verbose = {verbose}, quiet = {quiet}"
325 );
326 assert_eq!(
327 v.log_level_filter(),
328 *expected_filter,
329 "verbose = {verbose}, quiet = {quiet}"
330 );
331 }
332 }
333
334 #[test]
335 fn verbosity_warn_level() {
336 let tests = [
337 (0, 0, Some(Level::Warn), LevelFilter::Warn),
339 (1, 0, Some(Level::Info), LevelFilter::Info),
340 (2, 0, Some(Level::Debug), LevelFilter::Debug),
341 (3, 0, Some(Level::Trace), LevelFilter::Trace),
342 (4, 0, Some(Level::Trace), LevelFilter::Trace),
343 (255, 0, Some(Level::Trace), LevelFilter::Trace),
344 (0, 1, Some(Level::Error), LevelFilter::Error),
345 (0, 2, None, LevelFilter::Off),
346 (0, 3, None, LevelFilter::Off),
347 (0, 255, None, LevelFilter::Off),
348 (255, 255, Some(Level::Warn), LevelFilter::Warn),
349 ];
350
351 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
352 let v = Verbosity::<WarnLevel>::new(*verbose, *quiet);
353 assert_eq!(
354 v.log_level(),
355 *expected_level,
356 "verbose = {verbose}, quiet = {quiet}"
357 );
358 assert_eq!(
359 v.log_level_filter(),
360 *expected_filter,
361 "verbose = {verbose}, quiet = {quiet}"
362 );
363 }
364 }
365
366 #[test]
367 fn verbosity_info_level() {
368 let tests = [
369 (0, 0, Some(Level::Info), LevelFilter::Info),
371 (1, 0, Some(Level::Debug), LevelFilter::Debug),
372 (2, 0, Some(Level::Trace), LevelFilter::Trace),
373 (3, 0, Some(Level::Trace), LevelFilter::Trace),
374 (255, 0, Some(Level::Trace), LevelFilter::Trace),
375 (0, 1, Some(Level::Warn), LevelFilter::Warn),
376 (0, 2, Some(Level::Error), LevelFilter::Error),
377 (0, 3, None, LevelFilter::Off),
378 (0, 4, None, LevelFilter::Off),
379 (0, 255, None, LevelFilter::Off),
380 (255, 255, Some(Level::Info), LevelFilter::Info),
381 ];
382
383 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
384 let v = Verbosity::<InfoLevel>::new(*verbose, *quiet);
385 assert_eq!(
386 v.log_level(),
387 *expected_level,
388 "verbose = {verbose}, quiet = {quiet}"
389 );
390 assert_eq!(
391 v.log_level_filter(),
392 *expected_filter,
393 "verbose = {verbose}, quiet = {quiet}"
394 );
395 }
396 }
397
398 #[test]
399 fn verbosity_debug_level() {
400 let tests = [
401 (0, 0, Some(Level::Debug), LevelFilter::Debug),
403 (1, 0, Some(Level::Trace), LevelFilter::Trace),
404 (2, 0, Some(Level::Trace), LevelFilter::Trace),
405 (255, 0, Some(Level::Trace), LevelFilter::Trace),
406 (0, 1, Some(Level::Info), LevelFilter::Info),
407 (0, 2, Some(Level::Warn), LevelFilter::Warn),
408 (0, 3, Some(Level::Error), LevelFilter::Error),
409 (0, 4, None, LevelFilter::Off),
410 (0, 5, None, LevelFilter::Off),
411 (0, 255, None, LevelFilter::Off),
412 (255, 255, Some(Level::Debug), LevelFilter::Debug),
413 ];
414
415 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
416 let v = Verbosity::<DebugLevel>::new(*verbose, *quiet);
417 assert_eq!(
418 v.log_level(),
419 *expected_level,
420 "verbose = {verbose}, quiet = {quiet}"
421 );
422 assert_eq!(
423 v.log_level_filter(),
424 *expected_filter,
425 "verbose = {verbose}, quiet = {quiet}"
426 );
427 }
428 }
429
430 #[test]
431 fn verbosity_trace_level() {
432 let tests = [
433 (0, 0, Some(Level::Trace), LevelFilter::Trace),
435 (1, 0, Some(Level::Trace), LevelFilter::Trace),
436 (255, 0, Some(Level::Trace), LevelFilter::Trace),
437 (0, 1, Some(Level::Debug), LevelFilter::Debug),
438 (0, 2, Some(Level::Info), LevelFilter::Info),
439 (0, 3, Some(Level::Warn), LevelFilter::Warn),
440 (0, 4, Some(Level::Error), LevelFilter::Error),
441 (0, 5, None, LevelFilter::Off),
442 (0, 6, None, LevelFilter::Off),
443 (0, 255, None, LevelFilter::Off),
444 (255, 255, Some(Level::Trace), LevelFilter::Trace),
445 ];
446
447 for (verbose, quiet, expected_level, expected_filter) in tests.iter() {
448 let v = Verbosity::<TraceLevel>::new(*verbose, *quiet);
449 assert_eq!(
450 v.log_level(),
451 *expected_level,
452 "verbose = {verbose}, quiet = {quiet}"
453 );
454 assert_eq!(
455 v.log_level_filter(),
456 *expected_filter,
457 "verbose = {verbose}, quiet = {quiet}"
458 );
459 }
460 }
461}