declint_core/
config_set.rs1use std::path::{Path, PathBuf};
6
7use crate::config::{Config, ConfigError};
8use crate::config::Scope;
9
10pub const CONFIG_FILE: &str = ".declint.yaml";
12
13pub const CONFIG_DIR: &str = ".declint";
16
17pub const LEGACY_CONFIG_FILE: &str = "declint.yaml";
19
20#[derive(Debug, Clone)]
22pub struct NamedConfig {
23 pub path: PathBuf,
25 pub config: Config,
27}
28
29#[derive(Debug, Clone, Default)]
36pub struct ConfigSet {
37 configs: Vec<NamedConfig>,
38}
39
40#[derive(Debug, Clone)]
43pub struct ScopeEntry {
44 pub scope: Scope,
46 pub config: usize,
48 pub local: usize,
50}
51
52impl ConfigSet {
53 pub fn single(config: Config) -> Self {
55 Self {
56 configs: vec![NamedConfig {
57 path: PathBuf::new(),
58 config,
59 }],
60 }
61 }
62
63 pub fn load(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
66 let path = path.as_ref();
67 let shown = path.display().to_string();
68 if path.is_dir() {
69 let mut files: Vec<PathBuf> = std::fs::read_dir(path)
70 .map_err(|e| {
71 ConfigError::new(format!("cannot read config directory: {e}"))
72 .with_path(&shown)
73 })?
74 .filter_map(|entry| entry.ok().map(|e| e.path()))
75 .filter(|p| {
76 matches!(p.extension().and_then(|e| e.to_str()), Some("yaml") | Some("yml"))
77 })
78 .collect();
79 files.sort();
80 if files.is_empty() {
81 return Err(ConfigError::new(format!(
82 "no `.yaml` or `.yml` config files in `{shown}`"
83 )));
84 }
85 let mut configs = Vec::with_capacity(files.len());
86 for file in files {
87 let config = Config::load(&file)?;
88 configs.push(NamedConfig { path: file, config });
89 }
90 return Ok(Self { configs });
91 }
92 let config = Config::load(path)?;
93 Ok(Self {
94 configs: vec![NamedConfig { path: path.to_path_buf(), config }],
95 })
96 }
97
98 pub fn discover(start: &Path) -> Result<Self, ConfigError> {
103 let start = if start.is_dir() {
104 start
105 } else {
106 start.parent().unwrap_or(Path::new("."))
107 };
108 for dir in start.ancestors() {
109 for candidate in [dir.join(CONFIG_FILE), dir.join(CONFIG_DIR)] {
110 if candidate.is_file() || candidate.is_dir() {
111 return Self::load(&candidate);
112 }
113 }
114 let legacy = dir.join(LEGACY_CONFIG_FILE);
115 if legacy.is_file() {
116 return Self::load(&legacy);
117 }
118 }
119 Err(ConfigError::new(format!(
120 "no declint config found (looked for {CONFIG_FILE}, {CONFIG_DIR}/, \
121 {LEGACY_CONFIG_FILE} in `{}` and its parents)",
122 start.display()
123 )))
124 }
125
126 pub fn configs(&self) -> &[NamedConfig] {
128 &self.configs
129 }
130
131 pub fn scope_table(&self) -> Vec<ScopeEntry> {
135 let mut out = Vec::new();
136 for (config_index, named) in self.configs.iter().enumerate() {
137 for (local, scope) in named.config.scopes.iter().enumerate() {
138 out.push(ScopeEntry {
139 scope: scope.clone(),
140 config: config_index,
141 local,
142 });
143 }
144 }
145 out
146 }
147}