mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Refactor and document lune lib public members
This commit is contained in:
parent
3073fb8fec
commit
709a69aa82
14 changed files with 434 additions and 353 deletions
|
@ -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`.
|
||||
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.
|
||||
- Refactored the Lune rust crate to be much more user-friendly and documented all of the public functions.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
|
||||
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
||||
const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
|
||||
pub(crate) const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
|
||||
pub(crate) const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
||||
pub(crate) const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
|
||||
|
||||
/// Lune CLI
|
||||
#[derive(Parser, Debug, Default)]
|
||||
|
@ -160,7 +160,7 @@ 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
|
||||
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;
|
||||
Ok(match result {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,12 @@ use std::process::ExitCode;
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
mod cli;
|
||||
mod gen;
|
||||
mod utils;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod gen;
|
||||
pub(crate) mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use cli::Cli;
|
||||
|
||||
|
|
57
packages/cli/src/tests.rs
Normal file
57
packages/cli/src/tests.rs
Normal 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(())
|
||||
}
|
|
@ -5,20 +5,17 @@ use tokio::fs;
|
|||
|
||||
use crate::utils::table::TableBuilder;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<()> {
|
||||
lua.globals().raw_set(
|
||||
"fs",
|
||||
TableBuilder::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_readonly()?,
|
||||
)
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::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_readonly()
|
||||
}
|
||||
|
||||
async fn fs_read_file(_: &Lua, path: String) -> LuaResult<String> {
|
||||
|
|
|
@ -1,83 +1,117 @@
|
|||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
mod fs;
|
||||
mod net;
|
||||
mod process;
|
||||
mod require;
|
||||
mod stdio;
|
||||
mod task;
|
||||
mod top_level;
|
||||
|
||||
// Global tables
|
||||
|
||||
pub use fs::create as create_fs;
|
||||
pub use net::create as create_net;
|
||||
pub use process::create as create_process;
|
||||
pub use require::create as create_require;
|
||||
pub use stdio::create as create_stdio;
|
||||
pub use task::create as create_task;
|
||||
|
||||
// Individual top-level global values
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::utils::formatting::{format_label, pretty_format_multi_value};
|
||||
|
||||
pub fn create_top_level(lua: &Lua) -> LuaResult<()> {
|
||||
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)?;
|
||||
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")?;
|
||||
print.call(formatted)?;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
globals.raw_set(
|
||||
"info",
|
||||
lua.create_function(|lua, args: LuaMultiValue| {
|
||||
let print: LuaFunction = lua.named_registry_value("print")?;
|
||||
print.call(format!(
|
||||
"{}\n{}",
|
||||
format_label("info"),
|
||||
pretty_format_multi_value(&args)?
|
||||
))?;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
globals.raw_set(
|
||||
"warn",
|
||||
lua.create_function(|lua, args: LuaMultiValue| {
|
||||
let print: LuaFunction = lua.named_registry_value("print")?;
|
||||
print.call(format!(
|
||||
"{}\n{}",
|
||||
format_label("warn"),
|
||||
pretty_format_multi_value(&args)?
|
||||
))?;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
globals.raw_set(
|
||||
"error",
|
||||
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((
|
||||
format!(
|
||||
"{}\n{}",
|
||||
format_label("error"),
|
||||
pretty_format_multi_value(&multi)?
|
||||
),
|
||||
level,
|
||||
))?;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum LuneGlobal {
|
||||
Fs,
|
||||
Net,
|
||||
Process { args: Vec<String> },
|
||||
Require,
|
||||
Stdio,
|
||||
Task,
|
||||
TopLevel,
|
||||
}
|
||||
|
||||
impl Display for LuneGlobal {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Fs => "fs",
|
||||
Self::Net => "net",
|
||||
Self::Process { .. } => "process",
|
||||
Self::Require => "require",
|
||||
Self::Stdio => "stdio",
|
||||
Self::Task => "task",
|
||||
Self::TopLevel => "toplevel",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuneGlobal {
|
||||
/**
|
||||
Create a vector that contains all available Lune globals, with
|
||||
the [`LuneGlobal::Process`] global containing the given args.
|
||||
*/
|
||||
pub fn all<S: AsRef<str>>(args: &[S]) -> Vec<Self> {
|
||||
vec![
|
||||
Self::Fs,
|
||||
Self::Net,
|
||||
Self::Process {
|
||||
args: args.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||
},
|
||||
Self::Require,
|
||||
Self::Stdio,
|
||||
Self::Task,
|
||||
Self::TopLevel,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if this Lune global is a proxy global.
|
||||
|
||||
A proxy global is a global that re-implements or proxies functionality of one or
|
||||
more existing lua globals, and may store internal references to the original global(s).
|
||||
|
||||
This means that proxy globals should only be injected into a lua global
|
||||
environment once, since injecting twice or more will potentially break the
|
||||
functionality of the proxy global and / or cause undefined behavior.
|
||||
*/
|
||||
pub fn is_proxy(&self) -> bool {
|
||||
matches!(self, Self::Require | Self::TopLevel)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates the [`mlua::Table`] value for this Lune global.
|
||||
|
||||
Note that proxy globals should be handled with special care and that [`LuneGlobal::inject()`]
|
||||
should be preferred over manually creating and manipulating the value(s) of any Lune global.
|
||||
*/
|
||||
pub fn value<'a>(&'a self, lua: &'a Lua) -> LuaResult<LuaTable> {
|
||||
match self {
|
||||
LuneGlobal::Fs => fs::create(lua),
|
||||
LuneGlobal::Net => net::create(lua),
|
||||
LuneGlobal::Process { args } => process::create(lua, args.clone()),
|
||||
LuneGlobal::Require => require::create(lua),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::utils::{
|
|||
table::TableBuilder,
|
||||
};
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<()> {
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
// Create a reusable client for performing our
|
||||
// web requests and store it in the lua registry
|
||||
let mut default_headers = HeaderMap::new();
|
||||
|
@ -35,15 +35,12 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
|
|||
);
|
||||
lua.set_named_registry_value("NetClient", client)?;
|
||||
// Create the global table for net
|
||||
lua.globals().raw_set(
|
||||
"net",
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("jsonEncode", net_json_encode)?
|
||||
.with_function("jsonDecode", net_json_decode)?
|
||||
.with_async_function("request", net_request)?
|
||||
.with_async_function("serve", net_serve)?
|
||||
.build_readonly()?,
|
||||
)
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("jsonEncode", net_json_encode)?
|
||||
.with_function("jsonDecode", net_json_decode)?
|
||||
.with_async_function("request", net_request)?
|
||||
.with_async_function("serve", net_serve)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn net_json_encode(_: &Lua, (val, pretty): (LuaValue, Option<bool>)) -> LuaResult<String> {
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::utils::{
|
|||
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 mut cwd_str = cwd.to_string_lossy().to_string();
|
||||
if !cwd_str.ends_with('/') {
|
||||
|
@ -31,16 +31,13 @@ pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<()> {
|
|||
)?
|
||||
.build_readonly()?;
|
||||
// Create the full process table
|
||||
lua.globals().raw_set(
|
||||
"process",
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("args", args_tab)?
|
||||
.with_value("cwd", cwd_str)?
|
||||
.with_value("env", env_tab)?
|
||||
.with_async_function("exit", process_exit)?
|
||||
.with_async_function("spawn", process_spawn)?
|
||||
.build_readonly()?,
|
||||
)
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("args", args_tab)?
|
||||
.with_value("cwd", cwd_str)?
|
||||
.with_value("env", env_tab)?
|
||||
.with_async_function("exit", process_exit)?
|
||||
.with_async_function("spawn", process_spawn)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn process_env_get<'lua>(
|
||||
|
|
|
@ -7,10 +7,15 @@ use std::{
|
|||
use mlua::prelude::*;
|
||||
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
|
||||
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
|
||||
|
@ -28,8 +33,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
|
|||
let debug: LuaTable = lua.globals().raw_get("debug")?;
|
||||
let info: LuaFunction = debug.raw_get("info")?;
|
||||
lua.set_named_registry_value("require_getinfo", info)?;
|
||||
// Fetch the original require function and store it in the registry
|
||||
let require: LuaFunction = lua.globals().raw_get("require")?;
|
||||
// Store the original require function in the registry
|
||||
lua.set_named_registry_value("require_original", require)?;
|
||||
/*
|
||||
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
|
||||
lua.globals().raw_set("require", new_require)?;
|
||||
Ok(())
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("require", new_require)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
|
|
@ -8,32 +8,29 @@ use crate::utils::{
|
|||
table::TableBuilder,
|
||||
};
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<()> {
|
||||
lua.globals().raw_set(
|
||||
"stdio",
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("color", |_, color: 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)?);
|
||||
Ok(ansi_string)
|
||||
})?
|
||||
.with_function("format", |_, args: LuaMultiValue| {
|
||||
pretty_format_multi_value(&args)
|
||||
})?
|
||||
.with_function("write", |_, s: String| {
|
||||
print!("{s}");
|
||||
Ok(())
|
||||
})?
|
||||
.with_function("ewrite", |_, s: String| {
|
||||
eprint!("{s}");
|
||||
Ok(())
|
||||
})?
|
||||
.with_function("prompt", prompt)?
|
||||
.build_readonly()?,
|
||||
)
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("color", |_, color: 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)?);
|
||||
Ok(ansi_string)
|
||||
})?
|
||||
.with_function("format", |_, args: LuaMultiValue| {
|
||||
pretty_format_multi_value(&args)
|
||||
})?
|
||||
.with_function("write", |_, s: String| {
|
||||
print!("{s}");
|
||||
Ok(())
|
||||
})?
|
||||
.with_function("ewrite", |_, s: String| {
|
||||
eprint!("{s}");
|
||||
Ok(())
|
||||
})?
|
||||
.with_function("prompt", prompt)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn prompt_theme() -> ColorfulTheme {
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::utils::{
|
|||
|
||||
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
|
||||
// crate, so we need to fetch the function and store it in the registry
|
||||
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
|
||||
coroutine.raw_set("resume", lua.create_async_function(task_spawn)?)?;
|
||||
// Rest of the task library is normal, just async functions, no metatable
|
||||
lua.globals().raw_set(
|
||||
"task",
|
||||
TableBuilder::new(lua)?
|
||||
.with_async_function("cancel", task_cancel)?
|
||||
.with_async_function("delay", task_delay)?
|
||||
.with_async_function("defer", task_defer)?
|
||||
.with_async_function("spawn", task_spawn)?
|
||||
.with_async_function("wait", task_wait)?
|
||||
.build_readonly()?,
|
||||
)
|
||||
TableBuilder::new(lua)?
|
||||
.with_async_function("cancel", task_cancel)?
|
||||
.with_async_function("delay", task_delay)?
|
||||
.with_async_function("defer", task_defer)?
|
||||
.with_async_function("spawn", task_spawn)?
|
||||
.with_async_function("wait", task_wait)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn tof_to_thread<'a>(lua: &'a Lua, tof: LuaValue<'a>) -> LuaResult<LuaThread<'a>> {
|
||||
|
|
57
packages/lib/src/globals/top_level.rs
Normal file
57
packages/lib/src/globals/top_level.rs
Normal 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()
|
||||
}
|
|
@ -6,68 +6,82 @@ use tokio::{sync::mpsc, task};
|
|||
pub(crate) mod globals;
|
||||
pub(crate) mod utils;
|
||||
|
||||
use crate::{
|
||||
globals::{
|
||||
create_fs, create_net, create_process, create_require, create_stdio, create_task,
|
||||
create_top_level,
|
||||
},
|
||||
utils::{formatting::pretty_format_luau_error, message::LuneMessage},
|
||||
};
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum LuneGlobal {
|
||||
Fs,
|
||||
Net,
|
||||
Process,
|
||||
Require,
|
||||
Stdio,
|
||||
Task,
|
||||
TopLevel,
|
||||
}
|
||||
use crate::utils::{formatting::pretty_format_luau_error, message::LuneMessage};
|
||||
|
||||
impl LuneGlobal {
|
||||
pub fn get_all() -> Vec<Self> {
|
||||
vec![
|
||||
Self::Fs,
|
||||
Self::Net,
|
||||
Self::Process,
|
||||
Self::Require,
|
||||
Self::Stdio,
|
||||
Self::Task,
|
||||
Self::TopLevel,
|
||||
]
|
||||
}
|
||||
}
|
||||
pub use globals::LuneGlobal;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Lune {
|
||||
globals: HashSet<LuneGlobal>,
|
||||
args: Vec<String>,
|
||||
includes: HashSet<LuneGlobal>,
|
||||
excludes: HashSet<LuneGlobal>,
|
||||
}
|
||||
|
||||
impl Lune {
|
||||
/**
|
||||
Creates a new Lune script runner.
|
||||
*/
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_args(mut self, args: Vec<String>) -> Self {
|
||||
self.args = args;
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
Include a global in the lua environment created for running a Lune script.
|
||||
*/
|
||||
pub fn with_global(mut self, global: LuneGlobal) -> Self {
|
||||
self.globals.insert(global);
|
||||
self.includes.insert(global);
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
Include all globals in the lua environment created for running a Lune script.
|
||||
*/
|
||||
pub fn with_all_globals(mut self) -> Self {
|
||||
for global in LuneGlobal::get_all() {
|
||||
self.globals.insert(global);
|
||||
for global in LuneGlobal::all::<String>(&[]) {
|
||||
self.includes.insert(global);
|
||||
}
|
||||
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 (sender, mut receiver) = mpsc::channel::<LuneMessage>(64);
|
||||
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(&snd));
|
||||
// Add in wanted lune globals
|
||||
for global in &self.globals {
|
||||
match &global {
|
||||
LuneGlobal::Fs => create_fs(&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)?,
|
||||
for global in self.includes.clone() {
|
||||
if !self.excludes.contains(&global) {
|
||||
global.inject(&lua)?;
|
||||
}
|
||||
}
|
||||
// Spawn the main thread from our entrypoint script
|
||||
let script_lua = lua.clone();
|
||||
let script_name = name.to_string();
|
||||
let script_chunk = chunk.to_string();
|
||||
let script_name = script_name.to_string();
|
||||
let script_chunk = script_contents.to_string();
|
||||
let script_sender = snd.clone();
|
||||
script_sender
|
||||
.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
72
packages/lib/src/tests.rs
Normal 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",
|
||||
}
|
Loading…
Reference in a new issue