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::io::ErrorKind;
use std::env;
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<V: Into<ValueRef<'a>>>(config: V) -> Result<Self, E>;
/// Loads the TOML value into the config.
/// Missing values are set to their default values.
/// On error, logs an error message.
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()
}
}
}
/// Helper function that will call `ModuleConfig::from_config(config) if config is Some,
/// or `ModuleConfig::default()` if config is None.
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 {
/// Create `ValueDeserializer` wrapper and use it to call `Deserialize::deserialize` on it.
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 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<A, B> {
First(A),
Second(B),
}
/// A wrapper around `Vec<T>` 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<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() -> String {
Either::<T, Vec<T>>::schema_name()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
Either::<T, Vec<T>>::json_schema(gen)
}
fn is_referenceable() -> bool {
Either::<T, Vec<T>>::is_referenceable()
}
}
/// Root config of starship.
#[derive(Default)]
pub struct StarshipConfig {
pub config: Option<toml::Table>,
}
pub fn get_config_path() -> Option<String> {
if let Ok(path) = env::var("STARSHIP_CONFIG") {
// Use $STARSHIP_CONFIG as the config path if available
log::debug!("STARSHIP_CONFIG is set: {}", &path);
Some(path)
} else {
// Default to using ~/.config/starship.toml
log::debug!("STARSHIP_CONFIG is not set");
let config_path = utils::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);
Some(config_path_str)
}
}
impl StarshipConfig {
/// Initialize the Config struct
pub fn initialize() -> Self {
Self::config_from_file()
.map(|config|