mlua-extras 11.6.1

Extra helpers and functionality built on top of mlua for embedded lua development
docs.rs failed to build mlua-extras-11.6.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: mlua-extras-0.0.7

MLua Extras

[!NOTE] Feel free to use this crate and start working with ideas and features that could be useful.

Pull requests and contribution are encouraged

If you want to discuss this project, you can do that here


The goal of this project is to add a light convenience layer wrapping the mlua crate. The goal isn't to change the way that mlua is used, but instead to make lua embedded development in Rust more enjoyable.

Similar Projects

  • Tealr: A project to enhance and extend mlua with a focus in type information and documentation along with a type syntax in the lua code itself with the tealr syntax.
    • This crate is a great choice if you need: type syntax, type information, documentation generation

Features

  • Helper Traits

    • Require
      • call require which allows for a lua style call to get data from the lua engine.
      • ex: table.require::<String>("nested.tables.name")? == local name = require('nested.tables').name
    • Module
      • Syntax and API mimics UserData adding fields, functions and methods. Instead of createing a lua userdata type, it adds these values to a lua table.
      • This allows for all the data to be held in lua
      • This allows for a complex lua api without creating a bunch of userdata objects
    • LuaExtras
      • Manipulate the lua path and cpath variables with append, prepend, and set methods for each variant. It also includes the ability to add multiple paths with each variant.
      • Set global variables and functions with set_global("value", "value") and set_global_function("func", |lua, ()| Ok(())) which wold replace lua.globals().set("value", "value) and lua.globals().set("func", lua.create_function(|lua, ()| Ok(()))?) respectively
  • Typed Lua Traits

    • Typed
      • Generate a Type and Param for a rust type so it can be used both as a type and as a parameter for a function
    • TypedUserData
      • Typed variant of mlua::UserData with an additional add_documentation method to add doc comments to the UserData type
      • An extra document method is added to the TypedDataFields and TypedDataMethods for add_fields and add_methods. This will queue doc comments to be added to the next field or method that is added.
      • All types from function parameters and and return types are stored for fields, functions, and methods.
      • This trait is mainly used when generating type definitions. If it is called through the UserData derive macro it will ignore all types and documentation
    • TypedDataFields: Implemented on a generator for TypedUserData (add_fields)
    • TypedDataMethods: Implemented on a generator for TypedUserData (add_methods)
    • TypedDataDocumentation: Implemented on a generator for TypedUserData (add_documentation)
  • Derive Macros

    • Typed: Auto implement the Typed trait to get type information for both struct and enum
    • UserData: Auto implement the mlua::UserData trait for rust types that also implement TypedUserData. This will pass through the UserData add_methods and add_fields to the TypedUserData's version. This will ignore all documentation and types.
  • Macros

    • function: Write lua functions more like Rust's syntax

    Instead of this:

    lua.create_function(|lua, ()| Ok(())) 
    

    You can now write this:

    function!{
        lua fn name(lua) {
            Ok(())
        }
    }
    

    The difference isn't huge, but it could make the syntax more readable.

    [!NOTE] This also helps with assigning functions to nested tables.

    [!WARNING] This requires the LuaExtras trait when adding functions to nested tables with lua as the starting point. This requires the Require trait when starting from any other table.

    lua.require::<Table>("nested.fn").set("name", lua.create_function(|lua, ()| Ok(())));
    
    // vs
    
    function! {
        lua fn lua::nested::fn.name(lua) {
            Ok(())
        }
    }
    

Ideas and Planned Features

  • Fully featured definition file generation
  • Fully featured documentation generation
  • Fully featured addon generator when creating a lua modules with mlua's module feature
  • Better and more informative type errors associated with lua type definitions and output generation
  • More expressive way of defining exposed lua api types
    • Generic types
    • Doc comments for params and return types

References

Example Syntax

Helpers

use mlua::{Lua, Table, Function, Variadic, Value};

fn main() -> mlua::Result<()> {
    let lua = Lua::new();

    // Prepend path to the lua `path`
    let path = lua.globals().get::<Table>("package")?.get::<String>("path");
    lua.globals().get::<Table>("package")?.set("path", format!("?.lua;{path}"))?;

    let temp = lua.create_table()?;
    temp.set("getName", lua.create_function(|lua, ()| Ok("name"))?;

    // Get a nested function: `table.unpack`
    let unpack = lua.globals().get::<Table>("table")?.get::<_, Function>("unpack")?;
    // Call the `table.unpack` function
    let _ = unpack.call::<Variadic<Value>>(temp)?;
    Ok(())
}
use mlua_extras::{
    mlua::{self, Lua, Table, Variadic, Value}
    extras::{Require, LuaExtras},
    typed::TypedFunction,
    function,
};

fn main() -> mlua::Result<()> {
    let lua = Lua::new();

    // Prepend path to the lua `path`
    lua.prepend_path("?.lua")?;

    let temp = lua.create_table()?;
    function! {
        lua fn temp.name(lua) {
            Ok("name")
        }
    }

    // Get a nested function: `table.unpack`
    let unpack = lua.require::<TypedFunction<Table, Variadic<Value>>>("table.unpack")?;
    // Call the `table.unpack` function
    let _ = unpack.call(temp)?;
    Ok(())
}

Types

use serde::Deserialize;
use mlua_extras::{
    mlua::{self, Lua, Table, Variadic, Value},
    extras::{ Require, LuaExtras },
    typed::{
        generator::{Definition, Definitions, DefinitionFileGenerator},
        TypedFunction, TypedUserData
    },
    Typed, UserData, function,
};

#[derive(Default, Debug, Clone, Copy, Typed, Deserialize)]
enum SystemColor {
    #[default]
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Cyan,
    Magenta,
    White,
}

#[derive(Debug, Clone, Copy, Typed, UserData, Deserialize)]
#[serde(untagged)]
enum Color {
    System(SystemColor),
    Xterm(u8),
    Rgb(u8, u8, u8),
}
impl Default for Color {
    fn default() -> Self {
        Color::System(SystemColor::default())
    }
}
impl<'lua> FromLua<'lua> for Color {
    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> mlua::prelude::LuaResult<Self> {
        match value {
            Value::UserData(data) => data.borrow::<Self>().map(|v| *v),
            // Use serde deserialize if not userdata
            other => lua.from_value(other),
        }
    }
}

#[derive(Debug, Clone, Copy, Typed, UserData, Deserialize)]
struct Example {
    color: Color
}
impl TypedUserData for Example {
    fn add_documentation<F: mlua_extras::typed::TypedDataDocumentation<Self>>(docs: &mut F) {
        docs.add("This is a doc comment section for the overall type");
    }

    fn add_fields<'lua, F: TypedDataFields<'lua, Self>>(fields: &mut F) {
        fields
            .document("Example complex type")
            .add_field_method_get_set(
                "color",
                |_lua, this| Ok(this.color),
                |_lua, this, clr: Color| {
                    this.color = clr;
                    Ok(())
                },
            );
    }
}


fn main() -> mlua::Result<()> {
    let definitions = Definitions::generate()
        .define("init", Definition::generate()
            .register::<SystemColor>("System")?
            .register::<Color>("Color")?
            .register::<Example>("Example")
            .value_with::<Example, _>("example", Some("Example module"))
            .function::<Color, ()>("printColor", ())
            .function_with::<String, (), _>("greet", (), |func| {
                func.document("Greet the name that was passed in");
                func.param(0, |param| param.name("name").doc("Name of the person to greet"));
            })
        )
        .finish();

    let gen = DefinitionFileGenerator::new(definitions);
    for (name, writer) in gen.iter() {
        // Writes to a new file `init.d.lua`
        writer.write_file(name).unwrap();
    }
    println!();
    Ok(())
}

Produces the following definition file

--- init.d.lua
--- @meta

--- @alias System "Black"
---  | "Red"
---  | "Green"
---  | "Yellow"
---  | "Blue"
---  | "Cyan"
---  | "Magenta"
---  | "White"

--- @alias Color SystemColor
---  | integer
---  | [integer, integer, integer]

--- This is a doc comment section for the overall type
--- @class Example
--- Example complex type
--- @field color Color

--- Example module
--- @type Example
example = nil

--- Greet the name that was passed in
--- @param name string Name of the person to greet
function greet(name) end

--- @param param0 Color
function printColor(param0) end