minijinja-lua 0.6.0

a minijinja lua module using the minijinja rust crate
Documentation
// SPDX-License-Identifier: MIT

use std::{
    fmt,
    ops::{Deref, DerefMut},
};

use minijinja::Value as JinjaValue;
use mlua::LuaSerdeExt;

use crate::convert::{
    LuaAutoEscape,
    LuaUndefinedBehavior,
    lua_args_to_minijinja,
    lua_to_minijinja,
    minijinja_to_lua,
};

/// A [`mlua::UserData`] wrapper around a mutable [`minijinja::State`].
///
/// It can only be initialized within an [`mlua::Lua::scope`] callback, as it is not `'static`
#[derive(Debug)]
pub struct LuaState<'scope, 'template, 'env>(&'scope mut minijinja::State<'template, 'env>);

impl<'scope, 'template, 'env> LuaState<'scope, 'template, 'env> {
    fn state(&self) -> &minijinja::State<'template, 'env> {
        self.0
    }

    fn state_mut(&mut self) -> &mut minijinja::State<'template, 'env> {
        self.0
    }
}

impl<'scope, 'template, 'env> From<&'scope mut minijinja::State<'template, 'env>>
    for LuaState<'scope, 'template, 'env>
{
    fn from(value: &'scope mut minijinja::State<'template, 'env>) -> Self {
        LuaState(value)
    }
}

impl<'scope, 'template, 'env> From<LuaState<'scope, 'template, 'env>>
    for &'scope mut minijinja::State<'template, 'env>
{
    fn from(value: LuaState<'scope, 'template, 'env>) -> Self {
        value.0
    }
}

impl<'scope, 'template, 'env> Deref for LuaState<'scope, 'template, 'env> {
    type Target = minijinja::State<'template, 'env>;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<'scope, 'template, 'env> DerefMut for LuaState<'scope, 'template, 'env> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.0
    }
}

impl<'scope, 'template, 'env> fmt::Display for LuaState<'scope, 'template, 'env> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "State")
    }
}

impl<'scope, 'template, 'env> mlua::UserData for LuaState<'scope, 'template, 'env> {
    fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
        fields.add_meta_field("__name", "state");
    }

    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
        // The name of the current template
        methods.add_method("name", |_, this, ()| -> mlua::Result<String> {
            Ok(this.state().name().to_string())
        });

        // The current auto escape flag
        methods.add_method(
            "auto_escape",
            |_, this, ()| -> mlua::Result<LuaAutoEscape> { Ok(this.state().auto_escape().into()) },
        );

        // The current undefined behavior
        methods.add_method(
            "undefined_behavior",
            |_, this, ()| -> mlua::Result<LuaUndefinedBehavior> {
                Ok(this.state().undefined_behavior().into())
            },
        );

        // The name of the current block
        methods.add_method(
            "current_block",
            |_, this, ()| -> mlua::Result<Option<String>> {
                Ok(this.state().current_block().map(|s| s.to_string()))
            },
        );

        // Lookup a value by key in the current context
        methods.add_method(
            "lookup",
            |lua, this, name: mlua::BorrowedStr| -> mlua::Result<mlua::MultiValue> {
                // Since the context may contain dynamic objects, convert the returned value
                // through the custom layer before returning.
                Ok(this
                    .state()
                    .lookup(&name)
                    .and_then(|v| minijinja_to_lua(lua, &v))
                    .unwrap_or_default())
            },
        );

        // Call the named macro with the provided args.
        methods.add_method_mut(
            "call_macro",
            |lua,
             this,
             (name, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
             -> mlua::Result<String> {
                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);

                this.state_mut()
                    .call_macro(&name, &args)
                    .map_err(mlua::Error::external)
            },
        );

        // Render the named block
        methods.add_method_mut(
            "render_block",
            |_, this, block: mlua::BorrowedStr| -> mlua::Result<String> {
                this.state_mut()
                    .render_block(&block)
                    .map_err(mlua::Error::external)
            },
        );

        // A list of exported variables
        methods.add_method("exports", |_, this, ()| -> mlua::Result<Vec<String>> {
            Ok(this
                .state()
                .exports()
                .into_iter()
                .map(|i| i.to_string())
                .collect())
        });

        // A list of all known variables
        methods.add_method(
            "known_variables",
            |_, this, ()| -> mlua::Result<Vec<String>> {
                Ok(this
                    .state()
                    .known_variables()
                    .into_iter()
                    .map(|i| i.to_string())
                    .collect())
            },
        );

        // Apply the named filter with the provided args
        methods.add_method_mut(
            "apply_filter",
            |lua,
             this,
             (filter, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
             -> mlua::Result<mlua::MultiValue> {
                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);

                // Since the context may contain dynamic objects, convert the returned value
                // through the custom layer before returning.
                this.state_mut()
                    .apply_filter(&filter, &args)
                    .map(|v| minijinja_to_lua(lua, &v).unwrap_or_default())
                    .map_err(mlua::Error::external)
            },
        );

        // Perform the named test with the provided args
        methods.add_method_mut(
            "perform_test",
            |lua,
             this,
             (test, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
             -> mlua::Result<bool> {
                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);

                this.state_mut()
                    .perform_test(&test, &args)
                    .map_err(mlua::Error::external)
            },
        );

        // Format a value to a string
        methods.add_method_mut(
            "format",
            |lua, this, val: mlua::Value| -> mlua::Result<String> {
                let val = lua_to_minijinja(lua, &val).unwrap_or_default();

                this.state_mut().format(val).map_err(mlua::Error::external)
            },
        );

        // A tuple of the current and remaining fuel usage
        methods.add_method(
            "fuel_levels",
            |lua, this, ()| -> mlua::Result<mlua::Value> {
                lua.to_value(&this.state().fuel_levels())
            },
        );

        // Get a temp value.
        // See: https://docs.rs/minijinja/latest/minijinja/struct.State.html#method.get_temp
        methods.add_method(
            "get_temp",
            |lua, this, name: mlua::BorrowedStr| -> mlua::Result<mlua::MultiValue> {
                // Since the context may contain dynamic objects, convert the returned value
                // through the custom layer before returning.
                Ok(this
                    .state()
                    .get_temp(&name)
                    .and_then(|v| minijinja_to_lua(lua, &v))
                    .unwrap_or_default())
            },
        );

        // Set a temp value and return the old value
        methods.add_method_mut(
            "set_temp",
            |lua,
             this,
             (name, val): (mlua::BorrowedStr, mlua::Value)|
             -> mlua::Result<mlua::MultiValue> {
                if let Some(val) = lua_to_minijinja(lua, &val) {
                    Ok(this
                        .state_mut()
                        .set_temp(&name, val)
                        .and_then(|v| minijinja_to_lua(lua, &v))
                        .unwrap_or_default())
                } else {
                    Err(mlua::Error::FromLuaConversionError {
                        from: val.type_name(),
                        to: "minijinja::Value".to_string(),
                        message: None,
                    })
                }
            },
        );
    }
}