Refactor and document lune lib public members

This commit is contained in:
Filip Tibell 2023-02-10 12:14:28 +01:00
parent 3073fb8fec
commit 709a69aa82
No known key found for this signature in database
14 changed files with 434 additions and 353 deletions

View file

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `NetRequest` query parameters value has been changed to be a table of key-value pairs similar to `process.env`. - `NetRequest` query parameters value has been changed to be a table of key-value pairs similar to `process.env`.
If any query parameter is specified more than once in the request url, the value chosen will be the last one that was specified. If any query parameter is specified more than once in the request url, the value chosen will be the last one that was specified.
- The internal http client for `net.request` now reuses headers and connections for more efficient requests. - The internal http client for `net.request` now reuses headers and connections for more efficient requests.
- Refactored the Lune rust crate to be much more user-friendly and documented all of the public functions.
### Fixed ### Fixed

View file

@ -15,9 +15,9 @@ use crate::{
}, },
}; };
const LUNE_SELENE_FILE_NAME: &str = "lune.yml"; pub(crate) const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau"; pub(crate) const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json"; pub(crate) const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
/// Lune CLI /// Lune CLI
#[derive(Parser, Debug, Default)] #[derive(Parser, Debug, Default)]
@ -160,7 +160,7 @@ 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
let lune = Lune::new().with_args(self.script_args).with_all_globals(); let lune = Lune::new().with_all_globals_and_args(self.script_args);
let result = lune.run(&file_display_name, &file_contents).await; let result = lune.run(&file_display_name, &file_contents).await;
Ok(match result { Ok(match result {
Err(e) => { Err(e) => {
@ -171,64 +171,3 @@ impl Cli {
}) })
} }
} }
#[cfg(test)]
mod tests {
use std::env::{current_dir, set_current_dir};
use anyhow::{bail, Context, Result};
use serde_json::Value;
use tokio::fs::{create_dir_all, read_to_string, remove_file};
use super::{Cli, LUNE_LUAU_FILE_NAME, LUNE_SELENE_FILE_NAME};
async fn run_cli(cli: Cli) -> Result<()> {
let path = current_dir()
.context("Failed to get current dir")?
.join("bin");
create_dir_all(&path)
.await
.context("Failed to create bin dir")?;
set_current_dir(&path).context("Failed to set current dir")?;
cli.run().await?;
Ok(())
}
async fn ensure_file_exists_and_is_not_json(file_name: &str) -> Result<()> {
match read_to_string(file_name)
.await
.context("Failed to read definitions file")
{
Ok(file_contents) => match serde_json::from_str::<Value>(&file_contents) {
Err(_) => {
remove_file(file_name)
.await
.context("Failed to remove definitions file")?;
Ok(())
}
Ok(_) => bail!("Downloading selene definitions returned json, expected luau"),
},
Err(e) => bail!("Failed to download selene definitions!\n{e}"),
}
}
#[tokio::test]
async fn list() -> Result<()> {
Cli::list().run().await?;
Ok(())
}
#[tokio::test]
async fn download_selene_types() -> Result<()> {
run_cli(Cli::download_selene_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_SELENE_FILE_NAME).await?;
Ok(())
}
#[tokio::test]
async fn download_luau_types() -> Result<()> {
run_cli(Cli::download_luau_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_LUAU_FILE_NAME).await?;
Ok(())
}
}

View file

@ -13,9 +13,12 @@ use std::process::ExitCode;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
mod cli; pub(crate) mod cli;
mod gen; pub(crate) mod gen;
mod utils; pub(crate) mod utils;
#[cfg(test)]
mod tests;
use cli::Cli; use cli::Cli;

57
packages/cli/src/tests.rs Normal file
View file

