1use crate::Result;
4use asimov_env::paths::asimov_root;
5use asimov_module::{models::ModuleManifest, resolve::Resolver};
6use clientele::{Subcommand, SubcommandsProvider, SysexitsError::*};
7use miette::{miette, IntoDiagnostic};
8
9pub(crate) fn build_resolver(pattern: &str) -> miette::Result<Resolver> {
10 let mut resolver = Resolver::new();
11
12 let module_dir_path = asimov_root().join("modules");
13 let module_dir = std::fs::read_dir(&module_dir_path)
14 .map_err(|e| miette!("Failed to read module manifest directory: {e}"))?
15 .filter_map(Result::ok);
16
17 for entry in module_dir {
18 let filename = entry.file_name();
19 let filename = filename.to_string_lossy();
20 if !filename.ends_with(".yml") && !filename.ends_with(".yaml") {
21 continue;
22 }
23 let file = std::fs::File::open(entry.path()).into_diagnostic()?;
24
25 let manifest: ModuleManifest = serde_yml::from_reader(file).map_err(|e| {
26 miette!(
27 "Invalid module manifest at `{}`: {}",
28 entry.path().display(),
29 e
30 )
31 })?;
32
33 if !manifest
34 .provides
35 .programs
36 .iter()
37 .any(|program| program.split('-').next_back().is_some_and(|p| p == pattern))
38 {
39 continue;
40 }
41
42 resolver
43 .insert_manifest(&manifest)
44 .map_err(|e| miette!("{e}"))?;
45 }
46
47 Ok(resolver)
48}
49
50pub fn locate_subcommand(name: &str) -> Result<Subcommand> {
52 let libexec = asimov_root().join("libexec");
53 if libexec.exists() {
54 let file = std::fs::read_dir(libexec)?
55 .filter_map(Result::ok)
56 .find(|entry| {
57 entry.file_name().to_str().is_some_and(|filename| {
58 filename.starts_with("asimov-") && filename.ends_with(name)
59 })
60 });
61 if let Some(entry) = file {
62 return Ok(Subcommand {
63 name: format!("asimov-{}", name),
64 path: entry.path(),
65 });
66 }
67 }
68
69 match SubcommandsProvider::find("asimov-", name) {
70 Some(cmd) => Ok(cmd),
71 None => {
72 eprintln!("{}: command not found: {}{}", "asimov", "asimov-", name);
73 Err(EX_UNAVAILABLE)
74 }
75 }
76}