ninja-core 1.14.1

A powerful, cross-platform package manager and runtime for managing tools and plugins (shurikens)
Documentation
use chrono::prelude::*;
use log::{debug, error, info, warn};
use mlua::{ExternalError, Lua, LuaSerdeExt, Result, Table};
use serde_json::Value;
use std::{
    fs,
    path::{Path, PathBuf},
    time::Duration,
};

mod env_module;
mod fs_module;
mod ninja_module;
mod proc_module;
mod shared;
mod shell_module;

pub(crate) use env_module::make_env_module;
pub(crate) use fs_module::make_fs_module;
pub(crate) use ninja_module::make_ninja_module;
pub(crate) use proc_module::make_proc_module;
use shared::{FetchArgs, http_download, http_request};
pub(crate) use shell_module::make_shell_module;

pub async fn make_modules(
    lua: &Lua,
    cwd: Option<&Path>,
) -> Result<(Table, Table, Table, Table, Table, Table, Table, Table)> {
    debug!(
        "make_modules: cwd = {:?}",
        cwd.map(|p| p.display().to_string())
    );

    let fs_module = make_fs_module(lua, cwd)?;
    let env_module = make_env_module(lua, cwd)?;
    let shell_module = make_shell_module(lua, cwd)?;
    let time_module = lua.create_table()?;
    let json_module = lua.create_table()?;
    let http_module = lua.create_table()?;
    let log_module = lua.create_table()?;
    let proc_module = make_proc_module(lua, cwd)?;

    time_module.set(
        "year",
        lua.create_function(|_, _: ()| {
            let y = Utc::now().year();
            debug!("time.year -> {}", y);
            Ok(y)
        })?,
    )?;
    time_module.set(
        "month",
        lua.create_function(|_, _: ()| {
            let m = Utc::now().month();
            debug!("time.month -> {}", m);
            Ok(m)
        })?,
    )?;
    time_module.set(
        "day",
        lua.create_function(|_, _: ()| {
            let d = Utc::now().day();
            debug!("time.day -> {}", d);
            Ok(d)
        })?,
    )?;
    time_module.set(
        "hour",
        lua.create_function(|_, format: bool| {
            let now = Utc::now();
            if format {
                let (pm, hour) = now.hour12();
                debug!(
                    "time.hour(format=true) -> {} {}",
                    hour,
                    if pm { "PM" } else { "AM" }
                );
                Ok((hour, if pm { "PM" } else { "AM" }))
            } else {
                let h = now.hour();
                debug!("time.hour(format=false) -> {}", h);
                Ok((h, ""))
            }
        })?,
    )?;
    time_module.set(
        "minute",
        lua.create_function(|_, _: ()| {
            let m = Utc::now().minute();
            debug!("time.minute -> {}", m);
            Ok(m)
        })?,
    )?;
    time_module.set(
        "second",
        lua.create_function(|_, _: ()| {
            let s = Utc::now().second();
            debug!("time.second -> {}", s);
            Ok(s)
        })?,
    )?;
    time_module.set(
        "now",
        lua.create_function(|_, fmt: String| {
            let now = Utc::now().format(&fmt).to_string();
            debug!("time.now('{}') -> '{}'", fmt, now);
            Ok(now)
        })?,
    )?;
    time_module.set(
        "sleep",
        lua.create_function(|_, seconds: f64| {
            debug!("time.sleep: {} seconds", seconds);
            std::thread::sleep(Duration::from_secs_f64(seconds));
            Ok(())
        })?,
    )?;

    json_module.set(
        "encode",
        lua.create_function(|_, table: Table| {
            debug!("json.encode");
            serde_json::to_string(&table).map_err(|e| {
                error!("json.encode: failed: {}", e);
                e.into_lua_err()
            })
        })?,
    )?;
    json_module.set(
        "decode",
        lua.create_function(|lua, s: String| {
            debug!("json.decode: len={}", s.len());
            let val: Value = match serde_json::from_str(&s) {
                Ok(v) => v,
                Err(e) => {
                    error!("json.decode: invalid JSON: {} | '{}'", e, s);
                    Value::String(format!("Invalid JSON: {}", s))
                }
            };
            match lua.to_value(&val) {
                Ok(mlua::Value::Table(t)) => Ok(t),
                _ => {
                    error!("json.decode: expected object/array");
                    Err(mlua::Error::DeserializeError(
                        "Expected JSON object/array".into(),
                    ))
                }
            }
        })?,
    )?;

    log_module.set(
        "info",
        lua.create_function(|_, s: String| {
            info!("{}", s);
            Ok(())
        })?,
    )?;
    log_module.set(
        "warn",
        lua.create_function(|_, s: String| {
            warn!("{}", s);
            Ok(())
        })?,
    )?;
    log_module.set(
        "error",
        lua.create_function(|_, s: String| {
            error!("{}", s);
            Ok(())
        })?,
    )?;
    log_module.set(
        "debug",
        lua.create_function(|_, s: String| {
            debug!("{}", s);
            Ok(())
        })?,
    )?;

    http_module.set(
        "fetch",
        lua.create_async_function(|lua, (url, headers, method, body): FetchArgs| async move {
            debug!(
                "http.fetch: url='{}', headers={:?}, method={:?}, body={:?}",
                url, headers, method, body
            );
            let method = method.unwrap_or_else(|| "GET".to_string());
            let (status, response_body) = http_request(&method, &url, body, headers).await?;
            debug!(
                "http.fetch: url='{}' -> status={}, body_len={}",
                url,
                status,
                response_body.len()
            );
            let result_table = lua.create_table()?;
            result_table.set("status", status)?;
            result_table.set("body", response_body)?;
            Ok(result_table)
        })?,
    )?;

    http_module.set(
        "download",
        lua.create_async_function({
            let cwd_buf = shared::canonicalize_cwd(cwd);
            move |_, (url, dest): (String, PathBuf)| {
                let value = cwd_buf.clone();
                async move {
                    debug!("http.download: url='{}', dest='{}'", url, dest.display());
                    let bytes = http_download(&url).await?;
                    debug!("http.download: url='{}' -> {} bytes", url, bytes.len());

                    let dest = if let Some(cwd) = value.clone() {
                        cwd.join(dest)
                    } else {
                        dest
                    };

                    if let Some(parent) = dest.parent()
                        && let Err(e) = fs::create_dir_all(parent)
                    {
                        error!(
                            "http.download: failed to create parent directories for '{}': {}",
                            dest.display(),
                            e
                        );
                        return Err(mlua::Error::external(format!(
                            "Failed to create parent directories for '{}': {}",
                            dest.display(),
                            e
                        )));
                    }

                    match fs::write(&dest, &bytes) {
                        Ok(_) => {
                            debug!("http.download: successfully wrote to '{}'", dest.display());
                            Ok(())
                        }
                        Err(e) => {
                            error!(
                                "http.download: failed to write to '{}': {}",
                                dest.display(),
                                e
                            );
                            Err(mlua::Error::external(format!(
                                "Failed to write to '{}': {}",
                                dest.display(),
                                e
                            )))
                        }
                    }
                }
            }
        })?,
    )?;

    debug!("make_modules: all modules created");
    Ok((
        fs_module,
        env_module,
        shell_module,
        time_module,
        json_module,
        http_module,
        log_module,
        proc_module,
    ))
}