asimov_cli/
shared.rs

1// This is free and unencumbered software released into the public domain.
2
3use 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
50/// Locates the given subcommand or prints an error.
51pub 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}