alef_core/config/resolved/
lookups.rs1use std::collections::HashMap;
4
5use super::ResolvedCrateConfig;
6use crate::config::extras::Language;
7use crate::config::output::{BuildCommandConfig, CleanConfig, LintConfig, SetupConfig, TestConfig, UpdateConfig};
8use crate::config::tools::LangContext;
9use crate::config::{build_defaults, clean_defaults, lint_defaults, setup_defaults, test_defaults, update_defaults};
10
11impl ResolvedCrateConfig {
12 pub fn package_dir(&self, lang: Language) -> String {
15 let override_path = match lang {
16 Language::Python => self.python.as_ref().and_then(|c| c.scaffold_output.as_ref()),
17 Language::Node => self.node.as_ref().and_then(|c| c.scaffold_output.as_ref()),
18 Language::Ruby => self.ruby.as_ref().and_then(|c| c.scaffold_output.as_ref()),
19 Language::Php => self.php.as_ref().and_then(|c| c.scaffold_output.as_ref()),
20 Language::Elixir => self.elixir.as_ref().and_then(|c| c.scaffold_output.as_ref()),
21 _ => None,
22 };
23 if let Some(p) = override_path {
24 p.to_string_lossy().to_string()
25 } else {
26 match lang {
27 Language::Python => "packages/python".to_string(),
28 Language::Node => "packages/node".to_string(),
29 Language::Ruby => "packages/ruby".to_string(),
30 Language::Php => "packages/php".to_string(),
31 Language::Elixir => "packages/elixir".to_string(),
32 _ => format!("packages/{lang}"),
33 }
34 }
35 }
36
37 pub fn run_wrapper_for_language(&self, lang: Language) -> Option<&str> {
39 match lang {
40 Language::Python => self.python.as_ref().and_then(|c| c.run_wrapper.as_deref()),
41 Language::Node => self.node.as_ref().and_then(|c| c.run_wrapper.as_deref()),
42 Language::Ruby => self.ruby.as_ref().and_then(|c| c.run_wrapper.as_deref()),
43 Language::Php => self.php.as_ref().and_then(|c| c.run_wrapper.as_deref()),
44 Language::Elixir => self.elixir.as_ref().and_then(|c| c.run_wrapper.as_deref()),
45 Language::Wasm => self.wasm.as_ref().and_then(|c| c.run_wrapper.as_deref()),
46 Language::Go => self.go.as_ref().and_then(|c| c.run_wrapper.as_deref()),
47 Language::Java => self.java.as_ref().and_then(|c| c.run_wrapper.as_deref()),
48 Language::Csharp => self.csharp.as_ref().and_then(|c| c.run_wrapper.as_deref()),
49 Language::R => self.r.as_ref().and_then(|c| c.run_wrapper.as_deref()),
50 Language::Kotlin => self.kotlin.as_ref().and_then(|c| c.run_wrapper.as_deref()),
51 Language::Dart => self.dart.as_ref().and_then(|c| c.run_wrapper.as_deref()),
52 Language::Swift => self.swift.as_ref().and_then(|c| c.run_wrapper.as_deref()),
53 Language::Zig => self.zig.as_ref().and_then(|c| c.run_wrapper.as_deref()),
54 Language::Ffi | Language::Rust | Language::C => None,
55 }
56 }
57
58 pub fn extra_lint_paths_for_language(&self, lang: Language) -> &[String] {
60 match lang {
61 Language::Python => self
62 .python
63 .as_ref()
64 .map(|c| c.extra_lint_paths.as_slice())
65 .unwrap_or(&[]),
66 Language::Node => self.node.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
67 Language::Ruby => self.ruby.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
68 Language::Php => self.php.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
69 Language::Elixir => self
70 .elixir
71 .as_ref()
72 .map(|c| c.extra_lint_paths.as_slice())
73 .unwrap_or(&[]),
74 Language::Wasm => self.wasm.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
75 Language::Go => self.go.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
76 Language::Java => self.java.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
77 Language::Csharp => self
78 .csharp
79 .as_ref()
80 .map(|c| c.extra_lint_paths.as_slice())
81 .unwrap_or(&[]),
82 Language::R => self.r.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
83 Language::Kotlin => self
84 .kotlin
85 .as_ref()
86 .map(|c| c.extra_lint_paths.as_slice())
87 .unwrap_or(&[]),
88 Language::Dart => self.dart.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
89 Language::Swift => self
90 .swift
91 .as_ref()
92 .map(|c| c.extra_lint_paths.as_slice())
93 .unwrap_or(&[]),
94 Language::Zig => self.zig.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
95 Language::Ffi | Language::Rust | Language::C => &[],
96 }
97 }
98
99 pub fn project_file_for_language(&self, lang: Language) -> Option<&str> {
101 match lang {
102 Language::Java => self.java.as_ref().and_then(|c| c.project_file.as_deref()),
103 Language::Csharp => self.csharp.as_ref().and_then(|c| c.project_file.as_deref()),
104 _ => None,
105 }
106 }
107
108 pub fn lint_config_for_language(&self, lang: Language) -> LintConfig {
110 let lang_str = lang.to_string();
111 if let Some(explicit) = self.lint.get(&lang_str) {
112 return explicit.clone();
113 }
114 let output_dir = self.package_dir(lang);
115 let run_wrapper = self.run_wrapper_for_language(lang);
116 let extra_lint_paths = self.extra_lint_paths_for_language(lang);
117 let project_file = self.project_file_for_language(lang);
118 let ctx = LangContext {
119 tools: &self.tools,
120 run_wrapper,
121 extra_lint_paths,
122 project_file,
123 };
124 lint_defaults::default_lint_config(lang, &output_dir, &ctx)
125 }
126
127 pub fn update_config_for_language(&self, lang: Language) -> UpdateConfig {
129 let lang_str = lang.to_string();
130 if let Some(explicit) = self.update.get(&lang_str) {
131 return explicit.clone();
132 }
133 let output_dir = self.package_dir(lang);
134 let ctx = LangContext {
135 tools: &self.tools,
136 run_wrapper: None,
137 extra_lint_paths: &[],
138 project_file: None,
139 };
140 update_defaults::default_update_config(lang, &output_dir, &ctx)
141 }
142
143 pub fn test_config_for_language(&self, lang: Language) -> TestConfig {
145 let lang_str = lang.to_string();
146 if let Some(explicit) = self.test.get(&lang_str) {
147 return explicit.clone();
148 }
149 let output_dir = self.package_dir(lang);
150 let run_wrapper = self.run_wrapper_for_language(lang);
151 let project_file = self.project_file_for_language(lang);
152 let ctx = LangContext {
153 tools: &self.tools,
154 run_wrapper,
155 extra_lint_paths: &[],
156 project_file,
157 };
158 test_defaults::default_test_config(lang, &output_dir, &ctx)
159 }
160
161 pub fn setup_config_for_language(&self, lang: Language) -> SetupConfig {
163 let lang_str = lang.to_string();
164 if let Some(explicit) = self.setup.get(&lang_str) {
165 return explicit.clone();
166 }
167 let output_dir = self.package_dir(lang);
168 let ctx = LangContext {
169 tools: &self.tools,
170 run_wrapper: None,
171 extra_lint_paths: &[],
172 project_file: None,
173 };
174 setup_defaults::default_setup_config(lang, &output_dir, &ctx)
175 }
176
177 pub fn clean_config_for_language(&self, lang: Language) -> CleanConfig {
179 let lang_str = lang.to_string();
180 if let Some(explicit) = self.clean.get(&lang_str) {
181 return explicit.clone();
182 }
183 let output_dir = self.package_dir(lang);
184 let ctx = LangContext {
185 tools: &self.tools,
186 run_wrapper: None,
187 extra_lint_paths: &[],
188 project_file: None,
189 };
190 clean_defaults::default_clean_config(lang, &output_dir, &ctx)
191 }
192
193 pub fn build_command_config_for_language(&self, lang: Language) -> BuildCommandConfig {
195 let lang_str = lang.to_string();
196 if let Some(explicit) = self.build_commands.get(&lang_str) {
197 return explicit.clone();
198 }
199 let output_dir = self.package_dir(lang);
200 let run_wrapper = self.run_wrapper_for_language(lang);
201 let project_file = self.project_file_for_language(lang);
202 let ctx = LangContext {
203 tools: &self.tools,
204 run_wrapper,
205 extra_lint_paths: &[],
206 project_file,
207 };
208 build_defaults::default_build_config(lang, &output_dir, &self.name, &ctx)
209 }
210
211 pub fn features_for_language(&self, lang: Language) -> &[String] {
213 let override_features = match lang {
214 Language::Python => self.python.as_ref().and_then(|c| c.features.as_deref()),
215 Language::Node => self.node.as_ref().and_then(|c| c.features.as_deref()),
216 Language::Ruby => self.ruby.as_ref().and_then(|c| c.features.as_deref()),
217 Language::Php => self.php.as_ref().and_then(|c| c.features.as_deref()),
218 Language::Elixir => self.elixir.as_ref().and_then(|c| c.features.as_deref()),
219 Language::Wasm => self.wasm.as_ref().and_then(|c| c.features.as_deref()),
220 Language::Ffi => self.ffi.as_ref().and_then(|c| c.features.as_deref()),
221 Language::Go => self.go.as_ref().and_then(|c| c.features.as_deref()),
222 Language::Java => self.java.as_ref().and_then(|c| c.features.as_deref()),
223 Language::Kotlin => self.kotlin.as_ref().and_then(|c| c.features.as_deref()),
224 Language::Csharp => self.csharp.as_ref().and_then(|c| c.features.as_deref()),
225 Language::R => self.r.as_ref().and_then(|c| c.features.as_deref()),
226 Language::Zig => self.zig.as_ref().and_then(|c| c.features.as_deref()),
227 Language::Dart => self.dart.as_ref().and_then(|c| c.features.as_deref()),
228 Language::Swift => self.swift.as_ref().and_then(|c| c.features.as_deref()),
229 Language::Rust | Language::C => None,
230 };
231 override_features.unwrap_or(&self.features)
232 }
233
234 pub fn extra_deps_for_language(&self, lang: Language) -> HashMap<String, toml::Value> {
236 let mut deps = self.extra_dependencies.clone();
237 let lang_deps = match lang {
238 Language::Python => self.python.as_ref().map(|c| &c.extra_dependencies),
239 Language::Node => self.node.as_ref().map(|c| &c.extra_dependencies),
240 Language::Ruby => self.ruby.as_ref().map(|c| &c.extra_dependencies),
241 Language::Php => self.php.as_ref().map(|c| &c.extra_dependencies),
242 Language::Elixir => self.elixir.as_ref().map(|c| &c.extra_dependencies),
243 Language::Wasm => self.wasm.as_ref().map(|c| &c.extra_dependencies),
244 _ => None,
245 };
246 if let Some(lang_deps) = lang_deps {
247 deps.extend(lang_deps.iter().map(|(k, v)| (k.clone(), v.clone())));
248 }
249 let exclude: &[String] = match lang {
250 Language::Wasm => self
251 .wasm
252 .as_ref()
253 .map(|c| c.exclude_extra_dependencies.as_slice())
254 .unwrap_or(&[]),
255 Language::Dart => self
256 .dart
257 .as_ref()
258 .map(|c| c.exclude_extra_dependencies.as_slice())
259 .unwrap_or(&[]),
260 Language::Swift => self
261 .swift
262 .as_ref()
263 .map(|c| c.exclude_extra_dependencies.as_slice())
264 .unwrap_or(&[]),
265 _ => &[],
266 };
267 for key in exclude {
268 deps.remove(key);
269 }
270 deps
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::config::extras::Language;
277 use crate::config::new_config::NewAlefConfig;
278
279 fn resolved_one(toml: &str) -> super::super::ResolvedCrateConfig {
280 let cfg: NewAlefConfig = toml::from_str(toml).unwrap();
281 cfg.resolve().unwrap().remove(0)
282 }
283
284 fn minimal() -> super::super::ResolvedCrateConfig {
285 resolved_one(
286 r#"
287[workspace]
288languages = ["python", "node"]
289
290[[crates]]
291name = "test-lib"
292sources = ["src/lib.rs"]
293"#,
294 )
295 }
296
297 #[test]
298 fn resolved_lint_config_inherits_workspace_when_crate_unset() {
299 let r = resolved_one(
300 r#"
301[workspace]
302languages = ["python"]
303
304[workspace.lint.python]
305check = "ruff check ."
306
307[[crates]]
308name = "test-lib"
309sources = ["src/lib.rs"]
310"#,
311 );
312 let lint = r.lint_config_for_language(Language::Python);
313 assert_eq!(lint.check.unwrap().commands(), vec!["ruff check ."]);
314 }
315
316 #[test]
317 fn resolved_lint_config_crate_overrides_workspace_field_wholesale() {
318 let r = resolved_one(
319 r#"
320[workspace]
321languages = ["python"]
322
323[workspace.lint.python]
324check = "ruff check ."
325
326[[crates]]
327name = "test-lib"
328sources = ["src/lib.rs"]
329
330[crates.lint.python]
331check = "ruff check crates/test-lib-py/"
332"#,
333 );
334 let lint = r.lint_config_for_language(Language::Python);
335 assert_eq!(lint.check.unwrap().commands(), vec!["ruff check crates/test-lib-py/"]);
336 }
337
338 #[test]
339 fn resolved_features_per_language_overrides_crate_default() {
340 let r = resolved_one(
341 r#"
342[workspace]
343languages = ["python"]
344
345[[crates]]
346name = "test-lib"
347sources = ["src/lib.rs"]
348features = ["base"]
349
350[crates.python]
351features = ["python-extra"]
352"#,
353 );
354 assert_eq!(r.features_for_language(Language::Python), &["python-extra"]);
355 assert_eq!(r.features_for_language(Language::Node), &["base"]);
356 }
357
358 #[test]
359 fn resolved_extra_deps_crate_value_wins_on_key_collision() {
360 let r = resolved_one(
361 r#"
362[workspace]
363languages = ["python"]
364
365[[crates]]
366name = "test-lib"
367sources = ["src/lib.rs"]
368
369[crates.extra_dependencies]
370tokio = "1"
371
372[crates.python]
373extra_dependencies = { tokio = "2" }
374"#,
375 );
376 let deps = r.extra_deps_for_language(Language::Python);
377 let tokio = deps.get("tokio").unwrap().as_str().unwrap();
378 assert_eq!(tokio, "2", "per-language extra_dep should win on collision");
379 }
380
381 #[test]
382 fn resolved_extra_deps_excludes_apply_after_merge() {
383 let r = resolved_one(
384 r#"
385[workspace]
386languages = ["wasm"]
387
388[[crates]]
389name = "test-lib"
390sources = ["src/lib.rs"]
391
392[crates.extra_dependencies]
393tokio = "1"
394serde = "1"
395
396[crates.wasm]
397exclude_extra_dependencies = ["tokio"]
398"#,
399 );
400 let deps = r.extra_deps_for_language(Language::Wasm);
401 assert!(!deps.contains_key("tokio"), "excluded dep should be absent");
402 assert!(deps.contains_key("serde"), "non-excluded dep should be present");
403 }
404
405 #[test]
406 fn package_dir_defaults_are_correct() {
407 let r = minimal();
408 assert_eq!(r.package_dir(Language::Python), "packages/python");
409 assert_eq!(r.package_dir(Language::Node), "packages/node");
410 assert_eq!(r.package_dir(Language::Ruby), "packages/ruby");
411 assert_eq!(r.package_dir(Language::Go), "packages/go");
412 assert_eq!(r.package_dir(Language::Java), "packages/java");
413 }
414}