zoi-rs 1.7.0

Universal Package Manager & Environment Setup Tool
Documentation
use crate::pkg::{config, local, resolve, types};
use anyhow::{Result, anyhow};
use mlua::LuaSerdeExt;
use std::fs;

pub fn add(
    ext_name: &str,
    _yes: bool,
    plugin_manager: &crate::pkg::plugin::PluginManager,
) -> Result<()> {
    println!("Adding extension: {}", ext_name);

    let (pkg, _, _, _, registry_handle) = resolve::resolve_package_and_version(ext_name, false)?;

    if pkg.package_type != types::PackageType::Extension {
        return Err(anyhow!("'{}' is not an extension package.", ext_name));
    }

    let pkg_val = plugin_manager
        .lua
        .to_value(&pkg)
        .map_err(|e: mlua::Error| anyhow!(e.to_string()))?;
    plugin_manager.trigger_hook("on_pre_extension_add", Some(pkg_val))?;

    if let Some(extension_info) = pkg.extension {
        if extension_info.extension_type != "zoi" {
            return Err(anyhow!(
                "Unsupported extension type: {}",
                extension_info.extension_type
            ));
        }

        println!("Applying extension changes...");
        for change in extension_info.changes {
            match change {
                types::ExtensionChange::RepoGit { add } => {
                    println!("Adding git repository: {}", add);
                    config::clone_git_repo(&add)?;
                }
                types::ExtensionChange::RegistryRepo { add } => {
                    println!("Setting registry to: {}", add);
                    config::set_default_registry(&add)?;
                }
                types::ExtensionChange::RegistryAdd { add } => {
                    println!("Adding registry: {}", add);
                    config::add_added_registry(&add)?;
                }
                types::ExtensionChange::RepoAdd { add } => {
                    println!("Adding repository: {}", add);
                    config::add_repo(&add)?;
                }
                types::ExtensionChange::Project { add } => {
                    println!("Creating zoi.yaml...");
                    if std::path::Path::new("zoi.yaml").exists() {
                        return Err(anyhow!(
                            "A 'zoi.yaml' file already exists in the current directory. Please remove it first."
                        ));
                    }
                    fs::write("zoi.yaml", add)?;
                }
                types::ExtensionChange::Pgp { name, key } => {
                    println!("Adding PGP key: {} from {}", name, key);
                    if key.starts_with("http") {
                        crate::pkg::pgp::add_key_from_url(&key, &name)?;
                    } else {
                        crate::pkg::pgp::add_key_from_fingerprint(&key, &name)?;
                    }
                }
                types::ExtensionChange::Plugin { name, script } => {
                    println!("Adding plugin: {}", name);
                    let plugin_dir = crate::pkg::plugin::get_plugin_dir()?;
                    let plugin_path = plugin_dir.join(format!("{}.lua", name));
                    fs::write(plugin_path, script)?;
                }
                types::ExtensionChange::Hook { name, content } => {
                    println!("Adding global hook: {}", name);
                    let hooks_dir = crate::pkg::hooks::global::get_user_hooks_dir()?;
                    let hook_path = hooks_dir.join(format!("{}.hook.yaml", name));
                    fs::write(hook_path, content)?;
                }
            }
        }
    } else {
        return Err(anyhow!(
            "'{}' is an extension package but contains no extension data.",
            ext_name
        ));
    }

    let manifest = types::InstallManifest {
        name: pkg.name.clone(),
        version: pkg.version.clone().unwrap_or_default(),
        sub_package: None,
        repo: pkg.repo.clone(),
        registry_handle: registry_handle.unwrap_or_default(),
        package_type: pkg.package_type,
        reason: types::InstallReason::Direct,
        scope: pkg.scope,
        bins: None,
        conflicts: None,
        replaces: None,
        provides: None,
        backup: None,
        installed_dependencies: vec![],
        chosen_options: vec![],
        chosen_optionals: vec![],
        install_method: None,
        service: None,
        installed_files: vec![],
        installed_size: pkg.installed_size,
    };
    local::write_manifest(&manifest)?;

    let manifest_val = plugin_manager
        .lua
        .to_value(&manifest)
        .map_err(|e: mlua::Error| anyhow!(e.to_string()))?;
    plugin_manager.trigger_hook("on_post_extension_add", Some(manifest_val))?;

    println!("Successfully added extension '{}'.", ext_name);

    Ok(())
}

