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`.
|
- `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
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
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;
|
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> {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
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 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
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