use std::fmt;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Verbosity {
Quiet,
#[default]
Normal,
Verbose,
VeryVerbose,
}
impl Verbosity {
pub fn as_str(self) -> &'static str {
match self {
Verbosity::Quiet => "quiet",
Verbosity::Normal => "normal",
Verbosity::Verbose => "verbose",
Verbosity::VeryVerbose => "very-verbose",
}
}
pub fn shows_status(self) -> bool {
self >= Verbosity::Normal
}
pub fn shows_verbose(self) -> bool {
self >= Verbosity::Verbose
}
pub fn shows_very_verbose(self) -> bool {
self >= Verbosity::VeryVerbose
}
pub fn from_verbose_count(count: u8) -> Self {
match count {
0 => Verbosity::Normal,
1 => Verbosity::Verbose,
_ => Verbosity::VeryVerbose,
}
}
pub fn from_config_pair(
verbose: Option<bool>,
quiet: Option<bool>,
) -> Result<Option<Self>, InvalidVerbosityCombination> {
match (verbose, quiet) {
(Some(true), Some(true)) => Err(InvalidVerbosityCombination),
(Some(true), _) => Ok(Some(Verbosity::Verbose)),
(_, Some(true)) => Ok(Some(Verbosity::Quiet)),
_ => Ok(None),
}
}
pub fn parse_bool_env(variable: &'static str, raw: &str) -> Result<bool, VerbosityEnvError> {
if raw.is_empty() {
return Ok(false);
}
match raw {
"1" | "true" => Ok(true),
"0" | "false" => Ok(false),
_ => Err(VerbosityEnvError {
variable,
value: raw.to_owned(),
}),
}
}
}
impl fmt::Display for Verbosity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerbosityEnvError {
pub variable: &'static str,
pub value: String,
}
impl fmt::Display for VerbosityEnvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid {} value '{}'; expected one of: 1, 0, true, false",
self.variable, self.value
)
}
}
impl std::error::Error for VerbosityEnvError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidVerbosityCombination;
impl fmt::Display for InvalidVerbosityCombination {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("term.verbose and term.quiet cannot both be true")
}
}
impl std::error::Error for InvalidVerbosityCombination {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_normal() {
assert_eq!(Verbosity::default(), Verbosity::Normal);
}
#[test]
fn ordering_matches_intuition() {
assert!(Verbosity::Quiet < Verbosity::Normal);
assert!(Verbosity::Normal < Verbosity::Verbose);
assert!(Verbosity::Verbose < Verbosity::VeryVerbose);
}
#[test]
fn shows_predicates_match_thresholds() {
assert!(!Verbosity::Quiet.shows_status());
assert!(Verbosity::Normal.shows_status());
assert!(Verbosity::Verbose.shows_status());
assert!(!Verbosity::Normal.shows_verbose());
assert!(Verbosity::Verbose.shows_verbose());
assert!(Verbosity::VeryVerbose.shows_verbose());
assert!(!Verbosity::Verbose.shows_very_verbose());
assert!(Verbosity::VeryVerbose.shows_very_verbose());
}
#[test]
fn from_verbose_count_clamps_above_two() {
assert_eq!(Verbosity::from_verbose_count(0), Verbosity::Normal);
assert_eq!(Verbosity::from_verbose_count(1), Verbosity::Verbose);
assert_eq!(Verbosity::from_verbose_count(2), Verbosity::VeryVerbose);
assert_eq!(Verbosity::from_verbose_count(5), Verbosity::VeryVerbose);
assert_eq!(
Verbosity::from_verbose_count(u8::MAX),
Verbosity::VeryVerbose
);
}
#[test]
fn from_config_pair_handles_each_combination() {
assert_eq!(Verbosity::from_config_pair(None, None).unwrap(), None);
assert_eq!(
Verbosity::from_config_pair(Some(true), None).unwrap(),
Some(Verbosity::Verbose)
);
assert_eq!(
Verbosity::from_config_pair(None, Some(true)).unwrap(),
Some(Verbosity::Quiet)
);
assert_eq!(
Verbosity::from_config_pair(Some(false), Some(false)).unwrap(),
None
);
assert!(Verbosity::from_config_pair(Some(true), Some(true)).is_err());
}
#[test]
fn parse_bool_env_accepts_documented_values() {
for ok in ["1", "true"] {
assert!(Verbosity::parse_bool_env("X", ok).unwrap());
}
for falsy in ["0", "false", ""] {
assert!(!Verbosity::parse_bool_env("X", falsy).unwrap());
}
}
#[test]
fn parse_bool_env_rejects_unknown_value() {
let err = Verbosity::parse_bool_env("CABIN_TERM_VERBOSE", "loud").unwrap_err();
assert_eq!(
err.to_string(),
"invalid CABIN_TERM_VERBOSE value 'loud'; expected one of: 1, 0, true, false"
);
}
#[test]
fn invalid_combination_display_is_actionable() {
assert_eq!(
InvalidVerbosityCombination.to_string(),
"term.verbose and term.quiet cannot both be true"
);
}
}