use std::collections::HashMap;
use std::path::{Path, PathBuf};
use super::utils::{find_file_recursively, get_from_env, get_from_env_or_exit};
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Config {
pub link: LinkConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
link: LinkConfig::default(),
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct LinkConfig {
pub source: String,
pub destinations: Vec<String>,
}
impl Default for LinkConfig {
fn default() -> Self {
Self {
source: "".to_string(),
destinations: vec![],
}
}
}
impl LinkConfig {
pub fn expand_destinations(&self) -> Vec<PathBuf> {
self.destinations
.iter()
.map(|destination| {
if destination.starts_with('$') {
PathBuf::from(get_from_env_or_exit(&destination[1..]))
} else {
PathBuf::from(destination.as_str())
}
})
.collect()
}
pub fn expand_source(&self) -> PathBuf {
if self.source.starts_with('$') {
PathBuf::from(get_from_env_or_exit(&self.source[1..]))
} else {
PathBuf::from(self.source.as_str())
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct LinkDestinationConfig {
pub systems: HashMap<String, System>,
}
impl Default for LinkDestinationConfig {
fn default() -> Self {
Self {
systems: HashMap::new(),
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct System {
pub destination: Option<String>,
pub destinations: Option<Vec<String>>,
pub dumper: String,
pub extension: Option<String>,
pub extensions: Option<Vec<String>>,
pub extra_path: Option<String>,
}
impl Default for System {
fn default() -> Self {
Self {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
}
}
}
impl LinkDestinationConfig {
pub fn get_system_names(&self) -> Vec<String> {
self.systems.keys().map(|k| k.clone()).collect()
}
}
impl System {
pub fn get_destinations(&self, system: &str) -> Vec<String> {
if let Some(ref destinations) = self.destinations {
destinations.clone()
} else if let Some(ref destination) = self.destination {
vec![destination.clone()]
} else {
vec![system.to_string()]
}
}
pub fn get_extensions(&self, system: &str) -> Vec<String> {
if let Some(ref extensions) = self.extensions {
extensions.clone()
} else if let Some(ref extension) = self.extension {
vec![extension.clone()]
} else {
vec![system.to_string()]
}
}
}
pub fn load_config_recursively<T: serde::Serialize + serde::de::DeserializeOwned + Default>(
root: &Path,
) -> Result<T, String> {
let path = find_file_recursively(root, "retro.toml")?
.ok_or_else(|| "Failed to find retro.toml file".to_string())?;
let path_display = path.display();
confy::load_path(&path)
.map_err(|e| format!("Failed to load config from {}: {}", path_display, e))
}
pub fn load_global_config() -> Result<Config, String> {
let config: Config = if let Ok(path) = get_from_env("RETRO_CONFIG") {
let path_display = path.clone();
confy::load_path(PathBuf::from(path))
.map_err(|e| format!("Failed to load config from {}: {}", path_display, e))?
} else {
confy::load("retro", "retro").map_err(|e| format!("Failed to load global config: {}", e))?
};
Ok(config)
}
pub fn load_link_destination_config(
config_file: Option<PathBuf>,
) -> Result<LinkDestinationConfig, String> {
let config_path = config_file.unwrap_or(PathBuf::from("retro.toml"));
let config: LinkDestinationConfig = confy::load_path(&config_path).map_err(|e| {
format!(
"Failed to load config from {}: {}",
config_path.display(),
e
)
})?;
Ok(config)
}
#[cfg(test)]
mod tests {
use std::env::set_var;
use super::*;
#[test]
fn expand_destinations_with_enviroment_variable() {
set_var("TEST_EXPAND_DESTINATIONS_WITH_ENVIRONMENT_VARIABLE", "a");
let config = LinkConfig {
source: "$TEST_EXPAND_DESTINATIONS_WITH_ENVIRONMENT_VARIABLE".to_string(),
destinations: vec![],
};
let source = config.expand_source();
assert_eq!(source, PathBuf::from("a"));
}
#[test]
fn expand_destinations_without_environment_variables() {
let config = LinkConfig {
source: "".to_string(),
destinations: vec!["a".to_string(), "b".to_string()],
};
let destinations = config.expand_destinations();
assert_eq!(destinations, vec![PathBuf::from("a"), PathBuf::from("b")]);
}
#[test]
fn expand_source_with_enviroment_variable() {
set_var("TEST_EXPAND_SOURCE_WITH_ENVIROMENT_VARIABLE_1", "a");
set_var("TEST_EXPAND_SOURCE_WITH_ENVIROMENT_VARIABLE_2", "b");
let config = LinkConfig {
source: "".to_string(),
destinations: vec![
"$TEST_EXPAND_SOURCE_WITH_ENVIROMENT_VARIABLE_1".to_string(),
"$TEST_EXPAND_SOURCE_WITH_ENVIROMENT_VARIABLE_2".to_string(),
],
};
let destinations = config.expand_destinations();
assert_eq!(destinations, vec![PathBuf::from("a"), PathBuf::from("b")]);
}
#[test]
fn expand_source_without_environment_variable() {
let config = LinkConfig {
source: "a".to_string(),
destinations: vec![],
};
let source = config.expand_source();
assert_eq!(source, PathBuf::from("a"));
}
#[test]
fn link_destination_config_get_system_names() {
let system1 = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
let system2 = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
let config = LinkDestinationConfig {
systems: HashMap::from([
("system1".to_string(), system1),
("system2".to_string(), system2),
]),
};
let systems = config.get_system_names();
assert_eq!(systems.len(), 2);
assert!(systems.contains(&"system1".to_string()));
assert!(systems.contains(&"system2".to_string()));
}
#[test]
fn system_get_destinations_uses_destinations_first() {
let destinations = &["b".to_string(), "c".to_string()];
let system = System {
destination: Some("a".to_string()),
destinations: Some(destinations.to_vec()),
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
assert_eq!(system.get_destinations(&"".to_string()), destinations);
}
#[test]
fn system_get_destinations_uses_destination_second() {
let system = System {
destination: Some("a".to_string()),
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
assert_eq!(system.get_destinations(&"".to_string()), &["a".to_string()]);
}
#[test]
fn system_get_destinations_uses_system_last() {
let system = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
assert_eq!(system.get_destinations(&"abc".to_string()), &["abc"]);
}
#[test]
fn system_get_extensions_uses_extensions_first() {
let extensions = &["b".to_string(), "c".to_string()];
let system = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: Some("a".to_string()),
extensions: Some(extensions.to_vec()),
extra_path: None,
};
assert_eq!(system.get_extensions(&"".to_string()), extensions);
}
#[test]
fn system_get_extensions_uses_extension_second() {
let system = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: Some("a".to_string()),
extensions: None,
extra_path: None,
};
assert_eq!(system.get_extensions(&"".to_string()), &["a".to_string()]);
}
#[test]
fn system_get_extensions_uses_system_last() {
let system = System {
destination: None,
destinations: None,
dumper: "".to_string(),
extension: None,
extensions: None,
extra_path: None,
};
assert_eq!(system.get_extensions(&"abc".to_string()), &["abc"]);
}
}