use super::Configurable;
use crate::app::Cli;
use crate::config_file::Config;
use serde::Deserialize;
use serde::de::{self, Deserializer, Visitor};
use std::env;
use std::fmt;
#[derive(Clone, Debug, Default)]
pub struct Color {
pub when: ColorOption,
pub theme: ThemeOption,
}
impl Color {
pub fn configure_from(cli: &Cli, config: &Config) -> Self {
let when = ColorOption::configure_from(cli, config);
let theme = ThemeOption::from_config(config);
Self { when, theme }
}
}
#[derive(PartialEq, Eq, Debug, Clone, Default)]
pub enum ThemeOption {
NoColor,
#[default]
Default,
#[allow(dead_code)]
NoLscolors,
CustomLegacy(String),
Custom,
}
impl ThemeOption {
fn from_config(config: &Config) -> ThemeOption {
if config.classic == Some(true) {
ThemeOption::NoColor
} else {
config
.color
.as_ref()
.and_then(|c| c.theme.clone())
.unwrap_or_default()
}
}
}
impl<'de> de::Deserialize<'de> for ThemeOption {
fn deserialize<D>(deserializer: D) -> Result<ThemeOption, D::Error>
where
D: Deserializer<'de>,
{
struct ThemeOptionVisitor;
impl Visitor<'_> for ThemeOptionVisitor {
type Value = ThemeOption;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`default` or <theme-file-path>")
}
fn visit_str<E>(self, value: &str) -> Result<ThemeOption, E>
where
E: de::Error,
{
match value {
"default" => Ok(ThemeOption::Default),
"custom" => Ok(ThemeOption::Custom),
str => Ok(ThemeOption::CustomLegacy(str.to_string())),
}
}
}
deserializer.deserialize_identifier(ThemeOptionVisitor)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ColorOption {
Always,
#[default]
Auto,
Never,
}
impl ColorOption {
fn from_arg_str(value: &str) -> Self {
match value {
"always" => Self::Always,
"auto" => Self::Auto,
"never" => Self::Never,
other => unreachable!("Invalid value '{other}' for 'color'"),
}
}
}
impl Configurable<Self> for ColorOption {
fn from_cli(cli: &Cli) -> Option<Self> {
if cli.classic {
Some(Self::Never)
} else {
cli.color.as_deref().map(Self::from_arg_str)
}
}
fn from_config(config: &Config) -> Option<Self> {
if config.classic == Some(true) {
Some(Self::Never)
} else {
config.color.as_ref().and_then(|c| c.when)
}
}
fn from_environment() -> Option<Self> {
if env::var("NO_COLOR").is_ok() {
Some(Self::Never)
} else {
None
}
}
}
#[cfg(test)]
mod test_color_option {
use clap::Parser;
use super::ColorOption;
use crate::app::Cli;
use crate::config_file::{self, Config};
use crate::flags::Configurable;
#[test]
fn test_from_cli_none() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(None, ColorOption::from_cli(&cli));
}
#[test]
fn test_from_cli_always() {
let argv = ["lsd", "--color", "always"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(Some(ColorOption::Always), ColorOption::from_cli(&cli));
}
#[test]
fn test_from_cli_auto() {
let argv = ["lsd", "--color", "auto"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(Some(ColorOption::Auto), ColorOption::from_cli(&cli));
}
#[test]
fn test_from_cli_never() {
let argv = ["lsd", "--color", "never"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(Some(ColorOption::Never), ColorOption::from_cli(&cli));
}
#[test]
fn test_from_env_no_color() {
temp_env::with_var("NO_COLOR", Some("true"), || {
assert_eq!(Some(ColorOption::Never), ColorOption::from_environment());
});
}
#[test]
fn test_from_cli_classic_mode() {
let argv = ["lsd", "--color", "always", "--classic"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(Some(ColorOption::Never), ColorOption::from_cli(&cli));
}
#[test]
fn test_from_cli_color_multiple() {
let argv = ["lsd", "--color", "always", "--color", "never"];
let cli = Cli::try_parse_from(argv).unwrap();
assert_eq!(Some(ColorOption::Never), ColorOption::from_cli(&cli));
}
#[test]
fn test_from_config_none() {
assert_eq!(None, ColorOption::from_config(&Config::with_none()));
}
#[test]
fn test_from_config_always() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: Some(ColorOption::Always),
theme: None,
});
assert_eq!(Some(ColorOption::Always), ColorOption::from_config(&c));
}
#[test]
fn test_from_config_auto() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: Some(ColorOption::Auto),
theme: None,
});
assert_eq!(Some(ColorOption::Auto), ColorOption::from_config(&c));
}
#[test]
fn test_from_config_never() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: Some(ColorOption::Never),
theme: None,
});
assert_eq!(Some(ColorOption::Never), ColorOption::from_config(&c));
}
#[test]
fn test_from_config_classic_mode() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: Some(ColorOption::Always),
theme: None,
});
c.classic = Some(true);
assert_eq!(Some(ColorOption::Never), ColorOption::from_config(&c));
}
}
#[cfg(test)]
mod test_theme_option {
use super::ThemeOption;
use crate::config_file::{self, Config};
#[test]
fn test_from_config_none_default() {
assert_eq!(
ThemeOption::Default,
ThemeOption::from_config(&Config::with_none())
);
}
#[test]
fn test_from_config_default() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: None,
theme: Some(ThemeOption::Default),
});
assert_eq!(ThemeOption::Default, ThemeOption::from_config(&c));
}
#[test]
fn test_from_config_no_color() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: None,
theme: Some(ThemeOption::NoColor),
});
assert_eq!(ThemeOption::NoColor, ThemeOption::from_config(&c));
}
#[test]
fn test_from_config_no_lscolor() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: None,
theme: Some(ThemeOption::NoLscolors),
});
assert_eq!(ThemeOption::NoLscolors, ThemeOption::from_config(&c));
}
#[test]
fn test_from_config_bad_file_flag() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: None,
theme: Some(ThemeOption::CustomLegacy("not-existed".to_string())),
});
assert_eq!(
ThemeOption::CustomLegacy("not-existed".to_string()),
ThemeOption::from_config(&c)
);
}
#[test]
fn test_from_config_classic_mode() {
let mut c = Config::with_none();
c.color = Some(config_file::Color {
when: None,
theme: Some(ThemeOption::Default),
});
c.classic = Some(true);
assert_eq!(ThemeOption::NoColor, ThemeOption::from_config(&c));
}
}