Skip to content

Commit

Permalink
fix!: Give control over help text
Browse files Browse the repository at this point in the history
A runtime default means we can't change our help text off of it.  We
could accept the default as a const generics and match off of that for
help text but the user might want more control and, for now, we'd have
to expose all of that as integers.  So we're offering a trait with
zero-sized types to control the default level and different clap
settings, like help text.
  • Loading branch information
epage committed Feb 9, 2022
1 parent a2c2e40 commit a7e3fbe
Showing 1 changed file with 109 additions and 42 deletions.
151 changes: 109 additions & 42 deletions src/lib.rs
Expand Up @@ -2,85 +2,105 @@
//!
//! # Examples
//!
//! To get `--quiet` and `--verbose` flags through your entire program, just `flatten`
//! [`Verbosity`]:
//! ```rust,no_run
//! use clap::Parser;
//! use clap_verbosity_flag::Verbosity;
//!
//! /// Le CLI
//! #[derive(Debug, Parser)]
//! struct Cli {
//! #[clap(flatten)]
//! verbose: Verbosity,
//! }
//! # use clap::Parser;
//! # use clap_verbosity_flag::Verbosity;
//! #
//! # /// Le CLI
//! # #[derive(Debug, Parser)]
//! # struct Cli {
//! #[clap(flatten)]
//! verbose: Verbosity,
//! # }
//! ```
//!
//! You can then use this to configure your logger:
//! ```rust,no_run
//! # use clap::Parser;
//! # use clap_verbosity_flag::Verbosity;
//! #
//! # /// Le CLI
//! # #[derive(Debug, Parser)]
//! # struct Cli {
//! # #[clap(flatten)]
//! # verbose: Verbosity,
//! # }
//! let cli = Cli::parse();
//! env_logger::Builder::new()
//! .filter_level(cli.verbose.log_level_filter())
//! .init();
//! ```
//!
//! This will only report errors.
//! By default, this will only report errors.
//! - `-q` silences output
//! - `-v` show warnings
//! - `-vv` show info
//! - `-vvv` show debug
//! - `-vvvv` show trace

use log::Level;
use log::LevelFilter;
//!
//! You can also customize the default logging level:
//! ```rust,no_run
//! # use clap::Parser;
//! use clap_verbosity_flag::{Verbosity, InfoLevel};
//!
//! /// Le CLI
//! #[derive(Debug, Parser)]
//! struct Cli {
//! #[clap(flatten)]
//! verbose: Verbosity<InfoLevel>,
//! }
//! ```
//!
//! Or implement [`LogLevel`] yourself for more control.

#[derive(clap::Args, Debug, Clone)]
pub struct Verbosity {
/// Pass many times for more log output
///
/// By default, it'll only report errors. Passing `-v` one time also prints
/// warnings, `-vv` enables info logging, `-vvv` debug, and `-vvvv` trace.
#[clap(long, short = 'v', parse(from_occurrences), global = true)]
pub struct Verbosity<L: LogLevel = ErrorLevel> {
#[clap(
long,
short = 'v',
parse(from_occurrences),
global = true,
help = L::verbose_help(),
long_help = L::verbose_long_help(),
)]
verbose: i8,

/// Pass many times for less log output
#[clap(
long,
short = 'q',
parse(from_occurrences),
global = true,
conflicts_with = "verbose"
help = L::quiet_help(),
long_help = L::quiet_long_help(),
conflicts_with = "verbose",
)]
quiet: i8,

#[clap(skip)]
default: i8,
phantom: std::marker::PhantomData<L>,
}

impl Verbosity {
impl<L: LogLevel> Verbosity<L> {
/// Create a new verbosity instance by explicitly setting the values
pub fn new(verbose: i8, quiet: i8, default: i8) -> Verbosity {
pub fn new(verbose: i8, quiet: i8) -> Self {
Verbosity {
verbose,
quiet,
default,
phantom: std::marker::PhantomData,
}
}

/// Change the default level.
///
/// When the level is lower than `log::Error` (the default), multiple `-q`s will be needed for
/// complete silence
///
/// `None` means all output is disabled.
pub fn set_default(&mut self, level: Option<Level>) {
self.default = level_value(level);
}

/// Get the log level.
///
/// `None` means all output is disabled.
pub fn log_level(&self) -> Option<Level> {
pub fn log_level(&self) -> Option<log::Level> {
level_enum(self.verbosity())
}

/// Get the log level filter.
pub fn log_level_filter(&self) -> LevelFilter {
pub fn log_level_filter(&self) -> log::LevelFilter {
level_enum(self.verbosity())
.map(|l| l.to_level_filter())
.unwrap_or(log::LevelFilter::Off)
Expand All @@ -92,11 +112,11 @@ impl Verbosity {
}

fn verbosity(&self) -> i8 {
self.default - self.quiet + self.verbose
level_value(L::default()) - self.quiet + self.verbose
}
}

fn level_value(level: Option<Level>) -> i8 {
fn level_value(level: Option<log::Level>) -> i8 {
match level {
None => -1,
Some(log::Level::Error) => 0,
Expand All @@ -107,7 +127,7 @@ fn level_value(level: Option<Level>) -> i8 {
}
}

fn level_enum(verbosity: i8) -> Option<Level> {
fn level_enum(verbosity: i8) -> Option<log::Level> {
match verbosity {
std::i8::MIN..=-1 => None,
0 => Some(log::Level::Error),
Expand All @@ -120,19 +140,66 @@ fn level_enum(verbosity: i8) -> Option<Level> {

use std::fmt;

impl fmt::Display for Verbosity {
impl<L: LogLevel> fmt::Display for Verbosity<L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.verbosity())
}
}

pub trait LogLevel {
fn default() -> Option<log::Level>;

fn verbose_help() -> Option<&'static str> {
Some("More output per occurrence")
}

fn verbose_long_help() -> Option<&'static str> {
None
}

fn quiet_help() -> Option<&'static str> {
Some("Less output per occurrence")
}

fn quiet_long_help() -> Option<&'static str> {
None
}
}

#[derive(Copy, Clone, Debug, Default)]
pub struct ErrorLevel;

impl LogLevel for ErrorLevel {
fn default() -> Option<log::Level> {
Some(log::Level::Error)
}
}

#[derive(Copy, Clone, Debug, Default)]
pub struct WarnLevel;

impl LogLevel for WarnLevel {
fn default() -> Option<log::Level> {
Some(log::Level::Warn)
}
}

#[derive(Copy, Clone, Debug, Default)]
pub struct InfoLevel;

impl LogLevel for InfoLevel {
fn default() -> Option<log::Level> {
Some(log::Level::Info)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn verify_app() {
#[derive(Debug, clap::StructOpt)]
#[derive(Debug, clap::Parser)]
struct Cli {
#[clap(flatten)]
verbose: Verbosity,
Expand Down

0 comments on commit a7e3fbe

Please sign in to comment.