zoi-lua 1.23.4

Advanced Package Manager & Environment Orchestrator
Documentation
use crate::functions;
use anyhow::{Result, anyhow};
use mlua::{self, Lua, LuaSerdeExt, Table, Value};
use std::fs;
use std::path::Path;
use tar::Archive;
use walkdir::WalkDir;
use zoi_core::{types, utils};
use zstd::stream::read::Decoder as ZstdDecoder;

pub fn parse_lua_package_from_archive(
    archive_path: &Path,
    version_override: Option<&str>,
    scope: Option<types::Scope>,
    quiet: bool,
) -> Result<types::Package> {
    let platform = utils::get_platform()?;
    parse_lua_package_from_archive_for_platform(
        archive_path,
        &platform,
        version_override,
        scope,
        quiet,
    )
}

pub fn parse_lua_package_from_archive_for_platform(
    archive_path: &Path,
    platform: &str,
    version_override: Option<&str>,
    scope: Option<types::Scope>,
    quiet: bool,
) -> Result<types::Package> {
    let file = fs::File::open(archive_path)?;
    let decoder = ZstdDecoder::new(file)?;
    let mut archive = Archive::new(decoder);
    let temp_dir = tempfile::Builder::new()
        .prefix("zoi-arch-parse-")
        .tempdir()?;
    archive.unpack(temp_dir.path())?;

    let mut pkg_lua = None;
    for entry in WalkDir::new(temp_dir.path())
        .into_iter()
        .filter_map(|e| e.ok())
    {
        if entry.file_name().to_string_lossy().ends_with(".pkg.lua") {
            pkg_lua = Some(entry.path().to_path_buf());
            break;
        }
    }

    let pkg_lua_path = pkg_lua.ok_or_else(|| anyhow!("No .pkg.lua in archive"))?;
    parse_lua_package_from_file_for_platform(
        pkg_lua_path
            .to_str()
            .ok_or_else(|| anyhow!("Invalid path"))?,
        platform,
        version_override,
        scope,
        quiet,
    )
}

pub fn parse_lua_package_for_platform(
    file_path: &str,
    platform: &str,
    version_override: Option<&str>,
    scope: Option<types::Scope>,
    quiet: bool,
) -> Result<types::Package> {
    if file_path.ends_with(".zpa") || file_path.ends_with(".zsa") {
        return parse_lua_package_from_archive_for_platform(
            Path::new(file_path),
            platform,
            version_override,
            scope,
            quiet,
        );
    }
    parse_lua_package_from_file_for_platform(file_path, platform, version_override, scope, quiet)
}

fn parse_lua_package_from_file_for_platform(
    file_path: &str,
    platform: &str,
    version_override: Option<&str>,
    scope: Option<types::Scope>,
    quiet: bool,
) -> Result<types::Package> {
    let lua_code = fs::read_to_string(file_path)?;
    let lua = Lua::new();

    let pkg_meta_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    let pkg_deps_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    let pkg_updates_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    let pkg_hooks_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    let pkg_service_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("__ZoiPackageMeta", pkg_meta_table)
        .map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("__ZoiPackageDeps", pkg_deps_table)
        .map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("__ZoiPackageUpdates", pkg_updates_table)
        .map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("__ZoiPackageHooks", pkg_hooks_table)
        .map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("__ZoiPackageService", pkg_service_table)
        .map_err(|e| anyhow!(e.to_string()))?;

    let pkg_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
    lua.globals()
        .set("PKG", pkg_table)
        .map_err(|e| anyhow!(e.to_string()))?;

    functions::setup_lua_environment(
        &lua,
        platform,
        version_override,
        Some(file_path),
        None,
        None,
        None,
        None,
        scope,
        quiet,
    )
    .map_err(|e| anyhow!("Failed to setup Lua environment for '{}': {}", file_path, e))?;

    lua.load(&lua_code).exec().map_err(|e| {
        anyhow!(
            "Failed to execute Lua package file '{}':
{}",
            file_path,
            e
        )
    })?;

    let final_pkg_meta: Table = lua
        .globals()
        .get("__ZoiPackageMeta")
        .map_err(|e| anyhow!(e.to_string()))?;
    let final_pkg_deps: Table = lua
        .globals()
        .get("__ZoiPackageDeps")
        .map_err(|e| anyhow!(e.to_string()))?;
    let final_pkg_updates: Table = lua
        .globals()
        .get("__ZoiPackageUpdates")
        .map_err(|e| anyhow!(e.to_string()))?;
    let final_pkg_hooks: Table = lua
        .globals()
        .get("__ZoiPackageHooks")
        .map_err(|e| anyhow!(e.to_string()))?;
    let final_pkg_service: Table = lua
        .globals()
        .get("__ZoiPackageService")
        .map_err(|e| anyhow!(e.to_string()))?;

    let mut package: types::Package =
        lua.from_value(Value::Table(final_pkg_meta)).map_err(|e| {
            anyhow!(
                "Failed to parse 'metadata' block in package file '{}':
{}",
                file_path,
                e
            )
        })?;

    package.dependencies = if final_pkg_deps.is_empty() {
        None
    } else {
        Some(lua.from_value(Value::Table(final_pkg_deps)).map_err(|e| {
            anyhow!(
                "Failed to parse 'dependencies' block in package file '{}':
{}",
                file_path,
                e
            )
        })?)
    };

    package.updates = if final_pkg_updates.is_empty() {
        None
    } else {
        Some(
            lua.from_value(Value::Table(final_pkg_updates))
                .map_err(|e| {
                    anyhow!(
                        "Failed to parse 'updates' block in package file '{}':
{}",
                        file_path,
                        e
                    )
                })?,
        )
    };

    package.hooks = if final_pkg_hooks.is_empty() {
        None
    } else {
        Some(lua.from_value(Value::Table(final_pkg_hooks)).map_err(|e| {
            anyhow!(
                "Failed to parse 'hooks' block in package file '{}':
{}",
                file_path,
                e
            )
        })?)
    };

    package.service = if final_pkg_service.is_empty() {
        None
    } else {
        Some(
            lua.from_value(Value::Table(final_pkg_service))
                .map_err(|e| {
                    anyhow!(
                        "Failed to parse 'service' block in package file '{}':
{}",
                        file_path,
                        e
                    )
                })?,
        )
    };

    Ok(package)
}

pub fn parse_lua_package(
    file_path: &str,
    version_override: Option<&str>,
    scope: Option<types::Scope>,
    quiet: bool,
) -> Result<types::Package> {
    let platform = utils::get_platform()?;
    parse_lua_package_for_platform(file_path, &platform, version_override, scope, quiet)
}