use cabin_config::{
ConfigDiscoveryInputs, EffectiveConfig, discover_config_files, merge_loaded_files,
};
use cabin_core::{ColorChoice, ColorEnvError};
pub(crate) fn discover_early_config_color() -> Option<ColorChoice> {
let inputs = ConfigDiscoveryInputs::from_process(None);
let discovery = discover_config_files(&inputs).ok()?;
let effective: EffectiveConfig = merge_loaded_files(discovery.loaded_files);
effective.term.color.map(|c| c.choice)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub(crate) enum CliColorChoice {
Auto,
Always,
Never,
}
impl From<CliColorChoice> for ColorChoice {
fn from(value: CliColorChoice) -> Self {
match value {
CliColorChoice::Auto => ColorChoice::Auto,
CliColorChoice::Always => ColorChoice::Always,
CliColorChoice::Never => ColorChoice::Never,
}
}
}
pub(crate) fn resolve_color_choice<F>(
cli: Option<ColorChoice>,
env: F,
config: Option<ColorChoice>,
) -> Result<ColorChoice, ColorEnvError>
where
F: Fn(&str) -> Option<String>,
{
if let Some(choice) = cli {
return Ok(choice);
}
if let Some(raw) = env(cabin_env::CABIN_TERM_COLOR) {
if raw.is_empty() {
return Ok(config.unwrap_or_default());
}
return ColorChoice::from_env_value(&raw);
}
Ok(config.unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;
fn no_env(_: &str) -> Option<String> {
None
}
fn env_with<'a>(pairs: &'a [(&'a str, &'a str)]) -> impl Fn(&str) -> Option<String> + 'a {
move |key| {
pairs
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| (*v).to_owned())
}
}
#[test]
fn defaults_to_auto_with_no_inputs() {
let resolved = resolve_color_choice(None, no_env, None).unwrap();
assert_eq!(resolved, ColorChoice::Auto);
}
#[test]
fn cli_always_overrides_env_never() {
let resolved = resolve_color_choice(
Some(ColorChoice::Always),
env_with(&[(cabin_env::CABIN_TERM_COLOR, "never")]),
None,
)
.unwrap();
assert_eq!(resolved, ColorChoice::Always);
}
#[test]
fn cli_never_overrides_env_always() {
let resolved = resolve_color_choice(
Some(ColorChoice::Never),
env_with(&[(cabin_env::CABIN_TERM_COLOR, "always")]),
None,
)
.unwrap();
assert_eq!(resolved, ColorChoice::Never);
}
#[test]
fn env_always_applies_when_cli_omitted() {
let resolved = resolve_color_choice(
None,
env_with(&[(cabin_env::CABIN_TERM_COLOR, "always")]),
None,
)
.unwrap();
assert_eq!(resolved, ColorChoice::Always);
}
#[test]
fn env_never_applies_when_cli_omitted() {
let resolved = resolve_color_choice(
None,
env_with(&[(cabin_env::CABIN_TERM_COLOR, "never")]),
None,
)
.unwrap();
assert_eq!(resolved, ColorChoice::Never);
}
#[test]
fn invalid_env_bubbles_up_as_typed_error() {
let err = resolve_color_choice(
None,
env_with(&[(cabin_env::CABIN_TERM_COLOR, "sometimes")]),
None,
)
.unwrap_err();
assert_eq!(
err.to_string(),
"invalid CABIN_TERM_COLOR value 'sometimes'; expected one of: auto, always, never"
);
}
#[test]
fn cli_value_takes_precedence_over_invalid_env() {
let resolved = resolve_color_choice(
Some(ColorChoice::Auto),
env_with(&[(cabin_env::CABIN_TERM_COLOR, "sometimes")]),
None,
)
.unwrap();
assert_eq!(resolved, ColorChoice::Auto);
}
#[test]
fn empty_env_value_is_treated_as_unset() {
let resolved =
resolve_color_choice(None, env_with(&[(cabin_env::CABIN_TERM_COLOR, "")]), None)
.unwrap();
assert_eq!(resolved, ColorChoice::Auto);
}
#[test]
fn config_applies_only_when_cli_and_env_silent() {
let resolved = resolve_color_choice(None, no_env, Some(ColorChoice::Always)).unwrap();
assert_eq!(resolved, ColorChoice::Always);
}
#[test]
fn env_overrides_config() {
let resolved = resolve_color_choice(
None,
env_with(&[(cabin_env::CABIN_TERM_COLOR, "never")]),
Some(ColorChoice::Always),
)
.unwrap();
assert_eq!(resolved, ColorChoice::Never);
}
#[test]
fn cli_overrides_config_too() {
let resolved =
resolve_color_choice(Some(ColorChoice::Always), no_env, Some(ColorChoice::Never))
.unwrap();
assert_eq!(resolved, ColorChoice::Always);
}
#[test]
fn empty_env_falls_through_to_config() {
let resolved = resolve_color_choice(
None,
env_with(&[(cabin_env::CABIN_TERM_COLOR, "")]),
Some(ColorChoice::Always),
)
.unwrap();
assert_eq!(resolved, ColorChoice::Always);
}
}