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 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);
};

View file

@ -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<Table> {
pub async fn create(lua: Lua) -> Result<Lua> {
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<Table> {
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)
}

View file

@ -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<Table> {
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> {
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<String> {

View file

@ -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;

View file

@ -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<Table> {
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> {
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<bool>)) -> Result<String> {

View file

@ -9,7 +9,7 @@ use tokio::process::Command;
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
let inner_args = lua.create_table()?;
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_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<Value<'lua>> {

View file

@ -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<Table> {
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> {
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<f32>) -> Result<f32> {
async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
time::sleep(Duration::from_secs_f32(secs)).await;
Ok(secs)

View file

@ -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<String>) -> 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<Self> {
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<LuneGlobal>,
args: Vec<String>,
}
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)]
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())
}
}