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