use std::path::{Path, PathBuf};
use crate::config::{Config, ConfigError};
use crate::config::Scope;
pub const CONFIG_FILE: &str = ".declint.yaml";
pub const CONFIG_DIR: &str = ".declint";
pub const LEGACY_CONFIG_FILE: &str = "declint.yaml";
#[derive(Debug, Clone)]
pub struct NamedConfig {
pub path: PathBuf,
pub config: Config,
}
#[derive(Debug, Clone, Default)]
pub struct ConfigSet {
configs: Vec<NamedConfig>,
}
#[derive(Debug, Clone)]
pub struct ScopeEntry {
pub scope: Scope,
pub config: usize,
pub local: usize,
}
impl ConfigSet {
pub fn single(config: Config) -> Self {
Self {
configs: vec![NamedConfig {
path: PathBuf::new(),
config,
}],
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
let path = path.as_ref();
let shown = path.display().to_string();
if path.is_dir() {
let mut files: Vec<PathBuf> = std::fs::read_dir(path)
.map_err(|e| {
ConfigError::new(format!("cannot read config directory: {e}"))
.with_path(&shown)
})?
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|p| {
matches!(p.extension().and_then(|e| e.to_str()), Some("yaml") | Some("yml"))
})
.collect();
files.sort();
if files.is_empty() {
return Err(ConfigError::new(format!(
"no `.yaml` or `.yml` config files in `{shown}`"
)));
}
let mut configs = Vec::with_capacity(files.len());
for file in files {
let config = Config::load(&file)?;
configs.push(NamedConfig { path: file, config });
}
return Ok(Self { configs });
}
let config = Config::load(path)?;
Ok(Self {
configs: vec![NamedConfig { path: path.to_path_buf(), config }],
})
}
pub fn discover(start: &Path) -> Result<Self, ConfigError> {
let start = if start.is_dir() {
start
} else {
start.parent().unwrap_or(Path::new("."))
};
for dir in start.ancestors() {
for candidate in [dir.join(CONFIG_FILE), dir.join(CONFIG_DIR)] {
if candidate.is_file() || candidate.is_dir() {
return Self::load(&candidate);
}
}
let legacy = dir.join(LEGACY_CONFIG_FILE);
if legacy.is_file() {
return Self::load(&legacy);
}
}
Err(ConfigError::new(format!(
"no declint config found (looked for {CONFIG_FILE}, {CONFIG_DIR}/, \
{LEGACY_CONFIG_FILE} in `{}` and its parents)",
start.display()
)))
}
pub fn configs(&self) -> &[NamedConfig] {
&self.configs
}
pub fn scope_table(&self) -> Vec<ScopeEntry> {
let mut out = Vec::new();
for (config_index, named) in self.configs.iter().enumerate() {
for (local, scope) in named.config.scopes.iter().enumerate() {
out.push(ScopeEntry {
scope: scope.clone(),
config: config_index,
local,
});
}
}
out
}
}