pub fn remove(
    ext_name: &str,
    _yes: bool,
    plugin_manager: &crate::pkg::plugin::PluginManager,
) -> Result<()> {
    println!("Removing extension: {}", ext_name);

    let (pkg, _, _, _, _) = resolve::resolve_package_and_version(ext_name, false)?;

    let (manifest, scope) = if let Some(m) =
        local::is_package_installed(&pkg.name, None, types::Scope::User)?
    {
        (m, types::Scope::User)
    } else if let Some(m) = local::is_package_installed(&pkg.name, None, types::Scope::System)? {
        (m, types::Scope::System)
    } else {
        return Err(anyhow!("Extension '{}' is not installed.", ext_name));
    };

    let manifest_val = plugin_manager
        .lua
        .to_value(&manifest)
        .map_err(|e: mlua::Error| anyhow!(e.to_string()))?;
    plugin_manager.trigger_hook("on_pre_extension_remove", Some(manifest_val.clone()))?;

    if pkg.package_type != types::PackageType::Extension {
        return Err(anyhow!("'{}' is not an extension package.", ext_name));
    }

    if let Some(extension_info) = pkg.extension {
        if extension_info.extension_type != "zoi" {
            return Err(anyhow!(
                "Unsupported extension type: {}",
                extension_info.extension_type
            ));
        }

        println!("Reverting extension changes...");
        for change in extension_info.changes.iter().rev() {
            match change {
                types::ExtensionChange::RepoGit { add } => {
                    let repo_name = add
                        .trim_end_matches('/')
                        .split('/')
                        .next_back()
                        .unwrap_or_default()
                        .trim_end_matches(".git");
                    if !repo_name.is_empty() {
                        println!("Removing git repository: {}", repo_name);
                        if let Err(e) = config::remove_git_repo(repo_name) {
                            eprintln!("Warning: failed to remove git repo '{}': {}", repo_name, e);
                        }
                    }
                }
                types::ExtensionChange::RegistryRepo { add: _ } => {
                    let default_registry = "https://gitlab.com/Zillowe/Zillwen/Zusty/Zoidberg.git";
                    println!("Setting registry back to default");
                    if let Err(e) = config::set_default_registry(default_registry) {
                        eprintln!("Warning: failed to set registry to default: {}", e);
                    }
                }
                types::ExtensionChange::RegistryAdd { add } => {
                    println!("Removing registry: {}", add);
                    if let Err(e) = config::remove_added_registry(add) {
                        eprintln!("Warning: failed to remove registry '{}': {}", add, e);
                    }
                }
                types::ExtensionChange::RepoAdd { add } => {
                    println!("Removing repository: {}", add);
                    if let Err(e) = config::remove_repo(add) {
                        eprintln!("Warning: failed to remove repo '{}': {}", add, e);
                    }
                }
                types::ExtensionChange::Project { add: _ } => {
                    println!("Removing zoi.yaml...");
                    if let Err(e) = fs::remove_file("zoi.yaml") {
                        eprintln!("Warning: failed to remove 'zoi.yaml': {}", e);
                    }
                }
                types::ExtensionChange::Pgp { name, key: _ } => {
                    println!("Removing PGP key: {}", name);
                    if let Err(e) = crate::pkg::pgp::remove_key_by_name(name) {
                        eprintln!("Warning: failed to remove PGP key '{}': {}", name, e);
                    }
                }
                types::ExtensionChange::Plugin { name, script: _ } => {
                    println!("Removing plugin: {}", name);
                    let plugin_dir = crate::pkg::plugin::get_plugin_dir()?;
                    let plugin_path = plugin_dir.join(format!("{}.lua", name));
                    if plugin_path.exists()
                        && let Err(e) = fs::remove_file(plugin_path)
                    {
                        eprintln!("Warning: failed to remove plugin '{}': {}", name, e);
                    }
                }
                types::ExtensionChange::Hook { name, content: _ } => {
                    println!("Removing global hook: {}", name);
                    let hooks_dir = crate::pkg::hooks::global::get_user_hooks_dir()?;
                    let hook_path = hooks_dir.join(format!("{}.hook.yaml", name));
                    if hook_path.exists()
                        && let Err(e) = fs::remove_file(hook_path)
                    {
                        eprintln!("Warning: failed to remove global hook '{}': {}", name, e);
                    }
                }
            }
        }
    } else {
        return Err(anyhow!(
            "'{}' is an extension package but contains no extension data.",
            ext_name
        ));
    }

    let package_dir = local::get_package_dir(
        scope,
        &manifest.registry_handle,
        &manifest.repo,
        &manifest.name,
    )?;

    if package_dir.exists() {
        fs::remove_dir_all(&package_dir)?;
    }

    plugin_manager.trigger_hook("on_post_extension_remove", Some(manifest_val))?;

    println!("Successfully removed extension '{}'.", ext_name);

    Ok(())
}