Skip to main content

asimov_cli/
shared.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::Result;
4use asimov_module::{ModuleManifest, resolve::Module};
5use clientele::{Subcommand, SubcommandsProvider, SysexitsError::*};
6use color_print::{ceprintln, cstr};
7use std::rc::Rc;
8
9/// Locates the given subcommand or prints an error.
10pub fn locate_subcommand(name: &str) -> Result<Subcommand> {
11    match SubcommandsProvider::find("asimov-", name) {
12        Some(cmd) => Ok(cmd),
13        None => {
14            eprintln!("asimov: command not found: asimov-{}", name);
15            Err(EX_UNAVAILABLE)
16        },
17    }
18}
19
20const NO_MODULES_FOUND_HINT: &str = cstr!(
21    r#"<s,dim>hint:</> There appears to be no installed modules.
22<s,dim>hint:</> Modules may be discovered either on the site <u>https://asimov.directory/modules</>
23<s,dim>hint:</> or on the GitHub organization <u>https://github.com/asimov-modules</>
24<s,dim>hint:</> and installed with <s>asimov module install <<module>></>"#
25);
26
27pub async fn installed_modules(
28    registry: &asimov_registry::Registry,
29    filter: Option<&str>,
30) -> Result<Vec<ModuleManifest>> {
31    let modules = registry
32        .installed_modules()
33        .await
34        .map_err(|e| {
35            ceprintln!("<s,r>error:</> unable to access installed modules: {e}");
36            match e {
37                asimov_registry::error::InstalledModulesError::DirIo(_, err)
38                    if err.kind() == std::io::ErrorKind::NotFound =>
39                {
40                    ceprintln!("{NO_MODULES_FOUND_HINT}");
41                },
42                _ => (),
43            }
44            EX_UNAVAILABLE
45        })?
46        .into_iter()
47        .map(|manifest| manifest.manifest)
48        .filter(|manifest| {
49            if let Some(filter) = filter {
50                manifest
51                    .provides
52                    .programs
53                    .iter()
54                    .any(|program| program.split('-').next_back().is_some_and(|p| p == filter))
55            } else {
56                true
57            }
58        })
59        .collect();
60
61    Ok(modules)
62}
63
64pub async fn pick_module(
65    registry: &asimov_registry::Registry,
66    url: impl AsRef<str>,
67    modules: &[Rc<Module>],
68    filter: Option<&str>,
69) -> Result<Rc<Module>> {
70    let url = url.as_ref();
71
72    if let Some(filter) = filter {
73        let module = modules.iter().find(|m| m.name == filter).ok_or_else(|| {
74                ceprintln!("<s,r>error:</> failed to find a module named `{filter}` that supports handling the URL <s>{url}</>");
75                EX_SOFTWARE
76            })?;
77
78        let module_name = module.name.parse().map_err(|e| {
79            ceprintln!("<s,r>error:</> {e}");
80            EX_DATAERR
81        })?;
82
83        if !registry
84            .is_module_enabled(&module_name)
85            .await
86            .map_err(|e| {
87                ceprintln!(
88                    "<s,r>error:</> error while checking whether module <s>{}</> is enabled: {e}",
89                    module.name
90                );
91                EX_IOERR
92            })?
93        {
94            ceprintln!(
95                "<s,r>error:</> module <s>{}</> is not enabled.",
96                module.name
97            );
98            ceprintln!(
99                "<s,dim>hint:</> It can be enabled with: <s>asimov module enable {}</>",
100                module.name
101            );
102            Err(EX_UNAVAILABLE)
103        } else {
104            Ok(module.clone())
105        }
106    } else {
107        let mut iter = modules.iter();
108        loop {
109            let module = iter.next().ok_or_else(|| {
110                    ceprintln!(
111                        "<s,r>error:</> failed to find a module to handle the URL <s>{url}</>"
112                    );
113                    let module_count = modules.len();
114                    if module_count > 0 {
115                        if module_count == 1 {
116                            ceprintln!("<s,dim>hint:</> Found <s>{module_count}</> installed module that could handle this URL but is disabled.");
117                        } else {
118                            ceprintln!("<s,dim>hint:</> Found <s>{module_count}</> installed modules that could handle this URL but are disabled.");
119                        }
120                        ceprintln!("<s,dim>hint:</> A module can be enabled with: <s>asimov module enable <<module>></>");
121                        ceprintln!("<s,dim>hint:</> Available modules:");
122                        for module in modules {
123                            ceprintln!("<s,dim>hint:</>\t<s>{}</>", module.name);
124                        }
125                    }
126                    EX_UNAVAILABLE
127                })?;
128
129            let module_name = module.name.parse().map_err(|e| {
130                ceprintln!("<s,r>error:</> {e}");
131                EX_DATAERR
132            })?;
133
134            if registry
135                .is_module_enabled(&module_name)
136                .await
137                .map_err(|e| {
138                    ceprintln!(
139                        "<s,r>error:</> error while checking whether module <s>{}</> is enabled: {e}",
140                        module.name
141                    );
142                    EX_IOERR
143                })?
144            {
145                return Ok(module.clone());
146            }
147        }
148    }
149}