lychee/lychee-bin/src/verbosity.rs
Jakob 63cdb70e6d
Upgrade to 2024 edition (#1711)
* Upgrade to 2024 edition

* Revert expr_2021 -> expr

* resolve merge conflicts

* make lint happy
2025-05-24 18:23:23 +02:00

171 lines
4.3 KiB
Rust

use log::Level;
use log::LevelFilter;
use serde::Deserialize;
/// Control the verbosity of the CLI output
///
/// By default this will only report errors and warnings.
///
/// To control the verbosity, use the `-v` and `-q` flags on the command line:
/// - `-qq` silence output
/// - `-q` mute warnings
/// - `-v` show info
/// - `-vv` show debug
/// - `-vvv` show trace
#[derive(clap::Args, Debug, Default, Clone, PartialEq, Eq)]
#[command(arg_required_else_help = true)]
pub(crate) struct Verbosity {
/// Pass many times for more log output
///
/// By default, it'll only report errors and warnings.
/// Passing `-v` one time also prints info,
/// `-vv` enables debugging logging, `-vvv` trace.
#[arg(
long,
short = 'v',
action = clap::ArgAction::Count,
global = true,
help = Self::verbose_help(),
long_help = Self::verbose_long_help(),
conflicts_with = "quiet",
)]
verbose: u8,
#[arg(
long,
short = 'q',
action = clap::ArgAction::Count,
global = true,
help = Self::quiet_help(),
long_help = Self::quiet_long_help(),
conflicts_with = "verbose",
)]
quiet: u8,
}
impl Verbosity {
/// Get the log level.
///
/// `None` means all output is disabled.
pub(crate) const fn log_level(&self) -> Level {
level_enum(self.verbosity())
}
/// Get the log level filter.
pub(crate) fn log_level_filter(&self) -> LevelFilter {
level_enum(self.verbosity()).to_level_filter()
}
#[allow(clippy::cast_possible_wrap)]
const fn verbosity(&self) -> i8 {
level_value(log::Level::Warn) - (self.quiet as i8) + (self.verbose as i8)
}
const fn verbose_help() -> &'static str {
"Set verbosity level; more output per occurrence (e.g. `-v` or `-vv`)"
}
const fn verbose_long_help() -> Option<&'static str> {
None
}
const fn quiet_help() -> &'static str {
"Less output per occurrence (e.g. `-q` or `-qq`)"
}
const fn quiet_long_help() -> Option<&'static str> {
None
}
}
#[cfg(test)]
impl Verbosity {
pub(crate) const fn debug() -> Self {
Self {
#[allow(clippy::cast_sign_loss)]
verbose: level_value(log::Level::Debug) as u8,
quiet: 0,
}
}
}
// Implement Deserialize for `Verbosity`
// This can be deserialized from a string like "warn", "warning", or "Warning"
// for example
impl<'de> Deserialize<'de> for Verbosity {
#[allow(clippy::cast_sign_loss)]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let level = match s.to_lowercase().as_str() {
"error" => Level::Error,
"warn" | "warning" => Level::Warn,
"info" => Level::Info,
"debug" => Level::Debug,
"trace" => Level::Trace,
level => {
return Err(serde::de::Error::custom(format!(
"invalid log level `{level}`"
)));
}
};
Ok(Verbosity {
verbose: level_value(level) as u8,
quiet: 0,
})
}
}
const fn level_value(level: Level) -> i8 {
match level {
log::Level::Error => 0,
log::Level::Warn => 1,
log::Level::Info => 2,
log::Level::Debug => 3,
log::Level::Trace => 4,
}
}
const fn level_enum(verbosity: i8) -> log::Level {
match verbosity {
0 => log::Level::Error,
1 => log::Level::Warn,
2 => log::Level::Info,
3 => log::Level::Debug,
_ => log::Level::Trace,
}
}
use std::fmt;
impl fmt::Display for Verbosity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.verbose)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn verify_app() {
#[derive(Debug, clap::Parser)]
struct Cli {
#[clap(flatten)]
verbose: Verbosity,
}
use clap::CommandFactory;
Cli::command().debug_assert();
}
#[test]
fn test_default_log_level() {
let verbosity = Verbosity::default();
assert_eq!(verbosity.log_level(), Level::Warn);
assert!(verbosity.log_level() >= Level::Warn);
}
}