Skip to main content

asimov_cli/commands/module/
inspect.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::{BoxError, StandardOptions, SysexitsError::*};
4use asimov_module::ModuleName;
5use color_print::{ceprintln, cprintln};
6
7pub async fn inspect(
8    module_name: ModuleName,
9    output: String,
10    _flags: &StandardOptions,
11) -> Result<(), BoxError> {
12    let registry = asimov_registry::Registry::default();
13
14    let installed = registry.read_manifest(&module_name).await.map_err(|e| {
15        tracing::error!("failed to read manifest for module `{module_name}`: {e}");
16        if let asimov_registry::error::ManifestError::NotInstalled = e {
17            ceprintln!(
18                "<s,dim>hint:</> Check if the module is installed with: <s>asimov module list</>"
19            );
20        }
21        EX_UNAVAILABLE
22    })?;
23
24    let is_enabled = registry
25        .is_module_enabled(&module_name)
26        .await
27        .map_err(|e| {
28            tracing::error!("failed to check if module is enabled: {e}");
29            EX_UNAVAILABLE
30        })?;
31
32    let manifest = &installed.manifest;
33
34    let conf_vars = manifest
35        .config
36        .as_ref()
37        .map(|c| c.variables.as_slice())
38        .unwrap_or_default();
39
40    let conf_status: Vec<bool> = conf_vars
41        .iter()
42        .map(|var| match manifest.variable(&var.name, Some("default")) {
43            Ok(_) => true,
44            Err(asimov_module::ReadVarError::UnconfiguredVar(_)) => false,
45            Err(e) => {
46                tracing::warn!("failed to read configuration variable `{}`: {e}", var.name);
47                false
48            },
49        })
50        .collect();
51
52    match output.as_str() {
53        "json" => {
54            let config: Vec<serde_json::Value> = conf_vars
55                .iter()
56                .zip(&conf_status)
57                .map(|(var, is_set)| {
58                    serde_json::json!({
59                        "name": var.name,
60                        "description": var.description,
61                        "default": var.default_value.as_deref().filter(|_| !var.secret),
62                        "secret": var.secret,
63                        "required": var.is_required(),
64                        "set": is_set,
65                    })
66                })
67                .collect();
68
69            println!(
70                "{}",
71                serde_json::to_string_pretty(&serde_json::json!({
72                    "manifest": serde_json::to_value(&installed)?,
73                    "enabled": is_enabled,
74                    "config": config,
75                }))?
76            );
77        },
78        _ => {
79            if is_enabled {
80                cprintln!("<s,g>✓</> <s>{}</> (enabled)", manifest.name);
81            } else {
82                cprintln!("<s,r>✗</> <s>{}</> (disabled)", manifest.name);
83            }
84
85            if let Some(label) = &manifest.label {
86                cprintln!("<s>Label:</> {label}");
87            }
88            if let Some(title) = &manifest.title {
89                cprintln!("<s>Title:</> {title}");
90            }
91            if let Some(summary) = &manifest.summary {
92                cprintln!("<s>Summary:</> {summary}");
93            }
94            if let Some(version) = &installed.version {
95                cprintln!("<s>Version:</> {version}");
96            }
97
98            if !manifest.links.is_empty() {
99                let mut links = manifest.links.clone();
100                crate::sort_links(&manifest.name, &mut links);
101                cprintln!("<s>Links:</>");
102                for link in links {
103                    println!("  {link}");
104                }
105            }
106
107            if !manifest.provides.is_empty() {
108                cprintln!("<s>Programs:</>");
109                for program in &manifest.provides.programs {
110                    println!("  {program}");
111                }
112            }
113
114            if !manifest.handles.is_empty() {
115                cprintln!("<s>Handles:</>");
116                for (kind, values) in [
117                    ("URL protocols", &manifest.handles.url_protocols),
118                    ("URL prefixes", &manifest.handles.url_prefixes),
119                    ("URL patterns", &manifest.handles.url_patterns),
120                    ("file extensions", &manifest.handles.file_extensions),
121                    ("content types", &manifest.handles.content_types),
122                ] {
123                    if !values.is_empty() {
124                        println!("  {kind}: {}", values.join(", "));
125                    }
126                }
127            }
128
129            cprintln!("<s>Configuration:</>");
130            if conf_vars.is_empty() {
131                println!("  no configuration variables declared");
132            } else {
133                for (var, is_set) in conf_vars.iter().zip(&conf_status) {
134                    if *is_set {
135                        cprintln!("  <s,g>✓</> <s>{}</> (set)", var.name);
136                    } else if var.is_required() {
137                        cprintln!("  <s,r>✗</> <s>{}</> (required)", var.name);
138                    } else {
139                        cprintln!("  <dim>-</> <s>{}</> (unset)", var.name);
140                    }
141                    if let Some(description) = &var.description {
142                        println!("      {description}");
143                    }
144                    if let Some(default_value) = &var.default_value {
145                        if var.secret {
146                            println!("      default: ******");
147                        } else {
148                            println!("      default: {default_value}");
149                        }
150                    }
151                }
152            }
153        },
154    }
155
156    // The report is the output; whether the module is ready to use is the
157    // exit status, so that inspecting one doubles as checking it.
158    let missing: Vec<&str> = conf_vars
159        .iter()
160        .zip(&conf_status)
161        .filter(|(var, is_set)| var.is_required() && !**is_set)
162        .map(|(var, _)| var.name.as_str())
163        .collect();
164
165    if !missing.is_empty() {
166        ceprintln!(
167            "<s,dim>hint:</> Configure the missing variable(s) interactively with: <s>asimov module config setup {module_name}</>"
168        );
169        return Err(EX_CONFIG.into());
170    }
171
172    Ok(())
173}