With the luau feature, dream-ini is an l3i extension: it
describes the @dream/ini module, and a Rust host that runs Luau through l3i composes it into its
runtime. Scripts then import with the same importer as the command line. The crate never creates a
VM and never installs a global; both are the host’s decisions.
Dependencies and toolchain#
[dependencies]
dream-ini = { version = "0.4", default-features = false, features = ["luau"] }
l3i = "0.1"
default-features = false leaves out the GUI. l3i builds Luau itself, and only with clang, lld
and cross-language thin LTO: its build script refuses any other configuration and names the
missing piece. Cargo does not pass a dependency’s configuration on, so the host copies the policy
into its own .cargo/config.toml, as dream-ini does:
[env]
CXX = "clang++"
[target.x86_64-unknown-linux-gnu]
rustflags = ["-Clinker-plugin-lto", "-Clinker=clang", "-Clink-arg=-fuse-ld=lld"]
clang and rustc must use the same LLVM major version. The l3i toolchain notes have the lines for macOS and Windows, and the measurements behind the rule.
Composing the extension#
IniExtension goes into the host’s RuntimePlan, next to whatever else the host provides. Every
runtime made from the plan can require("@dream/ini"):
use dream_ini::luau::IniExtension;
use l3i::Runtime;
use l3i::extension::RuntimePlan;
fn main() -> l3i::Result<()> {
let plan = RuntimePlan::builder().extension(IniExtension).finalize()?;
let runtime = Runtime::from_plan(&plan)?;
runtime.exec(r#"
local ini = require("@dream/ini")
local cfg = ini.parseCfg("content=Morrowind.esm\n")
assert(cfg.content[1] == "Morrowind.esm")
"#)
}The dreamIni global#
Scripts written for 0.3 and earlier used a dreamIni global. A host that still wants one exposes
the module as a compatibility global through its policy; the global and require then return the
same table:
use dream_ini::luau::{IniExtension, MODULE};
use l3i::Runtime;
use l3i::extension::{RuntimePlan, RuntimePolicy};
fn main() -> l3i::Result<()> {
let policy = RuntimePolicy::new().compat_global(MODULE, "dreamIni");
let plan = RuntimePlan::builder().policy(policy).extension(IniExtension).finalize()?;
let runtime = Runtime::from_plan(&plan)?;
runtime.exec(r#"assert(dreamIni == require("@dream/ini"))"#)
}
MODULE is "@dream/ini".
Types for editors and checks#
Every function carries a Luau signature. plan.type_definitions() returns the .d.luau text for
everything in the plan, @dream/ini included, ready to save for an editor’s language server.
With l3i’s analysis feature, plan.check_definitions() type-checks those definitions; dream-ini’s
tests run it under the luau-analysis feature (cargo test --features luau-analysis), and
type-check a strict script against the module. The
module page shows what it generates.
What scripts get#
- Tables in, tables out. No userdata: a cfg or a parsed INI is a plain table of string arrays, which scripts can read, change and pass back.
- Strict options. A misspelled or
snake_caseoption is an error that lists the right names. - The disk, read-only.
importPathsreads the INI, the cfg and the folders it searches. Nothing in the module writes a file; the host decides what happens to the result. - UTF-8 paths. Paths in and out are strings; paths that are not valid UTF-8 are outside what the module supports.
The Luau API lists every function.
From 0.3#
lua::create_module and lua::register are gone, with mlua. Compose IniExtension as above.
The functions, their names and their results are the same, with three changes:
- Multimap values must be strings;
mluaturned numbers into strings. - Errors name the function and list the known options:
ini.importMaps: unknown option 'user_data'; known options are .... Import errors readdream.ini: <message>. parseInialso takes abuffer.