1#![doc = include_str!("../README.md")]
2
3use bpaf::Bpaf;
4
5#[derive(Debug, Clone, Bpaf)]
7#[bpaf(generate(cli_global_options))]
8#[allow(clippy::upper_case_acronyms)]
9pub struct CLIGlobalOptions {
10 #[bpaf(long("colors"), argument("off|force"))]
14 pub colors: Option<ColorsArg>,
15
16 #[bpaf(short('v'), long("verbose"), switch, fallback(false))]
19 pub verbose: bool,
20
21 #[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}