From 6b14bc3dc01f1bda6c2896167be2a3b63c31ed07 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sun, 22 Jan 2023 15:23:56 -0500 Subject: [PATCH] Pass ownership to global constructors to avoid lifetime issues --- src/cli/cli.rs | 5 +- src/lib/globals/console.rs | 52 ++++++++++--------- src/lib/globals/fs.rs | 28 ++++++----- src/lib/globals/mod.rs | 10 ++-- src/lib/globals/net.rs | 18 ++++--- src/lib/globals/process.rs | 18 ++++--- src/lib/globals/task.rs | 34 ++++--------- src/lib/lib.rs | 100 ++++++++++++++++++++++++++++--------- 8 files changed, 160 insertions(+), 105 deletions(-) diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 18b5a8f..d9bc98b 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -3,7 +3,7 @@ use std::fs::read_to_string; use anyhow::Result; use clap::{CommandFactory, Parser}; -use lune::run_lune; +use lune::Lune; use crate::utils::{files::find_parse_file_path, github::Client as GithubClient}; @@ -96,7 +96,8 @@ impl Cli { // Display the file path relative to cwd with no extensions in stack traces let file_display_name = file_path.with_extension("").display().to_string(); // Create a new lune object with all globals & run the script - if let Err(e) = run_lune(&file_display_name, &file_contents, self.script_args).await { + let lune = Lune::new().with_args(self.script_args).with_all_globals(); + if let Err(e) = lune.run(&file_display_name, &file_contents).await { eprintln!("{e}"); std::process::exit(1); }; diff --git a/src/lib/globals/console.rs b/src/lib/globals/console.rs index e030a29..14b543f 100644 --- a/src/lib/globals/console.rs +++ b/src/lib/globals/console.rs @@ -1,11 +1,11 @@ -use mlua::{Lua, MultiValue, Result, Table}; +use mlua::{Lua, MultiValue, Result}; use crate::utils::{ formatting::{flush_stdout, pretty_format_multi_value, print_color, print_label, print_style}, table_builder::ReadonlyTableBuilder, }; -pub async fn new(lua: &Lua) -> Result { +pub async fn create(lua: Lua) -> Result { let print = |args: &MultiValue, throw: bool| -> Result<()> { let s = pretty_format_multi_value(args)?; if throw { @@ -16,26 +16,30 @@ pub async fn new(lua: &Lua) -> Result
{ flush_stdout()?; Ok(()) }; - ReadonlyTableBuilder::new(lua)? - .with_function("resetColor", |_, _: ()| print_color("reset"))? - .with_function("setColor", |_, color: String| print_color(color))? - .with_function("resetStyle", |_, _: ()| print_style("reset"))? - .with_function("setStyle", |_, style: String| print_style(style))? - .with_function("format", |_, args: MultiValue| { - pretty_format_multi_value(&args) - })? - .with_function("log", move |_, args: MultiValue| print(&args, false))? - .with_function("info", move |_, args: MultiValue| { - print_label("info")?; - print(&args, false) - })? - .with_function("warn", move |_, args: MultiValue| { - print_label("warn")?; - print(&args, false) - })? - .with_function("error", move |_, args: MultiValue| { - print_label("error")?; - print(&args, true) - })? - .build() + lua.globals().raw_set( + "console", + ReadonlyTableBuilder::new(&lua)? + .with_function("resetColor", |_, _: ()| print_color("reset"))? + .with_function("setColor", |_, color: String| print_color(color))? + .with_function("resetStyle", |_, _: ()| print_style("reset"))? + .with_function("setStyle", |_, style: String| print_style(style))? + .with_function("format", |_, args: MultiValue| { + pretty_format_multi_value(&args) + })? + .with_function("log", move |_, args: MultiValue| print(&args, false))? + .with_function("info", move |_, args: MultiValue| { + print_label("info")?; + print(&args, false) + })? + .with_function("warn", move |_, args: MultiValue| { + print_label("warn")?; + print(&args, false) + })? + .with_function("error", move |_, args: MultiValue| { + print_label("error")?; + print(&args, true) + })? + .build()?, + )?; + Ok(lua) } diff --git a/src/lib/globals/fs.rs b/src/lib/globals/fs.rs index 9afd785..ab3c1b1 100644 --- a/src/lib/globals/fs.rs +++ b/src/lib/globals/fs.rs @@ -1,21 +1,25 @@ use std::path::{PathBuf, MAIN_SEPARATOR}; -use mlua::{Lua, Result, Table}; +use mlua::{Lua, Result}; use tokio::fs; use crate::utils::table_builder::ReadonlyTableBuilder; -pub async fn new(lua: &Lua) -> Result
{ - ReadonlyTableBuilder::new(lua)? - .with_async_function("readFile", fs_read_file)? - .with_async_function("readDir", fs_read_dir)? - .with_async_function("writeFile", fs_write_file)? - .with_async_function("writeDir", fs_write_dir)? - .with_async_function("removeFile", fs_remove_file)? - .with_async_function("removeDir", fs_remove_dir)? - .with_async_function("isFile", fs_is_file)? - .with_async_function("isDir", fs_is_dir)? - .build() +pub async fn create(lua: Lua) -> Result { + lua.globals().raw_set( + "fs", + ReadonlyTableBuilder::new(&lua)? + .with_async_function("readFile", fs_read_file)? + .with_async_function("readDir", fs_read_dir)? + .with_async_function("writeFile", fs_write_file)? + .with_async_function("writeDir", fs_write_dir)? + .with_async_function("removeFile", fs_remove_file)? + .with_async_function("removeDir", fs_remove_dir)? + .with_async_function("isFile", fs_is_file)? + .with_async_function("isDir", fs_is_dir)? + .build()?, + )?; + Ok(lua) } async fn fs_read_file(_: &Lua, path: String) -> Result { diff --git a/src/lib/globals/mod.rs b/src/lib/globals/mod.rs index 4ecc1c5..28ba9f0 100644 --- a/src/lib/globals/mod.rs +++ b/src/lib/globals/mod.rs @@ -4,8 +4,8 @@ mod net; mod process; mod task; -pub use console::new as new_console; -pub use fs::new as new_fs; -pub use net::new as new_net; -pub use process::new as new_process; -pub use task::new as new_task; +pub use console::create as create_console; +pub use fs::create as create_fs; +pub use net::create as create_net; +pub use process::create as create_process; +pub use task::create as create_task; diff --git a/src/lib/globals/net.rs b/src/lib/globals/net.rs index 2fea6e3..a1c5724 100644 --- a/src/lib/globals/net.rs +++ b/src/lib/globals/net.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, str::FromStr}; -use mlua::{Error, Lua, LuaSerdeExt, Result, Table, Value}; +use mlua::{Error, Lua, LuaSerdeExt, Result, Value}; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, Method, @@ -8,12 +8,16 @@ use reqwest::{ use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder}; -pub async fn new(lua: &Lua) -> Result
{ - ReadonlyTableBuilder::new(lua)? - .with_function("jsonEncode", net_json_encode)? - .with_function("jsonDecode", net_json_decode)? - .with_async_function("request", net_request)? - .build() +pub async fn create(lua: Lua) -> Result { + lua.globals().raw_set( + "net", + ReadonlyTableBuilder::new(&lua)? + .with_function("jsonEncode", net_json_encode)? + .with_function("jsonDecode", net_json_decode)? + .with_async_function("request", net_request)? + .build()?, + )?; + Ok(lua) } fn net_json_encode(_: &Lua, (val, pretty): (Value, Option)) -> Result { diff --git a/src/lib/globals/process.rs b/src/lib/globals/process.rs index 314c190..b5e472a 100644 --- a/src/lib/globals/process.rs +++ b/src/lib/globals/process.rs @@ -9,7 +9,7 @@ use tokio::process::Command; use crate::utils::table_builder::ReadonlyTableBuilder; -pub async fn new(lua: &Lua, args_vec: Vec) -> Result
{ +pub async fn create(lua: Lua, args_vec: Vec) -> Result { // Create readonly args array let inner_args = lua.create_table()?; for arg in &args_vec { @@ -36,12 +36,16 @@ pub async fn new(lua: &Lua, args_vec: Vec) -> Result
{ inner_env.set_metatable(Some(inner_env_meta)); inner_env.set_readonly(true); // Create the full process table - ReadonlyTableBuilder::new(lua)? - .with_table("args", inner_args)? - .with_table("env", inner_env)? - .with_function("exit", process_exit)? - .with_async_function("spawn", process_spawn)? - .build() + lua.globals().raw_set( + "process", + ReadonlyTableBuilder::new(&lua)? + .with_table("args", inner_args)? + .with_table("env", inner_env)? + .with_function("exit", process_exit)? + .with_async_function("spawn", process_spawn)? + .build()?, + )?; + Ok(lua) } fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result> { diff --git a/src/lib/globals/task.rs b/src/lib/globals/task.rs index 52ebf45..1a74252 100644 --- a/src/lib/globals/task.rs +++ b/src/lib/globals/task.rs @@ -1,40 +1,26 @@ use std::time::Duration; -use mlua::{Function, Lua, Result, Table, Value}; +use mlua::{Lua, Result}; use tokio::time; use crate::utils::table_builder::ReadonlyTableBuilder; const DEFAULT_SLEEP_DURATION: f32 = 1.0 / 60.0; -const TASK_LIB_LUAU: &str = include_str!("../luau/task.luau"); - -pub async fn new(lua: &Lua) -> Result
{ - let task_lib: Table = lua - .load(TASK_LIB_LUAU) - .set_name("task")? - .eval_async() - .await?; - // FUTURE: Properly implementing the task library in async rust is - // very complicated but should be done at some point, for now we will - // fall back to implementing only task.wait and doing the rest in lua - let task_cancel: Function = task_lib.raw_get("cancel")?; - let task_defer: Function = task_lib.raw_get("defer")?; - let task_delay: Function = task_lib.raw_get("delay")?; - let task_spawn: Function = task_lib.raw_get("spawn")?; - ReadonlyTableBuilder::new(lua)? - .with_value("cancel", Value::Function(task_cancel))? - .with_value("defer", Value::Function(task_defer))? - .with_value("delay", Value::Function(task_delay))? - .with_value("spawn", Value::Function(task_spawn))? - .with_async_function("wait", wait)? - .build() +pub async fn create(lua: Lua) -> Result { + lua.globals().raw_set( + "task", + ReadonlyTableBuilder::new(&lua)? + .with_async_function("wait", task_wait)? + .build()?, + )?; + Ok(lua) } // FIXME: It does seem possible to properly make an async wait // function with mlua right now, something breaks when using // async wait functions inside of coroutines -async fn wait(_: &Lua, duration: Option) -> Result { +async fn task_wait(_: &Lua, duration: Option) -> Result { let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION); time::sleep(Duration::from_secs_f32(secs)).await; Ok(secs) diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 4318a40..f45f2de 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use anyhow::{bail, Result}; use mlua::Lua; @@ -5,38 +7,85 @@ pub mod globals; pub mod utils; use crate::{ - globals::{new_console, new_fs, new_net, new_process, new_task}, + globals::{create_console, create_fs, create_net, create_process, create_task}, utils::formatting::{format_label, pretty_format_luau_error}, }; -pub async fn run_lune(name: &str, chunk: &str, args: Vec) -> Result<()> { - let lua = Lua::new(); - lua.sandbox(true)?; - // Add in all globals - { - let globals = lua.globals(); - globals.raw_set("console", new_console(&lua).await?)?; - globals.raw_set("fs", new_fs(&lua).await?)?; - globals.raw_set("net", new_net(&lua).await?)?; - globals.raw_set("process", new_process(&lua, args.clone()).await?)?; - globals.raw_set("task", new_task(&lua).await?)?; - globals.set_readonly(true); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum LuneGlobal { + Console, + Fs, + Net, + Process, + Task, +} + +impl LuneGlobal { + pub fn get_all() -> Vec { + vec![ + Self::Console, + Self::Fs, + Self::Net, + Self::Process, + Self::Task, + ] } - // Run the requested chunk asynchronously - let result = lua.load(chunk).set_name(name)?.exec_async().await; - match result { - Ok(_) => Ok(()), - Err(e) => bail!( - "\n{}\n{}", - format_label("ERROR"), - pretty_format_luau_error(&e) - ), +} + +#[derive(Clone, Debug, Default)] +pub struct Lune { + globals: HashSet, + args: Vec, +} + +impl Lune { + pub fn new() -> Self { + Self::default() + } + + pub fn with_args(mut self, args: Vec) -> Self { + self.args = args; + self + } + + pub fn with_global(mut self, global: LuneGlobal) -> Self { + self.globals.insert(global); + self + } + + pub fn with_all_globals(mut self) -> Self { + for global in LuneGlobal::get_all() { + self.globals.insert(global); + } + self + } + + pub async fn run(&self, name: &str, chunk: &str) -> Result<()> { + let mut lua = Lua::new(); + for global in &self.globals { + lua = match &global { + LuneGlobal::Console => create_console(lua).await?, + LuneGlobal::Fs => create_fs(lua).await?, + LuneGlobal::Net => create_net(lua).await?, + LuneGlobal::Process => create_process(lua, self.args.clone()).await?, + LuneGlobal::Task => create_task(lua).await?, + } + } + let result = lua.load(chunk).set_name(name)?.exec_async().await; + match result { + Ok(_) => Ok(()), + Err(e) => bail!( + "\n{}\n{}", + format_label("ERROR"), + pretty_format_luau_error(&e) + ), + } } } #[cfg(test)] mod tests { - use crate::run_lune; + use crate::Lune; macro_rules! run_tests { ($($name:ident: $value:expr,)*) => { @@ -53,7 +102,10 @@ mod tests { let script = tokio::fs::read_to_string(&path) .await .unwrap(); - if let Err(e) = run_lune($value, &script, args).await { + let lune = Lune::new() + .with_args(args) + .with_all_globals(); + if let Err(e) = lune.run($value, &script).await { panic!("\nTest '{}' failed!\n{}\n", $value, e.to_string()) } }