topcoat-cli 0.11.0

A modular, batteries-included Rust web framework for server-rendered apps.
Documentation
use clap::Args;
use console::style;
use topcoat_ui::manage::{self, AddAction, AddOptions, Package, Selection};

use super::PackageArg;
use crate::common::prompt;

#[derive(Args)]
pub(super) struct AddCommand {
    /// Names of the components to add (e.g. `button card`)
    #[arg(required_unless_present = "all", conflicts_with = "all")]
    components: Vec<String>,
    /// Add every component the registry offers, skipping those already
    /// installed (combine with `--overwrite` to replace them too)
    #[arg(short, long)]
    all: bool,
    /// Registry crate to add from (defaults to the built-in default registry)
    #[arg(short, long)]
    registry: Option<String>,
    /// Overwrite the component file if it already exists
    #[arg(short, long)]
    overwrite: bool,
    #[command(flatten)]
    package: PackageArg,
}

impl AddCommand {
    pub(super) fn run(self) {
        if let Err(error) = self.run_inner() {
            if error != prompt::CANCELLED {
                eprintln!("{}", style(error).red());
            }
            std::process::exit(1);
        }
    }

    fn run_inner(self) -> Result<(), String> {
        let package = Package::locate(self.package.package)?;
        let options = AddOptions {
            selection: if self.all {
                Selection::All
            } else {
                Selection::Named(self.components)
            },
            registry: self.registry,
            overwrite: self.overwrite,
        };

        let mut confirm = confirm;
        let entries = manage::add(&package, &options, &mut confirm)?;

        let mut written = false;
        for entry in entries {
            let file = style(format!("({})", entry.file.display())).dim();
            let name = style(entry.name).bold();
            match entry.action {
                AddAction::Installed => {
                    written = true;
                    println!(
                        "{} added {name} {file} from {}",
                        style("+").green(),
                        entry.registry
                    );
                }
                AddAction::Overwritten => {
                    written = true;
                    println!(
                        "{} updated {name} {file} from {}",
                        style("~").yellow(),
                        entry.registry
                    );
                }
                AddAction::Skipped => {
                    println!(
                        "{}",
                        style(format!("- skipped {name} {file}: already installed")).dim()
                    );
                }
            }
        }
        if !written {
            println!("{} already up to date", style("✓").green());
        }
        Ok(())
    }
}

/// Asks for confirmation, defaulting to no. Returns an error without an interactive
/// terminal. Use an explicit `--registry` to avoid registry-selection prompts.
fn confirm(question: &str) -> Result<bool, String> {
    if !prompt::is_interactive() {
        return Err(format!(
            "{question} (no terminal to prompt on; pass --registry to choose)"
        ));
    }

    cliclack::confirm(question)
        .initial_value(false)
        .interact()
        .map_err(|error| prompt::error(&error))
}