use crate::configs::StarshipRootConfig; use crate::utils; use ansi_term::{Color, Style}; use std::clone::Clone; use std::collections::HashMap; use std::marker::Sized; use std::env; use toml::Value; /// Root config of a module. pub trait RootModuleConfig<'a> where Self: ModuleConfig<'a>, { /// Create a new root module config with default values. fn new() -> Self; /// Load root module config from given Value and fill unset variables with default /// values. fn load(config: &'a Value) -> Self { Self::new().load_config(config) } /// Helper function that will call RootModuleConfig::load(config) if config is Some, /// or RootModuleConfig::new() if config is None. fn try_load(config: Option<&'a Value>) -> Self { if let Some(config) = config { Self::load(config) } else { Self::new() } } } /// Parsable config. pub trait ModuleConfig<'a> where Self: Sized + Clone, { /// Construct a `ModuleConfig` from a toml value. fn from_config(_config: &'a Value) -> Option { None } /// Merge `self` with config from a toml table. fn load_config(&self, config: &'a Value) -> Self { Self::from_config(config).unwrap_or_else(|| self.clone()) } } // TODO: Add logging to default implementations impl<'a> ModuleConfig<'a> for &'a str { fn from_config(config: &'a Value) -> Option { config.as_str() } } impl<'a> ModuleConfig<'a> for Style { fn from_config(config: &Value) -> Option { parse_style_string(config.as_str()?) } } impl<'a> ModuleConfig<'a> for bool { fn from_config(config: &Value) -> Option { config.as_bool() } } impl<'a> ModuleConfig<'a> for i64 { fn from_config(config: &Value) -> Option { config.as_integer() } } impl<'a> ModuleConfig<'a> for u64 { fn from_config(config: &Value) -> Option { match config { Value::Integer(value) => { // Converting i64 to u64 if *value > 0 { Some(*value as u64) } else { None } } Value::String(value) => value.parse::().ok(), _ => None, } } } impl<'a> ModuleConfig<'a> for f64 { fn from_config(config: &Value) -> Option { config.as_float() } } impl<'a> ModuleConfig<'a> for usize { fn from_config(config: &Value) -> Option { match config { Value::Integer(value) => { if *value > 0 { Some(*value as usize) } else { None } } Value::String(value) => value.parse::().ok(), _ => None, } } } impl<'a, T> ModuleConfig<'a> for Vec where T: ModuleConfig<'a>, { fn from_config(config: &'a Value) -> Option { config .as_array()? .iter() .map(|value| T::from_config(value)) .collect() } } impl<'a, T, S: ::std::hash::BuildHasher + Default> ModuleConfig<'a> for HashMap where T: ModuleConfig<'a>, S: Clone, { fn from_config(config: &'a Value) -> Option { let mut hm = HashMap::default(); for (x, y) in config.as_table()?.iter() { hm.insert(x.clone(), T::from_config(y)?); } Some(hm) } } impl<'a, T> ModuleConfig<'a> for Option where T: ModuleConfig<'a> + Sized, { fn from_config(config: &'a Value) -> Option { Some(T::from_config(config)) } } /// 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)] pub struct VecOr(pub Vec); impl<'a, T> ModuleConfig<'a> for VecOr where T: ModuleConfig<'a> + Sized, { fn from_config(config: &'a Value) -> Option { if let Some(item) = T::from_config(config) { return Some(VecOr(vec![item])); } let vec = config .as_array()? .iter() .map(|value| T::from_config(value)) .collect::>>()?; Some(VecOr(vec)) } } /// Root config of starship. pub struct StarshipConfig { pub config: Option, } impl StarshipConfig { /// Initialize the Config struct pub fn initialize() -> Self { if let Some(file_data) = Self::config_from_file() { StarshipConfig { config: Some(file_data), } } else { StarshipConfig { config: Some(Value::Table(toml::value::Table::new())), } } } /// Create a config from a starship configuration file fn config_from_file() -> Option { let file_path = if let Ok(path) = env::var("STARSHIP_CONFIG") { // Use $STARSHIP_CONFIG as the config path if available log::debug!("STARSHIP_CONFIG is set: \n{}", &path); path } else { // Default to using ~/.config/starship.toml log::debug!("STARSHIP_CONFIG is not set"); let config_path = dirs_next::home_dir()?.join(".config/starship.toml"); let config_path_str = config_path.to_str()?.to_owned(); log::debug!("Using default config path: {}", config_path_str); config_path_str }; let toml_content = match utils::read_file(&file_path) { Ok(content) => { log::trace!("Config file content: \n{}", &content); Some(content) } Err(e) => { log::debug!("Unable to read config file content: \n{}", &e); None } }?; let config = toml::from_str(&toml_content).ok()?; log::debug!("Config parsed: \n{:?}", &config); Some(config) } /// 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.config.as_ref()?.as_table()?.get(module_name); if module_config.is_some() { log::debug!( "Config found for \"{}\": \n{:?}", &module_name, &module_config ); } else { log::trace!("No config found for \"{}\"", &module_name); } module_config } /// 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_custom_modules()?.get(module_name); if module_config.is_some() { log::debug!( "Custom config found for \"{}\": \n{:?}", &module_name, &module_config ); } else { log::trace!("No custom config found for \"{}\"", &module_name); } module_config } /// Get the table of all the registered custom modules, if any pub fn get_custom_modules(&self) -> Option<&toml::value::Table> { self.config.as_ref()?.as_table()?.get("custom")?.as_table() } pub fn get_root_config(&self) -> StarshipRootConfig { if let Some(root_config) = &self.config { StarshipRootConfig::load(root_config) } else { StarshipRootConfig::new() } } } #[derive(Clone)] pub struct SegmentConfig<'a> { pub value: &'a str, pub style: Option