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}