Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: Give control over help text #35

Merged
merged 1 commit into from Feb 9, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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