zoi-cli 1.24.6

Advanced Package Manager & Environment Orchestrator
Documentation
use crate::pkg::{local, resolve, types};
use crate::utils;
use anyhow::{Result, anyhow};
use colored::*;
use std::fs;

fn print_dependency_group(group: &types::DependencyGroup, indent: usize) {
    let prefix = " ".repeat(indent * 2);
    let mut count = 0;

    let required = group.required();
    if !required.is_empty() {
        for dep in required {
            println!("{}- {} (required)", prefix, dep);
            count += 1;
        }
    }

    let options = group.options();
    if !options.is_empty() {
        for opt_group in options {
            println!(
                "{}{}: {} (choose {})",
                prefix,
                opt_group.name.bold(),
                opt_group.desc,
                if opt_group.all { "any" } else { "one" }
            );
            for dep in &opt_group.depends {
                println!("{}  - {}", prefix, dep);
                count += 1;
            }
        }
    }

    let optional = group.optional();
    if !optional.is_empty() {
        for dep in optional {
            println!("{}- {} (optional)", prefix, dep);
            count += 1;
        }
    }

    if let types::DependencyGroup::Complex(complex) = group
        && let Some(subs) = &complex.sub_packages
    {
        for (sub_name, sub_group) in subs {
            println!("{}{}:", prefix, sub_name.bold().cyan());
            print_dependency_group(sub_group, indent + 1);
            count += 1;
        }
    }

    if count == 0 {
        println!("{}- {}", prefix, "None".italic());
    }
}

pub fn run(source: &str, raw: bool, purl: bool) -> Result<()> {
    let mut source_str = source.trim().to_string();
    if purl {
        println!(
            "{} Fetching PURL package '{}'...",
            "::".bold().blue(),
            source_str
        );
        source_str = crate::pkg::purl::fetch_and_store_purl_package(&source_str)?;
    }
    let source = source_str.as_str();
    let resolved_source = resolve::resolve_source(source, None, false, false)?;

    if raw {
        let content = fs::read_to_string(&resolved_source.path)?;
        println!("{content}");
        return Ok(());
    }
    let mut pkg: types::Package = crate::pkg::lua::parser::parse_lua_package(
        resolved_source.path.to_str().ok_or_else(|| {
            anyhow!(
                "Path contains invalid UTF-8 characters: {:?}",
                resolved_source.path
            )
        })?,
        None,
        None,
        false,
    )?;
    if let Some(repo_name) = resolved_source.repo_name {
        pkg.repo = repo_name;
    }
    pkg.version = Some(
        resolve::get_default_version(&pkg, resolved_source.registry_handle.as_deref())
            .unwrap_or_else(|_| "N/A".to_string()),
    );

    let request = resolve::parse_source_string(source)?;
    let installed_manifest = match find_installed_manifest(&request) {
        Ok(manifest) => manifest,
        Err(e) => {
            eprintln!("Warning: could not check installation status: {}", e);
            None
        }
    };

    print_beautiful(&pkg, installed_manifest.as_ref());
    Ok(())
}

fn find_installed_manifest(
    request: &crate::pkg::resolve::PackageRequest,
) -> Result<Option<types::InstallManifest>> {
    let mut candidates = Vec::new();
    for scope in [
        types::Scope::User,
        types::Scope::System,
        types::Scope::Project,
    ] {
        candidates.extend(local::find_installed_manifests_matching(request, scope)?);
    }

    if candidates.is_empty() {
        return Ok(None);
    }

    let package_name = if let Some(sub) = &request.sub_package {
        format!("{}:{}", request.name, sub)
    } else {
        request.name.clone()
    };
    Ok(Some(
        crate::cmd::installed_select::choose_installed_manifest(&package_name, &candidates, false)?,
    ))
}

