use std::ffi::OsStr;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub(crate) enum StyleLevel {
Full,
Plain,
Bare,
}
impl StyleLevel {
pub(crate) const fn sheep(self) -> bool {
matches!(self, Self::Full)
}
pub(crate) const fn boxes(self) -> bool {
matches!(self, Self::Full | Self::Plain)
}
pub(crate) const fn colour(self) -> bool {
matches!(self, Self::Full | Self::Plain)
}
pub(crate) fn parse(raw: &str) -> Option<Self> {
match raw.trim().to_ascii_lowercase().as_str() {
"full" => Some(Self::Full),
"plain" => Some(Self::Plain),
"bare" => Some(Self::Bare),
_ => None,
}
}
}
impl fmt::Display for StyleLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Full => "full",
Self::Plain => "plain",
Self::Bare => "bare",
})
}
}
pub(crate) fn no_color_set(no_color: Option<&OsStr>) -> bool {
no_color.is_some_and(|value| !value.is_empty())
}
pub(crate) fn deep_colour_terminal(term: Option<&OsStr>, colorterm: Option<&OsStr>) -> bool {
colorterm.is_some_and(|value| {
let value = value.to_string_lossy();
value.contains("truecolor") || value.contains("24bit")
}) || term.is_some_and(|value| value.to_string_lossy().contains("256color"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Presentation {
pub(crate) level: StyleLevel,
pub(crate) colour: bool,
pub(crate) deep_colour: bool,
pub(crate) width: usize,
}
impl Presentation {
#[allow(dead_code)]
pub(crate) const BARE: Self = Self {
level: StyleLevel::Bare,
colour: false,
deep_colour: false,
width: 80,
};
pub(crate) fn new(
level: StyleLevel,
no_color: Option<&OsStr>,
term: Option<&OsStr>,
colorterm: Option<&OsStr>,
width: usize,
) -> Self {
Self {
level,
colour: level.colour() && !no_color_set(no_color),
deep_colour: deep_colour_terminal(term, colorterm),
width,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StyleSource {
Flag,
Env,
Config,
Default,
}
impl fmt::Display for StyleSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Flag => "--style",
Self::Env => "$SHEP_STYLE",
Self::Config => "shep.toml",
Self::Default => "the default",
})
}
}
pub(crate) fn resolve(
flag: Option<StyleLevel>,
env: Option<&str>,
config: Option<StyleLevel>,
) -> (StyleLevel, StyleSource) {
if let Some(level) = flag {
return (level, StyleSource::Flag);
}
if let Some(level) = env.and_then(StyleLevel::parse) {
return (level, StyleSource::Env);
}
if let Some(level) = config {
return (level, StyleSource::Config);
}
(StyleLevel::Full, StyleSource::Default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_flag_beats_the_env_beats_the_config_beats_the_default() {
assert_eq!(
resolve(
Some(StyleLevel::Bare),
Some("full"),
Some(StyleLevel::Plain)
),
(StyleLevel::Bare, StyleSource::Flag)
);
assert_eq!(
resolve(None, Some("bare"), Some(StyleLevel::Full)),
(StyleLevel::Bare, StyleSource::Env)
);
assert_eq!(
resolve(None, None, Some(StyleLevel::Plain)),
(StyleLevel::Plain, StyleSource::Config)
);
assert_eq!(
resolve(None, None, None),
(StyleLevel::Full, StyleSource::Default)
);
}
#[test]
fn an_unparseable_env_value_falls_through_to_the_next_source() {
assert_eq!(
resolve(None, Some("shiny"), Some(StyleLevel::Bare)),
(StyleLevel::Bare, StyleSource::Config)
);
}
#[test]
fn each_level_answers_all_three_questions() {
assert_eq!(
(
StyleLevel::Full.sheep(),
StyleLevel::Full.boxes(),
StyleLevel::Full.colour()
),
(true, true, true)
);
assert_eq!(
(
StyleLevel::Plain.sheep(),
StyleLevel::Plain.boxes(),
StyleLevel::Plain.colour()
),
(false, true, true)
);
assert_eq!(
(
StyleLevel::Bare.sheep(),
StyleLevel::Bare.boxes(),
StyleLevel::Bare.colour()
),
(false, false, false)
);
}
#[test]
fn no_color_set_treats_an_empty_value_as_unset() {
assert!(!no_color_set(None));
assert!(!no_color_set(Some(OsStr::new(""))));
assert!(no_color_set(Some(OsStr::new("1"))));
}
#[test]
fn deep_colour_terminal_reads_colorterm_then_term() {
assert!(!deep_colour_terminal(None, None));
assert!(!deep_colour_terminal(Some(OsStr::new("vt100")), None));
assert!(deep_colour_terminal(
Some(OsStr::new("xterm-256color")),
None
));
assert!(deep_colour_terminal(None, Some(OsStr::new("truecolor"))));
assert!(deep_colour_terminal(
Some(OsStr::new("dumb")),
Some(OsStr::new("24bit"))
));
}
#[test]
fn presentation_new_folds_no_color_into_the_levels_own_answer() {
let full_untouched = Presentation::new(StyleLevel::Full, None, None, None, 80);
assert!(full_untouched.colour);
let full_vetoed =
Presentation::new(StyleLevel::Full, Some(OsStr::new("1")), None, None, 80);
assert!(!full_vetoed.colour);
let bare_with_no_color_unset = Presentation::new(
StyleLevel::Bare,
None,
Some(OsStr::new("xterm-256color")),
None,
80,
);
assert!(
!bare_with_no_color_unset.colour,
"bare never asked for colour; NO_COLOR being unset does not grant it"
);
}
#[test]
fn presentation_new_resolves_deep_colour_from_the_terminal() {
let deep = Presentation::new(
StyleLevel::Full,
None,
Some(OsStr::new("xterm-256color")),
None,
80,
);
assert!(deep.deep_colour);
let shallow =
Presentation::new(StyleLevel::Full, None, Some(OsStr::new("vt100")), None, 80);
assert!(!shallow.deep_colour);
}
}