cabin_core/
term_verbosity.rs1use std::fmt;
18
19#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum Verbosity {
30 Quiet,
35 #[default]
38 Normal,
39 Verbose,
42 VeryVerbose,
47}
48
49impl Verbosity {
50 pub fn as_str(self) -> &'static str {
53 match self {
54 Verbosity::Quiet => "quiet",
55 Verbosity::Normal => "normal",
56 Verbosity::Verbose => "verbose",
57 Verbosity::VeryVerbose => "very-verbose",
58 }
59 }
60
61 pub fn shows_status(self) -> bool {
63 self >= Verbosity::Normal
64 }
65
66 pub fn shows_verbose(self) -> bool {
68 self >= Verbosity::Verbose
69 }
70
71 pub fn shows_very_verbose(self) -> bool {
73 self >= Verbosity::VeryVerbose
74 }
75
76 pub fn from_verbose_count(count: u8) -> Self {
80 match count {
81 0 => Verbosity::Normal,
82 1 => Verbosity::Verbose,
83 _ => Verbosity::VeryVerbose,
84 }
85 }
86
87 pub fn from_config_pair(
97 verbose: Option<bool>,
98 quiet: Option<bool>,
99 ) -> Result<Option<Self>, InvalidVerbosityCombination> {
100 match (verbose, quiet) {
101 (Some(true), Some(true)) => Err(InvalidVerbosityCombination),
102 (Some(true), _) => Ok(Some(Verbosity::Verbose)),
103 (_, Some(true)) => Ok(Some(Verbosity::Quiet)),
104 _ => Ok(None),
105 }
106 }
107
108 pub fn parse_bool_env(variable: &'static str, raw: &str) -> Result<bool, VerbosityEnvError> {
118 if raw.is_empty() {
119 return Ok(false);
120 }
121 match raw {
122 "1" | "true" => Ok(true),
123 "0" | "false" => Ok(false),
124 _ => Err(VerbosityEnvError {
125 variable,
126 value: raw.to_owned(),
127 }),
128 }
129 }
130}
131
132impl fmt::Display for Verbosity {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 f.write_str(self.as_str())
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct VerbosityEnvError {
143 pub variable: &'static str,
144 pub value: String,
145}
146
147impl fmt::Display for VerbosityEnvError {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(
150 f,
151 "invalid {} value '{}'; expected one of: 1, 0, true, false",
152 self.variable, self.value
153 )
154 }
155}
156
157impl std::error::Error for VerbosityEnvError {}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct InvalidVerbosityCombination;
164
165impl fmt::Display for InvalidVerbosityCombination {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.write_str("term.verbose and term.quiet cannot both be true")
168 }
169}
170
171impl std::error::Error for InvalidVerbosityCombination {}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn default_is_normal() {
179 assert_eq!(Verbosity::default(), Verbosity::Normal);
180 }
181
182 #[test]
183 fn ordering_matches_intuition() {
184 assert!(Verbosity::Quiet < Verbosity::Normal);
185 assert!(Verbosity::Normal < Verbosity::Verbose);
186 assert!(Verbosity::Verbose < Verbosity::VeryVerbose);
187 }
188
189 #[test]
190 fn shows_predicates_match_thresholds() {
191 assert!(!Verbosity::Quiet.shows_status());
192 assert!(Verbosity::Normal.shows_status());
193 assert!(Verbosity::Verbose.shows_status());
194 assert!(!Verbosity::Normal.shows_verbose());
195 assert!(Verbosity::Verbose.shows_verbose());
196 assert!(Verbosity::VeryVerbose.shows_verbose());
197 assert!(!Verbosity::Verbose.shows_very_verbose());
198 assert!(Verbosity::VeryVerbose.shows_very_verbose());
199 }
200
201 #[test]
202 fn from_verbose_count_clamps_above_two() {
203 assert_eq!(Verbosity::from_verbose_count(0), Verbosity::Normal);
204 assert_eq!(Verbosity::from_verbose_count(1), Verbosity::Verbose);
205 assert_eq!(Verbosity::from_verbose_count(2), Verbosity::VeryVerbose);
206 assert_eq!(Verbosity::from_verbose_count(5), Verbosity::VeryVerbose);
207 assert_eq!(
208 Verbosity::from_verbose_count(u8::MAX),
209 Verbosity::VeryVerbose
210 );
211 }
212
213 #[test]
214 fn from_config_pair_handles_each_combination() {
215 assert_eq!(Verbosity::from_config_pair(None, None).unwrap(), None);
216 assert_eq!(
217 Verbosity::from_config_pair(Some(true), None).unwrap(),
218 Some(Verbosity::Verbose)
219 );
220 assert_eq!(
221 Verbosity::from_config_pair(None, Some(true)).unwrap(),
222 Some(Verbosity::Quiet)
223 );
224 assert_eq!(
225 Verbosity::from_config_pair(Some(false), Some(false)).unwrap(),
226 None
227 );
228 assert!(Verbosity::from_config_pair(Some(true), Some(true)).is_err());
229 }
230
231 #[test]
232 fn parse_bool_env_accepts_documented_values() {
233 for ok in ["1", "true"] {
234 assert!(Verbosity::parse_bool_env("X", ok).unwrap());
235 }
236 for falsy in ["0", "false", ""] {
237 assert!(!Verbosity::parse_bool_env("X", falsy).unwrap());
238 }
239 }
240
241 #[test]
242 fn parse_bool_env_rejects_unknown_value() {
243 let err = Verbosity::parse_bool_env("CABIN_TERM_VERBOSE", "loud").unwrap_err();
244 assert_eq!(
245 err.to_string(),
246 "invalid CABIN_TERM_VERBOSE value 'loud'; expected one of: 1, 0, true, false"
247 );
248 }
249
250 #[test]
251 fn invalid_combination_display_is_actionable() {
252 assert_eq!(
253 InvalidVerbosityCombination.to_string(),
254 "term.verbose and term.quiet cannot both be true"
255 );
256 }
257}