Skip to main content

pimalaya_cli/clap/
args.rs

1//! Shared clap argument and flag parsers.
2
3use std::path::PathBuf;
4
5use clap::{Parser, ValueEnum};
6use log::LevelFilter;
7
8use crate::clap::parsers::path_parser;
9
10/// The account name flag parser.
11#[derive(Debug, Default, Parser)]
12pub struct AccountFlag {
13    /// Override the default account.
14    ///
15    /// An account name corresponds to an entry in the table at the
16    /// root level of your TOML configuration file.
17    #[arg(long = "account", short = 'a', global = true)]
18    #[arg(name = "account_name", value_name = "NAME")]
19    pub name: Option<String>,
20}
21
22/// The config path flags parser, one path per flag.
23#[derive(Debug, Default, Parser)]
24pub struct ConfigFlags {
25    /// Override the default configuration file path.
26    ///
27    /// Paths are shell-expanded, then canonicalized when applicable.
28    /// Repeat the flag to merge several files: the first one is the base
29    /// and the next ones are merged on top, which is how a public config
30    /// and a private one combine.
31    #[arg(long = "config", short = 'c', global = true)]
32    #[arg(name = "config_paths", value_name = "PATH", value_parser = path_parser)]
33    pub paths: Vec<PathBuf>,
34}
35
36/// The account name flag parser.
37#[derive(Debug, Default, Parser)]
38pub struct AccountArg {
39    /// Override the default account.
40    ///
41    /// An account name corresponds to an entry in the table at the
42    /// root level of your TOML configuration file.
43    #[arg(name = "account_name", value_name = "NAME")]
44    pub name: String,
45}
46
47/// The JSON output flag parser.
48#[derive(Debug, Default, Parser)]
49pub struct JsonFlag {
50    /// Enable JSON output.
51    ///
52    /// When set, command output (data or errors) is displayed as JSON
53    /// string.
54    #[arg(long = "json", name = "json", global = true)]
55    pub enabled: bool,
56}
57
58/// The page token argument parser.
59#[derive(Debug, Default, Parser)]
60pub struct PageTokenArg {
61    /// Resume a listing at the given page.
62    ///
63    /// The token is the `nextPage` value a previous listing printed,
64    /// given back as it is.
65    #[arg(long = "page-token", name = "page_token", value_name = "TOKEN")]
66    pub token: Option<String>,
67}
68
69/// The log level flag parser.
70#[derive(Debug, Default, Parser)]
71pub struct LogFlags {
72    /// Filter log output by level.
73    ///
74    /// When omitted, the `RUST_LOG` environment variable is consulted
75    /// (it supports per-target filters, see the `env_logger`
76    /// documentation). When present, this flag overrides `RUST_LOG`
77    /// entirely.
78    #[arg(name = "log-level", long = "log-level", visible_alias = "log")]
79    #[arg(value_enum, value_name = "LEVEL", global = true)]
80    pub level: Option<LogLevel>,
81
82    /// Append log output to the given file instead of stderr.
83    ///
84    /// Useful when interactive prompts (which always write to stderr)
85    /// would otherwise be intermixed with log lines. The file is
86    /// opened in append mode and created if it does not exist.
87    #[arg(long = "log-file", global = true, name = "log-file")]
88    #[arg(value_name = "PATH", value_parser = path_parser)]
89    pub file: Option<PathBuf>,
90}
91
92/// Log level matching [`log::LevelFilter`].
93#[derive(Debug, Default, Clone, Copy, ValueEnum)]
94pub enum LogLevel {
95    /// No logging.
96    #[default]
97    Off,
98    /// Errors only.
99    Error,
100    /// Warnings and errors.
101    Warn,
102    /// Informational messages and above.
103    Info,
104    /// Debug messages and above.
105    Debug,
106    /// Trace messages and above (most verbose).
107    Trace,
108}
109
110impl From<LogLevel> for LevelFilter {
111    fn from(level: LogLevel) -> Self {
112        match level {
113            LogLevel::Off => Self::Off,
114            LogLevel::Error => Self::Error,
115            LogLevel::Warn => Self::Warn,
116            LogLevel::Info => Self::Info,
117            LogLevel::Debug => Self::Debug,
118            LogLevel::Trace => Self::Trace,
119        }
120    }
121}
122
123/// Builds the long version string (version, enabled features, build
124/// target and git revision) for a binary's `--version` output.
125#[macro_export]
126macro_rules! long_version {
127    () => {
128        concat!(
129            "v",
130            env!("CARGO_PKG_VERSION"),
131            " ",
132            env!("CARGO_FEATURES"),
133            "\nbuild: ",
134            env!("CARGO_CFG_TARGET_OS"),
135            " ",
136            env!("CARGO_CFG_TARGET_ENV"),
137            " ",
138            env!("CARGO_CFG_TARGET_ARCH"),
139            "\ngit: ",
140            env!("GIT_DESCRIBE"),
141            ", rev ",
142            env!("GIT_REV"),
143        )
144    };
145}
146
147/// Builds the --help footer, with link to bug tracker and sponsor.
148#[macro_export]
149macro_rules! footer {
150    () => {
151        concat!(
152            "Report bugs to:\n",
153            "  ", env!("CARGO_PKG_REPOSITORY"), "/issues/\n",
154            "\n",
155            "Sponsoring:\n",
156            "  https://pimalaya.org/sponsor/\n",
157            "\n",
158            "This program is part of Pimalaya, free software funded entirely by grants and donations.\n",
159            "If you find it useful, consider supporting its development.\n",
160        )
161    };
162}
163
164#[cfg(test)]
165mod tests {
166    use std::path::PathBuf;
167
168    use clap::Parser;
169
170    use crate::clap::args::ConfigFlags;
171
172    #[test]
173    fn a_config_path_is_never_split() {
174        let args = ["app", "-c", r"C:\a.toml", "-c", "b:c;d.toml"];
175        let flags = ConfigFlags::try_parse_from(args).unwrap();
176        let paths = [PathBuf::from(r"C:\a.toml"), PathBuf::from("b:c;d.toml")];
177        assert_eq!(flags.paths, paths);
178    }
179}