@ -0,0 +1,57 @@
use std::env::{current_dir, set_current_dir};
use anyhow::{bail, Context, Result};
use serde_json::Value;
use tokio::fs::{create_dir_all, read_to_string, remove_file};
use crate::cli::{Cli, LUNE_LUAU_FILE_NAME, LUNE_SELENE_FILE_NAME};
async fn run_cli(cli: Cli) -> Result<()> {
let path = current_dir()
.context("Failed to get current dir")?
.join("bin");
create_dir_all(&path)
.await
.context("Failed to create bin dir")?;
set_current_dir(&path).context("Failed to set current dir")?;
cli.run().await?;
Ok(())
}
async fn ensure_file_exists_and_is_not_json(file_name: &str) -> Result<()> {
match read_to_string(file_name)
.await
.context("Failed to read definitions file")
{
Ok(file_contents) => match serde_json::from_str::<Value>(&file_contents) {
Err(_) => {
remove_file(file_name)
.await
.context("Failed to remove definitions file")?;
Ok(())
}
Ok(_) => bail!("Downloading selene definitions returned json, expected luau"),
},
Err(e) => bail!("Failed to download selene definitions!\n{e}"),
}
}
#[tokio::test]
async fn list() -> Result<()> {
Cli::list().run().await?;
Ok(())
}
#[tokio::test]
async fn download_selene_types() -> Result<()> {
run_cli(Cli::download_selene_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_SELENE_FILE_NAME).await?;
Ok(())
}
#[tokio::test]
async fn download_luau_types() -> Result<()> {
run_cli(Cli::download_luau_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_LUAU_FILE_NAME).await?;
Ok(())
}

View file

