1use crate::level::Level;
2use crate::pipeline::{ConditionalPipelines, parse_conditional_pipeline};
3use std::collections::{HashMap, HashSet};
4use std::env;
5use std::fs;
6use std::path::PathBuf;
7
8#[derive(Debug)]
10pub struct RunfConfig {
11 pub level: Level,
12 pub disabled: HashSet<String>,
13 pub allowed: Option<HashSet<String>>,
15 pub data_dir: PathBuf,
16 pub plugin_dir: PathBuf,
17 pub home_dir: PathBuf,
18 pub pipelines: HashMap<String, ConditionalPipelines>,
21}
22
23impl RunfConfig {
24 pub fn resolve() -> Self {
26 let home_dir = env::var("LOWFAT_HOME")
27 .map(PathBuf::from)
28 .unwrap_or_else(|_| {
29 dirs_home().join(".lowfat")
30 });
31
32 let data_dir = env::var("LOWFAT_DATA")
33 .map(PathBuf::from)
34 .unwrap_or_else(|_| {
35 env::var("XDG_DATA_HOME")
36 .map(PathBuf::from)
37 .unwrap_or_else(|_| dirs_home().join(".local/share"))
38 .join("lowfat")
39 });
40
41 let plugin_dir = home_dir.join("plugins");
42
43 let mut level = Level::Full;
45 let mut disabled = HashSet::new();
46 let mut allowed: Option<HashSet<String>> = None;
47 let mut pipeline_lines: HashMap<String, Vec<(String, String)>> = HashMap::new();
50 let mut pipelines = HashMap::new();
51
52 if let Some(config_path) = find_config() {
54 if let Ok(content) = fs::read_to_string(&config_path) {
55 for line in content.lines() {
56 let line = line.trim();
57 if line.is_empty() || line.starts_with('#') {
58 continue;
59 }
60 if let Some(val) = line.strip_prefix("level=") {
61 if let Ok(l) = val.parse() {
62 level = l;
63 }
64 } else if let Some(val) = line.strip_prefix("filters=") {
65 allowed = Some(
66 val.split(',').map(|s| s.trim().to_string()).collect(),
67 );
68 } else if let Some(val) = line.strip_prefix("disable=") {
69 for name in val.split(',') {
70 disabled.insert(name.trim().to_string());
71 }
72 } else if let Some(rest) = line.strip_prefix("pipeline.") {
73 if let Some((key, spec)) = rest.split_once('=') {
77 let key = key.trim();
78 let spec = spec.trim().to_string();
79 let (cmd, condition) = match key.split_once('.') {
81 Some((c, cond)) => (c.to_string(), cond.to_string()),
82 None => (key.to_string(), String::new()),
83 };
84 pipeline_lines
85 .entry(cmd)
86 .or_default()
87 .push((condition, spec));
88 }
89 }
90 }
91 }
92 }
93
94 for (cmd, lines) in pipeline_lines {
96 pipelines.insert(cmd, parse_conditional_pipeline(&lines));
97 }
98
99 if let Ok(val) = env::var("LOWFAT_DISABLE") {
101 for name in val.split(',') {
102 disabled.insert(name.trim().to_string());
103 }
104 }
105
106 if let Ok(val) = env::var("LOWFAT_LEVEL") {
108 if let Ok(l) = val.parse() {
109 level = l;
110 }
111 }
112
113 RunfConfig {
114 level,
115 disabled,
116 allowed,
117 data_dir,
118 plugin_dir,
119 home_dir,
120 pipelines,
121 }
122 }
123
124 pub fn pipeline_for(&self, cmd: &str) -> Option<&ConditionalPipelines> {
126 self.pipelines.get(cmd)
127 }
128
129 pub fn is_enabled(&self, name: &str) -> bool {
131 if self.disabled.contains(name) {
132 return false;
133 }
134 if let Some(ref allowed) = self.allowed {
135 return allowed.contains(name);
136 }
137 true
138 }
139}
140
141pub fn find_config() -> Option<PathBuf> {
143 let mut dir = env::current_dir().ok()?;
144 loop {
145 let candidate = dir.join(".lowfat");
146 if candidate.is_file() {
147 return Some(candidate);
148 }
149 if !dir.pop() {
150 return None;
151 }
152 }
153}
154
155fn dirs_home() -> PathBuf {
156 env::var("HOME")
157 .map(PathBuf::from)
158 .unwrap_or_else(|_| PathBuf::from("/tmp"))
159}
160
161pub fn find_config_display() -> Option<PathBuf> {
163 find_config()
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn is_enabled_default() {
172 let config = RunfConfig {
173 level: Level::Full,
174 disabled: HashSet::new(),
175 allowed: None,
176 data_dir: PathBuf::new(),
177 plugin_dir: PathBuf::new(),
178 home_dir: PathBuf::new(),
179 pipelines: HashMap::new(),
180 };
181 assert!(config.is_enabled("git"));
182 assert!(config.is_enabled("docker"));
183 }
184
185 #[test]
186 fn is_enabled_disabled() {
187 let mut disabled = HashSet::new();
188 disabled.insert("npm".to_string());
189 let config = RunfConfig {
190 level: Level::Full,
191 disabled,
192 allowed: None,
193 data_dir: PathBuf::new(),
194 plugin_dir: PathBuf::new(),
195 home_dir: PathBuf::new(),
196 pipelines: HashMap::new(),
197 };
198 assert!(!config.is_enabled("npm"));
199 assert!(config.is_enabled("git"));
200 }
201
202 #[test]
203 fn is_enabled_whitelist() {
204 let mut allowed = HashSet::new();
205 allowed.insert("git".to_string());
206 allowed.insert("docker".to_string());
207 let config = RunfConfig {
208 level: Level::Full,
209 disabled: HashSet::new(),
210 allowed: Some(allowed),
211 data_dir: PathBuf::new(),
212 plugin_dir: PathBuf::new(),
213 home_dir: PathBuf::new(),
214 pipelines: HashMap::new(),
215 };
216 assert!(config.is_enabled("git"));
217 assert!(!config.is_enabled("npm"));
218 }
219}