libpt_cli/
args.rs

1//! Utilities for parsing options and arguments on the start of a CLI application
2
3use clap::Parser;
4use libpt_log::Level;
5#[cfg(feature = "log")]
6use log;
7use serde::{Deserialize, Serialize};
8
9/// Custom help template for displaying command-line usage information
10///
11/// This template modifies the default template provided by Clap to include additional information
12/// and customize the layout of the help message.
13///
14/// Differences from the default template:
15/// - Includes the application version and author information at the end
16///
17/// Apply like this:
18/// ```
19/// # use libpt_cli::args::HELP_TEMPLATE;
20/// use clap::Parser;
21/// #[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)]
22/// #[command(help_template = HELP_TEMPLATE, author, version)]
23/// pub struct MyArgs {
24///     /// show more details
25///     #[arg(short, long)]
26///     pub verbose: bool,
27/// }
28/// ```
29///
30/// ## Example
31///
32/// Don't forget to set `authors` in your `Cargo.toml`!
33///
34/// ```bash
35/// $ cargo run -- -h
36/// about: short
37///
38/// Usage: aaa [OPTIONS]
39///
40/// Options:
41///   -v, --verbose  show more details
42///   -h, --help     Print help (see more with '--help')
43///   -V, --version  Print version
44///
45/// aaa: 0.1.0
46/// Author: Christoph J. Scherr <software@cscherr.de>
47///
48/// ```
49pub const HELP_TEMPLATE: &str = r"{about-section}
50{usage-heading} {usage}
51
52{all-args}{tab}
53
54{name}: {version}
55Author: {author-with-newline}
56";
57
58/// Transform -v and -q flags to some kind of loglevel
59///
60/// # Example
61///
62/// Include this into your [clap] derive struct like this:
63///
64/// ```
65/// use libpt_cli::args::VerbosityLevel;
66/// use clap::Parser;
67///
68/// #[derive(Parser, Debug)]
69/// pub struct Opts {
70///     #[command(flatten)]
71///     pub verbose: VerbosityLevel,
72///     #[arg(short, long)]
73///     pub mynum: usize,
74/// }
75///
76/// ```
77///
78/// Get the loglevel like this:
79///
80/// ```no_run
81/// use libpt_cli::args::VerbosityLevel;
82/// use libpt_log::Level;
83/// use clap::Parser;
84///
85/// # #[derive(Parser, Debug)]
86/// # pub struct Opts {
87/// #     #[command(flatten)]
88/// #     pub verbose: VerbosityLevel,
89/// # }
90///
91/// fn main() {
92///     let opts = Opts::parse();
93///
94///     // Level might be None if the user wants no output at all.
95///     // for the 'tracing' level:
96///     let level: Level = opts.verbose.level();
97/// }
98/// ```
99#[derive(Parser, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
100pub struct VerbosityLevel {
101    /// make the output more verbose
102    #[arg(
103        long,
104        short = 'v',
105        action = clap::ArgAction::Count, // NOTE: this forces u8 type for some reason
106        global = true,
107        // help = L::verbose_help(),
108        // long_help = L::verbose_long_help(),
109    )]
110    verbose: u8,
111
112    /// make the output less verbose
113    ///
114    /// ( -qqq for completely quiet)
115    #[arg(
116        long,
117        short = 'q',
118        action = clap::ArgAction::Count,
119        global = true,
120        conflicts_with = "verbose",
121    )]
122    quiet: u8,
123}
124
125impl VerbosityLevel {
126    /// true only if no verbose and no quiet was set (user is using defaults)
127    #[inline]
128    #[must_use]
129    #[allow(clippy::missing_const_for_fn)] // the values of self can change
130    pub fn changed(&self) -> bool {
131        self.verbose != 0 || self.quiet != 0
132    }
133    #[inline]
134    #[must_use]
135    fn value(&self) -> u8 {
136        Self::level_value(Level::INFO)
137            .saturating_sub((self.quiet).min(10))
138            .saturating_add((self.verbose).min(10))
139    }
140
141    /// get the [Level] for that [`VerbosityLevel`]
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use libpt_log::Level; // reexport: tracing
147    /// use libpt_cli::args::VerbosityLevel;
148    ///
149    /// let verbosity_level = VerbosityLevel::INFO;
150    /// assert_eq!(verbosity_level.level(), Level::INFO);
151    /// ```
152    #[inline]
153    #[must_use]
154    pub fn level(&self) -> Level {
155        let v = self.value();
156        match v {
157            0 => Level::ERROR,
158            1 => Level::WARN,
159            2 => Level::INFO,
160            3 => Level::DEBUG,
161            4 => Level::TRACE,
162            _ => {
163                if v > 4 {
164                    Level::TRACE
165                } else {
166                    /* v < 0 */
167                    Level::ERROR
168                }
169            }
170        }
171    }
172
173    /// get the [`log::Level`] for that `VerbosityLevel`
174    ///
175    /// This is the method for the [log] crate, which I use less often.
176    ///
177    /// [None] means that absolutely no output is wanted (completely quiet)
178    #[inline]
179    #[must_use]
180    #[cfg(feature = "log")]
181    pub fn level_for_log_crate(&self) -> log::Level {
182        match self.level() {
183            Level::TRACE => log::Level::Trace,
184            Level::DEBUG => log::Level::Debug,
185            Level::INFO => log::Level::Info,
186            Level::WARN => log::Level::Warn,
187            Level::ERROR => log::Level::Error,
188        }
189    }
190
191    #[inline]
192    #[must_use]
193    const fn level_value(level: Level) -> u8 {
194        match level {
195            Level::TRACE => 4,
196            Level::DEBUG => 3,
197            Level::INFO => 2,
198            Level::WARN => 1,
199            Level::ERROR => 0,
200        }
201    }
202
203    /// # Examples
204    ///
205    /// ```
206    /// use libpt_log::Level; // reexport: tracing
207    /// use libpt_cli::args::VerbosityLevel;
208    ///
209    /// let verbosity_level = VerbosityLevel::TRACE;
210    /// assert_eq!(verbosity_level.level(), Level::TRACE);
211    /// ```
212    pub const TRACE: Self = Self {
213        verbose: 2,
214        quiet: 0,
215    };
216    /// # Examples
217    ///
218    /// ```
219    /// use libpt_log::Level; // reexport: tracing
220    /// use libpt_cli::args::VerbosityLevel;
221    ///
222    /// let verbosity_level = VerbosityLevel::DEBUG;
223    /// assert_eq!(verbosity_level.level(), Level::DEBUG);
224    /// ```
225    pub const DEBUG: Self = Self {
226        verbose: 1,
227        quiet: 0,
228    };
229    /// # Examples
230    ///
231    /// ```
232    /// use libpt_log::Level; // reexport: tracing
233    /// use libpt_cli::args::VerbosityLevel;
234    ///
235    /// let verbosity_level = VerbosityLevel::INFO;
236    /// assert_eq!(verbosity_level.level(), Level::INFO);
237    /// ```
238    pub const INFO: Self = Self {
239        verbose: 0,
240        quiet: 0,
241    };
242    /// # Examples
243    ///
244    /// ```
245    /// use libpt_log::Level; // reexport: tracing
246    /// use libpt_cli::args::VerbosityLevel;
247    ///
248    /// let verbosity_level = VerbosityLevel::WARN;
249    /// assert_eq!(verbosity_level.level(), Level::WARN);
250    /// ```
251    pub const WARN: Self = Self {
252        verbose: 0,
253        quiet: 1,
254    };
255    /// # Examples
256    ///
257    /// ```
258    /// use libpt_log::Level; // reexport: tracing
259    /// use libpt_cli::args::VerbosityLevel;
260    ///
261    /// let verbosity_level = VerbosityLevel::ERROR;
262    /// assert_eq!(verbosity_level.level(), Level::ERROR);
263    /// ```
264    pub const ERROR: Self = Self {
265        verbose: 0,
266        quiet: 2,
267    };
268}
269
270impl std::fmt::Debug for VerbosityLevel {
271    #[inline]
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        write!(f, "{:?}", self.level())
274    }
275}
276
277impl Default for VerbosityLevel {
278    fn default() -> Self {
279        Self::INFO
280    }
281}