Skip to main content

lintel_cli_common/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use bpaf::Bpaf;
4
5/// Global options applied to all commands
6#[derive(Debug, Clone, Bpaf)]
7#[bpaf(generate(cli_global_options))]
8#[allow(clippy::upper_case_acronyms)]
9pub struct CLIGlobalOptions {
10    /// Set the formatting mode for markup: "off" prints everything as plain text,
11    /// "force" forces the formatting of markup using ANSI even if the console
12    /// output is determined to be incompatible
13    #[bpaf(long("colors"), argument("off|force"))]
14    pub colors: Option<ColorsArg>,
15
16    /// Print additional diagnostics, and some diagnostics show more information.
17    /// Also, print out what files were processed and which ones were modified.
18    #[bpaf(short('v'), long("verbose"), switch, fallback(false))]
19    pub verbose: bool,
20
21    /// The level of logging. In order, from the most verbose to the least verbose:
22    /// debug, info, warn, error.
23    #[bpaf(
24        long("log-level"),
25        argument("none|debug|info|warn|error"),
26        fallback(LogLevel::None),
27        display_fallback
28    )]
29    pub log_level: LogLevel,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum ColorsArg {
34    Off,
35    Force,
36}
37
38impl core::str::FromStr for ColorsArg {
39    type Err = String;
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        match s {
42            "off" => Ok(Self::Off),
43            "force" => Ok(Self::Force),
44            _ => Err(format!("expected 'off' or 'force', got '{s}'")),
45        }
46    }
47}
48
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
50pub enum LogLevel {
51    #[default]
52    None,
53    Debug,
54    Info,
55    Warn,
56    Error,
57}
58
59impl core::str::FromStr for LogLevel {
60    type Err = String;
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        match s {
63            "none" => Ok(Self::None),
64            "debug" => Ok(Self::Debug),
65            "info" => Ok(Self::Info),
66            "warn" => Ok(Self::Warn),
67            "error" => Ok(Self::Error),
68            _ => Err(format!(
69                "expected 'none', 'debug', 'info', 'warn', or 'error', got '{s}'"
70            )),
71        }
72    }
73}
74
75impl core::fmt::Display for LogLevel {
76    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        match self {
78            Self::None => write!(f, "none"),
79            Self::Debug => write!(f, "debug"),
80            Self::Info => write!(f, "info"),
81            Self::Warn => write!(f, "warn"),
82            Self::Error => write!(f, "error"),
83        }
84    }
85}
86
87#[cfg(test)]
88#[allow(clippy::unwrap_used)]
89mod tests {
90    use super::*;
91    use bpaf::Parser;
92
93    fn opts() -> bpaf::OptionParser<CLIGlobalOptions> {
94        cli_global_options().to_options()
95    }
96
97    #[test]
98    fn defaults() {
99        let parsed = opts().run_inner(&[]).unwrap();
100        assert!(!parsed.verbose);
101        assert_eq!(parsed.log_level, LogLevel::None);
102        assert!(parsed.colors.is_none());
103    }
104
105    #[test]
106    fn verbose_short() {
107        let parsed = opts().run_inner(&["-v"]).unwrap();
108        assert!(parsed.verbose);
109    }
110
111    #[test]
112    fn verbose_long() {
113        let parsed = opts().run_inner(&["--verbose"]).unwrap();
114        assert!(parsed.verbose);
115    }
116
117    #[test]
118    fn log_level_debug() {
119        let parsed = opts().run_inner(&["--log-level", "debug"]).unwrap();
120        assert_eq!(parsed.log_level, LogLevel::Debug);
121    }
122
123    #[test]
124    fn log_level_info() {
125        let parsed = opts().run_inner(&["--log-level", "info"]).unwrap();
126        assert_eq!(parsed.log_level, LogLevel::Info);
127    }
128
129    #[test]
130    fn log_level_warn() {
131        let parsed = opts().run_inner(&["--log-level", "warn"]).unwrap();
132        assert_eq!(parsed.log_level, LogLevel::Warn);
133    }
134
135    #[test]
136    fn log_level_error() {
137        let parsed = opts().run_inner(&["--log-level", "error"]).unwrap();
138        assert_eq!(parsed.log_level, LogLevel::Error);
139    }
140
141    #[test]
142    fn log_level_invalid() {
143        assert!(opts().run_inner(&["--log-level", "trace"]).is_err());
144    }
145
146    #[test]
147    fn colors_off() {
148        let parsed = opts().run_inner(&["--colors", "off"]).unwrap();
149        assert_eq!(parsed.colors, Some(ColorsArg::Off));
150    }
151
152    #[test]
153    fn colors_force() {
154        let parsed = opts().run_inner(&["--colors", "force"]).unwrap();
155        assert_eq!(parsed.colors, Some(ColorsArg::Force));
156    }
157
158    #[test]
159    fn colors_invalid() {
160        assert!(opts().run_inner(&["--colors", "auto"]).is_err());
161    }
162
163    #[test]
164    fn combined_flags() {
165        let parsed = opts()
166            .run_inner(&["-v", "--log-level", "debug", "--colors", "force"])
167            .unwrap();
168        assert!(parsed.verbose);
169        assert_eq!(parsed.log_level, LogLevel::Debug);
170        assert_eq!(parsed.colors, Some(ColorsArg::Force));
171    }
172}