use crate::ConfigType;
use crate::enums::{GetConfigError, OneOrMany, TendrilMode};
use crate::env_ext::get_home_dir;
use crate::path_ext::UniPath;
use crate::tendril::RawTendril;
use serde::{de, Deserialize, Deserializer, Serialize};
use std::path::PathBuf;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct SerdeConfig {
#[serde(default)]
pub tendrils: indexmap::IndexMap<String, OneOrMany<TendrilSet>>
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Config {
pub raw_tendrils: Vec<RawTendril>
}
impl From<SerdeConfig> for Config {
fn from(serde_cfg: SerdeConfig) -> Self {
let raw_tendrils = serde_cfg.tendrils.into_iter().map(|(k, v)| {
let remote_specs: Vec<TendrilSet> = v.into();
remote_specs.into_iter().map(move |spec| {
let mode = match (spec.dir_merge, spec.link) {
(true, false) => TendrilMode::CopyMerge,
(false, false) => TendrilMode::CopyOverwrite,
(_, true) => TendrilMode::Link,
};
let local = k.clone();
let profiles = spec.profiles.clone();
spec.remotes.into_iter().map(move |r| -> RawTendril {
RawTendril {
local: local.clone(),
remote: r.clone(),
mode: mode.clone(),
profiles: profiles.clone(),
}
})
}).flatten()
}).flatten().collect();
Config {
raw_tendrils
}
}
}
#[cfg(any(test, feature = "_test_utils"))]
impl From<Config> for SerdeConfig {
fn from(cfg: Config) -> Self {
use indexmap::IndexMap;
let mut tendril_map: IndexMap<String, OneOrMany<TendrilSet>> = indexmap::IndexMap::new();
for raw in cfg.raw_tendrils.into_iter() {
let local = raw.local.clone();
let added_sets: Vec<TendrilSet>;
if tendril_map.contains_key(&local) {
let mut exist_sets: Vec<TendrilSet> = tendril_map.get(&local).unwrap().to_owned().into();
exist_sets.push(raw.into());
added_sets = exist_sets;
}
else {
added_sets = vec![raw.into()];
}
tendril_map.insert(local, added_sets.into());
}
SerdeConfig { tendrils: tendril_map }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct GlobalConfig {
#[serde(rename = "default-repo-path")]
pub default_repo_path: Option<PathBuf>,
#[serde(rename = "default-profiles")]
pub default_profiles: Option<Vec<String>>,
}
impl GlobalConfig {
fn new() -> GlobalConfig {
GlobalConfig {
default_repo_path: None,
default_profiles: None,
}
}
}
pub struct LazyCachedGlobalConfig {
cached_cfg: Option<Result<GlobalConfig, GetConfigError>>
}
impl LazyCachedGlobalConfig {
pub fn new() -> LazyCachedGlobalConfig {
LazyCachedGlobalConfig {
cached_cfg: None,
}
}
pub fn eval(&mut self) -> Result<GlobalConfig, GetConfigError> {
match &self.cached_cfg {
Some(v) => v.clone(),
None => {
let global_cfg = get_global_config();
self.cached_cfg = Some(global_cfg.clone());
global_cfg
},
}
}
#[cfg(test)]
pub fn mock_w_parse_err() -> LazyCachedGlobalConfig {
let mut cfg = Self::new();
cfg.cached_cfg = Some(Err(GetConfigError::ParseError {
cfg_type: ConfigType::Global,
msg: String::from("MOCK VALUE"),
}));
cfg
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct TendrilSet {
#[serde(deserialize_with = "one_or_many_to_vec")]
pub remotes: Vec<String>,
#[serde(rename = "dir-merge")]
#[serde(default)]
pub dir_merge: bool,
#[serde(default)]
pub link: bool,
#[serde(default)]
#[serde(deserialize_with = "one_or_many_to_vec")]
pub profiles: Vec<String>,
}
#[cfg(any(test, feature = "_test_utils"))]
impl From<RawTendril> for TendrilSet {
fn from(raw: RawTendril) -> Self {
let (dir_merge, link) = match raw.mode {
TendrilMode::CopyMerge => (true, false),
TendrilMode::CopyOverwrite => (false, false),
TendrilMode::Link => (false, true),
};
TendrilSet {
remotes: vec![raw.remote],
dir_merge,
link,
profiles: raw.profiles,
}
}
}
#[cfg(any(test, feature = "_test_utils"))]
pub fn serialize_config(cfg: Config) -> String {
let serde_cfg: SerdeConfig = cfg.into();
serde_json::to_string(&serde_cfg).unwrap()
}
fn one_or_many_to_vec<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<String>, D::Error> {
let one_or_many: OneOrMany<String> =
de::Deserialize::deserialize(deserializer)?;
Ok(one_or_many.into())
}
pub(crate) fn get_config(
td_repo: &UniPath,
) -> Result<Config, GetConfigError> {
let config_file_path = td_repo.inner().join(".tendrils/tendrils.json");
let config_file_contents = std::fs::read_to_string(config_file_path)?;
let serde_config = parse_config(&config_file_contents)?;
Ok(serde_config.into())
}
pub(crate) fn get_global_config() -> Result<GlobalConfig, GetConfigError> {
let home_dir = match get_home_dir() {
Some(v) => v,
None => return Ok(GlobalConfig::new()),
};
let config_file_path = PathBuf::from(home_dir).join(".tendrils/global-config.json");
let config_file_contents = match std::fs::read_to_string(config_file_path) {
Ok(v) => v,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(GlobalConfig::new())
}
Err(e) => return Err(
Into::<GetConfigError>::into(e).with_cfg_type(ConfigType::Global)
)
};
match parse_global_config(&config_file_contents) {
Ok(v) => Ok(v),
Err(e) => Err(
Into::<GetConfigError>::into(e).with_cfg_type(ConfigType::Global)
),
}
}
fn parse_config(
json: &str
) -> Result<Config, serde_json::Error> {
match serde_json::from_str::<SerdeConfig>(json) {
Ok(raw) => Ok(raw.into()),
Err(e) => Err(e)
}
}
#[cfg(test)]
pub fn parse_config_expose(
json: &str,
) -> Result<Config, serde_json::Error> {
parse_config(json)
}
fn parse_global_config(
json: &str
) -> Result<GlobalConfig, serde_json::Error> {
serde_json::from_str::<GlobalConfig>(json)
}