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<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>,
}
impl StarshipConfig {
/// Initialize the Config struct
pub fn initialize(config_file_path: &Option<OsString>) -> 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<OsString>) -> 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(