zoi-lua 1.25.4

Advanced Package Manager & Environment Orchestrator
Documentation
use std::fs;
use std::path::Path;

use mlua::{self, Lua, LuaSerdeExt, Table, Value};

/// Exposes the core Package DSL and lifecycle functions to the Lua environment.
///
/// This module defines the primary entry points for a `.pkg.lua` script:
/// - `metadata`: Defines the static `Package` struct fields.
/// - `dependencies`: Defines the runtime and build dependency graph.
/// - `prepare`/`build`/`package`: Placeholder functions that the maintainer
///   overrides to define the build logic.
/// - `IMPORT`/`INCLUDE`: Helpers for modular package definitions.
///
/// These functions bridge the declarative metadata and the imperative build
/// logic. Adds the `IMPORT` function to the Lua environment for importing data
/// from other files.
///
/// # Errors
///
/// Returns an error if the `IMPORT` function cannot be set in the global
/// environment.
pub fn add_import_util(
    lua: &Lua,
    current_path: &Path
) -> Result<(), mlua::Error> {
    let current_path_buf = current_path.to_path_buf();
    let import_fn = lua.create_function(move |lua, file_name: String| {
        if let Ok(refs) = lua.globals().get::<Table>("__ZoiReferencedFiles") {
            let _ = refs.push(file_name.clone());
        }
        let parent = current_path_buf.parent().ok_or_else(|| {
            mlua::Error::RuntimeError(
                "Could not determine parent directory of package file"
                    .to_string()
            )
        })?;
        let path = parent.join(&file_name);
        let content = fs::read_to_string(&path)
            .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;

        if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
            match extension {
                "json" => {
                    let value: serde_json::Value =
                        serde_json::from_str(&content).map_err(|e| {
                            mlua::Error::RuntimeError(e.to_string())
                        })?;
                    return lua.to_value(&value);
                }
                "yaml" | "yml" => {
                    let value: serde_yaml::Value =
                        serde_yaml::from_str(&content).map_err(|e| {
                            mlua::Error::RuntimeError(e.to_string())
                        })?;
                    return lua.to_value(&value);
                }
                "toml" => {
                    let value: toml::Value =
                        toml::from_str(&content).map_err(|e| {
                            mlua::Error::RuntimeError(e.to_string())
                        })?;
                    return lua.to_value(&value);
                }
                _ => {
                    return lua.to_value(&content);
                }
            }
        }

        lua.to_value(&content)
    })?;
    lua.globals().set("IMPORT", import_fn)?;
    Ok(())
}

/// Adds the `INCLUDE` function to the Lua environment for executing other Lua
/// files.
///
/// # Errors
///
/// Returns an error if the `INCLUDE` function cannot be set in the global
/// environment.
pub fn add_include_util(
    lua: &Lua,
    current_path: &Path
) -> Result<(), mlua::Error> {
    let current_path_buf = current_path.to_path_buf();
    let include_fn = lua.create_function(
        move |lua, file_name: String| -> Result<(), mlua::Error> {
            if let Ok(refs) = lua.globals().get::<Table>("__ZoiReferencedFiles")
            {
                let _ = refs.push(file_name.clone());
            }
            let parent = current_path_buf.parent().ok_or_else(|| {
                mlua::Error::RuntimeError(
                    "Could not determine parent directory of package file"
                        .to_string()
                )
            })?;
            let path = parent.join(file_name);
            let code = fs::read_to_string(path)
                .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
            lua.load(&code).exec()?;
            Ok(())
        }
    )?;
    lua.globals().set("INCLUDE", include_fn)?;
    Ok(())
}

/// Adds core lifecycle functions (`metadata`, `dependencies`, `hooks`, etc.) to
/// the Lua environment.
///
/// # Errors
///
/// Returns an error if any of the lifecycle functions cannot be set in the
/// global environment.
pub fn add_package_lifecycle_functions(lua: &Lua) -> Result<(), mlua::Error> {
    let metadata_fn = lua.create_function(move |lua, pkg_def: Table| {
        if let Ok(meta_table) = lua.globals().get::<Table>("__ZoiPackageMeta")
            && let Ok(pkg_global) = lua.globals().get::<Table>("PKG")
        {
            for pair in pkg_def.pairs::<Value, Value>() {
                let (key, value) = pair?;
                meta_table.set(key.clone(), value.clone())?;
                pkg_global.set(key, value)?;
            }
        }
        Ok(())
    })?;
    lua.globals().set("metadata", metadata_fn)?;

    let dependencies_fn =
        lua.create_function(move |lua, deps_def: Table| {
            if let Ok(deps_table) =
                lua.globals().get::<Table>("__ZoiPackageDeps")
            {
                for pair in deps_def.pairs::<String, Value>() {
                    let (key, value) = pair?;
                    deps_table.set(key, value)?;
                }
            }
            Ok(())
        })?;
    lua.globals().set("dependencies", dependencies_fn)?;

    let updates_fn = lua.create_function(move |lua, updates_list: Table| {
        if let Ok(updates_table) =
            lua.globals().get::<Table>("__ZoiPackageUpdates")
        {
            for pair in updates_list.pairs::<Value, Table>() {
                let (_, update_info) = pair?;
                updates_table.push(update_info)?;
            }
        }
        Ok(())
    })?;
    lua.globals().set("updates", updates_fn)?;

    let hooks_fn = lua.create_function(move |lua, hooks_def: Table| {
        if let Ok(hooks_table) = lua.globals().get::<Table>("__ZoiPackageHooks")
        {
            for pair in hooks_def.pairs::<String, Value>() {
                let (key, value) = pair?;
                hooks_table.set(key, value)?;
            }
        }
        Ok(())
    })?;
    lua.globals().set("hooks", hooks_fn)?;

    let service_fn = lua.create_function(move |lua, service_def: Table| {
        if let Ok(service_table) =
            lua.globals().get::<Table>("__ZoiPackageService")
        {
            for pair in service_def.pairs::<String, Value>() {
                let (key, value) = pair?;
                service_table.set(key, value)?;
            }
        }
        Ok(())
    })?;
    lua.globals().set("service", service_fn)?;

    let prepare_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
    lua.globals().set("prepare", prepare_fn)?;
    let package_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
    lua.globals().set("package", package_fn)?;
    let verify_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(true))?;
    lua.globals().set("verify", verify_fn)?;
    let test_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(true))?;
    lua.globals().set("test", test_fn)?;
    let uninstall_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
    lua.globals().set("uninstall", uninstall_fn)?;

    Ok(())
}