nvim_utils/
builder.rs

1//! Builder pattern for creating lua modules in a readable, declarative way
2
3use std::collections::HashMap;
4
5use crate::prelude::*;
6
7#[cfg(feature = "async")]
8#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
9use std::future::Future;
10
11/// Wraps some of the boilerplate for building a lua module using `mlua` in a nice builder pattern.<br>
12/// Includes functions for operating on a reference to the builder, as well as for consuming the builder.
13///
14/// # Examples
15/// ```rust
16/// use nvim_utils::prelude::*; // imports mlua::prelude::* and the `vim` module
17/// use nvim_utils::builder::ModuleBuilder;
18///
19/// fn my_plugin(lua: &Lua) -> LuaResult<LuaTable> {
20///     // A method that adds two numbers and pushes the result to a table
21///     let add = |lua: &Lua, (this, a, b): (LuaTable, i32, i32)| -> LuaResult<()> {
22///         let results = this.get::<_, LuaTable>("results")?;
23///         results.push(a + b)?;
24///         Ok(())
25///     };
26///
27///     // Consuming the builder
28///     let module = ModuleBuilder::new(lua)
29///         .with_table_empty("results")?
30///         .with_fn("add", add)?
31///         .build()?;
32///
33///     // Using a mutable reference to the builder
34///     let mut builder = ModuleBuilder::new(lua);
35///     builder.add_table_empty("results")?;
36///     builder.add_fn("add", add)?;
37///     let module = builder.build()?;
38///
39///     // If you need to return a LuaValue instead of a LuaTable, you can use mlua's `to_lua` method instead of `build`
40///     // let value = builder.to_lua(lua)?;
41///
42///     Ok(module)
43/// }
44/// ```
45#[derive(Debug)]
46pub struct ModuleBuilder<'a> {
47    fields: HashMap<String, LuaValue<'a>>,
48    lua: &'a Lua,
49}
50
51impl<'a> ModuleBuilder<'a> {
52    /// Creates a new module builder
53    pub fn new(lua: &'a Lua) -> Self {
54        Self {
55            fields: HashMap::new(),
56            lua,
57        }
58    }
59
60    fn check_collision(&self, name: &str) -> LuaResult<()> {
61        if self.fields.contains_key(name) {
62            Err(LuaError::RuntimeError(format!(
63                "Module already contains a field named {}",
64                name
65            )))
66        } else {
67            Ok(())
68        }
69    }
70
71    /// Produces an iterator over the fields in the builder
72    pub fn fields(&'a self) -> impl Iterator<Item = (&String, &LuaValue)> {
73        self.fields.iter()
74    }
75
76    /// Produces a mutable iterator over the fields in the builder
77    pub fn fields_mut(&'a mut self) -> impl Iterator<Item = (&String, &mut LuaValue)> {
78        self.fields.iter_mut()
79    }
80
81    /// Adds a function to the module
82    pub fn add_fn<A, R, F>(&mut self, name: &str, func: F) -> LuaResult<&mut Self>
83    where
84        F: 'static + Send + Fn(&'a Lua, A) -> LuaResult<R>,
85        A: FromLuaMulti<'a>,
86        R: ToLuaMulti<'a>,
87    {
88        self.check_collision(name)?;
89        let func = self.lua.create_function(func)?;
90        self.fields.insert(name.to_owned(), self.lua.pack(func)?);
91        Ok(self)
92    }
93
94    #[cfg(feature = "async")]
95    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
96    /// Adds an async function to the module
97    pub fn add_fn_async<A, R, F, FR>(&mut self, name: &str, func: F) -> LuaResult<&mut Self>
98    where
99        F: 'static + Send + Fn(&'a Lua, A) -> FR,
100        A: FromLuaMulti<'a>,
101        R: ToLuaMulti<'a>,
102        FR: 'a + Send + Future<Output = LuaResult<R>>,
103    {
104        self.check_collision(name)?;
105        let func = self.lua.create_async_function(func)?;
106        self.fields.insert(name.to_owned(), self.lua.pack(func)?);
107        Ok(self)
108    }
109
110    /// Adds a C function to the module
111    pub fn add_c_fn(&mut self, name: &str, func: mlua::lua_CFunction) -> LuaResult<&mut Self> {
112        self.check_collision(name)?;
113        unsafe {
114            let func = self.lua.create_c_function(func)?;
115            self.fields.insert(name.to_owned(), self.lua.pack(func)?);
116        }
117        Ok(self)
118    }
119
120    /// Adds a table to the module
121    pub fn add_table(&mut self, name: &str, table: LuaTable<'a>) -> LuaResult<&mut Self> {
122        self.check_collision(name)?;
123        self.fields.insert(name.to_owned(), self.lua.pack(table)?);
124        Ok(self)
125    }
126
127    /// Adds a table to the module from an iterator
128    pub fn add_table_from<K, V, I>(&mut self, name: &str, iterator: I) -> LuaResult<&mut Self>
129    where
130        K: ToLua<'a>,
131        V: ToLua<'a>,
132        I: IntoIterator<Item = (K, V)>,
133    {
134        self.check_collision(name)?;
135        self.fields.insert(
136            name.to_owned(),
137            self.lua.pack(self.lua.create_table_from(iterator)?)?,
138        );
139        Ok(self)
140    }
141
142    /// Adds an empty table to the module
143    pub fn add_table_empty(&mut self, name: &str) -> LuaResult<&mut Self> {
144        self.check_collision(name)?;
145        let table = self.lua.create_table()?;
146        self.fields.insert(name.to_owned(), self.lua.pack(table)?);
147        Ok(self)
148    }
149
150    /// Adds a lua value (any) to the module
151    pub fn add_value(&mut self, name: &str, value: impl ToLua<'a>) -> LuaResult<&mut Self> {
152        self.check_collision(name)?;
153        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
154        Ok(self)
155    }
156
157    /// Adds a string to the module
158    pub fn add_string(&mut self, name: &str, value: &str) -> LuaResult<&mut Self> {
159        self.check_collision(name)?;
160        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
161        Ok(self)
162    }
163
164    /// Adds an integer to the module
165    pub fn add_int(&mut self, name: &str, value: i64) -> LuaResult<&mut Self> {
166        self.check_collision(name)?;
167        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
168        Ok(self)
169    }
170
171    /// Adds a number to the module
172    pub fn add_float(&mut self, name: &str, value: f64) -> LuaResult<&mut Self> {
173        self.check_collision(name)?;
174        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
175        Ok(self)
176    }
177
178    /// Adds a boolean to the module
179    pub fn add_bool(&mut self, name: &str, value: bool) -> LuaResult<&mut Self> {
180        self.check_collision(name)?;
181        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
182        Ok(self)
183    }
184
185    /// Adds a function to the module, consuming and returning the builder
186    pub fn with_fn<A, R, F>(mut self, name: &str, func: F) -> LuaResult<Self>
187    where
188        F: 'static + Send + Fn(&'a Lua, A) -> LuaResult<R>,
189        A: FromLuaMulti<'a>,
190        R: ToLuaMulti<'a>,
191    {
192        self.check_collision(name)?;
193        let func = self.lua.create_function(func)?;
194        self.fields.insert(name.to_owned(), self.lua.pack(func)?);
195        Ok(self)
196    }
197
198    #[cfg(feature = "async")]
199    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
200    /// Adds an async function to the module, consuming and returning the builder
201    pub fn with_fn_async<A, R, F, FR>(mut self, name: &str, func: F) -> LuaResult<Self>
202    where
203        F: 'static + Send + Fn(&'a Lua, A) -> FR,
204        A: FromLuaMulti<'a>,
205        R: ToLuaMulti<'a>,
206        FR: 'a + Send + Future<Output = LuaResult<R>>,
207    {
208        self.check_collision(name)?;
209        let func = self.lua.create_async_function(func)?;
210        self.fields.insert(name.to_owned(), self.lua.pack(func)?);
211        Ok(self)
212    }
213
214    /// Adds a C function to the module, consuming and returning the builder
215    pub fn with_c_fn(mut self, name: &str, func: mlua::lua_CFunction) -> LuaResult<Self> {
216        self.check_collision(name)?;
217        unsafe {
218            let func = self.lua.create_c_function(func)?;
219            self.fields.insert(name.to_owned(), self.lua.pack(func)?);
220        }
221        Ok(self)
222    }
223
224    /// Adds a table to the module, consuming and returning the builder
225    pub fn with_table(mut self, name: &str, table: LuaTable<'a>) -> LuaResult<Self> {
226        self.check_collision(name)?;
227        self.fields.insert(name.to_owned(), self.lua.pack(table)?);
228        Ok(self)
229    }
230
231    /// Adds a table to the module from an iterator, consuming and returning the builder
232    pub fn with_table_from<K, V, I>(mut self, name: &str, iterator: I) -> LuaResult<Self>
233    where
234        K: ToLua<'a>,
235        V: ToLua<'a>,
236        I: IntoIterator<Item = (K, V)>,
237    {
238        self.check_collision(name)?;
239        self.fields.insert(
240            name.to_owned(),
241            self.lua.pack(self.lua.create_table_from(iterator)?)?,
242        );
243        Ok(self)
244    }
245
246    /// Adds an empty table to the module, consuming and returning the builder
247    pub fn with_table_empty(mut self, name: &str) -> LuaResult<Self> {
248        self.check_collision(name)?;
249        let table = self.lua.create_table()?;
250        self.fields.insert(name.to_owned(), self.lua.pack(table)?);
251        Ok(self)
252    }
253
254    /// Adds a lua value (any) to the module, consuming and returning the builder
255    pub fn with_value(mut self, name: &str, value: impl ToLua<'a>) -> LuaResult<Self> {
256        self.check_collision(name)?;
257        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
258        Ok(self)
259    }
260
261    /// Adds a string to the module, consuming and returning the builder
262    pub fn with_string(mut self, name: &str, value: &str) -> LuaResult<Self> {
263        self.check_collision(name)?;
264        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
265        Ok(self)
266    }
267
268    /// Adds an integer to the module, consuming and returning the builder
269    pub fn with_int(mut self, name: &str, value: i64) -> LuaResult<Self> {
270        self.check_collision(name)?;
271        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
272        Ok(self)
273    }
274
275    /// Adds a number to the module, consuming and returning the builder
276    pub fn with_float(mut self, name: &str, value: f64) -> LuaResult<Self> {
277        self.check_collision(name)?;
278        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
279        Ok(self)
280    }
281
282    /// Adds a boolean to the module, consuming and returning the builder
283    pub fn with_bool(&mut self, name: &str, value: bool) -> LuaResult<&mut Self> {
284        self.check_collision(name)?;
285        self.fields.insert(name.to_owned(), value.to_lua(self.lua)?);
286        Ok(self)
287    }
288
289    /// Consumes the builder and returns the module as a table
290    pub fn build(self) -> LuaResult<LuaTable<'a>> {
291        let module = self.lua.create_table()?;
292        for (name, value) in self.fields {
293            module.set(name, value)?;
294        }
295        Ok(module)
296    }
297}
298
299impl<'a> ToLua<'a> for ModuleBuilder<'a> {
300    fn to_lua(self, lua: &'a Lua) -> LuaResult<LuaValue<'a>> {
301        Ok(lua.pack(self.build()?)?)
302    }
303}