1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
63
64#![cfg(feature = "serde")]
65pub mod serde_support;
66
67pub use log::Level;
68pub use log::LevelFilter;
69
70#[derive(clap::Args, Debug, Clone, Default)]
71pub struct Verbosity<L: LogLevel = ErrorLevel> {
72 #[arg(
73 long,
74 short = 'v',
75 action = clap::ArgAction::Count,
76 global = true,
77 help = L::verbose_help(),
78 long_help = L::verbose_long_help(),
79 )]
80 verbose: u8,
81
82 #[arg(
83 long,
84 short = 'q',
85 action = clap::ArgAction::Count,
86 global = true,
87 help = L::quiet_help(),
88 long_help = L::quiet_long_help(),
89 conflicts_with = "verbose",
90 )]
91 quiet: u8,
92
93 #[arg(skip)]
94 phantom: std::marker::PhantomData<L>,
95}
96
97impl<L: LogLevel> Verbosity<L> {
98 pub fn new(verbose: u8, quiet: u8) -> Self {
100 Verbosity {
101 verbose,
102 quiet,
103 phantom: std::marker::PhantomData,
104 }
105 }
106
107 pub fn log_level(&self) -> Option<log::Level> {
111 level_enum(self.verbosity())
112 }
113
114 pub fn log_level_filter(&self) -> log::LevelFilter {
116 level_enum(self.verbosity())
117 .map(|l| l.to_level_filter())
118 .unwrap_or(log::LevelFilter::Off)
119 }
120
121 pub fn is_silent(&self) -> bool {
123 self.log_level().is_none()
124 }
125
126 fn verbosity(&self) -> i8 {
127 level_value(L::default()) - (self.quiet as i8) + (self.verbose as i8)
128 }
129}
130
131pub fn level_value(level: Option<log::Level>) -> i8 {
133 match level {
134 None => -1,
135 Some(log::Level::Error) => 0,
136 Some(log::Level::Warn) => 1,
137 Some(log::Level::Info) => 2,
138 Some(log::Level::Debug) => 3,
139 Some(log::Level::Trace) => 4,
140 }
141}
142
143pub fn level_enum(verbosity: i8) -> Option<log::Level> {
145 match verbosity {
146 std::i8::MIN..=-1 => None,
147 0 => Some(log::Level::Error),
148 1 => Some(log::Level::Warn),
149 2 => Some(log::Level::Info),
150 3 => Some(log::Level::Debug),
151 4..=std::i8::MAX => Some(log::Level::Trace),
152 }
153}
154
155use std::fmt;
156
157impl<L: LogLevel> fmt::Display for Verbosity<L> {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 write!(f, "{}", self.verbosity())
160 }
161}
162
163pub trait LogLevel {
164 fn default() -> Option<log::Level>;
165
166 fn verbose_help() -> Option<&'static str> {
167 Some("More output per occurrence")
168 }
169
170 fn verbose_long_help() -> Option<&'static str> {
171 None
172 }
173
174 fn quiet_help() -> Option<&'static str> {
175 Some("Less output per occurrence")
176 }
177
178 fn quiet_long_help() -> Option<&'static str> {
179 None
180 }
181}
182
183#[derive(Copy, Clone, Debug, Default)]
184pub struct ErrorLevel;
185
186impl LogLevel for ErrorLevel {
187 fn default() -> Option<log::Level> {
188 Some(log::Level::Error)
189 }
190}
191
192#[derive(Copy, Clone, Debug, Default)]
193pub struct WarnLevel;
194
195impl LogLevel for WarnLevel {
196 fn default() -> Option<log::Level> {
197 Some(log::Level::Warn)
198 }
199}
200
201#[derive(Copy, Clone, Debug, Default)]
202pub struct InfoLevel;
203
204impl LogLevel for InfoLevel {
205 fn default() -> Option<log::Level> {
206 Some(log::Level::Info)
207 }
208}
209
210#[cfg(test)]
211mod test {
212 use super::*;
213
214 #[test]
215 fn verify_app() {
216 #[derive(Debug, clap::Parser)]
217 struct Cli {
218 #[command(flatten)]
219 verbose: Verbosity,
220 }
221
222 use clap::CommandFactory;
223 Cli::command().debug_assert()
224 }
225}