Skip to main content

mdvault_core/scripting/
bindings.rs

1//! Lua bindings for mdvault functionality.
2//!
3//! This module provides the `mdv` global table with bindings to
4//! mdvault's date math and template rendering engines.
5
6use mlua::{Function, Lua, Result as LuaResult, Table, Value};
7use std::collections::HashMap;
8
9use crate::templates::engine::{RenderContext, render_string};
10use crate::vars::datemath::{evaluate_date_expr, is_date_expr, parse_date_expr};
11
12/// Register the `mdv` global table with all bindings.
13///
14/// After calling this function, Lua scripts can use:
15/// - `mdv.date(expr, format?)` - Evaluate date math expressions
16/// - `mdv.render(template, context)` - Render templates with variables
17/// - `mdv.is_date_expr(str)` - Check if string is a date expression
18pub fn register_mdv_table(lua: &Lua) -> LuaResult<()> {
19    let mdv = lua.create_table()?;
20
21    mdv.set("date", create_date_fn(lua)?)?;
22    mdv.set("render", create_render_fn(lua)?)?;
23    mdv.set("is_date_expr", create_is_date_expr_fn(lua)?)?;
24
25    lua.globals().set("mdv", mdv)?;
26    Ok(())
27}
28
29/// Create the `mdv.date(expr, format?)` function.
30///
31/// # Examples (in Lua)
32///
33/// ```lua
34/// mdv.date("today")           -- "2025-12-29"
35/// mdv.date("today + 7d")      -- "2026-01-05"
36/// mdv.date("today", "%B %d")  -- "December 29"
37/// mdv.date("now", "%H:%M")    -- "14:30"
38/// ```
39fn create_date_fn(lua: &Lua) -> LuaResult<Function> {
40    lua.create_function(|_, args: (String, Option<String>)| {
41        let (expr, format_override) = args;
42
43        // Parse the expression
44        let mut parsed =
45            parse_date_expr(&expr).map_err(|e| mlua::Error::runtime(e.to_string()))?;
46
47        // Override format if provided as second argument
48        if let Some(fmt) = format_override {
49            parsed.format = Some(fmt);
50        }
51
52        Ok(evaluate_date_expr(&parsed))
53    })
54}
55
56/// Create the `mdv.render(template, context)` function.
57///
58/// # Examples (in Lua)
59///
60/// ```lua
61/// mdv.render("Hello {{name}}", { name = "World" })  -- "Hello World"
62/// mdv.render("Count: {{n}}", { n = 42 })            -- "Count: 42"
63/// ```
64fn create_render_fn(lua: &Lua) -> LuaResult<Function> {
65    lua.create_function(|_, args: (String, Table)| {
66        let (template, ctx_table) = args;
67
68        // Convert Lua table to RenderContext (HashMap<String, String>)
69        let mut ctx: RenderContext = HashMap::new();
70
71        for pair in ctx_table.pairs::<String, Value>() {
72            let (key, value) = pair?;
73            let str_value = lua_value_to_string(&key, value)?;
74            ctx.insert(key, str_value);
75        }
76
77        render_string(&template, &ctx).map_err(|e| mlua::Error::runtime(e.to_string()))
78    })
79}
80
81/// Create the `mdv.is_date_expr(str)` function.
82///
83/// # Examples (in Lua)
84///
85/// ```lua
86/// mdv.is_date_expr("today + 1d")  -- true
87/// mdv.is_date_expr("hello")       -- false
88/// ```
89fn create_is_date_expr_fn(lua: &Lua) -> LuaResult<Function> {
90    lua.create_function(|_, s: String| Ok(is_date_expr(&s)))
91}
92
93/// Convert a Lua value to a string for use in template context.
94fn lua_value_to_string(key: &str, value: Value) -> LuaResult<String> {
95    match value {
96        Value::String(s) => Ok(s.to_str()?.to_string()),
97        Value::Integer(i) => Ok(i.to_string()),
98        Value::Number(n) => Ok(n.to_string()),
99        Value::Boolean(b) => Ok(b.to_string()),
100        Value::Nil => Ok(String::new()),
101        _ => Err(mlua::Error::runtime(format!(
102            "context value for '{}' must be string, number, boolean, or nil",
103            key
104        ))),
105    }
106}