fn print_beautiful(
    pkg: &crate::pkg::types::Package,
    installed_manifest: Option<&types::InstallManifest>,
) {
    let mut version_display = if pkg.epoch > 0 {
        format!("{}:", pkg.epoch)
    } else {
        String::new()
    };

    version_display.push_str(pkg.version.as_deref().unwrap_or("N/A"));

    if pkg.revision != "1" {
        version_display = format!("{}-{}", version_display, pkg.revision);
    }

    let repo_display = if pkg.repo.starts_with("git/") {
        format!("#git@{}", &pkg.repo[4..])
    } else {
        pkg.repo.clone()
    };

    println!(
        "{} {} - {}",
        pkg.name.bold().green(),
        version_display.dimmed(),
        repo_display
    );
    if let Some(website) = &pkg.website {
        println!("Website: {}", website.cyan().underline());
    }
    if !pkg.git.is_empty() {
        println!("Git Repo: {}", pkg.git.cyan().underline());
    }
    println!("{}", pkg.description);

    if let Some(subs) = &pkg.sub_packages {
        println!("{}: {}", "Sub-packages".bold(), subs.join(", "));
        if let Some(main_subs) = &pkg.main_subs {
            println!("{}: {}", "Main sub-packages".bold(), main_subs.join(", "));
        }
    }

    if let Some(manifest) = installed_manifest {
        let status_text = if let Some(sub) = &manifest.sub_package {
            format!("Installed ({})", sub)
        } else {
            "Installed".to_string()
        };

        let installed_version_display = if manifest.revision != "1" {
            format!("{}-{}", manifest.version, manifest.revision)
        } else {
            manifest.version.clone()
        };

        println!(
            "{}: {} ({})",
            "Status".bold(),
            status_text.green(),
            installed_version_display
        );
    } else {
        println!("{}: {}", "Status".bold(), "Not Installed".red());
    }

    if !pkg.license.is_empty() {
        println!("{}: {}", "License".bold(), pkg.license);
        utils::check_license(&pkg.license);
    }

    let mut maintainer_line = format!(
        "{}: {} <{}>",
        "Maintainer".bold(),
        pkg.maintainer.name,
        pkg.maintainer.email
    );
    if let Some(website) = &pkg.maintainer.website {
        maintainer_line.push_str(&format!(" - {}", website.cyan().underline()));
    }
    println!("{}", maintainer_line);

    if let Some(author) = &pkg.author {
        let mut author_line = format!("{}: {}", "Author".bold(), author.name);
        if let Some(email) = &author.email {
            author_line.push_str(&format!(" <{}>", email));
        }
        if let Some(website) = &author.website {
            author_line.push_str(&format!(" - {}", website.cyan().underline()));
        }
        println!("{}", author_line);
    }

    let type_display = match pkg.package_type {
        crate::pkg::types::PackageType::Package => "Package",
        crate::pkg::types::PackageType::Collection => "Collection",
        crate::pkg::types::PackageType::App => "App",
        crate::pkg::types::PackageType::Extension => "Extension",
    };
    println!("{}: {}", "Type".bold(), type_display);

    let scope_display = match pkg.scope {
        crate::pkg::types::Scope::User => "User",
        crate::pkg::types::Scope::System => "System",
        crate::pkg::types::Scope::Project => "Project",
    };
    println!("{}: {}", "Scope".bold(), scope_display);

    if !pkg.tags.is_empty() {
        println!("{}: {}", "Tags".bold(), pkg.tags.join(", "));
    }

    if let Some(bins) = &pkg.bins
        && !bins.is_empty()
    {
        println!("{}: {}", "Provides".bold(), bins.join(", ").green());
    }

    if let Some(conflicts) = &pkg.conflicts
        && !conflicts.is_empty()
    {
        println!("{}: {}", "Conflicts".bold(), conflicts.join(", ").red());
    }

    if pkg.package_type == crate::pkg::types::PackageType::Package {
        println!("{}: {}", "Available types".bold(), pkg.types.join(", "));
    }

    if let Some(service) = &pkg.service {
        println!("\n{}:", "Service".bold());
        println!("  Run: {}", service.run.cyan());
        println!(
            "  Run at load: {}",
            if service.run_at_load {
                "Yes".green()
            } else {
                "No".yellow()
            }
        );
    }

    if let Some(deps) = &pkg.dependencies {
        println!("\n{}:", "Dependencies".bold());

        if let Some(runtime) = &deps.runtime {
            println!("  Runtime:");
            print_dependency_group(runtime, 2);
        }

        if let Some(build_deps) = &deps.build {
            println!("  Build Dependencies:");
            match build_deps {
                types::BuildDependencies::Group(group) => {
                    print_dependency_group(group, 2);
                }
                types::BuildDependencies::Typed(typed_build_deps) => {
                    for (name, group) in &typed_build_deps.types {
                        println!("    {}:", name.cyan());
                        print_dependency_group(group, 3);
                    }
                }
            }
        }

        if let Some(test_deps) = &deps.test {
            println!("  Test Dependencies:");
            print_dependency_group(test_deps, 2);
        }
    }
}