Skip to main content

minijinja_lua/
state.rs

1// SPDX-License-Identifier: MIT
2
3use std::{
4    fmt,
5    ops::{Deref, DerefMut},
6};
7
8use minijinja::Value as JinjaValue;
9use mlua::LuaSerdeExt;
10
11use crate::convert::{
12    LuaAutoEscape,
13    LuaUndefinedBehavior,
14    lua_args_to_minijinja,
15    lua_to_minijinja,
16    minijinja_to_lua,
17};
18
19/// A [`mlua::UserData`] wrapper around a mutable [`minijinja::State`].
20///
21/// It can only be initialized within an [`mlua::Lua::scope`] callback, as it is not `'static`
22#[derive(Debug)]
23pub struct LuaState<'scope, 'template, 'env>(&'scope mut minijinja::State<'template, 'env>);
24
25impl<'scope, 'template, 'env> LuaState<'scope, 'template, 'env> {
26    fn state(&self) -> &minijinja::State<'template, 'env> {
27        self.0
28    }
29
30    fn state_mut(&mut self) -> &mut minijinja::State<'template, 'env> {
31        self.0
32    }
33}
34
35impl<'scope, 'template, 'env> From<&'scope mut minijinja::State<'template, 'env>>
36    for LuaState<'scope, 'template, 'env>
37{
38    fn from(value: &'scope mut minijinja::State<'template, 'env>) -> Self {
39        LuaState(value)
40    }
41}
42
43impl<'scope, 'template, 'env> From<LuaState<'scope, 'template, 'env>>
44    for &'scope mut minijinja::State<'template, 'env>
45{
46    fn from(value: LuaState<'scope, 'template, 'env>) -> Self {
47        value.0
48    }
49}
50
51impl<'scope, 'template, 'env> Deref for LuaState<'scope, 'template, 'env> {
52    type Target = minijinja::State<'template, 'env>;
53
54    fn deref(&self) -> &Self::Target {
55        self.0
56    }
57}
58
59impl<'scope, 'template, 'env> DerefMut for LuaState<'scope, 'template, 'env> {
60    fn deref_mut(&mut self) -> &mut Self::Target {
61        self.0
62    }
63}
64
65impl<'scope, 'template, 'env> fmt::Display for LuaState<'scope, 'template, 'env> {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "State")
68    }
69}
70
71impl<'scope, 'template, 'env> mlua::UserData for LuaState<'scope, 'template, 'env> {
72    fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
73        fields.add_meta_field("__name", "state");
74    }
75
76    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
77        // The name of the current template
78        methods.add_method("name", |_, this, ()| -> mlua::Result<String> {
79            Ok(this.state().name().to_string())
80        });
81
82        // The current auto escape flag
83        methods.add_method(
84            "auto_escape",
85            |_, this, ()| -> mlua::Result<LuaAutoEscape> { Ok(this.state().auto_escape().into()) },
86        );
87
88        // The current undefined behavior
89        methods.add_method(
90            "undefined_behavior",
91            |_, this, ()| -> mlua::Result<LuaUndefinedBehavior> {
92                Ok(this.state().undefined_behavior().into())
93            },
94        );
95
96        // The name of the current block
97        methods.add_method(
98            "current_block",
99            |_, this, ()| -> mlua::Result<Option<String>> {
100                Ok(this.state().current_block().map(|s| s.to_string()))
101            },
102        );
103
104        // Lookup a value by key in the current context
105        methods.add_method(
106            "lookup",
107            |lua, this, name: mlua::BorrowedStr| -> mlua::Result<mlua::MultiValue> {
108                // Since the context may contain dynamic objects, convert the returned value
109                // through the custom layer before returning.
110                Ok(this
111                    .state()
112                    .lookup(&name)
113                    .and_then(|v| minijinja_to_lua(lua, &v))
114                    .unwrap_or_default())
115            },
116        );
117
118        // Call the named macro with the provided args.
119        methods.add_method_mut(
120            "call_macro",
121            |lua,
122             this,
123             (name, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
124             -> mlua::Result<String> {
125                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);
126
127                this.state_mut()
128                    .call_macro(&name, &args)
129                    .map_err(mlua::Error::external)
130            },
131        );
132
133        // Render the named block
134        methods.add_method_mut(
135            "render_block",
136            |_, this, block: mlua::BorrowedStr| -> mlua::Result<String> {
137                this.state_mut()
138                    .render_block(&block)
139                    .map_err(mlua::Error::external)
140            },
141        );
142
143        // A list of exported variables
144        methods.add_method("exports", |_, this, ()| -> mlua::Result<Vec<String>> {
145            Ok(this
146                .state()
147                .exports()
148                .into_iter()
149                .map(|i| i.to_string())
150                .collect())
151        });
152
153        // A list of all known variables
154        methods.add_method(
155            "known_variables",
156            |_, this, ()| -> mlua::Result<Vec<String>> {
157                Ok(this
158                    .state()
159                    .known_variables()
160                    .into_iter()
161                    .map(|i| i.to_string())
162                    .collect())
163            },
164        );
165
166        // Apply the named filter with the provided args
167        methods.add_method_mut(
168            "apply_filter",
169            |lua,
170             this,
171             (filter, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
172             -> mlua::Result<mlua::MultiValue> {
173                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);
174
175                // Since the context may contain dynamic objects, convert the returned value
176                // through the custom layer before returning.
177                this.state_mut()
178                    .apply_filter(&filter, &args)
179                    .map(|v| minijinja_to_lua(lua, &v).unwrap_or_default())
180                    .map_err(mlua::Error::external)
181            },
182        );
183
184        // Perform the named test with the provided args
185        methods.add_method_mut(
186            "perform_test",
187            |lua,
188             this,
189             (test, mut args): (mlua::BorrowedStr, mlua::MultiValue)|
190             -> mlua::Result<bool> {
191                let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, &mut args, true);
192
193                this.state_mut()
194                    .perform_test(&test, &args)
195                    .map_err(mlua::Error::external)
196            },
197        );
198
199        // Format a value to a string
200        methods.add_method_mut(
201            "format",
202            |lua, this, val: mlua::Value| -> mlua::Result<String> {
203                let val = lua_to_minijinja(lua, &val).unwrap_or_default();
204
205                this.state_mut().format(val).map_err(mlua::Error::external)
206            },
207        );
208
209        // A tuple of the current and remaining fuel usage
210        methods.add_method(
211            "fuel_levels",
212            |lua, this, ()| -> mlua::Result<mlua::Value> {
213                lua.to_value(&this.state().fuel_levels())
214            },
215        );
216
217        // Get a temp value.
218        // See: https://docs.rs/minijinja/latest/minijinja/struct.State.html#method.get_temp
219        methods.add_method(
220            "get_temp",
221            |lua, this, name: mlua::BorrowedStr| -> mlua::Result<mlua::MultiValue> {
222                // Since the context may contain dynamic objects, convert the returned value
223                // through the custom layer before returning.
224                Ok(this
225                    .state()
226                    .get_temp(&name)
227                    .and_then(|v| minijinja_to_lua(lua, &v))
228                    .unwrap_or_default())
229            },
230        );
231
232        // Set a temp value and return the old value
233        methods.add_method_mut(
234            "set_temp",
235            |lua,
236             this,
237             (name, val): (mlua::BorrowedStr, mlua::Value)|
238             -> mlua::Result<mlua::MultiValue> {
239                if let Some(val) = lua_to_minijinja(lua, &val) {
240                    Ok(this
241                        .state_mut()
242                        .set_temp(&name, val)
243                        .and_then(|v| minijinja_to_lua(lua, &v))
244                        .unwrap_or_default())
245                } else {
246                    Err(mlua::Error::FromLuaConversionError {
247                        from: val.type_name(),
248                        to: "minijinja::Value".to_string(),
249                        message: None,
250                    })
251                }
252            },
253        );
254    }
255}