use crate::configs::Palette; use crate::context::Context; use crate::serde_utils::{ValueDeserializer, ValueRef}; use crate::utils; use nu_ansi_term::Color; use serde::{ de::value::Error as ValueError, de::Error as SerdeError, Deserialize, Deserializer, Serialize, }; use std::borrow::Cow; use std::clone::Clone; use std::collections::HashMap; use std::ffi::OsString; use std::io::ErrorKind; use toml::Value; /// Root config of a module. pub trait ModuleConfig<'a, E> where Self: Default, E: SerdeError, { /// Construct a `ModuleConfig` from a toml value. fn from_config>>(config: V) -> Result; /// Loads the TOML value into the config. /// Missing values are set to their default values. /// On error, logs an error message. fn load>>(config: V) -> Self { match Self::from_config(config) { Ok(config) => config, Err(e) => { log::warn!("Failed to load config value: {}", e); Self::default() } } } /// Helper function that will call `ModuleConfig::from_config(config) if config is Some, /// or `ModuleConfig::default()` if config is None. fn try_load>>(config: Option) -> Self { config.map(Into::into).map(Self::load).unwrap_or_default() } } impl<'a, T: Deserialize<'a> + Default> ModuleConfig<'a, ValueError> for T { /// Create `ValueDeserializer` wrapper and use it to call `Deserialize::deserialize` on it. fn from_config>>(config: V) -> Result { let config = config.into(); let deserializer = ValueDeserializer::new(config); T::deserialize(deserializer).or_else(|err| { // If the error is an unrecognized key, print a warning and run // deserialize ignoring that error. Otherwise, just return the error 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 { First(A), Second(B), } /// A wrapper around `Vec` that implements `ModuleConfig`, and either /// accepts a value of type `T` or a list of values of type `T`. #[derive(Clone, Default, Serialize)] pub struct VecOr(pub Vec); impl<'de, T> Deserialize<'de> for VecOr where T: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let either = Either::, T>::deserialize(deserializer)?; match either { Either::First(v) => Ok(Self(v)), Either::Second(s) => Ok(Self(vec![s])), } } } #[cfg(feature = "config-schema")] impl schemars::JsonSchema for VecOr where T: schemars::JsonSchema + Sized, { fn schema_name() -> String { Either::>::schema_name() } fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { Either::>::json_schema(gen) } fn is_referenceable() -> bool { Either::>::is_referenceable() } } /// Root config of starship. #[derive(Default)] pub struct StarshipConfig { pub config: Option, } impl StarshipConfig { /// Initialize the Config struct pub fn initialize(config_file_path: &Option) -> Self { Self::config_from_file(config_file_path) .map(|config| Self { config: Some(config), }) .unwrap_or_default() } /// Create a config from a starship configuration file fn config_from_file(config_file_path: &Option) -> Option { 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) -> Option { 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 } } } /// Get the subset of the table for a module by its name 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 } /// Get the value of the config in a specific path 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); // Assumes all keys except the last in path has a table for option in table_options { match prev_table.get(*option) { Some(value) => match value.as_table() { Some(value) => { prev_table = value; } None => { log::trace!( "No config found for \"{}\": \"{}\" is not a table", path.join("."), &option ); return None; } }, None => { log::trace!( "No config found for \"{}\": Option \"{}\" not found", 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 } /// Get the subset of the table for a custom module by its name 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 } /// Get the table of all the registered custom modules, if any pub fn get_custom_modules(&self) -> Option<&toml::value::Table> { self.get_config(&["custom"])?.as_table() } /// Get the table of all the registered `env_var` modules, if any pub fn get_env_var_modules(&self) -> Option<&toml::value::Table> { self.get_config(&["env_var"])?.as_table() } } /// Deserialize a style string in the starship format with serde pub fn deserialize_style<'de, D>(de: D) -> Result 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)] /// Wrapper for `nu_ansi_term::Style` that supports referencing the previous style's foreground/background color. pub struct Style { style: nu_ansi_term::Style, bg: Option, fg: Option, } 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(&self, f: F) -> Self where F: FnOnce(&nu_ansi_term::Style) -> nu_ansi_term::Style, { Style { 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 for Style { fn from(value: nu_ansi_term::Style) -> Self { Style { style: value, ..Default::default() } } } impl From for Style { fn from(value: nu_ansi_term::Color) -> Self { Style { style: value.into(), ..Default::default() } } } /** Parse a style string which represents an ansi style. Valid tokens in the style string include the following: - 'fg:' (specifies that the color read should be a foreground color) - 'bg:' (specifies that the color read should be a background color) - 'underline' - 'bold' - 'italic' - 'inverted' - 'blink' - 'prev_fg' (specifies the color should be the previous foreground color) - 'prev_bg' (specifies the color should be the previous background color) - '' (see the `parse_color_string` doc for valid color strings) */ pub fn parse_style_string(style_string: &str, context: Option<&Context>) -> Option