use crate::configs::Palette;
use crate::context::Context;
use crate::serde_utils::ValueDeserializer;
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(config: &'a Value) -> 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(config: &'a Value) -> 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<&'a Value>) -> Self {
config.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: &'a Value) -> Result<Self, ValueError> {
let deserializer = ValueDeserializer::new(config);
T::deserialize(deserializer)
}
}
#[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.
pub struct StarshipConfig {
pub config: Option<Value>,
}
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 {
if let Some(file_data) = Self::config_from_file() {
Self {
config: Some(file_data),
}
} else {
Self {
config: Some(Value::Table(toml::value::Table::new())),
}
}
}
/// Create a config from a starship configuration file
fn config_from_file() -> Option<Value> {
let file_path = get_config_path()?;
let toml_content = match utils::read_file(&file_path) {
Ok(content) => {
log::trace!("Config file content: \"\n{}\"", &content);
Some(content)
}
Err(e) => {
let level = if e.<