shine-cli 2.0.0-rc.1

Give personal automation a reviewable lifecycle
Documentation
use crate::colors;
use crate::sys::execution::{status_symbol, status_text};
use crate::sys::run_manifest::SysRunEntry;
use crate::sys::{LoadedSysPreset, ResolvedSelection, SysDriverKind, SysItem, SysItemMode};
use anyhow::Result;

pub(in crate::sys) fn print_available_item(item: &SysItem, entry: Option<&SysRunEntry>) {
    let kind = item_mode_name(item.mode);
    let status = entry
        .map(|entry| {
            let symbol = status_symbol(entry.status);
            format!(
                "{} {}",
                colors::symbol(symbol),
                colors::status_label(status_text(entry.status), symbol)
            )
        })
        .unwrap_or_else(|| colors::dim("not recorded"));
    println!(
        "    {:<18} {:<9} {}  {}",
        item.id,
        format!("[{kind}]"),
        colors::bold(&item.label),
        status
    );
    if !item.description.is_empty() {
        println!("      {}", colors::dim(&item.description));
    }
    if item.mode == SysItemMode::Managed {
        println!(
            "      {}",
            colors::dim(&format!("Run: shine sys apply {}", item.id))
        );
    }
}

pub(in crate::sys) fn item_mode_name(mode: SysItemMode) -> &'static str {
    match mode {
        SysItemMode::Init => "init",
        SysItemMode::Managed => "managed",
    }
}

pub(in crate::sys) fn driver_name(driver: SysDriverKind) -> &'static str {
    match driver {
        SysDriverKind::Script => "script",
        SysDriverKind::SplitDns => "split-dns",
        SysDriverKind::ManagedFile => "managed-file",
    }
}

pub(in crate::sys) async fn print_dry_run(
    os_id: &str,
    loaded: &LoadedSysPreset,
    selection: &ResolvedSelection,
    sys_shell: &str,
    proxy_env: &[(&'static str, String)],
    previews: &[String],
) -> Result<()> {
    println!("{}", colors::dim("[dry-run] System init preview"));
    println!("  OS: {os_id}");
    println!("  Shell: {sys_shell}");
    println!("  Selection: {}", selection.source.describe());
    if !proxy_env.is_empty() {
        println!("  Proxy env:");
        for (key, value) in proxy_env {
            println!("    {}", colors::dim(&format!("{key}={value}")));
        }
    }
    println!(
        "  Items: {}",
        if selection.item_ids.is_empty() {
            "(none)".to_string()
        } else {
            selection.item_ids.join(", ")
        }
    );
    println!("  Commands:");
    for preview in previews {
        println!("    {preview}");
    }
    let integrations = selection
        .item_ids
        .iter()
        .filter_map(|item_id| {
            loaded
                .manifest
                .items
                .iter()
                .find(|item| item.id == *item_id)
        })
        .flat_map(|item| {
            item.shell
                .iter()
                .map(move |integration| (item, integration))
        })
        .collect::<Vec<_>>();
    if !integrations.is_empty() {
        println!("  Shell integrations:");
        for (item, integration) in integrations {
            let action = if let Some(fragment) = &integration.fragment {
                format!("fragment {fragment}")
            } else if integration.path.is_some() {
                "PATH".to_string()
            } else if !integration.env.is_empty() {
                "environment".to_string()
            } else if !integration.eval_argv.is_empty() {
                "guarded eval".to_string()
            } else if integration.source.is_some() {
                "guarded source".to_string()
            } else {
                "aliases".to_string()
            };
            println!(
                "    sys/{}: {} · {:?} · {}",
                item.id,
                integration.phase.as_str(),
                integration.shells,
                action
            );
        }
    }
    if !selection.item_ids.is_empty() {
        println!("    shine internal sys profile pre/post update");
    }
    println!();
    Ok(())
}