2024-06-28 00:40:52 +02:00
|
|
|
//! Utilities for parsing options and arguments on the start of a CLI application
|
|
|
|
|
2024-06-28 00:05:54 +02:00
|
|
|
use clap::Parser;
|
2024-06-28 00:40:52 +02:00
|
|
|
#[cfg(feature = "log")]
|
2024-06-28 00:05:54 +02:00
|
|
|
use libpt_log::Level;
|
2024-06-28 00:40:52 +02:00
|
|
|
#[cfg(feature = "log")]
|
|
|
|
use log;
|
2024-06-28 00:05:54 +02:00
|
|
|
|
|
|
|
/// Custom help template for displaying command-line usage information
|
|
|
|
///
|
|
|
|
/// This template modifies the default template provided by Clap to include additional information
|
|
|
|
/// and customize the layout of the help message.
|
|
|
|
///
|
|
|
|
/// Differences from the default template:
|
|
|
|
/// - Includes the application version and author information at the end
|
|
|
|
///
|
|
|
|
/// Apply like this:
|
|
|
|
/// ```
|
|
|
|
/// # use libpt_cli::args::HELP_TEMPLATE;
|
|
|
|
/// use clap::Parser;
|
|
|
|
/// #[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
/// #[command(help_template = HELP_TEMPLATE)]
|
|
|
|
/// pub struct MyArgs {
|
|
|
|
/// /// show more details
|
|
|
|
/// #[arg(short, long)]
|
|
|
|
/// pub verbose: bool,
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// Don't forget to set `authors` in your `Cargo.toml`!
|
|
|
|
///
|
|
|
|
/// ```bash
|
|
|
|
/// $ cargo run -- -h
|
|
|
|
/// about: short
|
|
|
|
///
|
|
|
|
/// Usage: aaa [OPTIONS]
|
|
|
|
///
|
|
|
|
/// Options:
|
|
|
|
/// -v, --verbose show more details
|
|
|
|
/// -h, --help Print help (see more with '--help')
|
|
|
|
/// -V, --version Print version
|
|
|
|
///
|
|
|
|
/// aaa: 0.1.0
|
|
|
|
/// Author: Christoph J. Scherr <software@cscherr.de>
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
pub const HELP_TEMPLATE: &str = r#"{about-section}
|
|
|
|
{usage-heading} {usage}
|
|
|
|
|
|
|
|
{all-args}{tab}
|
|
|
|
|
|
|
|
{name}: {version}
|
|
|
|
Author: {author-with-newline}
|
|
|
|
"#;
|
|
|
|
|
2024-06-28 00:40:52 +02:00
|
|
|
/// Transform -v and -q flags to some kind of loglevel
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// Include this into your [clap] derive struct like this:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use libpt_cli::args::VerbosityLevel;
|
|
|
|
/// use clap::Parser;
|
|
|
|
///
|
|
|
|
/// #[derive(Parser, Debug)]
|
|
|
|
/// pub struct Opts {
|
|
|
|
/// #[command(flatten)]
|
|
|
|
/// pub verbose: VerbosityLevel,
|
|
|
|
/// #[arg(short, long)]
|
|
|
|
/// pub mynum: usize,
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Get the loglevel like this:
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// # use libpt_cli::args::VerbosityLevel;
|
|
|
|
/// use libpt_log::Level;
|
|
|
|
/// # use clap::Parser;
|
|
|
|
/// use log;
|
|
|
|
///
|
|
|
|
/// # #[derive(Parser, Debug)]
|
|
|
|
/// # pub struct Opts {
|
|
|
|
/// # #[command(flatten)]
|
|
|
|
/// # pub verbose: VerbosityLevel,
|
|
|
|
/// # }
|
|
|
|
///
|
|
|
|
/// fn main() {
|
|
|
|
/// let opts = Opts::parse();
|
|
|
|
///
|
|
|
|
/// // Level might be None if the user wants no output at all.
|
|
|
|
/// // for the 'tracing' level:
|
|
|
|
/// let level: Option<Level> = opts.verbose.level();
|
|
|
|
/// // for the 'log' level:
|
|
|
|
/// let llevel: Option<log::Level> = opts.verbose.level_for_log_crate();
|
|
|
|
/// }
|
|
|
|
/// ```
|
2024-06-28 00:05:54 +02:00
|
|
|
#[derive(Parser, Clone, PartialEq, Eq, Hash)]
|
2024-06-28 00:40:52 +02:00
|
|
|
#[cfg(feature = "log")]
|
2024-06-28 00:05:54 +02:00
|
|
|
pub struct VerbosityLevel {
|
|
|
|
/// make the output more verbose
|
|
|
|
#[arg(
|
|
|
|
long,
|
|
|
|
short = 'v',
|
|
|
|
action = clap::ArgAction::Count,
|
|
|
|
global = true,
|
|
|
|
// help = L::verbose_help(),
|
|
|
|
// long_help = L::verbose_long_help(),
|
|
|
|
)]
|
|
|
|
verbose: u8,
|
|
|
|
|
|
|
|
/// make the output less verbose
|
|
|
|
///
|
|
|
|
/// ( -qqq for completely quiet)
|
|
|
|
#[arg(
|
|
|
|
long,
|
|
|
|
short = 'q',
|
|
|
|
action = clap::ArgAction::Count,
|
|
|
|
global = true,
|
|
|
|
conflicts_with = "verbose",
|
|
|
|
)]
|
|
|
|
quiet: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VerbosityLevel {
|
|
|
|
/// true only if no verbose and no quiet was set (user is using defaults)
|
|
|
|
#[inline]
|
|
|
|
pub fn changed(&self) -> bool {
|
|
|
|
self.verbose != 0 || self.quiet != 0
|
|
|
|
}
|
|
|
|
#[inline]
|
|
|
|
fn value(&self) -> i8 {
|
|
|
|
let v = Self::level_value(Level::INFO) - (self.quiet as i8) + (self.verbose as i8);
|
|
|
|
if v > Self::level_value(Level::TRACE) {
|
|
|
|
Self::level_value(Level::TRACE)
|
|
|
|
} else {
|
|
|
|
v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// get the [Level] for that VerbosityLevel
|
|
|
|
///
|
|
|
|
/// [None] means that absolutely no output is wanted (completely quiet)
|
|
|
|
#[inline]
|
|
|
|
pub fn level(&self) -> Option<Level> {
|
|
|
|
Some(match self.value() {
|
|
|
|
0 => Level::ERROR,
|
|
|
|
1 => Level::WARN,
|
|
|
|
2 => Level::INFO,
|
|
|
|
3 => Level::DEBUG,
|
|
|
|
4 => Level::TRACE,
|
|
|
|
_ => return None,
|
|
|
|
})
|
|
|
|
}
|
2024-06-28 00:40:52 +02:00
|
|
|
|
|
|
|
/// get the [log::Level] for that VerbosityLevel
|
|
|
|
///
|
|
|
|
/// This is the method for the [log] crate, which I use less often.
|
|
|
|
///
|
|
|
|
/// [None] means that absolutely no output is wanted (completely quiet)
|
|
|
|
#[inline]
|
|
|
|
pub fn level_for_log_crate(&self) -> Option<log::Level> {
|
|
|
|
self.level().map(|ll| match ll {
|
|
|
|
Level::TRACE => log::Level::Trace,
|
|
|
|
Level::DEBUG => log::Level::Debug,
|
|
|
|
Level::INFO => log::Level::Info,
|
|
|
|
Level::WARN => log::Level::Warn,
|
|
|
|
Level::ERROR => log::Level::Error,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-28 00:05:54 +02:00
|
|
|
#[inline]
|
|
|
|
fn level_value(level: Level) -> i8 {
|
|
|
|
match level {
|
|
|
|
Level::TRACE => 4,
|
|
|
|
Level::DEBUG => 3,
|
|
|
|
Level::INFO => 2,
|
|
|
|
Level::WARN => 1,
|
|
|
|
Level::ERROR => 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Debug for VerbosityLevel {
|
2024-06-28 00:40:52 +02:00
|
|
|
#[inline]
|
2024-06-28 00:05:54 +02:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(f, "{:?}", self.level())
|
|
|
|
}
|
|
|
|
}
|