@ -5,20 +5,17 @@ use tokio::fs;
use crate::utils::table::TableBuilder; use crate::utils::table::TableBuilder;
pub fn create(lua: &Lua) -> LuaResult<()> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
lua.globals().raw_set( TableBuilder::new(lua)?
"fs", .with_async_function("readFile", fs_read_file)?
TableBuilder::new(lua)? .with_async_function("readDir", fs_read_dir)?
.with_async_function("readFile", fs_read_file)? .with_async_function("writeFile", fs_write_file)?
.with_async_function("readDir", fs_read_dir)? .with_async_function("writeDir", fs_write_dir)?
.with_async_function("writeFile", fs_write_file)? .with_async_function("removeFile", fs_remove_file)?
.with_async_function("writeDir", fs_write_dir)? .with_async_function("removeDir", fs_remove_dir)?
.with_async_function("removeFile", fs_remove_file)? .with_async_function("isFile", fs_is_file)?
.with_async_function("removeDir", fs_remove_dir)? .with_async_function("isDir", fs_is_dir)?
.with_async_function("isFile", fs_is_file)? .build_readonly()
.with_async_function("isDir", fs_is_dir)?
.build_readonly()?,
)
} }
async fn fs_read_file(_: &Lua, path: String) -> LuaResult<String> { async fn fs_read_file(_: &Lua, path: String) -> LuaResult<String> {

View file

@ -1,83 +1,117 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use mlua::prelude::*;
mod fs; mod fs;
mod net; mod net;
mod process; mod process;
mod require; mod require;
mod stdio; mod stdio;
mod task; mod task;
mod top_level;
// Global tables #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LuneGlobal {
pub use fs::create as create_fs; Fs,
pub use net::create as create_net; Net,
pub use process::create as create_process; Process { args: Vec<String> },
pub use require::create as create_require; Require,
pub use stdio::create as create_stdio; Stdio,
pub use task::create as create_task; Task,
TopLevel,
// Individual top-level global values }
use mlua::prelude::*; impl Display for LuneGlobal {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use crate::utils::formatting::{format_label, pretty_format_multi_value}; write!(
f,
pub fn create_top_level(lua: &Lua) -> LuaResult<()> { "{}",
let globals = lua.globals(); match self {
// HACK: We need to preserve the default behavior of the Self::Fs => "fs",
// print and error functions, for pcall and such, which Self::Net => "net",
// is really tricky to do from scratch so we will just Self::Process { .. } => "process",
// proxy the default print and error functions here Self::Require => "require",
let print_fn: LuaFunction = globals.raw_get("print")?; Self::Stdio => "stdio",
let error_fn: LuaFunction = globals.raw_get("error")?; Self::Task => "task",
lua.set_named_registry_value("print", print_fn)?; Self::TopLevel => "toplevel",
lua.set_named_registry_value("error", error_fn)?; }
globals.raw_set( )
"print", }
lua.create_function(|lua, args: LuaMultiValue| { }
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?; impl LuneGlobal {
print.call(formatted)?; /**
Ok(()) Create a vector that contains all available Lune globals, with
})?, the [`LuneGlobal::Process`] global containing the given args.
)?; */
globals.raw_set( pub fn all<S: AsRef<str>>(args: &[S]) -> Vec<Self> {
"info", vec![
lua.create_function(|lua, args: LuaMultiValue| { Self::Fs,
let print: LuaFunction = lua.named_registry_value("print")?; Self::Net,
print.call(format!( Self::Process {
"{}\n{}", args: args.iter().map(|s| s.as_ref().to_string()).collect(),
format_label("info"), },
pretty_format_multi_value(&args)? Self::Require,
))?; Self::Stdio,
Ok(()) Self::Task,
})?, Self::TopLevel,
)?; ]
globals.raw_set( }
"warn",
lua.create_function(|lua, args: LuaMultiValue| { /**
let print: LuaFunction = lua.named_registry_value("print")?; Checks if this Lune global is a proxy global.
print.call(format!(
"{}\n{}", A proxy global is a global that re-implements or proxies functionality of one or
format_label("warn"), more existing lua globals, and may store internal references to the original global(s).
pretty_format_multi_value(&args)?
))?; This means that proxy globals should only be injected into a lua global
Ok(()) environment once, since injecting twice or more will potentially break the
})?, functionality of the proxy global and / or cause undefined behavior.
)?; */
globals.raw_set( pub fn is_proxy(&self) -> bool {
"error", matches!(self, Self::Require | Self::TopLevel)
lua.create_function(|lua, (arg, level): (LuaValue, Option<u32>)| { }
let error: LuaFunction = lua.named_registry_value("error")?;
let multi = arg.to_lua_multi(lua)?; /**
error.call(( Creates the [`mlua::Table`] value for this Lune global.
format!(
"{}\n{}", Note that proxy globals should be handled with special care and that [`LuneGlobal::inject()`]
format_label("error"), should be preferred over manually creating and manipulating the value(s) of any Lune global.
pretty_format_multi_value(&multi)? */
), pub fn value<'a>(&'a self, lua: &'a Lua) -> LuaResult<LuaTable> {
level, match self {
))?; LuneGlobal::Fs => fs::create(lua),
Ok(()) LuneGlobal::Net => net::create(lua),
})?, LuneGlobal::Process { args } => process::create(lua, args.clone()),
)?; LuneGlobal::Require => require::create(lua),
Ok(()) LuneGlobal::Stdio => stdio::create(lua),
LuneGlobal::Task => task::create(lua),
LuneGlobal::TopLevel => top_level::create(lua),
}
}
/**
Injects the Lune global into a lua global environment.
This takes ownership since proxy Lune globals should
only ever be injected into a lua global environment once.
Refer to [`LuneGlobal::is_top_level()`] for more info on proxy globals.
*/
pub fn inject(self, lua: &Lua) -> LuaResult<()> {
let globals = lua.globals();
let table = self.value(lua)?;
// NOTE: Top level globals are special, the values
// *in* the table they return should be set directly,
// instead of setting the table itself as the global
if self.is_proxy() {
for pair in table.pairs::<LuaValue, LuaValue>() {
let (key, value) = pair?;
globals.raw_set(key, value)?;
}
Ok(())
} else {
globals.raw_set(self.to_string(), table)
}
}
} }

View file

@ -19,7 +19,7 @@ use crate::utils::{
table::TableBuilder, table::TableBuilder,
}; };
pub fn create(lua: &Lua) -> LuaResult<()> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
// Create a reusable client for performing our // Create a reusable client for performing our
// web requests and store it in the lua registry // web requests and store it in the lua registry
let mut default_headers = HeaderMap::new(); let mut default_headers = HeaderMap::new();
@ -35,15 +35,12 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
); );
lua.set_named_registry_value("NetClient", client)?; lua.set_named_registry_value("NetClient", client)?;
// Create the global table for net // Create the global table for net
lua.globals().raw_set( TableBuilder::new(lua)?
"net", .with_function("jsonEncode", net_json_encode)?
TableBuilder::new(lua)? .with_function("jsonDecode", net_json_decode)?
.with_function("jsonEncode", net_json_encode)? .with_async_function("request", net_request)?
.with_function("jsonDecode", net_json_decode)? .with_async_function("serve", net_serve)?
.with_async_function("request", net_request)? .build_readonly()
.with_async_function("serve", net_serve)?
.build_readonly()?,
)
} }
fn net_json_encode(_: &Lua, (val, pretty): (LuaValue, Option<bool>)) -> LuaResult<String> { fn net_json_encode(_: &Lua, (val, pretty): (LuaValue, Option<bool>)) -> LuaResult<String> {

View file

@ -10,7 +10,7 @@ use crate::utils::{
table::TableBuilder, table::TableBuilder,
}; };
pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<()> { pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<LuaTable> {
let cwd = env::current_dir()?.canonicalize()?; let cwd = env::current_dir()?.canonicalize()?;
let mut cwd_str = cwd.to_string_lossy().to_string(); let mut cwd_str = cwd.to_string_lossy().to_string();
if !cwd_str.ends_with('/') { if !cwd_str.ends_with('/') {
@ -31,16 +31,13 @@ pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<()> {
)? )?
.build_readonly()?; .build_readonly()?;
// Create the full process table // Create the full process table
lua.globals().raw_set( TableBuilder::new(lua)?
"process", .with_value("args", args_tab)?
TableBuilder::new(lua)? .with_value("cwd", cwd_str)?
.with_value("args", args_tab)? .with_value("env", env_tab)?
.with_value("cwd", cwd_str)? .with_async_function("exit", process_exit)?
.with_value("env", env_tab)? .with_async_function("spawn", process_spawn)?
.with_async_function("exit", process_exit)? .build_readonly()
.with_async_function("spawn", process_spawn)?
.build_readonly()?,
)
} }
fn process_env_get<'lua>( fn process_env_get<'lua>(

View file

@ -7,10 +7,15 @@ use std::{
use mlua::prelude::*; use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, RawOsStr}; use os_str_bytes::{OsStrBytes, RawOsStr};
pub fn create(lua: &Lua) -> LuaResult<()> { use crate::utils::table::TableBuilder;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
let require: LuaFunction = lua.globals().raw_get("require")?;
// Preserve original require behavior if we have a special env var set // Preserve original require behavior if we have a special env var set
if env::var_os("LUAU_PWD_REQUIRE").is_some() { if env::var_os("LUAU_PWD_REQUIRE").is_some() {
return Ok(()); return TableBuilder::new(lua)?
.with_value("require", require)?
.build_readonly();
} }
/* /*
Store the current working directory so that we can use it later Store the current working directory so that we can use it later
@ -28,8 +33,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
let debug: LuaTable = lua.globals().raw_get("debug")?; let debug: LuaTable = lua.globals().raw_get("debug")?;
let info: LuaFunction = debug.raw_get("info")?; let info: LuaFunction = debug.raw_get("info")?;
lua.set_named_registry_value("require_getinfo", info)?; lua.set_named_registry_value("require_getinfo", info)?;
// Fetch the original require function and store it in the registry // Store the original require function in the registry
let require: LuaFunction = lua.globals().raw_get("require")?;
lua.set_named_registry_value("require_original", require)?; lua.set_named_registry_value("require_original", require)?;
/* /*
Create a new function that fetches the file name from the current thread, Create a new function that fetches the file name from the current thread,
@ -90,6 +94,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
} }
})?; })?;
// Override the original require global with our monkey-patched one // Override the original require global with our monkey-patched one
lua.globals().raw_set("require", new_require)?; TableBuilder::new(lua)?
Ok(()) .with_value("require", new_require)?
.build_readonly()
} }

View file

@ -8,32 +8,29 @@ use crate::utils::{
table::TableBuilder, table::TableBuilder,
}; };
pub fn create(lua: &Lua) -> LuaResult<()> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
lua.globals().raw_set( TableBuilder::new(lua)?
"stdio", .with_function("color", |_, color: String| {
TableBuilder::new(lua)? let ansi_string = format_style(style_from_color_str(&color)?);
.with_function("color", |_, color: String| { Ok(ansi_string)
let ansi_string = format_style(style_from_color_str(&color)?); })?
Ok(ansi_string) .with_function("style", |_, style: String| {
})? let ansi_string = format_style(style_from_style_str(&style)?);
.with_function("style", |_, style: String| { Ok(ansi_string)
let ansi_string = format_style(style_from_style_str(&style)?); })?
Ok(ansi_string) .with_function("format", |_, args: LuaMultiValue| {
})? pretty_format_multi_value(&args)
.with_function("format", |_, args: LuaMultiValue| { })?
pretty_format_multi_value(&args) .with_function("write", |_, s: String| {
})? print!("{s}");
.with_function("write", |_, s: String| { Ok(())
print!("{s}"); })?
Ok(()) .with_function("ewrite", |_, s: String| {
})? eprint!("{s}");
.with_function("ewrite", |_, s: String| { Ok(())
eprint!("{s}"); })?
Ok(()) .with_function("prompt", prompt)?
})? .build_readonly()
.with_function("prompt", prompt)?
.build_readonly()?,
)
} }
fn prompt_theme() -> ColorfulTheme { fn prompt_theme() -> ColorfulTheme {

View file

@ -13,7 +13,7 @@ use crate::utils::{
const MINIMUM_WAIT_OR_DELAY_DURATION: f32 = 10.0 / 1_000.0; // 10ms const MINIMUM_WAIT_OR_DELAY_DURATION: f32 = 10.0 / 1_000.0; // 10ms
pub fn create(lua: &Lua) -> LuaResult<()> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
// HACK: There is no way to call coroutine.close directly from the mlua // HACK: There is no way to call coroutine.close directly from the mlua
// crate, so we need to fetch the function and store it in the registry // crate, so we need to fetch the function and store it in the registry
let coroutine: LuaTable = lua.globals().raw_get("coroutine")?; let coroutine: LuaTable = lua.globals().raw_get("coroutine")?;
@ -24,16 +24,13 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
// overwrite the original coroutine.resume function with it to fix that // overwrite the original coroutine.resume function with it to fix that
coroutine.raw_set("resume", lua.create_async_function(task_spawn)?)?; coroutine.raw_set("resume", lua.create_async_function(task_spawn)?)?;
// Rest of the task library is normal, just async functions, no metatable // Rest of the task library is normal, just async functions, no metatable
lua.globals().raw_set( TableBuilder::new(lua)?
"task", .with_async_function("cancel", task_cancel)?
TableBuilder::new(lua)? .with_async_function("delay", task_delay)?
.with_async_function("cancel", task_cancel)? .with_async_function("defer", task_defer)?
.with_async_function("delay", task_delay)? .with_async_function("spawn", task_spawn)?
.with_async_function("defer", task_defer)? .with_async_function("wait", task_wait)?
.with_async_function("spawn", task_spawn)? .build_readonly()
.with_async_function("wait", task_wait)?
.build_readonly()?,
)
} }
fn tof_to_thread<'a>(lua: &'a Lua, tof: LuaValue<'a>) -> LuaResult<LuaThread<'a>> { fn tof_to_thread<'a>(lua: &'a Lua, tof: LuaValue<'a>) -> LuaResult<LuaThread<'a>> {

View file

@ -0,0 +1,57 @@
use mlua::prelude::*;
use crate::utils::{
formatting::{format_label, pretty_format_multi_value},
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
let globals = lua.globals();
// HACK: We need to preserve the default behavior of the
// print and error functions, for pcall and such, which
// is really tricky to do from scratch so we will just
// proxy the default print and error functions here
let print_fn: LuaFunction = globals.raw_get("print")?;
let error_fn: LuaFunction = globals.raw_get("error")?;
lua.set_named_registry_value("print", print_fn)?;
lua.set_named_registry_value("error", error_fn)?;
TableBuilder::new(lua)?
.with_function("print", |lua, args: LuaMultiValue| {
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(formatted)?;
Ok(())
})?
.with_function("info", |lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("info"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?
.with_function("warn", |lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?
.with_function("error", |lua, (arg, level): (LuaValue, Option<u32>)| {
let error: LuaFunction = lua.named_registry_value("error")?;
let multi = arg.to_lua_multi(lua)?;
error.call((
format!(
"{}\n{}",
format_label("error"),
pretty_format_multi_value(&multi)?
),
level,
))?;
Ok(())
})?
.build_readonly()
}

View file

@ -6,68 +6,82 @@ use tokio::{sync::mpsc, task};
pub(crate) mod globals; pub(crate) mod globals;
pub(crate) mod utils; pub(crate) mod utils;
use crate::{ #[cfg(test)]
globals::{ mod tests;
create_fs, create_net, create_process, create_require, create_stdio, create_task,
create_top_level,
},
utils::{formatting::pretty_format_luau_error, message::LuneMessage},
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] use crate::utils::{formatting::pretty_format_luau_error, message::LuneMessage};
pub enum LuneGlobal {
Fs,
Net,
Process,
Require,
Stdio,
Task,
TopLevel,
}
impl LuneGlobal { pub use globals::LuneGlobal;
pub fn get_all() -> Vec<Self> {
vec![
Self::Fs,
Self::Net,
Self::Process,
Self::Require,
Self::Stdio,
Self::Task,
Self::TopLevel,
]
}
}
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Lune { pub struct Lune {
globals: HashSet<LuneGlobal>, includes: HashSet<LuneGlobal>,
args: Vec<String>, excludes: HashSet<LuneGlobal>,
} }
impl Lune { impl Lune {
/**
Creates a new Lune script runner.
*/
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn with_args(mut self, args: Vec<String>) -> Self { /**
self.args = args; Include a global in the lua environment created for running a Lune script.
self */
}
pub fn with_global(mut self, global: LuneGlobal) -> Self { pub fn with_global(mut self, global: LuneGlobal) -> Self {
self.globals.insert(global); self.includes.insert(global);
self self
} }
/**
Include all globals in the lua environment created for running a Lune script.
*/
pub fn with_all_globals(mut self) -> Self { pub fn with_all_globals(mut self) -> Self {
for global in LuneGlobal::get_all() { for global in LuneGlobal::all::<String>(&[]) {
self.globals.insert(global); self.includes.insert(global);
} }
self self
} }
pub async fn run(&self, name: &str, chunk: &str) -> Result<ExitCode, LuaError> { /**
Include all globals in the lua environment created for running a
Lune script, as well as supplying args for [`LuneGlobal::Process`].
*/
pub fn with_all_globals_and_args(mut self, args: Vec<String>) -> Self {
for global in LuneGlobal::all(&args) {
self.includes.insert(global);
}
self
}
/**
Exclude a global from the lua environment created for running a Lune script.
This should be preferred over manually iterating and filtering
which Lune globals to add to the global environment.
*/
pub fn without_global(mut self, global: LuneGlobal) -> Self {
self.excludes.insert(global);
self
}
/**
Runs a Lune script.
This will create a new sandboxed Luau environment with the configured
globals and arguments, running inside of a [`tokio::task::LocalSet`].
Some Lune globals such as [`LuneGlobal::Process`] may spawn
separate tokio tasks on other threads, but the Luau environment
itself is guaranteed to run on a single thread in the local set.
*/
pub async fn run(
&self,
script_name: &str,
script_contents: &str,
) -> Result<ExitCode, LuaError> {
let task_set = task::LocalSet::new(); let task_set = task::LocalSet::new();
let (sender, mut receiver) = mpsc::channel::<LuneMessage>(64); let (sender, mut receiver) = mpsc::channel::<LuneMessage>(64);
let lua = Arc::new(mlua::Lua::new()); let lua = Arc::new(mlua::Lua::new());
@ -75,21 +89,15 @@ impl Lune {
lua.set_app_data(Arc::downgrade(&lua)); lua.set_app_data(Arc::downgrade(&lua));
lua.set_app_data(Arc::downgrade(&snd)); lua.set_app_data(Arc::downgrade(&snd));
// Add in wanted lune globals // Add in wanted lune globals
for global in &self.globals { for global in self.includes.clone() {
match &global { if !self.excludes.contains(&global) {
LuneGlobal::Fs => create_fs(&lua)?, global.inject(&lua)?;
LuneGlobal::Net => create_net(&lua)?,
LuneGlobal::Process => create_process(&lua, self.args.clone())?,
LuneGlobal::Require => create_require(&lua)?,
LuneGlobal::Stdio => create_stdio(&lua)?,
LuneGlobal::Task => create_task(&lua)?,
LuneGlobal::TopLevel => create_top_level(&lua)?,
} }
} }
// Spawn the main thread from our entrypoint script // Spawn the main thread from our entrypoint script
let script_lua = lua.clone(); let script_lua = lua.clone();
let script_name = name.to_string(); let script_name = script_name.to_string();
let script_chunk = chunk.to_string(); let script_chunk = script_contents.to_string();
let script_sender = snd.clone(); let script_sender = snd.clone();
script_sender script_sender
.send(LuneMessage::Spawned) .send(LuneMessage::Spawned)
@ -165,83 +173,3 @@ impl Lune {
} }
} }
} }
#[cfg(test)]
mod tests {
use std::{env::set_current_dir, path::PathBuf, process::ExitCode};
use anyhow::Result;
use console::set_colors_enabled;
use console::set_colors_enabled_stderr;
use tokio::fs::read_to_string;
use crate::Lune;
const ARGS: &[&str] = &["Foo", "Bar"];
macro_rules! run_tests {
($($name:ident: $value:expr,)*) => {
$(
#[tokio::test]
async fn $name() -> Result<ExitCode> {
// Disable styling for stdout and stderr since
// some tests rely on output not being styled
set_colors_enabled(false);
set_colors_enabled_stderr(false);
// NOTE: This path is relative to the lib
// package, not the cwd or workspace root,
// so we need to cd to the repo root first
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = crate_dir.join("../../").canonicalize()?;
set_current_dir(root_dir)?;
// The rest of the test logic can continue as normal
let full_name = format!("tests/{}.luau", $value);
let script = read_to_string(&full_name).await?;
let lune = Lune::new()
.with_args(
ARGS
.clone()
.iter()
.map(ToString::to_string)
.collect()
)
.with_all_globals();
let script_name = full_name.strip_suffix(".luau").unwrap();
let exit_code = lune.run(&script_name, &script).await?;
Ok(exit_code)
}
)*
}
}
run_tests! {
fs_files: "fs/files",
fs_dirs: "fs/dirs",
net_request_codes: "net/request/codes",
net_request_methods: "net/request/methods",
net_request_redirect: "net/request/redirect",
net_json_decode: "net/json/decode",
net_json_encode: "net/json/encode",
net_serve: "net/serve",
process_args: "process/args",
process_cwd: "process/cwd",
process_env: "process/env",
process_exit: "process/exit",
process_spawn: "process/spawn",
require_children: "require/tests/children",
require_invalid: "require/tests/invalid",
require_nested: "require/tests/nested",
require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings",
stdio_format: "stdio/format",
stdio_color: "stdio/color",
stdio_style: "stdio/style",
stdio_write: "stdio/write",
stdio_ewrite: "stdio/ewrite",
task_cancel: "task/cancel",
task_defer: "task/defer",
task_delay: "task/delay",
task_spawn: "task/spawn",
task_wait: "task/wait",
}
}

72
packages/lib/src/tests.rs Normal file
View file

@ -0,0 +1,72 @@
use std::{env::set_current_dir, path::PathBuf, process::ExitCode};
use anyhow::Result;
use console::set_colors_enabled;
use console::set_colors_enabled_stderr;
use tokio::fs::read_to_string;
use crate::Lune;
const ARGS: &[&str] = &["Foo", "Bar"];
macro_rules! create_tests {
($($name:ident: $value:expr,)*) => { $(
#[tokio::test]
async fn $name() -> Result<ExitCode> {
// Disable styling for stdout and stderr since
// some tests rely on output not being styled
set_colors_enabled(false);
set_colors_enabled_stderr(false);
// NOTE: This path is relative to the lib
// package, not the cwd or workspace root,
// so we need to cd to the repo root first
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = crate_dir.join("../../").canonicalize()?;
set_current_dir(root_dir)?;
// The rest of the test logic can continue as normal
let full_name = format!("tests/{}.luau", $value);
let script = read_to_string(&full_name).await?;
let lune = Lune::new().with_all_globals_and_args(
ARGS
.clone()
.iter()
.map(ToString::to_string)
.collect()
);
let script_name = full_name.strip_suffix(".luau").unwrap();
let exit_code = lune.run(&script_name, &script).await?;
Ok(exit_code)
}
)* }
}
create_tests! {
fs_files: "fs/files",
fs_dirs: "fs/dirs",
net_request_codes: "net/request/codes",
net_request_methods: "net/request/methods",
net_request_redirect: "net/request/redirect",
net_json_decode: "net/json/decode",
net_json_encode: "net/json/encode",
net_serve: "net/serve",
process_args: "process/args",
process_cwd: "process/cwd",
process_env: "process/env",
process_exit: "process/exit",
process_spawn: "process/spawn",
require_children: "require/tests/children",
require_invalid: "require/tests/invalid",
require_nested: "require/tests/nested",
require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings",
stdio_format: "stdio/format",
stdio_color: "stdio/color",
stdio_style: "stdio/style",
stdio_write: "stdio/write",
stdio_ewrite: "stdio/ewrite",
task_cancel: "task/cancel",
task_defer: "task/defer",
task_delay: "task/delay",
task_spawn: "task/spawn",
task_wait: "task/wait",
}