use crate::configs::Palette;
use crate::context::Context;
use crate::utils;
use crate::utils::serde::{ValueDeserializer, ValueRef};
use nu_ansi_term::Color;
use serde::{
Deserialize, Deserializer, Serialize, de::Error as SerdeError, de::value::Error as ValueError,
};
use std::borrow::Cow;
use std::clone::Clone;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::ErrorKind;
use toml::Value;
pub trait ModuleConfig<'a, E>
where
Self: Default,
E: SerdeError,
{
fn from_config<V: Into<ValueRef<'a>>>(config: V) -> Result<Self, E>;
fn load<V: Into<ValueRef<'a>>>(config: V) -> Self {
match Self::from_config(config) {
Ok(config) => config,
Err(e) => {
log::warn!("Failed to load config value: {e}");
Self::default()
}
}
}
fn try_load<V: Into<ValueRef<'a>>>(config: Option<V>) -> Self {
config.map(Into::into).map(Self::load).unwrap_or_default()
}
}
impl<'a, T: Deserialize<'a> + Default> ModuleConfig<'a, ValueError> for T {
fn from_config<V: Into<ValueRef<'a>>>(config: V) -> Result<Self, ValueError> {
let config = config.into();
let deserializer = ValueDeserializer::new(config);
T::deserialize(deserializer).or_else(|err| {
if err.to_string().contains("Unknown key") {
log::warn!("{err}");
let deserializer2 = ValueDeserializer::new(config).with_allow_unknown_keys();
T::deserialize(deserializer2)
} else {
Err(err)
}
})
}
}
#[derive(Clone, Deserialize, Serialize)]
#[cfg_attr(
feature = "config-schema",
derive(schemars::JsonSchema),
schemars(deny_unknown_fields)
)]
#[serde(untagged)]
pub enum Either<A, B> {
First(A),
Second(B),
}
#[derive(Clone, Default, Serialize)]
pub struct VecOr<T>(pub Vec<T>);
impl<'de, T> Deserialize<'de> for VecOr<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let either = Either::<Vec<T>, T>::deserialize(deserializer)?;
match either {
Either::First(v) => Ok(Self(v)),
Either::Second(s) => Ok(Self(vec![s])),
}
}
}
#[cfg(feature = "config-schema")]
impl<T> schemars::JsonSchema for VecOr<T>
where
T: schemars::JsonSchema + Sized,
{
fn schema_name() -> Cow<'static, str> {
Cow::Owned(format!("VecOr_{}", T::schema_name()))
}
fn schema_id() -> Cow<'static, str> {
Cow::Owned(format!("{}::VecOr<{}>", module_path!(), T::schema_id()))
}
fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
Either::<T, Vec<T>>::json_schema(generator)
}
}
#[derive(Default)]
pub struct StarshipConfig {
pub config: Option<toml::Table>,
}
impl StarshipConfig {
pub fn initialize(config_file_path: Option<&OsStr>) -> Self {
Self::config_from_file(config_file_path)
.map(|config| Self {
config: Some(config),
})
.unwrap_or_default()
}
fn config_from_file(config_file_path: Option<&OsStr>) -> Option<toml::Table> {
let toml_content = Self::read_config_content_as_str(config_file_path)?;
match toml::from_str(&toml_content) {
Ok(parsed) => {
log::debug!("Config parsed: {:?}", &parsed);
Some(parsed)
}
Err(error) => {
log::error!("Unable to parse the config file: {error}");
None
}
}
}
pub fn read_config_content_as_str(config_file_path: Option<&OsStr>) -> Option<String> {
if config_file_path.is_none() {
log::debug!(
"Unable to determine `config_file_path`. Perhaps `utils::home_dir` is not defined on your platform?"
);
return None;
}
let config_file_path = config_file_path.as_ref().unwrap();
match utils::read_file(config_file_path) {
Ok(content) => {
log::trace!("Config file content: \"\n{}\"", &content);
Some(content)
}
Err(e) => {
let level = if e.kind() == ErrorKind::NotFound {
log::Level::Debug
} else {
log::Level::Error
};
log::log!(level, "Unable to read config file content: {}", &e);
None
}
}
}
pub fn get_module_config(&self, module_name: &str) -> Option<&Value> {
let module_config = self.get_config(&[module_name]);
if module_config.is_some() {
log::debug!(
"Config found for \"{}\": {:?}",
&module_name,
&module_config
);
}
module_config
}
pub fn get_config(&self, path: &[&str]) -> Option<&Value> {
let mut prev_table = self.config.as_ref()?;
assert_ne!(
path.len(),
0,
"Starship::get_config called with an empty path"
);
let (table_options, _) = path.split_at(path.len() - 1);
for option in table_options {
if let Some(value) = prev_table.get(*option) {
if let Some(value) = value.as_table() {
prev_table = value;
} else {
log::trace!(
"No config found for \"{}\": \"{}\" is not a table",
path.join("."),
&option
);
return None;
}
} else if prev_table.contains_key(*option) {
log::trace!(
"No config found for \"{}\": \"{}\" is not a table",
path.join("."),
&option
);
return None;
}
}
let last_option = path.last().unwrap();
let value = prev_table.get(*last_option);
if value.is_none() {
log::trace!(
"No config found for \"{}\": Option \"{}\" not found",
path.join("."),
&last_option
);
}
value
}
pub fn get_custom_module_config(&self, module_name: &str) -> Option<&Value> {
let module_config = self.get_config(&["custom", module_name]);
if module_config.is_some() {
log::debug!(
"Custom config found for \"{}\": {:?}",
&module_name,
&module_config
);
}
module_config
}
pub fn get_custom_modules(&self) -> Option<&toml::value::Table> {
self.get_config(&["custom"])?.as_table()
}
pub fn get_env_var_modules(&self) -> Option<&toml::value::Table> {
self.get_config(&["env_var"])?.as_table()
}
}
pub fn deserialize_style<'de, D>(de: D) -> Result<Style, D::Error>
where
D: Deserializer<'de>,
{
Cow::<'_, str>::deserialize(de).and_then(|s| {
parse_style_string(s.as_ref(), None).ok_or_else(|| D::Error::custom("Invalid style string"))
})
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum PrevColor {
Fg,
Bg,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Style {
style: nu_ansi_term::Style,
bg: Option<PrevColor>,
fg: Option<PrevColor>,
}
impl Style {
pub fn to_ansi_style(&self, prev: Option<&nu_ansi_term::Style>) -> nu_ansi_term::Style {
let Some(prev_style) = prev else {
return self.style;
};
let mut current = self.style;
if let Some(prev_color) = self.bg {
match prev_color {
PrevColor::Fg => current.background = prev_style.foreground,
PrevColor::Bg => current.background = prev_style.background,
}
}
if let Some(prev_color) = self.fg {
match prev_color {
PrevColor::Fg => current.foreground = prev_style.foreground,
PrevColor::Bg => current.foreground = prev_style.background,
}
}
current
}
fn map_style<F>(&self, f: F) -> Self
where
F: FnOnce(&nu_ansi_term::Style) -> nu_ansi_term::Style,
{
Self {
style: f(&self.style),
..*self
}
}
fn fg(&self, prev_color: PrevColor) -> Self {
Self {
fg: Some(prev_color),
..*self
}
}
fn bg(&self, prev_color: PrevColor) -> Self {
Self {
bg: Some(prev_color),
..*self
}
}
}
impl From<nu_ansi_term::Style> for Style {
fn from(value: nu_ansi_term::Style) -> Self {
Self {
style: value,
..Default::default()
}
}
}
impl From<nu_ansi_term::Color> for Style {
fn from(value: nu_ansi_term::Color) -> Self {
Self {
style: value.into(),
..Default::default()
}
}
}
pub fn parse_style_string(style_string: &str, context: Option<&Context>) -> Option<Style> {
style_string
.split_whitespace()
.try_fold(Style::default(), |style, token| {
let token = token.to_lowercase();
let (token, col_fg) = if token.as_str().starts_with("fg:") {
(token.trim_start_matches("fg:").to_owned(), true)
} else if token.as_str().starts_with("bg:") {
(token.trim_start_matches("bg:").to_owned(), false)
} else {
(token, true) };
match token.as_str() {
"underline" => Some(style.map_style(nu_ansi_term::Style::underline)),
"bold" => Some(style.map_style(nu_ansi_term::Style::bold)),
"italic" => Some(style.map_style(nu_ansi_term::Style::italic)),
"dimmed" => Some(style.map_style(nu_ansi_term::Style::dimmed)),
"inverted" => Some(style.map_style(nu_ansi_term::Style::reverse)),
"blink" => Some(style.map_style(nu_ansi_term::Style::blink)),
"hidden" => Some(style.map_style(nu_ansi_term::Style::hidden)),
"strikethrough" => Some(style.map_style(nu_ansi_term::Style::strikethrough)),
"prev_fg" if col_fg => Some(style.fg(PrevColor::Fg)),
"prev_fg" => Some(style.bg(PrevColor::Fg)),
"prev_bg" if col_fg => Some(style.fg(PrevColor::Bg)),
"prev_bg" => Some(style.bg(PrevColor::Bg)),
color_string => {
if color_string == "none" && col_fg {
None } else {
let parsed = parse_color_string(
color_string,
context.and_then(|x| {
get_palette(
&x.root_config.palettes,
x.root_config.palette.as_deref(),
)
}),
);
if !col_fg && parsed.is_none() {
let mut new_style = style;
new_style.style.background = Option::None;
Some(new_style)
} else {
parsed.map(|ansi_color| {
if col_fg {
style.map_style(|s| s.fg(ansi_color))
} else {
style.map_style(|s| s.on(ansi_color))
}
})
}
}
}
}
})
}
fn parse_color_string(
color_string: &str,
palette: Option<&Palette>,
) -> Option<nu_ansi_term::Color> {
log::trace!("Parsing color_string: {color_string}");
if color_string.starts_with('#') {
log::trace!("Attempting to read hexadecimal color string: {color_string}");
if color_string.len() != 7 {
log::debug!("Could not parse hexadecimal string: {color_string}");
return None;
}
let r: u8 = u8::from_str_radix(&color_string[1..3], 16).ok()?;
let g: u8 = u8::from_str_radix(&color_string[3..5], 16).ok()?;
let b: u8 = u8::from_str_radix(&color_string[5..7], 16).ok()?;
log::trace!("Read RGB color string: {r},{g},{b}");
return Some(Color::Rgb(r, g, b));
}
if let Result::Ok(ansi_color_num) = color_string.parse::<u8>() {
log::trace!("Read ANSI color string: {ansi_color_num}");
return Some(Color::Fixed(ansi_color_num));
}
if let Some(palette_color) = palette.as_ref().and_then(|x| x.get(color_string)) {
log::trace!("Read user-defined color string: {color_string} defined as {palette_color}");
return parse_color_string(palette_color, None);
}
let predefined_color = match color_string.to_lowercase().as_str() {
"black" => Some(Color::Black),
"red" => Some(Color::Red),
"green" => Some(Color::Green),
"yellow" => Some(Color::Yellow),
"blue" => Some(Color::Blue),
"purple" => Some(Color::Purple),
"cyan" => Some(Color::Cyan),
"white" => Some(Color::White),
"bright-black" => Some(Color::DarkGray), "bright-red" => Some(Color::LightRed),
"bright-green" => Some(Color::LightGreen),
"bright-yellow" => Some(Color::LightYellow),
"bright-blue" => Some(Color::LightBlue),
"bright-purple" => Some(Color::LightPurple),
"bright-cyan" => Some(Color::LightCyan),
"bright-white" => Some(Color::LightGray),
_ => None,
};
if predefined_color.is_some() {
log::trace!("Read predefined color: {color_string}");
} else {
log::debug!("Could not parse color in string: {color_string}");
}
predefined_color
}
fn get_palette<'a>(
palettes: &'a HashMap<String, Palette>,
palette_name: Option<&str>,
) -> Option<&'a Palette> {
if let Some(palette_name) = palette_name {
let palette = palettes.get(palette_name);
if palette.is_some() {
log::trace!("Found color palette: {palette_name}");
} else {
log::warn!("Could not find color palette: {palette_name}");
}
palette
} else {
log::trace!("No color palette specified, using defaults");
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use nu_ansi_term::Style as AnsiStyle;
#[derive(Default, Clone, Debug, PartialEq)]
struct StyleWrapper(Style);
impl<'de> Deserialize<'de> for StyleWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_style(deserializer).map(Self)
}
}
#[test]
fn test_load_config() {
#[derive(Clone, Default, Deserialize)]
struct TestConfig<'a> {
pub symbol: &'a str,
pub disabled: bool,
pub some_array: Vec<&'a str>,
}
let config = toml::toml! {
symbol = "T "
disabled = true
some_array = ["A"]
};
let rust_config = TestConfig::from_config(&config).unwrap();
assert_eq!(rust_config.symbol, "T ");
assert!(rust_config.disabled);
assert_eq!(rust_config.some_array, vec!["A"]);
}
#[test]
fn test_load_nested_config() {
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
struct TestConfig<'a> {
#[serde(borrow)]
pub untracked: SegmentDisplayConfig<'a>,
#[serde(borrow)]
pub modified: SegmentDisplayConfig<'a>,
}
#[derive(PartialEq, Debug, Clone, Default, Deserialize)]
#[serde(default)]
struct SegmentDisplayConfig<'a> {
pub value: &'a str,
#[serde(deserialize_with = "deserialize_style")]
pub style: Style,
}
let config = toml::toml! {
untracked.value = "x"
modified = { value = "∙", style = "red" }
};
let git_status_config = TestConfig::from_config(&config).unwrap();
assert_eq!(
git_status_config.untracked,
SegmentDisplayConfig {
value: "x",
style: Style::default(),
}
);
assert_eq!(
git_status_config.modified,
SegmentDisplayConfig {
value: "∙",
style: Color::Red.normal().into(),
}
);
}
#[test]
fn test_load_optional_config() {
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
struct TestConfig<'a> {
pub optional: Option<&'a str>,
pub hidden: Option<&'a str>,
}
let config = toml::toml! {
optional = "test"
};
let rust_config = TestConfig::from_config(&config).unwrap();
assert_eq!(rust_config.optional, Some("test"));
assert_eq!(rust_config.hidden, None);
}
#[test]
fn test_load_enum_config() {
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
struct TestConfig {
pub switch_a: Switch,
pub switch_b: Switch,
pub switch_c: Switch,
}
#[derive(Debug, PartialEq, Clone, Default)]
enum Switch {
On,
#[default]
Off,
}
impl<'de> Deserialize<'de> for Switch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_ascii_lowercase().as_str() {
"on" => Ok(Self::On),
_ => Ok(Self::Off),
}
}
}
let config = toml::toml! {
switch_a = "on"
switch_b = "any"
};
let rust_config = TestConfig::from_config(&config).unwrap();
assert_eq!(rust_config.switch_a, Switch::On);
assert_eq!(rust_config.switch_b, Switch::Off);
assert_eq!(rust_config.switch_c, Switch::Off);
}
#[test]
fn test_load_unknown_key_config() {
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
struct TestConfig<'a> {
pub foo: &'a str,
}
let config = toml::toml! {
foo = "test"
bar = "ignore me"
};
let rust_config = TestConfig::from_config(&config);
assert!(rust_config.is_ok());
assert_eq!(rust_config.unwrap().foo, "test");
}
#[test]
fn test_from_string() {
let config = Value::String(String::from("S"));
assert_eq!(<&str>::from_config(&config).unwrap(), "S");
}
#[test]
fn test_from_bool() {
let config = Value::Boolean(true);
assert!(<bool>::from_config(&config).unwrap());
}
#[test]
fn test_from_i64() {
let config = Value::Integer(42);
assert_eq!(<i64>::from_config(&config).unwrap(), 42);
}
#[test]
fn test_from_style() {
let config = Value::from("red bold");
assert_eq!(
<StyleWrapper>::from_config(&config).unwrap().0,
Color::Red.bold().into()
);
}
#[test]
fn test_from_hex_color_style() {
let config = Value::from("#00000");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("#0000000");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("#NOTHEX");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("#a12BcD");
assert_eq!(
<StyleWrapper>::from_config(&config).unwrap().0,
Color::Rgb(0xA1, 0x2B, 0xCD).into()
);
}
#[test]
fn test_from_vec() {
let config: Value = Value::Array(vec![Value::from("S")]);
assert_eq!(<Vec<&str>>::from_config(&config).unwrap(), vec!["S"]);
}
#[test]
fn test_from_option() {
let config: Value = Value::String(String::from("S"));
assert_eq!(<Option<&str>>::from_config(&config).unwrap(), Some("S"));
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.to_ansi_style(None).is_bold);
assert!(mystyle.to_ansi_style(None).is_italic);
assert!(mystyle.to_ansi_style(None).is_underline);
assert!(mystyle.to_ansi_style(None).is_dimmed);
assert_eq!(
mystyle.to_ansi_style(None),
AnsiStyle::new()
.bold()
.italic()
.underline()
.dimmed()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_inverted_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD InVeRTed");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.to_ansi_style(None).is_bold);
assert!(mystyle.to_ansi_style(None).is_italic);
assert!(mystyle.to_ansi_style(None).is_underline);
assert!(mystyle.to_ansi_style(None).is_dimmed);
assert!(mystyle.to_ansi_style(None).is_reverse);
assert_eq!(
mystyle.to_ansi_style(None),
AnsiStyle::new()
.bold()
.italic()
.underline()
.dimmed()
.reverse()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_blink_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD bLiNk");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.to_ansi_style(None).is_bold);
assert!(mystyle.to_ansi_style(None).is_italic);
assert!(mystyle.to_ansi_style(None).is_underline);
assert!(mystyle.to_ansi_style(None).is_dimmed);
assert!(mystyle.to_ansi_style(None).is_blink);
assert_eq!(
mystyle.to_ansi_style(None),
AnsiStyle::new()
.bold()
.italic()
.underline()
.dimmed()
.blink()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_hidden_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD hIDDen");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.to_ansi_style(None).is_bold);
assert!(mystyle.to_ansi_style(None).is_italic);
assert!(mystyle.to_ansi_style(None).is_underline);
assert!(mystyle.to_ansi_style(None).is_dimmed);
assert!(mystyle.to_ansi_style(None).is_hidden);
assert_eq!(
mystyle.to_ansi_style(None),
AnsiStyle::new()
.bold()
.italic()
.underline()
.dimmed()
.hidden()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_strikethrough_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD StRiKEthROUgh");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.to_ansi_style(None).is_bold);
assert!(mystyle.to_ansi_style(None).is_italic);
assert!(mystyle.to_ansi_style(None).is_underline);
assert!(mystyle.to_ansi_style(None).is_dimmed);
assert!(mystyle.to_ansi_style(None).is_strikethrough);
assert_eq!(
mystyle.to_ansi_style(None),
AnsiStyle::new()
.bold()
.italic()
.underline()
.dimmed()
.strikethrough()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_plain_and_broken_styles() {
let config = Value::from("");
let plain_style = <StyleWrapper>::from_config(&config).unwrap().0;
assert_eq!(plain_style.to_ansi_style(None), AnsiStyle::new());
let config = Value::from("djklgfhjkldhlhk;j");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("fg:red bg:green bold none");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("none fg:red bg:green bold");
assert!(<StyleWrapper>::from_config(&config).is_err());
}
#[test]
fn table_get_styles_with_none() {
let config = Value::from("fg:red bg:none none");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("none fg:red bg:none");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("fg:red none bg:none");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("fg:none bg:black");
assert!(<StyleWrapper>::from_config(&config).is_err());
let config = Value::from("fg:red bg:none");
assert_eq!(
<StyleWrapper>::from_config(&config).unwrap().0,
Color::Red.normal().into()
);
let config = Value::from("fg:red bg:none bold");
assert_eq!(
<StyleWrapper>::from_config(&config).unwrap().0,
Color::Red.bold().into()
);
let config = Value::from("fg:red bg:green bold bg:none");
assert_eq!(
<StyleWrapper>::from_config(&config).unwrap().0,
Color::Red.bold().into()
);
}
#[test]
fn table_get_styles_previous() {
let both_prevfg = <StyleWrapper>::from_config(&Value::from(
"bold fg:black fg:prev_bg bg:prev_fg underline",
))
.unwrap()
.0;
assert_eq!(
both_prevfg.to_ansi_style(None),
AnsiStyle::default().fg(Color::Black).bold().underline()
);
let prev_style = AnsiStyle::new()
.underline()
.fg(Color::Yellow)
.on(Color::Red);
assert_eq!(
both_prevfg.to_ansi_style(Some(&prev_style)),
AnsiStyle::new()
.fg(Color::Red)
.on(Color::Yellow)
.bold()
.underline()
);
let fg_prev_fg = <StyleWrapper>::from_config(&Value::from("fg:prev_fg"))
.unwrap()
.0;
assert_eq!(
fg_prev_fg.to_ansi_style(Some(&prev_style)),
AnsiStyle::new().fg(Color::Yellow)
);
let fg_prev_bg = <StyleWrapper>::from_config(&Value::from("fg:prev_bg"))
.unwrap()
.0;
assert_eq!(
fg_prev_bg.to_ansi_style(Some(&prev_style)),
AnsiStyle::new().fg(Color::Red)
);
let bg_prev_fg = <StyleWrapper>::from_config(&Value::from("bg:prev_fg"))
.unwrap()
.0;
assert_eq!(
bg_prev_fg.to_ansi_style(Some(&prev_style)),
AnsiStyle::new().on(Color::Yellow)
);
let bg_prev_bg = <StyleWrapper>::from_config(&Value::from("bg:prev_bg"))
.unwrap()
.0;
assert_eq!(
bg_prev_bg.to_ansi_style(Some(&prev_style)),
AnsiStyle::new().on(Color::Red)
);
}
#[test]
fn table_get_styles_ordered() {
let config = Value::from("bg:#050505 underline fg:120");
let flipped_style = <StyleWrapper>::from_config(&config).unwrap().0;
assert_eq!(
flipped_style.to_ansi_style(None),
AnsiStyle::new()
.underline()
.fg(Color::Fixed(120))
.on(Color::Rgb(5, 5, 5))
);
let config = Value::from("bg:120 bg:125 bg:127 fg:127 122 125");
let multi_style = <StyleWrapper>::from_config(&config).unwrap().0;
assert_eq!(
multi_style.to_ansi_style(None),
AnsiStyle::new().fg(Color::Fixed(125)).on(Color::Fixed(127))
);
}
#[test]
fn table_get_colors_palette() {
let mut palette = Palette::new();
palette.insert("mustard".to_string(), "#af8700".to_string());
palette.insert("sky-blue".to_string(), "51".to_string());
palette.insert("red".to_string(), "#d70000".to_string());
palette.insert("blue".to_string(), "17".to_string());
palette.insert("green".to_string(), "green".to_string());
assert_eq!(
parse_color_string("mustard", Some(&palette)),
Some(Color::Rgb(175, 135, 0))
);
assert_eq!(
parse_color_string("sky-blue", Some(&palette)),
Some(Color::Fixed(51))
);
assert_eq!(
parse_color_string("red", Some(&palette)),
Some(Color::Rgb(215, 0, 0))
);
assert_eq!(
parse_color_string("blue", Some(&palette)),
Some(Color::Fixed(17))
);
assert_eq!(
parse_color_string("green", Some(&palette)),
Some(Color::Green)
);
}
#[test]
fn table_get_palette() {
let mut palette1 = Palette::new();
palette1.insert("test-color".to_string(), "123".to_string());
let mut palette2 = Palette::new();
palette2.insert("test-color".to_string(), "#ABCDEF".to_string());
let mut palettes = HashMap::<String, Palette>::new();
palettes.insert("palette1".to_string(), palette1);
palettes.insert("palette2".to_string(), palette2);
assert_eq!(
get_palette(&palettes, Some("palette1"))
.unwrap()
.get("test-color")
.unwrap(),
"123"
);
assert_eq!(
get_palette(&palettes, Some("palette2"))
.unwrap()
.get("test-color")
.unwrap(),
"#ABCDEF"
);
assert!(get_palette(&palettes, Some("palette3")).is_none());
assert!(get_palette(&palettes, None).is_none());
}
#[test]
fn read_config_no_config_file_path_provided() {
assert_eq!(
None,
StarshipConfig::read_config_content_as_str(None),
"if the platform doesn't have utils::home_dir(), it should return None"
);
}
#[cfg(feature = "config-schema")]
#[test]
fn vec_or_schema_ids_distinguish_nesting() {
use schemars::JsonSchema;
assert_ne!(
VecOr::<VecOr<&str>>::schema_id(),
VecOr::<&str>::schema_id(),
);
assert_ne!(
VecOr::<VecOr<&str>>::schema_name(),
VecOr::<&str>::schema_name(),
);
}
}