Pass ownership to global constructors to avoid lifetime issues

This commit is contained in:
Filip Tibell 2023-01-22 15:23:56 -05:00
parent 6d432171e5
commit 6b14bc3dc0
No known key found for this signature in database
8 changed files with 160 additions and 105 deletions

View file

@ -3,7 +3,7 @@ use std::fs::read_to_string;
use anyhow::Result; use anyhow::Result;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use lune::run_lune; use lune::Lune;
use crate::utils::{files::find_parse_file_path, github::Client as GithubClient}; 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 // Display the file path relative to cwd with no extensions in stack traces
let file_display_name = file_path.with_extension("").display().to_string(); let file_display_name = file_path.with_extension("").display().to_string();
// Create a new lune object with all globals & run the script // 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}"); eprintln!("{e}");
std::process::exit(1); std::process::exit(1);
}; };

View file

@ -1,11 +1,11 @@
use mlua::{Lua, MultiValue, Result, Table}; use mlua::{Lua, MultiValue, Result};
use crate::utils::{ use crate::utils::{
formatting::{flush_stdout, pretty_format_multi_value, print_color, print_label, print_style}, formatting::{flush_stdout, pretty_format_multi_value, print_color, print_label, print_style},
table_builder::ReadonlyTableBuilder, table_builder::ReadonlyTableBuilder,
}; };
pub async fn new(lua: &Lua) -> Result<Table> { pub async fn create(lua: Lua) -> Result<Lua> {
let print = |args: &MultiValue, throw: bool| -> Result<()> { let print = |args: &MultiValue, throw: bool| -> Result<()> {
let s = pretty_format_multi_value(args)?; let s = pretty_format_multi_value(args)?;
if throw { if throw {
@ -16,26 +16,30 @@ pub async fn new(lua: &Lua) -> Result<Table> {
flush_stdout()?; flush_stdout()?;
Ok(()) Ok(())
}; };
ReadonlyTableBuilder::new(lua)? lua.globals().raw_set(
.with_function("resetColor", |_, _: ()| print_color("reset"))? "console",
.with_function("setColor", |_, color: String| print_color(color))? ReadonlyTableBuilder::new(&lua)?
.with_function("resetStyle", |_, _: ()| print_style("reset"))? .with_function("resetColor", |_, _: ()| print_color("reset"))?
.with_function("setStyle", |_, style: String| print_style(style))? .with_function("setColor", |_, color: String| print_color(color))?
.with_function("format", |_, args: MultiValue| { .with_function("resetStyle", |_, _: ()| print_style("reset"))?
pretty_format_multi_value(&args) .with_function("setStyle", |_, style: String| print_style(style))?
})? .with_function("format", |_, args: MultiValue| {
.with_function("log", move |_, args: MultiValue| print(&args, false))? pretty_format_multi_value(&args)
.with_function("info", move |_, args: MultiValue| { })?
print_label("info")?; .with_function("log", move |_, args: MultiValue| print(&args, false))?
print(&args, false) .with_function("info", move |_, args: MultiValue| {
})? print_label("info")?;
.with_function("warn", move |_, args: MultiValue| { print(&args, false)
print_label("warn")?; })?
print(&args, false) .with_function("warn", move |_, args: MultiValue| {
})? print_label("warn")?;
.with_function("error", move |_, args: MultiValue| { print(&args, false)
print_label("error")?; })?
print(&args, true) .with_function("error", move |_, args: MultiValue| {
})? print_label("error")?;
.build() print(&args, true)
})?
.build()?,
)?;
Ok(lua)
} }

View file

@ -1,21 +1,25 @@
use std::path::{PathBuf, MAIN_SEPARATOR}; use std::path::{PathBuf, MAIN_SEPARATOR};
use mlua::{Lua, Result, Table}; use mlua::{Lua, Result};
use tokio::fs; use tokio::fs;
use crate::utils::table_builder::ReadonlyTableBuilder; use crate::utils::table_builder::ReadonlyTableBuilder;
pub async fn new(lua: &Lua) -> Result<Table> { pub async fn create(lua: Lua) -> Result<Lua> {
ReadonlyTableBuilder::new(lua)? lua.globals().raw_set(
.with_async_function("readFile", fs_read_file)? "fs",
.with_async_function("readDir", fs_read_dir)? ReadonlyTableBuilder::new(&lua)?
.with_async_function("writeFile", fs_write_file)? .with_async_function("readFile", fs_read_file)?
.with_async_function("writeDir", fs_write_dir)? .with_async_function("readDir", fs_read_dir)?
.with_async_function("removeFile", fs_remove_file)? .with_async_function("writeFile", fs_write_file)?
.with_async_function("removeDir", fs_remove_dir)? .with_async_function("writeDir", fs_write_dir)?
.with_async_function("isFile", fs_is_file)? .with_async_function("removeFile", fs_remove_file)?
.with_async_function("isDir", fs_is_dir)? .with_async_function("removeDir", fs_remove_dir)?
.build() .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<String> { async fn fs_read_file(_: &Lua, path: String) -> Result<String> {

View file

@ -4,8 +4,8 @@ mod net;
mod process; mod process;
mod task; mod task;
pub use console::new as new_console; pub use console::create as create_console;
pub use fs::new as new_fs; pub use fs::create as create_fs;
pub use net::new as new_net; pub use net::create as create_net;
pub use process::new as new_process; pub use process::create as create_process;
pub use task::new as new_task; pub use task::create as create_task;

View file

@ -1,6 +1,6 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use mlua::{Error, Lua, LuaSerdeExt, Result, Table, Value}; use mlua::{Error, Lua, LuaSerdeExt, Result, Value};
use reqwest::{ use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue}, header::{HeaderMap, HeaderName, HeaderValue},
Method, Method,
@ -8,12 +8,16 @@ use reqwest::{
use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder}; use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder};
pub async fn new(lua: &Lua) -> Result<Table> { pub async fn create(lua: Lua) -> Result<Lua> {
ReadonlyTableBuilder::new(lua)? lua.globals().raw_set(
.with_function("jsonEncode", net_json_encode)? "net",
.with_function("jsonDecode", net_json_decode)? ReadonlyTableBuilder::new(&lua)?
.with_async_function("request", net_request)? .with_function("jsonEncode", net_json_encode)?
.build() .with_function("jsonDecode", net_json_decode)?
.with_async_function("request", net_request)?
.build()?,
)?;
Ok(lua)
} }
fn net_json_encode(_: &Lua, (val, pretty): (Value, Option<bool>)) -> Result<String> { fn net_json_encode(_: &Lua, (val, pretty): (Value, Option<bool>)) -> Result<String> {

View file

@ -9,7 +9,7 @@ use tokio::process::Command;
use crate::utils::table_builder::ReadonlyTableBuilder; use crate::utils::table_builder::ReadonlyTableBuilder;
pub async fn new(lua: &Lua, args_vec: Vec<String>) -> Result<Table> { pub async fn create(lua: Lua, args_vec: Vec<String>) -> Result<Lua> {
// Create readonly args array // Create readonly args array
let inner_args = lua.create_table()?; let inner_args = lua.create_table()?;
for arg in &args_vec { for arg in &args_vec {
@ -36,12 +36,16 @@ pub async fn new(lua: &Lua, args_vec: Vec<String>) -> Result<Table> {
inner_env.set_metatable(Some(inner_env_meta)); inner_env.set_metatable(Some(inner_env_meta));
inner_env.set_readonly(true); inner_env.set_readonly(true);
// Create the full process table // Create the full process table
ReadonlyTableBuilder::new(lua)? lua.globals().raw_set(
.with_table("args", inner_args)? "process",
.with_table("env", inner_env)? ReadonlyTableBuilder::new(&lua)?
.with_function("exit", process_exit)? .with_table("args", inner_args)?
.with_async_function("spawn", process_spawn)? .with_table("env", inner_env)?
.build() .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<Value<'lua>> { fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {

View file

@ -1,40 +1,26 @@
use std::time::Duration; use std::time::Duration;
use mlua::{Function, Lua, Result, Table, Value}; use mlua::{Lua, Result};
use tokio::time; use tokio::time;
use crate::utils::table_builder::ReadonlyTableBuilder; use crate::utils::table_builder::ReadonlyTableBuilder;
const DEFAULT_SLEEP_DURATION: f32 = 1.0 / 60.0; const DEFAULT_SLEEP_DURATION: f32 = 1.0 / 60.0;
const TASK_LIB_LUAU: &str = include_str!("../luau/task.luau"); pub async fn create(lua: Lua) -> Result<Lua> {
lua.globals().raw_set(
pub async fn new(lua: &Lua) -> Result<Table> { "task",
let task_lib: Table = lua ReadonlyTableBuilder::new(&lua)?
.load(TASK_LIB_LUAU) .with_async_function("wait", task_wait)?
.set_name("task")? .build()?,
.eval_async() )?;
.await?; Ok(lua)
// 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()
} }
// FIXME: It does seem possible to properly make an async wait // FIXME: It does seem possible to properly make an async wait
// function with mlua right now, something breaks when using // function with mlua right now, something breaks when using
// async wait functions inside of coroutines // async wait functions inside of coroutines
async fn wait(_: &Lua, duration: Option<f32>) -> Result<f32> { async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION); let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
time::sleep(Duration::from_secs_f32(secs)).await; time::sleep(Duration::from_secs_f32(secs)).await;
Ok(secs) Ok(secs)

View file

@ -1,3 +1,5 @@
use std::collections::HashSet;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use mlua::Lua; use mlua::Lua;
@ -5,38 +7,85 @@ pub mod globals;
pub mod utils; pub mod utils;
use crate::{ 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}, utils::formatting::{format_label, pretty_format_luau_error},
}; };
pub async fn run_lune(name: &str, chunk: &str, args: Vec<String>) -> Result<()> { #[derive(Clone, Debug, PartialEq, Eq, Hash)]
let lua = Lua::new(); pub enum LuneGlobal {
lua.sandbox(true)?; Console,
// Add in all globals Fs,
{ Net,
let globals = lua.globals(); Process,
globals.raw_set("console", new_console(&lua).await?)?; Task,
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?)?; impl LuneGlobal {
globals.raw_set("task", new_task(&lua).await?)?; pub fn get_all() -> Vec<Self> {
globals.set_readonly(true); 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 { #[derive(Clone, Debug, Default)]
Ok(_) => Ok(()), pub struct Lune {
Err(e) => bail!( globals: HashSet<LuneGlobal>,
"\n{}\n{}", args: Vec<String>,
format_label("ERROR"), }
pretty_format_luau_error(&e)
), impl Lune {
pub fn new() -> Self {
Self::default()
}
pub fn with_args(mut self, args: Vec<String>) -> 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)] #[cfg(test)]
mod tests { mod tests {
use crate::run_lune; use crate::Lune;
macro_rules! run_tests { macro_rules! run_tests {
($($name:ident: $value:expr,)*) => { ($($name:ident: $value:expr,)*) => {
@ -53,7 +102,10 @@ mod tests {
let script = tokio::fs::read_to_string(&path) let script = tokio::fs::read_to_string(&path)
.await .await
.unwrap(); .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()) panic!("\nTest '{}' failed!\n{}\n", $value, e.to_string())
} }
} }