Skip to main content

stellar_scaffold_cli/commands/ext/
ls.rs

1use cargo_metadata::MetadataCommand;
2use clap::Parser;
3use stellar_cli::print::Print;
4
5use crate::commands::build::clients::ScaffoldEnv;
6use crate::commands::build::env_toml;
7use crate::extension::{ExtensionListStatus, list};
8
9const H_NAME: &str = "NAME";
10const H_VERSION: &str = "VERSION";
11const H_STATUS: &str = "STATUS";
12const H_HOOKS: &str = "HOOKS";
13
14#[derive(Parser, Debug)]
15pub struct Cmd {
16    /// Scaffold environment whose extension list to inspect.
17    #[arg(
18        env = "STELLAR_SCAFFOLD_ENV",
19        value_enum,
20        default_value = "development"
21    )]
22    pub env: ScaffoldEnv,
23}
24
25#[derive(thiserror::Error, Debug)]
26pub enum Error {
27    #[error(transparent)]
28    Metadata(#[from] cargo_metadata::Error),
29    #[error(transparent)]
30    Env(#[from] env_toml::Error),
31}
32
33impl Cmd {
34    pub fn run(&self, global_args: &stellar_cli::commands::global::Args) -> Result<(), Error> {
35        let printer = Print::new(global_args.quiet);
36
37        let metadata = MetadataCommand::new().no_deps().exec()?;
38        let workspace_root = metadata.workspace_root.as_std_path();
39
40        let Some(current_env) = env_toml::Environment::get(workspace_root, &self.env)? else {
41            printer.warnln(format!(
42                "No environments.toml found or no {:?} environment configured.",
43                self.env
44            ));
45            return Ok(());
46        };
47
48        if current_env.extensions.is_empty() {
49            printer.infoln(format!(
50                "No extensions configured for the {:?} environment.",
51                self.env
52            ));
53            return Ok(());
54        }
55
56        let entries = list(&current_env.extensions);
57
58        // Compute column widths (at least as wide as the header label).
59        let name_w = entries
60            .iter()
61            .map(|e| e.name.len())
62            .max()
63            .unwrap_or(0)
64            .max(H_NAME.len());
65        let version_w = entries
66            .iter()
67            .map(|e| match &e.status {
68                ExtensionListStatus::Found { version, .. } => version.len(),
69                _ => 1, // "-"
70            })
71            .max()
72            .unwrap_or(0)
73            .max(H_VERSION.len());
74        let status_w = entries
75            .iter()
76            .map(|e| status_str(&e.status).len())
77            .max()
78            .unwrap_or(0)
79            .max(H_STATUS.len());
80
81        // Header + separator.
82        println!("{H_NAME:<name_w$}  {H_VERSION:<version_w$}  {H_STATUS:<status_w$}  {H_HOOKS}",);
83        println!(
84            "{:-<name_w$}  {:-<version_w$}  {:-<status_w$}  {:-<hooks_w$}",
85            "",
86            "",
87            "",
88            "",
89            hooks_w = H_HOOKS.len(),
90        );
91
92        // Rows.
93        for entry in &entries {
94            let version = match &entry.status {
95                ExtensionListStatus::Found { version, .. } => version.as_str(),
96                _ => "-",
97            };
98            let hooks = match &entry.status {
99                ExtensionListStatus::Found { hooks, .. } if !hooks.is_empty() => hooks.join(", "),
100                _ => "-".to_string(),
101            };
102            let name = &entry.name;
103            let status = status_str(&entry.status);
104            println!("{name:<name_w$}  {version:<version_w$}  {status:<status_w$}  {hooks}");
105        }
106
107        Ok(())
108    }
109}
110
111fn status_str(status: &ExtensionListStatus) -> &'static str {
112    match status {
113        ExtensionListStatus::Found { .. } => "found",
114        ExtensionListStatus::MissingBinary => "missing",
115        ExtensionListStatus::ManifestError(_) => "error",
116    }
117}