use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ProjectConfig {
pub plugin: BTreeMap<String, String>,
pub scripts: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: toml::Table,
}
impl ProjectConfig {
pub fn load_from(path: &Path) -> Result<Option<Self>> {
match std::fs::read_to_string(path) {
Ok(text) => {
let cfg: Self = toml::from_str(&text).with_context(|| {
format!("failed to parse the project config: {}", path.display())
})?;
Ok(Some(cfg))
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(e)
.with_context(|| format!("failed to read the project config: {}", path.display())),
}
}
pub fn overlay(&mut self, other: ProjectConfig) {
for (k, v) in other.extra {
self.extra.insert(k, v);
}
self.plugin.extend(other.plugin);
self.scripts.extend(other.scripts);
}
}
#[derive(Debug, Clone, Default)]
pub struct MergedProjectConfig {
pub plugin: BTreeMap<String, String>,
#[allow(dead_code)]
pub scripts: BTreeMap<String, String>,
pub sources: Vec<PathBuf>,
}
impl MergedProjectConfig {
pub fn from_paths_near_to_far(paths: &[PathBuf]) -> Result<Self> {
let mut merged = ProjectConfig::default();
let mut found = Vec::new();
for path in paths.iter().rev() {
if let Some(cfg) = ProjectConfig::load_from(path)? {
merged.overlay(cfg);
found.push(path.clone());
}
}
found.reverse();
Ok(Self {
plugin: merged.plugin,
scripts: merged.scripts,
sources: found,
})
}
pub fn pinned_plugin(&self, family: &str) -> Option<&str> {
self.plugin.get(family).map(String::as_str)
}
pub fn pinned_families(&self) -> Vec<&str> {
self.plugin.keys().map(String::as_str).collect()
}
}