diff --git a/src/old/builtins/fs.rs b/src/old/builtins/fs.rs deleted file mode 100644 index d2b9bba..0000000 --- a/src/old/builtins/fs.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::io::ErrorKind as IoErrorKind; -use std::path::{PathBuf, MAIN_SEPARATOR}; - -use mlua::prelude::*; -use tokio::fs; - -use crate::lune_temp::lua::{ - fs::{copy, FsMetadata, FsWriteOptions}, - table::TableBuilder, -}; - -pub fn create(lua: &'static Lua) -> LuaResult { - 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("metadata", fs_metadata)? - .with_async_function("isFile", fs_is_file)? - .with_async_function("isDir", fs_is_dir)? - .with_async_function("move", fs_move)? - .with_async_function("copy", fs_copy)? - .build_readonly() -} - -async fn fs_read_file(lua: &Lua, path: String) -> LuaResult { - let bytes = fs::read(&path).await.into_lua_err()?; - lua.create_string(bytes) -} - -async fn fs_read_dir(_: &Lua, path: String) -> LuaResult> { - let mut dir_strings = Vec::new(); - let mut dir = fs::read_dir(&path).await.into_lua_err()?; - while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? { - if let Some(dir_path_str) = dir_entry.path().to_str() { - dir_strings.push(dir_path_str.to_owned()); - } else { - return Err(LuaError::RuntimeError(format!( - "File path could not be converted into a string: '{}'", - dir_entry.path().display() - ))); - } - } - let mut dir_string_prefix = path; - if !dir_string_prefix.ends_with(MAIN_SEPARATOR) { - dir_string_prefix.push(MAIN_SEPARATOR); - } - let dir_strings_no_prefix = dir_strings - .iter() - .map(|inner_path| { - inner_path - .trim() - .trim_start_matches(&dir_string_prefix) - .to_owned() - }) - .collect::>(); - Ok(dir_strings_no_prefix) -} - -async fn fs_write_file(_: &Lua, (path, contents): (String, LuaString<'_>)) -> LuaResult<()> { - fs::write(&path, &contents.as_bytes()).await.into_lua_err() -} - -async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> { - fs::create_dir_all(&path).await.into_lua_err() -} - -async fn fs_remove_file(_: &Lua, path: String) -> LuaResult<()> { - fs::remove_file(&path).await.into_lua_err() -} - -async fn fs_remove_dir(_: &Lua, path: String) -> LuaResult<()> { - fs::remove_dir_all(&path).await.into_lua_err() -} - -async fn fs_metadata(_: &Lua, path: String) -> LuaResult { - match fs::metadata(path).await { - Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()), - Ok(meta) => Ok(FsMetadata::from(meta)), - Err(e) => Err(e.into()), - } -} - -async fn fs_is_file(_: &Lua, path: String) -> LuaResult { - match fs::metadata(path).await { - Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false), - Ok(meta) => Ok(meta.is_file()), - Err(e) => Err(e.into()), - } -} - -async fn fs_is_dir(_: &Lua, path: String) -> LuaResult { - match fs::metadata(path).await { - Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false), - Ok(meta) => Ok(meta.is_dir()), - Err(e) => Err(e.into()), - } -} - -async fn fs_move(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> { - let path_from = PathBuf::from(from); - if !path_from.exists() { - return Err(LuaError::RuntimeError(format!( - "No file or directory exists at the path '{}'", - path_from.display() - ))); - } - let path_to = PathBuf::from(to); - if !options.overwrite && path_to.exists() { - return Err(LuaError::RuntimeError(format!( - "A file or directory already exists at the path '{}'", - path_to.display() - ))); - } - fs::rename(path_from, path_to).await.into_lua_err()?; - Ok(()) -} - -async fn fs_copy(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> { - copy(from, to, options).await -} diff --git a/src/old/builtins/luau.rs b/src/old/builtins/luau.rs deleted file mode 100644 index 54aeb6a..0000000 --- a/src/old/builtins/luau.rs +++ /dev/null @@ -1,39 +0,0 @@ -use mlua::prelude::*; - -use crate::lune_temp::lua::{ - luau::{LuauCompileOptions, LuauLoadOptions}, - table::TableBuilder, -}; - -const BYTECODE_ERROR_BYTE: u8 = 0; - -pub fn create(lua: &'static Lua) -> LuaResult { - TableBuilder::new(lua)? - .with_function("compile", compile_source)? - .with_function("load", load_source)? - .build_readonly() -} - -fn compile_source<'lua>( - lua: &'lua Lua, - (source, options): (LuaString<'lua>, LuauCompileOptions), -) -> LuaResult> { - let bytecode = options.into_compiler().compile(source); - - match bytecode.first() { - Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError( - String::from_utf8_lossy(&bytecode).into_owned(), - )), - Some(_) => lua.create_string(bytecode), - None => panic!("Compiling resulted in empty bytecode"), - } -} - -fn load_source<'lua>( - lua: &'lua Lua, - (source, options): (LuaString<'lua>, LuauLoadOptions), -) -> LuaResult> { - lua.load(source.as_bytes()) - .set_name(options.debug_name) - .into_function() -} diff --git a/src/old/builtins/mod.rs b/src/old/builtins/mod.rs deleted file mode 100644 index 46aefbe..0000000 --- a/src/old/builtins/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod fs; -pub mod luau; -pub mod net; -pub mod process; -pub mod serde; -pub mod stdio; -pub mod task; -pub mod top_level; - -#[cfg(feature = "roblox")] -pub mod roblox; diff --git a/src/old/builtins/net.rs b/src/old/builtins/net.rs deleted file mode 100644 index e313928..0000000 --- a/src/old/builtins/net.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::collections::HashMap; - -use mlua::prelude::*; - -use console::style; -use hyper::{ - header::{CONTENT_ENCODING, CONTENT_LENGTH}, - Server, -}; -use tokio::{sync::mpsc, task}; - -use crate::lune_temp::lua::{ - net::{ - NetClient, NetClientBuilder, NetLocalExec, NetService, NetWebSocket, RequestConfig, - ServeConfig, - }, - serde::{decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat}, - table::TableBuilder, - task::{TaskScheduler, TaskSchedulerAsyncExt}, -}; - -pub fn create(lua: &'static Lua) -> LuaResult { - // Create a reusable client for performing our - // web requests and store it in the lua registry, - // allowing us to reuse headers and internal structs - let client = NetClientBuilder::new() - .headers(&[("User-Agent", create_user_agent_header())])? - .build()?; - lua.set_named_registry_value("net.client", client)?; - // Create the global table for 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("socket", net_socket)? - .with_async_function("serve", net_serve)? - .with_function("urlEncode", net_url_encode)? - .with_function("urlDecode", net_url_decode)? - .build_readonly() -} - -fn create_user_agent_header() -> String { - let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY") - .trim_start_matches("https://github.com/") - .split_once('/') - .unwrap(); - format!("{github_owner}-{github_repo}-cli") -} - -fn net_json_encode<'lua>( - lua: &'lua Lua, - (val, pretty): (LuaValue<'lua>, Option), -) -> LuaResult> { - EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default())) - .serialize_to_string(lua, val) -} - -fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult> { - EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json) -} - -async fn net_request<'lua>( - lua: &'static Lua, - config: RequestConfig<'lua>, -) -> LuaResult> { - // Create and send the request - let client: LuaUserDataRef = lua.named_registry_value("net.client")?; - let mut request = client.request(config.method, &config.url); - for (query, value) in config.query { - request = request.query(&[(query.to_str()?, value.to_str()?)]); - } - for (header, value) in config.headers { - request = request.header(header.to_str()?, value.to_str()?); - } - let res = request - .body(config.body.unwrap_or_default()) - .send() - .await - .into_lua_err()?; - // Extract status, headers - let res_status = res.status().as_u16(); - let res_status_text = res.status().canonical_reason(); - let mut res_headers = res - .headers() - .iter() - .map(|(name, value)| { - ( - name.as_str().to_string(), - value.to_str().unwrap().to_owned(), - ) - }) - .collect::>(); - // Read response bytes - let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec(); - // Check for extra options, decompression - if config.options.decompress { - // NOTE: Header names are guaranteed to be lowercase because of the above - // transformations of them into the hashmap, so we can compare directly - let format = res_headers.iter().find_map(|(name, val)| { - if name == CONTENT_ENCODING.as_str() { - CompressDecompressFormat::detect_from_header_str(val) - } else { - None - } - }); - if let Some(format) = format { - res_bytes = decompress(format, res_bytes).await?; - let content_encoding_header_str = CONTENT_ENCODING.as_str(); - let content_length_header_str = CONTENT_LENGTH.as_str(); - res_headers.retain(|name, _| { - name != content_encoding_header_str && name != content_length_header_str - }); - } - } - // Construct and return a readonly lua table with results - TableBuilder::new(lua)? - .with_value("ok", (200..300).contains(&res_status))? - .with_value("statusCode", res_status)? - .with_value("statusMessage", res_status_text)? - .with_value("headers", res_headers)? - .with_value("body", lua.create_string(&res_bytes)?)? - .build_readonly() -} - -async fn net_socket<'lua>(lua: &'static Lua, url: String) -> LuaResult { - let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?; - NetWebSocket::new(ws).into_lua_table(lua) -} - -async fn net_serve<'lua>( - lua: &'static Lua, - (port, config): (u16, ServeConfig<'lua>), -) -> LuaResult> { - // Note that we need to use a mpsc here and not - // a oneshot channel since we move the sender - // into our table with the stop function - let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1); - let server_request_callback = lua.create_registry_value(config.handle_request)?; - let server_websocket_callback = config.handle_web_socket.map(|handler| { - lua.create_registry_value(handler) - .expect("Failed to store websocket handler") - }); - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption"); - // Bind first to make sure that we can bind to this address - let bound = match Server::try_bind(&([127, 0, 0, 1], port).into()) { - Err(e) => { - return Err(LuaError::external(format!( - "Failed to bind to localhost on port {port}\n{}", - format!("{e}").replace( - "error creating server listener: ", - &format!("{}", style("> ").dim()) - ) - ))); - } - Ok(bound) => bound, - }; - // Register a background task to prevent the task scheduler from - // exiting early and start up our web server on the bound address - let task = sched.register_background_task(); - let server = bound - .http1_only(true) // Web sockets can only use http1 - .http1_keepalive(true) // Web sockets must be kept alive - .executor(NetLocalExec) - .serve(NetService::new( - lua, - server_request_callback, - server_websocket_callback, - )) - .with_graceful_shutdown(async move { - task.unregister(Ok(())); - shutdown_rx - .recv() - .await - .expect("Server was stopped instantly"); - shutdown_rx.close(); - }); - // Spawn a new tokio task so we don't block - task::spawn_local(server); - // Create a new read-only table that contains methods - // for manipulating server behavior and shutting it down - let handle_stop = move |_, _: ()| match shutdown_tx.try_send(()) { - Ok(_) => Ok(()), - Err(_) => Err(LuaError::RuntimeError( - "Server has already been stopped".to_string(), - )), - }; - TableBuilder::new(lua)? - .with_function("stop", handle_stop)? - .build_readonly() -} - -fn net_url_encode<'lua>( - lua: &'lua Lua, - (lua_string, as_binary): (LuaString<'lua>, Option), -) -> LuaResult> { - if matches!(as_binary, Some(true)) { - urlencoding::encode_binary(lua_string.as_bytes()).into_lua(lua) - } else { - urlencoding::encode(lua_string.to_str()?).into_lua(lua) - } -} - -fn net_url_decode<'lua>( - lua: &'lua Lua, - (lua_string, as_binary): (LuaString<'lua>, Option), -) -> LuaResult> { - if matches!(as_binary, Some(true)) { - urlencoding::decode_binary(lua_string.as_bytes()).into_lua(lua) - } else { - urlencoding::decode(lua_string.to_str()?) - .map_err(|e| LuaError::RuntimeError(format!("Encountered invalid encoding - {e}")))? - .into_lua(lua) - } -} diff --git a/src/old/builtins/process.rs b/src/old/builtins/process.rs deleted file mode 100644 index 6ef0fa8..0000000 --- a/src/old/builtins/process.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::{ - collections::HashMap, - env::{self, consts}, - path::{self, PathBuf}, - process::{ExitCode, Stdio}, -}; - -use directories::UserDirs; -use dunce::canonicalize; -use mlua::prelude::*; -use os_str_bytes::RawOsString; -use tokio::process::Command; - -use crate::lune_temp::lua::{ - process::pipe_and_inherit_child_process_stdio, table::TableBuilder, task::TaskScheduler, -}; - -const PROCESS_EXIT_IMPL_LUA: &str = r#" -exit(...) -yield() -"#; - -pub fn create(lua: &'static Lua, args_vec: Vec) -> LuaResult { - let cwd_str = { - let cwd = canonicalize(env::current_dir()?)?; - let cwd_str = cwd.to_string_lossy().to_string(); - if !cwd_str.ends_with(path::MAIN_SEPARATOR) { - format!("{cwd_str}{}", path::MAIN_SEPARATOR) - } else { - cwd_str - } - }; - // Create constants for OS & processor architecture - let os = lua.create_string(&consts::OS.to_lowercase())?; - let arch = lua.create_string(&consts::ARCH.to_lowercase())?; - // Create readonly args array - let args_tab = TableBuilder::new(lua)? - .with_sequential_values(args_vec)? - .build_readonly()?; - // Create proxied table for env that gets & sets real env vars - let env_tab = TableBuilder::new(lua)? - .with_metatable( - TableBuilder::new(lua)? - .with_function(LuaMetaMethod::Index.name(), process_env_get)? - .with_function(LuaMetaMethod::NewIndex.name(), process_env_set)? - .with_function(LuaMetaMethod::Iter.name(), process_env_iter)? - .build_readonly()?, - )? - .build_readonly()?; - // Create our process exit function, this is a bit involved since - // we have no way to yield from c / rust, we need to load a lua - // chunk that will set the exit code and yield for us instead - let process_exit_env_yield: LuaFunction = lua.named_registry_value("co.yield")?; - let process_exit_env_exit: LuaFunction = lua.create_function(|lua, code: Option| { - let exit_code = code.map_or(ExitCode::SUCCESS, ExitCode::from); - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption"); - sched.set_exit_code(exit_code); - Ok(()) - })?; - let process_exit = lua - .load(PROCESS_EXIT_IMPL_LUA) - .set_name("=process.exit") - .set_environment( - TableBuilder::new(lua)? - .with_value("yield", process_exit_env_yield)? - .with_value("exit", process_exit_env_exit)? - .build_readonly()?, - ) - .into_function()?; - // Create the full process table - TableBuilder::new(lua)? - .with_value("os", os)? - .with_value("arch", arch)? - .with_value("args", args_tab)? - .with_value("cwd", cwd_str)? - .with_value("env", env_tab)? - .with_value("exit", process_exit)? - .with_async_function("spawn", process_spawn)? - .build_readonly() -} - -fn process_env_get<'lua>( - lua: &'lua Lua, - (_, key): (LuaValue<'lua>, String), -) -> LuaResult> { - match env::var_os(key) { - Some(value) => { - let raw_value = RawOsString::new(value); - Ok(LuaValue::String( - lua.create_string(raw_value.as_raw_bytes())?, - )) - } - None => Ok(LuaValue::Nil), - } -} - -fn process_env_set<'lua>( - _: &'lua Lua, - (_, key, value): (LuaValue<'lua>, String, Option), -) -> LuaResult<()> { - // Make sure key is valid, otherwise set_var will panic - if key.is_empty() { - Err(LuaError::RuntimeError("Key must not be empty".to_string())) - } else if key.contains('=') { - Err(LuaError::RuntimeError( - "Key must not contain the equals character '='".to_string(), - )) - } else if key.contains('\0') { - Err(LuaError::RuntimeError( - "Key must not contain the NUL character".to_string(), - )) - } else { - match value { - Some(value) => { - // Make sure value is valid, otherwise set_var will panic - if value.contains('\0') { - Err(LuaError::RuntimeError( - "Value must not contain the NUL character".to_string(), - )) - } else { - env::set_var(&key, &value); - Ok(()) - } - } - None => { - env::remove_var(&key); - Ok(()) - } - } - } -} - -fn process_env_iter<'lua>( - lua: &'lua Lua, - (_, _): (LuaValue<'lua>, ()), -) -> LuaResult> { - let mut vars = env::vars_os().collect::>().into_iter(); - lua.create_function_mut(move |lua, _: ()| match vars.next() { - Some((key, value)) => { - let raw_key = RawOsString::new(key); - let raw_value = RawOsString::new(value); - Ok(( - LuaValue::String(lua.create_string(raw_key.as_raw_bytes())?), - LuaValue::String(lua.create_string(raw_value.as_raw_bytes())?), - )) - } - None => Ok((LuaValue::Nil, LuaValue::Nil)), - }) -} - -async fn process_spawn<'lua>( - lua: &'static Lua, - (mut program, args, options): (String, Option>, Option>), -) -> LuaResult> { - // Parse any given options or create defaults - let (child_cwd, child_envs, child_shell, child_stdio_inherit) = match options { - Some(options) => { - let mut cwd = env::current_dir()?; - let mut envs = HashMap::new(); - let mut shell = None; - let mut inherit = false; - match options.raw_get("cwd")? { - LuaValue::Nil => {} - LuaValue::String(s) => { - cwd = PathBuf::from(s.to_string_lossy().to_string()); - // Substitute leading tilde (~) for the actual home dir - if cwd.starts_with("~") { - if let Some(user_dirs) = UserDirs::new() { - cwd = user_dirs.home_dir().join(cwd.strip_prefix("~").unwrap()) - } - }; - if !cwd.exists() { - return Err(LuaError::RuntimeError( - "Invalid value for option 'cwd' - path does not exist".to_string(), - )); - } - } - value => { - return Err(LuaError::RuntimeError(format!( - "Invalid type for option 'cwd' - expected 'string', got '{}'", - value.type_name() - ))) - } - } - match options.raw_get("env")? { - LuaValue::Nil => {} - LuaValue::Table(t) => { - for pair in t.pairs::() { - let (k, v) = pair?; - envs.insert(k, v); - } - } - value => { - return Err(LuaError::RuntimeError(format!( - "Invalid type for option 'env' - expected 'table', got '{}'", - value.type_name() - ))) - } - } - match options.raw_get("shell")? { - LuaValue::Nil => {} - LuaValue::String(s) => shell = Some(s.to_string_lossy().to_string()), - LuaValue::Boolean(true) => { - shell = match env::consts::FAMILY { - "unix" => Some("/bin/sh".to_string()), - "windows" => Some("/bin/sh".to_string()), - _ => None, - }; - } - value => { - return Err(LuaError::RuntimeError(format!( - "Invalid type for option 'shell' - expected 'true' or 'string', got '{}'", - value.type_name() - ))) - } - } - match options.raw_get("stdio")? { - LuaValue::Nil => {} - LuaValue::String(s) => { - match s.to_str()? { - "inherit" => { - inherit = true; - }, - "default" => { - inherit = false; - } - _ => return Err(LuaError::RuntimeError( - format!("Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'", s.to_string_lossy()), - )) - } - } - value => { - return Err(LuaError::RuntimeError(format!( - "Invalid type for option 'stdio' - expected 'string', got '{}'", - value.type_name() - ))) - } - } - Ok::<_, LuaError>((cwd, envs, shell, inherit)) - } - None => Ok((env::current_dir()?, HashMap::new(), None, false)), - }?; - // Run a shell using the command param if wanted - let child_args = if let Some(shell) = child_shell { - let shell_args = match args { - Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))], - None => vec!["-c".to_string(), program], - }; - program = shell; - Some(shell_args) - } else { - args - }; - // Create command with the wanted options - let mut cmd = match child_args { - None => Command::new(program), - Some(args) => { - let mut cmd = Command::new(program); - cmd.args(args); - cmd - } - }; - // Set dir to run in and env variables - cmd.current_dir(child_cwd); - cmd.envs(child_envs); - // Spawn the child process - let child = cmd - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - // Inherit the output and stderr if wanted - let result = if child_stdio_inherit { - pipe_and_inherit_child_process_stdio(child).await - } else { - let output = child.wait_with_output().await?; - Ok((output.status, output.stdout, output.stderr)) - }; - // Extract result - let (status, stdout, stderr) = result?; - // NOTE: If an exit code was not given by the child process, - // we default to 1 if it yielded any error output, otherwise 0 - let code = status.code().unwrap_or(match stderr.is_empty() { - true => 0, - false => 1, - }); - // Construct and return a readonly lua table with results - TableBuilder::new(lua)? - .with_value("ok", code == 0)? - .with_value("code", code)? - .with_value("stdout", lua.create_string(&stdout)?)? - .with_value("stderr", lua.create_string(&stderr)?)? - .build_readonly() -} diff --git a/src/old/builtins/roblox.rs b/src/old/builtins/roblox.rs deleted file mode 100644 index 2664f03..0000000 --- a/src/old/builtins/roblox.rs +++ /dev/null @@ -1,104 +0,0 @@ -use mlua::prelude::*; -use once_cell::sync::OnceCell; - -use crate::roblox::{ - self, - document::{Document, DocumentError, DocumentFormat, DocumentKind}, - instance::Instance, - reflection::Database as ReflectionDatabase, -}; - -use tokio::task; - -use crate::lune_temp::lua::table::TableBuilder; - -static REFLECTION_DATABASE: OnceCell = OnceCell::new(); - -pub fn create(lua: &'static Lua) -> LuaResult { - let mut roblox_constants = Vec::new(); - let roblox_module = roblox::module(lua)?; - for pair in roblox_module.pairs::() { - roblox_constants.push(pair?); - } - TableBuilder::new(lua)? - .with_values(roblox_constants)? - .with_async_function("deserializePlace", deserialize_place)? - .with_async_function("deserializeModel", deserialize_model)? - .with_async_function("serializePlace", serialize_place)? - .with_async_function("serializeModel", serialize_model)? - .with_function("getAuthCookie", get_auth_cookie)? - .with_function("getReflectionDatabase", get_reflection_database)? - .build_readonly() -} - -async fn deserialize_place<'lua>( - lua: &'lua Lua, - contents: LuaString<'lua>, -) -> LuaResult> { - let bytes = contents.as_bytes().to_vec(); - let fut = task::spawn_blocking(move || { - let doc = Document::from_bytes(bytes, DocumentKind::Place)?; - let data_model = doc.into_data_model_instance()?; - Ok::<_, DocumentError>(data_model) - }); - fut.await.into_lua_err()??.into_lua(lua) -} - -async fn deserialize_model<'lua>( - lua: &'lua Lua, - contents: LuaString<'lua>, -) -> LuaResult> { - let bytes = contents.as_bytes().to_vec(); - let fut = task::spawn_blocking(move || { - let doc = Document::from_bytes(bytes, DocumentKind::Model)?; - let instance_array = doc.into_instance_array()?; - Ok::<_, DocumentError>(instance_array) - }); - fut.await.into_lua_err()??.into_lua(lua) -} - -async fn serialize_place<'lua>( - lua: &'lua Lua, - (data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option), -) -> LuaResult> { - let data_model = (*data_model).clone(); - let fut = task::spawn_blocking(move || { - let doc = Document::from_data_model_instance(data_model)?; - let bytes = doc.to_bytes_with_format(match as_xml { - Some(true) => DocumentFormat::Xml, - _ => DocumentFormat::Binary, - })?; - Ok::<_, DocumentError>(bytes) - }); - let bytes = fut.await.into_lua_err()??; - lua.create_string(bytes) -} - -async fn serialize_model<'lua>( - lua: &'lua Lua, - (instances, as_xml): (Vec>, Option), -) -> LuaResult> { - let instances = instances.iter().map(|i| (*i).clone()).collect(); - let fut = task::spawn_blocking(move || { - let doc = Document::from_instance_array(instances)?; - let bytes = doc.to_bytes_with_format(match as_xml { - Some(true) => DocumentFormat::Xml, - _ => DocumentFormat::Binary, - })?; - Ok::<_, DocumentError>(bytes) - }); - let bytes = fut.await.into_lua_err()??; - lua.create_string(bytes) -} - -fn get_auth_cookie(_: &Lua, raw: Option) -> LuaResult> { - if matches!(raw, Some(true)) { - Ok(rbx_cookie::get_value()) - } else { - Ok(rbx_cookie::get()) - } -} - -fn get_reflection_database(_: &Lua, _: ()) -> LuaResult { - Ok(*REFLECTION_DATABASE.get_or_init(ReflectionDatabase::new)) -} diff --git a/src/old/builtins/serde.rs b/src/old/builtins/serde.rs deleted file mode 100644 index 09a4abb..0000000 --- a/src/old/builtins/serde.rs +++ /dev/null @@ -1,49 +0,0 @@ -use mlua::prelude::*; - -use crate::lune_temp::lua::{ - serde::{ - compress, decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat, - }, - table::TableBuilder, -}; - -pub fn create(lua: &'static Lua) -> LuaResult { - TableBuilder::new(lua)? - .with_function("encode", serde_encode)? - .with_function("decode", serde_decode)? - .with_async_function("compress", serde_compress)? - .with_async_function("decompress", serde_decompress)? - .build_readonly() -} - -fn serde_encode<'lua>( - lua: &'lua Lua, - (format, val, pretty): (EncodeDecodeFormat, LuaValue<'lua>, Option), -) -> LuaResult> { - let config = EncodeDecodeConfig::from((format, pretty.unwrap_or_default())); - config.serialize_to_string(lua, val) -} - -fn serde_decode<'lua>( - lua: &'lua Lua, - (format, str): (EncodeDecodeFormat, LuaString<'lua>), -) -> LuaResult> { - let config = EncodeDecodeConfig::from(format); - config.deserialize_from_string(lua, str) -} - -async fn serde_compress<'lua>( - lua: &'lua Lua, - (format, str): (CompressDecompressFormat, LuaString<'lua>), -) -> LuaResult> { - let bytes = compress(format, str).await?; - lua.create_string(bytes) -} - -async fn serde_decompress<'lua>( - lua: &'lua Lua, - (format, str): (CompressDecompressFormat, LuaString<'lua>), -) -> LuaResult> { - let bytes = decompress(format, str).await?; - lua.create_string(bytes) -} diff --git a/src/old/builtins/stdio.rs b/src/old/builtins/stdio.rs deleted file mode 100644 index 98a768d..0000000 --- a/src/old/builtins/stdio.rs +++ /dev/null @@ -1,99 +0,0 @@ -use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select}; -use mlua::prelude::*; -use tokio::{ - io::{self, AsyncWriteExt}, - task, -}; - -use crate::lune_temp::lua::{ - stdio::{ - formatting::{ - format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str, - }, - prompt::{PromptKind, PromptOptions, PromptResult}, - }, - table::TableBuilder, -}; - -pub fn create(lua: &'static Lua) -> LuaResult { - 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_async_function("write", |_, s: LuaString| async move { - let mut stdout = io::stdout(); - stdout.write_all(s.as_bytes()).await?; - stdout.flush().await?; - Ok(()) - })? - .with_async_function("ewrite", |_, s: LuaString| async move { - let mut stderr = io::stderr(); - stderr.write_all(s.as_bytes()).await?; - stderr.flush().await?; - Ok(()) - })? - .with_async_function("prompt", |_, options: PromptOptions| async move { - task::spawn_blocking(move || prompt(options)) - .await - .into_lua_err()? - })? - .build_readonly() -} - -fn prompt_theme() -> ColorfulTheme { - ColorfulTheme::default() -} - -fn prompt(options: PromptOptions) -> LuaResult { - let theme = prompt_theme(); - match options.kind { - PromptKind::Text => { - let input: String = Input::with_theme(&theme) - .allow_empty(true) - .with_prompt(options.text.unwrap_or_default()) - .with_initial_text(options.default_string.unwrap_or_default()) - .interact_text()?; - Ok(PromptResult::String(input)) - } - PromptKind::Confirm => { - let mut prompt = Confirm::with_theme(&theme); - if let Some(b) = options.default_bool { - prompt.default(b); - }; - let result = prompt - .with_prompt(&options.text.expect("Missing text in prompt options")) - .interact()?; - Ok(PromptResult::Boolean(result)) - } - PromptKind::Select => { - let chosen = Select::with_theme(&prompt_theme()) - .with_prompt(&options.text.unwrap_or_default()) - .items(&options.options.expect("Missing options in prompt options")) - .interact_opt()?; - Ok(match chosen { - Some(idx) => PromptResult::Index(idx + 1), - None => PromptResult::None, - }) - } - PromptKind::MultiSelect => { - let chosen = MultiSelect::with_theme(&prompt_theme()) - .with_prompt(&options.text.unwrap_or_default()) - .items(&options.options.expect("Missing options in prompt options")) - .interact_opt()?; - Ok(match chosen { - None => PromptResult::None, - Some(indices) => { - PromptResult::Indices(indices.iter().map(|idx| *idx + 1).collect()) - } - }) - } - } -} diff --git a/src/old/builtins/task.rs b/src/old/builtins/task.rs deleted file mode 100644 index 3db015a..0000000 --- a/src/old/builtins/task.rs +++ /dev/null @@ -1,168 +0,0 @@ -use mlua::prelude::*; - -use crate::lune_temp::lua::{ - async_ext::LuaAsyncExt, - table::TableBuilder, - task::{ - LuaThreadOrFunction, LuaThreadOrTaskReference, TaskKind, TaskReference, TaskScheduler, - TaskSchedulerScheduleExt, - }, -}; - -const SPAWN_IMPL_LUA: &str = r#" -scheduleNext(thread()) -local task = scheduleNext(...) -yield() -return task -"#; - -pub fn create(lua: &'static Lua) -> LuaResult> { - lua.app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler in app data"); - /* - 1. Schedule the current thread at the front - 2. Schedule the wanted task arg at the front, - the previous schedule now comes right after - 3. Give control over to the scheduler, which will - resume the above tasks in order when its ready - - The spawn function needs special treatment, - we need to yield right away to allow the - spawned task to run until first yield - */ - let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?; - let task_spawn = lua - .load(SPAWN_IMPL_LUA) - .set_name("task.spawn") - .set_environment( - TableBuilder::new(lua)? - .with_function("thread", |lua, _: ()| Ok(lua.current_thread()))? - .with_value("yield", task_spawn_env_yield)? - .with_function( - "scheduleNext", - |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - sched.schedule_blocking(tof.into_thread(lua)?, args) - }, - )? - .build_readonly()?, - ) - .into_function()?; - // Functions in the built-in coroutine library also need to be - // replaced, these are a bit different than the ones above because - // calling resume or the function that wrap returns must return - // whatever lua value(s) that the thread or task yielded back - let globals = lua.globals(); - let coroutine = globals.get::<_, LuaTable>("coroutine")?; - coroutine.set("status", lua.create_function(coroutine_status)?)?; - coroutine.set("resume", lua.create_function(coroutine_resume)?)?; - coroutine.set("wrap", lua.create_function(coroutine_wrap)?)?; - // All good, return the task scheduler lib - TableBuilder::new(lua)? - .with_value("wait", lua.create_waiter_function()?)? - .with_value("spawn", task_spawn)? - .with_function("cancel", task_cancel)? - .with_function("defer", task_defer)? - .with_function("delay", task_delay)? - .build_readonly() -} - -/* - Basic task functions -*/ - -fn task_cancel(lua: &Lua, task: LuaUserDataRef) -> LuaResult<()> { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - sched.remove_task(*task)?; - Ok(()) -} - -fn task_defer( - lua: &Lua, - (tof, args): (LuaThreadOrFunction, LuaMultiValue), -) -> LuaResult { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - sched.schedule_blocking_deferred(tof.into_thread(lua)?, args) -} - -fn task_delay( - lua: &Lua, - (secs, tof, args): (f64, LuaThreadOrFunction, LuaMultiValue), -) -> LuaResult { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - sched.schedule_blocking_after_seconds(secs, tof.into_thread(lua)?, args) -} - -/* - Coroutine library overrides for compat with task scheduler -*/ - -fn coroutine_status<'a>( - lua: &'a Lua, - value: LuaThreadOrTaskReference<'a>, -) -> LuaResult> { - Ok(match value { - LuaThreadOrTaskReference::Thread(thread) => { - let get_status: LuaFunction = lua.named_registry_value("co.status")?; - get_status.call(thread)? - } - LuaThreadOrTaskReference::TaskReference(task) => { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - sched - .get_task_status(task) - .unwrap_or_else(|| lua.create_string("dead").unwrap()) - } - }) -} - -fn coroutine_resume<'lua>( - lua: &'lua Lua, - (value, arguments): (LuaThreadOrTaskReference, LuaMultiValue<'lua>), -) -> LuaResult<(bool, LuaMultiValue<'lua>)> { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - if sched.current_task().is_none() { - return Err(LuaError::RuntimeError( - "No current task to inherit".to_string(), - )); - } - let current = sched.current_task().unwrap(); - let result = match value { - LuaThreadOrTaskReference::Thread(t) => { - let task = sched.create_task(TaskKind::Instant, t, Some(arguments), true)?; - sched.resume_task(task, None) - } - LuaThreadOrTaskReference::TaskReference(t) => sched.resume_task(t, Some(Ok(arguments))), - }; - sched.force_set_current_task(Some(current)); - match result { - Ok(rets) => Ok((true, rets.1)), - Err(e) => Ok((false, e.into_lua_multi(lua)?)), - } -} - -fn coroutine_wrap<'lua>(lua: &'lua Lua, func: LuaFunction) -> LuaResult> { - let task = lua.app_data_ref::<&TaskScheduler>().unwrap().create_task( - TaskKind::Instant, - lua.create_thread(func)?, - None, - false, - )?; - lua.create_function(move |lua, args: LuaMultiValue| { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - if sched.current_task().is_none() { - return Err(LuaError::RuntimeError( - "No current task to inherit".to_string(), - )); - } - let current = sched.current_task().unwrap(); - let result = lua - .app_data_ref::<&TaskScheduler>() - .unwrap() - .resume_task(task, Some(Ok(args))); - sched.force_set_current_task(Some(current)); - match result { - Ok(rets) => Ok(rets.1), - Err(e) => Err(e), - } - }) -} diff --git a/src/old/builtins/top_level.rs b/src/old/builtins/top_level.rs deleted file mode 100644 index 0261a21..0000000 --- a/src/old/builtins/top_level.rs +++ /dev/null @@ -1,80 +0,0 @@ -use mlua::prelude::*; -use std::io::{self, Write as _}; - -#[cfg(feature = "roblox")] -use crate::roblox::datatypes::extension::RobloxUserdataTypenameExt; - -use crate::lune_temp::lua::{ - stdio::formatting::{format_label, pretty_format_multi_value}, - task::TaskReference, -}; - -pub fn print(_: &Lua, args: LuaMultiValue) -> LuaResult<()> { - let formatted = format!("{}\n", pretty_format_multi_value(&args)?); - let mut stdout = io::stdout(); - stdout.write_all(formatted.as_bytes())?; - stdout.flush()?; - Ok(()) -} - -pub fn warn(_: &Lua, args: LuaMultiValue) -> LuaResult<()> { - let formatted = format!( - "{}\n{}", - format_label("warn"), - pretty_format_multi_value(&args)? - ); - let mut stdout = io::stdout(); - stdout.write_all(formatted.as_bytes())?; - stdout.flush()?; - Ok(()) -} - -// HACK: We need to preserve the default behavior of -// the lua error function, for pcall and such, which -// is really tricky to do from scratch so we will -// just proxy the default function here instead - -pub fn error(lua: &Lua, (arg, level): (LuaValue, Option)) -> LuaResult<()> { - let error: LuaFunction = lua.named_registry_value("error")?; - let trace: LuaFunction = lua.named_registry_value("dbg.trace")?; - error.call(( - LuaError::CallbackError { - traceback: format!("override traceback:{}", trace.call::<_, String>(())?), - cause: LuaError::external(format!( - "{}\n{}", - format_label("error"), - pretty_format_multi_value(&arg.into_lua_multi(lua)?)? - )) - .into(), - }, - level, - ))?; - Ok(()) -} - -pub fn proxy_type<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult> { - if let LuaValue::UserData(u) = &value { - if u.is::() { - return lua.create_string("thread"); - } - } - lua.named_registry_value::("type")?.call(value) -} - -pub fn proxy_typeof<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult> { - if let LuaValue::UserData(u) = &value { - if u.is::() { - return lua.create_string("thread"); - } - #[cfg(feature = "roblox")] - { - if let Some(type_name) = u.roblox_type_name() { - return lua.create_string(type_name); - } - } - } - lua.named_registry_value::("typeof")? - .call(value) -} - -// TODO: Add an override for tostring that formats errors in a nicer way diff --git a/src/old/error.rs b/src/old/error.rs deleted file mode 100644 index e184e2e..0000000 --- a/src/old/error.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - error::Error, - fmt::{Debug, Display, Formatter, Result as FmtResult}, -}; - -use mlua::prelude::*; - -use crate::lune_temp::lua::stdio::formatting::pretty_format_luau_error; - -/** - An opaque error type for formatted lua errors. -*/ -#[derive(Debug, Clone)] -pub struct LuneError { - error: LuaError, - disable_colors: bool, -} - -impl LuneError { - /** - Enables colorization of the error message when formatted using the [`Display`] trait. - - Colorization is enabled by default. - */ - #[doc(hidden)] - pub fn enable_colors(mut self) -> Self { - self.disable_colors = false; - self - } - - /** - Disables colorization of the error message when formatted using the [`Display`] trait. - - Colorization is enabled by default. - */ - #[doc(hidden)] - pub fn disable_colors(mut self) -> Self { - self.disable_colors = true; - self - } - - /** - Returns `true` if the error can likely be fixed by appending more input to the source code. - - See [`mlua::Error::SyntaxError`] for more information. - */ - pub fn is_incomplete_input(&self) -> bool { - matches!( - self.error, - LuaError::SyntaxError { - incomplete_input: true, - .. - } - ) - } -} - -impl From for LuneError { - fn from(value: LuaError) -> Self { - Self { - error: value, - disable_colors: false, - } - } -} - -impl Display for LuneError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{}", - pretty_format_luau_error(&self.error, !self.disable_colors) - ) - } -} - -impl Error for LuneError { - // TODO: Comment this out when we are ready to also re-export - // `mlua` as part of our public library interface in Lune - // fn cause(&self) -> Option<&dyn Error> { - // Some(&self.error) - // } -} diff --git a/src/old/importer/mod.rs b/src/old/importer/mod.rs deleted file mode 100644 index 86165cc..0000000 --- a/src/old/importer/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -use mlua::prelude::*; - -mod require; -mod require_waker; - -use crate::lune_temp::builtins::{self, top_level}; - -pub fn create(lua: &'static Lua, args: Vec) -> LuaResult<()> { - // Create all builtins - let builtins = vec![ - ("fs", builtins::fs::create(lua)?), - ("net", builtins::net::create(lua)?), - ("process", builtins::process::create(lua, args)?), - ("serde", builtins::serde::create(lua)?), - ("stdio", builtins::stdio::create(lua)?), - ("task", builtins::task::create(lua)?), - ("luau", builtins::luau::create(lua)?), - #[cfg(feature = "roblox")] - ("roblox", builtins::roblox::create(lua)?), - ]; - - // Create our importer (require) with builtins - let require_fn = require::create(lua, builtins)?; - - // Create all top-level globals - let globals = vec![ - ("require", require_fn), - ("print", lua.create_function(top_level::print)?), - ("warn", lua.create_function(top_level::warn)?), - ("error", lua.create_function(top_level::error)?), - ("type", lua.create_function(top_level::proxy_type)?), - ("typeof", lua.create_function(top_level::proxy_typeof)?), - ]; - - // Set top-level globals - let lua_globals = lua.globals(); - for (name, global) in globals { - lua_globals.set(name, global)?; - } - - Ok(()) -} diff --git a/src/old/importer/require.rs b/src/old/importer/require.rs deleted file mode 100644 index 0f1d7f4..0000000 --- a/src/old/importer/require.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, - env::current_dir, - path::{self, PathBuf}, - sync::Arc, -}; - -use mlua::prelude::*; -use tokio::fs; -use tokio::sync::Mutex as AsyncMutex; - -use crate::lune_temp::lua::{ - table::TableBuilder, - task::{TaskScheduler, TaskSchedulerScheduleExt}, -}; - -use super::require_waker::{RequireWakerFuture, RequireWakerState}; - -const REQUIRE_IMPL_LUA: &str = r#" -local source = info(1, "s") -if source == '[string "require"]' then - source = info(2, "s") -end -load(context, source, ...) -return yield() -"#; - -type RequireWakersVec<'lua> = Vec>>>; - -fn append_extension_and_canonicalize( - path: impl Into, - ext: &'static str, -) -> Result { - let mut new = path.into(); - match new.extension() { - // FUTURE: There's probably a better way to do this than converting to a lossy string - Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())), - None => new.set_extension(ext), - }; - dunce::canonicalize(new) -} - -#[derive(Debug, Clone, Default)] -struct RequireContext<'lua> { - // NOTE: We need to use arc here so that mlua clones - // the reference and not the entire inner value(s) - builtins: Arc>>, - cached: Arc>>>>, - wakers: Arc>>>, - locks: Arc>>, - pwd: String, -} - -impl<'lua> RequireContext<'lua> { - pub fn new(lua: &'lua Lua, builtins_vec: Vec<(K, V)>) -> LuaResult - where - K: Into, - V: IntoLua<'lua>, - { - let mut pwd = current_dir() - .expect("Failed to access current working directory") - .to_string_lossy() - .to_string(); - if !pwd.ends_with(path::MAIN_SEPARATOR) { - pwd = format!("{pwd}{}", path::MAIN_SEPARATOR) - } - let mut builtins = HashMap::new(); - for (key, value) in builtins_vec { - builtins.insert(key.into(), value.into_lua_multi(lua)?); - } - Ok(Self { - pwd, - builtins: Arc::new(builtins), - ..Default::default() - }) - } - - pub fn is_locked(&self, absolute_path: &str) -> bool { - self.locks.borrow().contains(absolute_path) - } - - pub fn set_locked(&self, absolute_path: &str) -> bool { - self.locks.borrow_mut().insert(absolute_path.to_string()) - } - - pub fn set_unlocked(&self, absolute_path: &str) -> bool { - self.locks.borrow_mut().remove(absolute_path) - } - - pub fn try_acquire_lock_sync(&self, absolute_path: &str) -> bool { - if self.is_locked(absolute_path) { - false - } else { - self.set_locked(absolute_path); - true - } - } - - pub fn set_cached(&self, absolute_path: &str, result: &LuaResult>) { - self.cached - .borrow_mut() - .insert(absolute_path.to_string(), result.clone()); - if let Some(wakers) = self.wakers.borrow_mut().remove(absolute_path) { - for waker in wakers { - waker - .try_lock() - .expect("Failed to lock waker") - .finalize(result.clone()); - } - } - } - - pub fn wait_for_cache(self, absolute_path: &str) -> RequireWakerFuture<'lua> { - let state = RequireWakerState::new(); - let fut = RequireWakerFuture::new(&state); - self.wakers - .borrow_mut() - .entry(absolute_path.to_string()) - .or_insert_with(Vec::new) - .push(Arc::clone(&state)); - fut - } - - pub fn get_paths( - &self, - require_source: String, - require_path: String, - ) -> LuaResult<(String, String)> { - if require_path.starts_with('@') { - return Ok((require_path.clone(), require_path)); - } - let path_relative_to_pwd = PathBuf::from( - &require_source - .trim_start_matches("[string \"") - .trim_end_matches("\"]"), - ) - .parent() - .unwrap() - .join(&require_path); - // Try to normalize and resolve relative path segments such as './' and '../' - let file_path = match ( - append_extension_and_canonicalize(&path_relative_to_pwd, "luau"), - append_extension_and_canonicalize(&path_relative_to_pwd, "lua"), - ) { - (Ok(luau), _) => luau, - (_, Ok(lua)) => lua, - // If we did not find a luau/lua file at the wanted path, - // we should also look for "init" files in directories - _ => { - let init_dir_path = path_relative_to_pwd.join("init"); - match ( - append_extension_and_canonicalize(&init_dir_path, "luau"), - append_extension_and_canonicalize(&init_dir_path, "lua"), - ) { - (Ok(luau), _) => luau, - (_, Ok(lua)) => lua, - _ => { - return Err(LuaError::RuntimeError(format!( - "File does not exist at path '{require_path}'" - ))); - } - } - } - }; - let absolute = file_path.to_string_lossy().to_string(); - let relative = absolute.trim_start_matches(&self.pwd).to_string(); - Ok((absolute, relative)) - } -} - -impl<'lua> LuaUserData for RequireContext<'lua> {} - -fn load_builtin<'lua>( - _lua: &'lua Lua, - context: RequireContext<'lua>, - module_name: String, - _has_acquired_lock: bool, -) -> LuaResult> { - match context.builtins.get(&module_name) { - Some(module) => Ok(module.clone()), - None => Err(LuaError::RuntimeError(format!( - "No builtin module exists with the name '{}'", - module_name - ))), - } -} - -async fn load_file<'lua>( - lua: &'lua Lua, - context: RequireContext<'lua>, - absolute_path: String, - relative_path: String, - has_acquired_lock: bool, -) -> LuaResult> { - let cached = { context.cached.borrow().get(&absolute_path).cloned() }; - match cached { - Some(cached) => cached, - None => { - if !has_acquired_lock { - return context.wait_for_cache(&absolute_path).await; - } - // Try to read the wanted file, note that we use bytes instead of reading - // to a string since lua scripts are not necessarily valid utf-8 strings - let contents = fs::read(&absolute_path).await.into_lua_err()?; - // Use a name without extensions for loading the chunk, some - // other code assumes the require path is without extensions - let path_relative_no_extension = relative_path - .trim_end_matches(".lua") - .trim_end_matches(".luau"); - // Load the file into a thread - let loaded_func = lua - .load(contents) - .set_name(path_relative_no_extension) - .into_function()?; - let loaded_thread = lua.create_thread(loaded_func)?; - // Run the thread and wait for completion using the native task scheduler waker - let task_fut = { - let sched = lua.app_data_ref::<&TaskScheduler>().unwrap(); - let task = sched.schedule_blocking(loaded_thread, LuaMultiValue::new())?; - sched.wait_for_task_completion(task) - }; - // Wait for the thread to finish running, cache + return our result, - // notify any other threads that are also waiting on this to finish - let rets = task_fut.await; - context.set_cached(&absolute_path, &rets); - rets - } - } -} - -async fn load<'lua>( - lua: &'lua Lua, - context: LuaUserDataRef<'lua, RequireContext<'lua>>, - absolute_path: String, - relative_path: String, - has_acquired_lock: bool, -) -> LuaResult> { - let result = if absolute_path == relative_path && absolute_path.starts_with('@') { - if let Some(module_name) = absolute_path.strip_prefix("@lune/") { - load_builtin( - lua, - context.clone(), - module_name.to_string(), - has_acquired_lock, - ) - } else { - // FUTURE: '@' can be used a special prefix for users to set their own - // paths relative to a project file, similar to typescript paths config - // https://www.typescriptlang.org/tsconfig#paths - Err(LuaError::RuntimeError( - "Require paths prefixed by '@' are not yet supported".to_string(), - )) - } - } else { - load_file( - lua, - context.clone(), - absolute_path.to_string(), - relative_path, - has_acquired_lock, - ) - .await - }; - if has_acquired_lock { - context.set_unlocked(&absolute_path); - } - result -} - -pub fn create(lua: &'static Lua, builtins: Vec<(K, V)>) -> LuaResult -where - K: Clone + Into, - V: Clone + IntoLua<'static>, -{ - let require_context = RequireContext::new(lua, builtins)?; - let require_yield: LuaFunction = lua.named_registry_value("co.yield")?; - let require_info: LuaFunction = lua.named_registry_value("dbg.info")?; - let require_print: LuaFunction = lua.named_registry_value("print")?; - - let require_env = TableBuilder::new(lua)? - .with_value("context", require_context)? - .with_value("yield", require_yield)? - .with_value("info", require_info)? - .with_value("print", require_print)? - .with_function( - "load", - |lua, - (context, require_source, require_path): ( - LuaUserDataRef, - String, - String, - )| { - let (absolute_path, relative_path) = - context.get_paths(require_source, require_path)?; - // NOTE: We can not acquire the lock in the async part of the require - // load process since several requires may have happened for the - // same path before the async load task even gets a chance to run - let has_lock = context.try_acquire_lock_sync(&absolute_path); - let fut = load(lua, context, absolute_path, relative_path, has_lock); - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler as a lua app data"); - sched.queue_async_task_inherited(lua.current_thread(), None, async { - let rets = fut.await?; - let mult = rets.into_lua_multi(lua)?; - Ok(Some(mult)) - }) - }, - )? - .build_readonly()?; - - let require_fn_lua = lua - .load(REQUIRE_IMPL_LUA) - .set_name("require") - .set_environment(require_env) - .into_function()?; - Ok(require_fn_lua) -} diff --git a/src/old/importer/require_waker.rs b/src/old/importer/require_waker.rs deleted file mode 100644 index 4a43b87..0000000 --- a/src/old/importer/require_waker.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::{ - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll, Waker}, -}; - -use tokio::sync::Mutex as AsyncMutex; - -use mlua::prelude::*; - -#[derive(Debug, Clone)] -pub(super) struct RequireWakerState<'lua> { - rets: Option>>, - waker: Option, -} - -impl<'lua> RequireWakerState<'lua> { - pub fn new() -> Arc> { - Arc::new(AsyncMutex::new(RequireWakerState { - rets: None, - waker: None, - })) - } - - pub fn finalize(&mut self, rets: LuaResult>) { - self.rets = Some(rets); - if let Some(waker) = self.waker.take() { - waker.wake(); - } - } -} - -#[derive(Debug)] -pub(super) struct RequireWakerFuture<'lua> { - state: Arc>>, -} - -impl<'lua> RequireWakerFuture<'lua> { - pub fn new(state: &Arc>>) -> Self { - Self { - state: Arc::clone(state), - } - } -} - -impl<'lua> Clone for RequireWakerFuture<'lua> { - fn clone(&self) -> Self { - Self { - state: Arc::clone(&self.state), - } - } -} - -impl<'lua> Future for RequireWakerFuture<'lua> { - type Output = LuaResult>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut shared_state = self.state.try_lock().unwrap(); - if let Some(rets) = shared_state.rets.clone() { - Poll::Ready(rets) - } else { - shared_state.waker = Some(cx.waker().clone()); - Poll::Pending - } - } -} diff --git a/src/old/lua/async_ext.rs b/src/old/lua/async_ext.rs deleted file mode 100644 index 7e6fef9..0000000 --- a/src/old/lua/async_ext.rs +++ /dev/null @@ -1,93 +0,0 @@ -use async_trait::async_trait; -use futures_util::Future; -use mlua::prelude::*; - -use crate::lune_temp::{lua::table::TableBuilder, lua::task::TaskScheduler}; - -use super::task::TaskSchedulerAsyncExt; - -const ASYNC_IMPL_LUA: &str = r#" -resumeAsync(...) -return yield() -"#; - -const WAIT_IMPL_LUA: &str = r#" -resumeAfter(...) -return yield() -"#; - -#[async_trait(?Send)] -pub trait LuaAsyncExt { - fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult> - where - A: FromLuaMulti<'static>, - R: IntoLuaMulti<'static>, - F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>; - - fn create_waiter_function<'lua>(self) -> LuaResult>; -} - -impl LuaAsyncExt for &'static Lua { - /** - Creates a function callable from Lua that runs an async - closure and returns the results of it to the call site. - */ - fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult> - where - A: FromLuaMulti<'static>, - R: IntoLuaMulti<'static>, - F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>, - { - let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?; - let async_env = TableBuilder::new(self)? - .with_value("yield", async_env_yield)? - .with_function("resumeAsync", move |lua: &Lua, args: A| { - let thread = lua.current_thread(); - let fut = func(lua, args); - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler as a lua app data"); - sched.queue_async_task(thread, None, async { - let rets = fut.await?; - let mult = rets.into_lua_multi(lua)?; - Ok(Some(mult)) - }) - })? - .build_readonly()?; - let async_func = self - .load(ASYNC_IMPL_LUA) - .set_name("async") - .set_environment(async_env) - .into_function()?; - Ok(async_func) - } - - /** - Creates a special async function that waits the - desired amount of time, inheriting the guid of the - current thread / task for proper cancellation. - - This will yield the lua thread calling the function until the - desired time has passed and the scheduler resumes the thread. - */ - fn create_waiter_function<'lua>(self) -> LuaResult> { - let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?; - let async_env = TableBuilder::new(self)? - .with_value("yield", async_env_yield)? - .with_function("resumeAfter", move |lua: &Lua, duration: Option| { - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler as a lua app data"); - sched.schedule_wait(lua.current_thread(), duration) - })? - .build_readonly()?; - let async_func = self - .load(WAIT_IMPL_LUA) - .set_name("wait") - .set_environment(async_env) - .into_function()?; - Ok(async_func) - } -} diff --git a/src/old/lua/create.rs b/src/old/lua/create.rs deleted file mode 100644 index 2a97474..0000000 --- a/src/old/lua/create.rs +++ /dev/null @@ -1,184 +0,0 @@ -use mlua::{prelude::*, Compiler as LuaCompiler}; - -/* - - Level 0 is the call to info - - Level 1 is the load call in create() below where we load this into a function - - Level 2 is the call to the trace, which we also want to skip, so start at 3 - - Also note that we must match the mlua traceback format here so that we - can pattern match and beautify it properly later on when outputting it -*/ -const TRACE_IMPL_LUA: &str = r#" -local lines = {} -for level = 3, 16 do - local parts = {} - local source, line, name = info(level, "sln") - if source then - push(parts, source) - else - break - end - if line == -1 then - line = nil - end - if name and #name <= 0 then - name = nil - end - if line then - push(parts, format("%d", line)) - end - if name and #parts > 1 then - push(parts, format(" in function '%s'", name)) - elseif name then - push(parts, format("in function '%s'", name)) - end - if #parts > 0 then - push(lines, concat(parts, ":")) - end -end -if #lines > 0 then - return concat(lines, "\n") -else - return nil -end -"#; - -/** - Stores the following globals in the Lua registry: - - | Registry Name | Global | - |-----------------|-------------------| - | `"print"` | `print` | - | `"error"` | `error` | - | `"type"` | `type` | - | `"typeof"` | `typeof` | - | `"pcall"` | `pcall` | - | `"xpcall"` | `xpcall` | - | `"tostring"` | `tostring` | - | `"tonumber"` | `tonumber` | - | `"co.yield"` | `coroutine.yield` | - | `"co.close"` | `coroutine.close` | - | `"tab.pack"` | `table.pack` | - | `"tab.unpack"` | `table.unpack` | - | `"tab.freeze"` | `table.freeze` | - | `"tab.getmeta"` | `getmetatable` | - | `"tab.setmeta"` | `setmetatable` | - | `"dbg.info"` | `debug.info` | - | `"dbg.trace"` | `debug.traceback` | - - These globals can then be modified safely from other runtime code. -*/ -fn store_globals_in_registry(lua: &Lua) -> LuaResult<()> { - // Extract some global tables that we will extract - // built-in functions from and store in the registry - let globals = lua.globals(); - let debug: LuaTable = globals.get("debug")?; - let string: LuaTable = globals.get("string")?; - let table: LuaTable = globals.get("table")?; - let coroutine: LuaTable = globals.get("coroutine")?; - - // Store original lua global functions in the registry so we can use - // them later without passing them around and dealing with lifetimes - lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?; - lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?; - lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?; - lua.set_named_registry_value("typeof", globals.get::<_, LuaFunction>("typeof")?)?; - lua.set_named_registry_value("xpcall", globals.get::<_, LuaFunction>("xpcall")?)?; - lua.set_named_registry_value("pcall", globals.get::<_, LuaFunction>("pcall")?)?; - lua.set_named_registry_value("tostring", globals.get::<_, LuaFunction>("tostring")?)?; - lua.set_named_registry_value("tonumber", globals.get::<_, LuaFunction>("tonumber")?)?; - lua.set_named_registry_value("co.status", coroutine.get::<_, LuaFunction>("status")?)?; - lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?; - lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?; - lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?; - lua.set_named_registry_value("tab.pack", table.get::<_, LuaFunction>("pack")?)?; - lua.set_named_registry_value("tab.unpack", table.get::<_, LuaFunction>("unpack")?)?; - lua.set_named_registry_value("tab.freeze", table.get::<_, LuaFunction>("freeze")?)?; - lua.set_named_registry_value( - "tab.getmeta", - globals.get::<_, LuaFunction>("getmetatable")?, - )?; - lua.set_named_registry_value( - "tab.setmeta", - globals.get::<_, LuaFunction>("setmetatable")?, - )?; - - // Create a trace function that can be called to obtain a full stack trace from - // lua, this is not possible to do from rust when using our manual scheduler - let dbg_trace_env = lua.create_table_with_capacity(0, 1)?; - dbg_trace_env.set("info", debug.get::<_, LuaFunction>("info")?)?; - dbg_trace_env.set("push", table.get::<_, LuaFunction>("insert")?)?; - dbg_trace_env.set("concat", table.get::<_, LuaFunction>("concat")?)?; - dbg_trace_env.set("format", string.get::<_, LuaFunction>("format")?)?; - - let dbg_trace_fn = lua - .load(TRACE_IMPL_LUA) - .set_name("=dbg.trace") - .set_environment(dbg_trace_env) - .into_function()?; - lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?; - - Ok(()) -} - -/** - Sets the `_VERSION` global to a value matching the string `Lune x.y.z+w` where - `x.y.z` is the current Lune runtime version and `w` is the current Luau version -*/ -fn set_global_version(lua: &Lua) -> LuaResult<()> { - let luau_version_full = lua - .globals() - .get::<_, LuaString>("_VERSION") - .expect("Missing _VERSION global"); - let luau_version = luau_version_full - .to_str()? - .strip_prefix("Luau 0.") - .expect("_VERSION global is formatted incorrectly") - .trim(); - if luau_version.is_empty() { - panic!("_VERSION global is missing version number") - } - lua.globals().set( - "_VERSION", - lua.create_string(&format!( - "Lune {lune}+{luau}", - lune = env!("CARGO_PKG_VERSION"), - luau = luau_version, - ))?, - ) -} - -/** - Creates a _G table that is separate from our built-in globals -*/ -fn set_global_table(lua: &Lua) -> LuaResult<()> { - lua.globals().set("_G", lua.create_table()?) -} - -/** - Enables JIT and sets default compiler settings for the Lua struct. -*/ -fn init_compiler_settings(lua: &Lua) { - lua.enable_jit(true); - lua.set_compiler( - LuaCompiler::default() - .set_coverage_level(0) - .set_debug_level(1) - .set_optimization_level(1), - ); -} - -/** - Creates a new [`mlua::Lua`] struct with compiler, - registry, and globals customized for the Lune runtime. - - Refer to the source code for additional details and specifics. -*/ -pub fn create() -> LuaResult { - let lua = Lua::new(); - init_compiler_settings(&lua); - store_globals_in_registry(&lua)?; - set_global_version(&lua)?; - set_global_table(&lua)?; - Ok(lua) -} diff --git a/src/old/lua/fs/copy.rs b/src/old/lua/fs/copy.rs deleted file mode 100644 index 00f37c3..0000000 --- a/src/old/lua/fs/copy.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::collections::VecDeque; -use std::io::ErrorKind; -use std::path::{Path, PathBuf}; - -use mlua::prelude::*; -use tokio::fs; - -use super::FsWriteOptions; - -pub struct CopyContents { - // Vec<(relative depth, path)> - pub dirs: Vec<(usize, PathBuf)>, - pub files: Vec<(usize, PathBuf)>, -} - -async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult { - let mut dirs = Vec::new(); - let mut files = Vec::new(); - - let mut queue = VecDeque::new(); - - let normalized_root = fs::canonicalize(&root).await.map_err(|e| { - LuaError::RuntimeError(format!("Failed to canonicalize root directory path\n{e}")) - })?; - - // Push initial children of the root path into the queue - let mut entries = fs::read_dir(&normalized_root).await?; - while let Some(entry) = entries.next_entry().await? { - queue.push_back((1, entry.path())); - } - - // Go through the current queue, pushing to it - // when we find any new descendant directories - // FUTURE: Try to do async reading here concurrently to speed it up a bit - while let Some((current_depth, current_path)) = queue.pop_front() { - let meta = fs::metadata(¤t_path).await?; - if meta.is_symlink() { - return Err(LuaError::RuntimeError(format!( - "Symlinks are not yet supported, encountered at path '{}'", - current_path.display() - ))); - } else if meta.is_dir() { - // FUTURE: Add an option in FsWriteOptions for max depth and limit it here - let mut entries = fs::read_dir(¤t_path).await?; - while let Some(entry) = entries.next_entry().await? { - queue.push_back((current_depth + 1, entry.path())); - } - dirs.push((current_depth, current_path)); - } else { - files.push((current_depth, current_path)); - } - } - - // Ensure that all directory and file paths are relative to the root path - // SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe - for (_, dir) in dirs.iter_mut() { - *dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf() - } - for (_, file) in files.iter_mut() { - *file = file.strip_prefix(&normalized_root).unwrap().to_path_buf() - } - - // FUTURE: Deduplicate paths such that these directories: - // - foo/ - // - foo/bar/ - // - foo/bar/baz/ - // turn into a single foo/bar/baz/ and let create_dir_all do the heavy lifting - - Ok(CopyContents { dirs, files }) -} - -async fn ensure_no_dir_exists(path: impl AsRef) -> LuaResult<()> { - let path = path.as_ref(); - match fs::metadata(&path).await { - Ok(meta) if meta.is_dir() => Err(LuaError::RuntimeError(format!( - "A directory already exists at the path '{}'", - path.display() - ))), - _ => Ok(()), - } -} - -async fn ensure_no_file_exists(path: impl AsRef) -> LuaResult<()> { - let path = path.as_ref(); - match fs::metadata(&path).await { - Ok(meta) if meta.is_file() => Err(LuaError::RuntimeError(format!( - "A file already exists at the path '{}'", - path.display() - ))), - _ => Ok(()), - } -} - -pub async fn copy( - source: impl AsRef, - target: impl AsRef, - options: FsWriteOptions, -) -> LuaResult<()> { - let source = source.as_ref(); - let target = target.as_ref(); - - // Check if we got a file or directory - we will handle them differently below - let (is_dir, is_file) = match fs::metadata(&source).await { - Ok(meta) => (meta.is_dir(), meta.is_file()), - Err(e) if e.kind() == ErrorKind::NotFound => { - return Err(LuaError::RuntimeError(format!( - "No file or directory exists at the path '{}'", - source.display() - ))) - } - Err(e) => return Err(e.into()), - }; - if !is_file && !is_dir { - return Err(LuaError::RuntimeError(format!( - "The given path '{}' is not a file or a directory", - source.display() - ))); - } - - // Perform copying: - // - // 1. If we are not allowed to overwrite, make sure nothing exists at the target path - // 2. If we are allowed to overwrite, remove any previous entry at the path - // 3. Write all directories first - // 4. Write all files - - if !options.overwrite { - if is_file { - ensure_no_file_exists(target).await?; - } else if is_dir { - ensure_no_dir_exists(target).await?; - } - } - - if is_file { - fs::copy(source, target).await?; - } else if is_dir { - let contents = get_contents_at(source.to_path_buf(), options).await?; - - if options.overwrite { - fs::remove_dir_all(target).await?; - } - - // FUTURE: Write dirs / files concurrently - // to potentially speed these operations up - for (_, dir) in &contents.dirs { - fs::create_dir_all(target.join(dir)).await?; - } - for (_, file) in &contents.files { - fs::copy(source.join(file), target.join(file)).await?; - } - } - - Ok(()) -} diff --git a/src/old/lua/fs/metadata.rs b/src/old/lua/fs/metadata.rs deleted file mode 100644 index a5e1ddf..0000000 --- a/src/old/lua/fs/metadata.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::{ - fmt, - fs::{FileType as StdFileType, Metadata as StdMetadata, Permissions as StdPermissions}, - io::Result as IoResult, - str::FromStr, - time::SystemTime, -}; - -use mlua::prelude::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FsMetadataKind { - None, - File, - Dir, - Symlink, -} - -impl fmt::Display for FsMetadataKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::None => "none", - Self::File => "file", - Self::Dir => "dir", - Self::Symlink => "symlink", - } - ) - } -} - -impl FromStr for FsMetadataKind { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s.trim().to_ascii_lowercase().as_ref() { - "none" => Ok(Self::None), - "file" => Ok(Self::File), - "dir" => Ok(Self::Dir), - "symlink" => Ok(Self::Symlink), - _ => Err("Invalid metadata kind"), - } - } -} - -impl From for FsMetadataKind { - fn from(value: StdFileType) -> Self { - if value.is_file() { - Self::File - } else if value.is_dir() { - Self::Dir - } else if value.is_symlink() { - Self::Symlink - } else { - panic!("Encountered unknown filesystem filetype") - } - } -} - -impl<'lua> IntoLua<'lua> for FsMetadataKind { - fn into_lua(self, lua: &'lua Lua) -> LuaResult> { - if self == Self::None { - Ok(LuaValue::Nil) - } else { - self.to_string().into_lua(lua) - } - } -} - -#[derive(Debug, Clone)] -pub struct FsPermissions { - pub(crate) read_only: bool, -} - -impl From for FsPermissions { - fn from(value: StdPermissions) -> Self { - Self { - read_only: value.readonly(), - } - } -} - -impl<'lua> IntoLua<'lua> for FsPermissions { - fn into_lua(self, lua: &'lua Lua) -> LuaResult> { - let tab = lua.create_table_with_capacity(0, 1)?; - tab.set("readOnly", self.read_only)?; - tab.set_readonly(true); - Ok(LuaValue::Table(tab)) - } -} - -#[derive(Debug, Clone)] -pub struct FsMetadata { - pub(crate) kind: FsMetadataKind, - pub(crate) exists: bool, - pub(crate) created_at: Option, - pub(crate) modified_at: Option, - pub(crate) accessed_at: Option, - pub(crate) permissions: Option, -} - -impl FsMetadata { - pub fn not_found() -> Self { - Self { - kind: FsMetadataKind::None, - exists: false, - created_at: None, - modified_at: None, - accessed_at: None, - permissions: None, - } - } -} - -impl<'lua> IntoLua<'lua> for FsMetadata { - fn into_lua(self, lua: &'lua Lua) -> LuaResult> { - let tab = lua.create_table_with_capacity(0, 5)?; - tab.set("kind", self.kind)?; - tab.set("exists", self.exists)?; - tab.set("createdAt", self.created_at)?; - tab.set("modifiedAt", self.modified_at)?; - tab.set("accessedAt", self.accessed_at)?; - tab.set("permissions", self.permissions)?; - tab.set_readonly(true); - Ok(LuaValue::Table(tab)) - } -} - -impl From for FsMetadata { - fn from(value: StdMetadata) -> Self { - Self { - kind: value.file_type().into(), - exists: true, - // FUTURE: Turn these into DateTime structs instead when that's implemented - created_at: system_time_to_timestamp(value.created()), - modified_at: system_time_to_timestamp(value.modified()), - accessed_at: system_time_to_timestamp(value.accessed()), - permissions: Some(FsPermissions::from(value.permissions())), - } - } -} - -fn system_time_to_timestamp(res: IoResult) -> Option { - match res { - Ok(t) => match t.duration_since(SystemTime::UNIX_EPOCH) { - Ok(d) => Some(d.as_secs_f64()), - Err(_) => None, - }, - Err(_) => None, - } -} diff --git a/src/old/lua/fs/mod.rs b/src/old/lua/fs/mod.rs deleted file mode 100644 index 82378d7..0000000 --- a/src/old/lua/fs/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod copy; -mod metadata; -mod options; - -pub use copy::copy; -pub use metadata::FsMetadata; -pub use options::FsWriteOptions; diff --git a/src/old/lua/fs/options.rs b/src/old/lua/fs/options.rs deleted file mode 100644 index d33c8f4..0000000 --- a/src/old/lua/fs/options.rs +++ /dev/null @@ -1,31 +0,0 @@ -use mlua::prelude::*; - -#[derive(Debug, Clone, Copy)] -pub struct FsWriteOptions { - pub(crate) overwrite: bool, -} - -impl<'lua> FromLua<'lua> for FsWriteOptions { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - Ok(match value { - LuaValue::Nil => Self { overwrite: false }, - LuaValue::Boolean(b) => Self { overwrite: b }, - LuaValue::Table(t) => { - let overwrite: Option = t.get("overwrite")?; - Self { - overwrite: overwrite.unwrap_or(false), - } - } - _ => { - return Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "FsWriteOptions", - message: Some(format!( - "Invalid write options - expected boolean or table, got {}", - value.type_name() - )), - }) - } - }) - } -} diff --git a/src/old/lua/luau/mod.rs b/src/old/lua/luau/mod.rs deleted file mode 100644 index bf42f1f..0000000 --- a/src/old/lua/luau/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod options; - -pub use options::{LuauCompileOptions, LuauLoadOptions}; diff --git a/src/old/lua/luau/options.rs b/src/old/lua/luau/options.rs deleted file mode 100644 index d1febe6..0000000 --- a/src/old/lua/luau/options.rs +++ /dev/null @@ -1,116 +0,0 @@ -use mlua::prelude::*; -use mlua::Compiler as LuaCompiler; - -const DEFAULT_DEBUG_NAME: &str = "luau.load(...)"; - -pub struct LuauCompileOptions { - pub(crate) optimization_level: u8, - pub(crate) coverage_level: u8, - pub(crate) debug_level: u8, -} - -impl LuauCompileOptions { - pub fn into_compiler(self) -> LuaCompiler { - LuaCompiler::default() - .set_optimization_level(self.optimization_level) - .set_coverage_level(self.coverage_level) - .set_debug_level(self.debug_level) - } -} - -impl Default for LuauCompileOptions { - fn default() -> Self { - // NOTE: This is the same as LuaCompiler::default() values, but they are - // not accessible from outside of mlua so we need to recreate them here. - Self { - optimization_level: 1, - coverage_level: 0, - debug_level: 1, - } - } -} - -impl<'lua> FromLua<'lua> for LuauCompileOptions { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - Ok(match value { - LuaValue::Nil => Self::default(), - LuaValue::Table(t) => { - let mut options = Self::default(); - - let get_and_check = |name: &'static str| -> LuaResult> { - match t.get(name)? { - Some(n @ (0 | 1 | 2)) => Ok(Some(n)), - Some(n) => Err(LuaError::runtime(format!( - "'{name}' must be one of: 0, 1, or 2 - got {n}" - ))), - None => Ok(None), - } - }; - - if let Some(optimization_level) = get_and_check("optimizationLevel")? { - options.optimization_level = optimization_level; - } - if let Some(coverage_level) = get_and_check("coverageLevel")? { - options.coverage_level = coverage_level; - } - if let Some(debug_level) = get_and_check("debugLevel")? { - options.debug_level = debug_level; - } - - options - } - _ => { - return Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "CompileOptions", - message: Some(format!( - "Invalid compile options - expected table, got {}", - value.type_name() - )), - }) - } - }) - } -} - -pub struct LuauLoadOptions { - pub(crate) debug_name: String, -} - -impl Default for LuauLoadOptions { - fn default() -> Self { - Self { - debug_name: DEFAULT_DEBUG_NAME.to_string(), - } - } -} - -impl<'lua> FromLua<'lua> for LuauLoadOptions { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - Ok(match value { - LuaValue::Nil => Self::default(), - LuaValue::Table(t) => { - let mut options = Self::default(); - - if let Some(debug_name) = t.get("debugName")? { - options.debug_name = debug_name; - } - - options - } - LuaValue::String(s) => Self { - debug_name: s.to_string_lossy().to_string(), - }, - _ => { - return Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "LoadOptions", - message: Some(format!( - "Invalid load options - expected string or table, got {}", - value.type_name() - )), - }) - } - }) - } -} diff --git a/src/old/lua/mod.rs b/src/old/lua/mod.rs deleted file mode 100644 index 944ea44..0000000 --- a/src/old/lua/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod create; - -pub mod async_ext; -pub mod fs; -pub mod luau; -pub mod net; -pub mod process; -pub mod serde; -pub mod stdio; -pub mod table; -pub mod task; - -pub use create::create as create_lune_lua; diff --git a/src/old/lua/net/client.rs b/src/old/lua/net/client.rs deleted file mode 100644 index 2120c58..0000000 --- a/src/old/lua/net/client.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::str::FromStr; - -use mlua::prelude::*; - -use hyper::{header::HeaderName, http::HeaderValue, HeaderMap}; -use reqwest::{IntoUrl, Method, RequestBuilder}; - -pub struct NetClientBuilder { - builder: reqwest::ClientBuilder, -} - -impl NetClientBuilder { - pub fn new() -> NetClientBuilder { - Self { - builder: reqwest::ClientBuilder::new(), - } - } - - pub fn headers(mut self, headers: &[(K, V)]) -> LuaResult - where - K: AsRef, - V: AsRef<[u8]>, - { - let mut map = HeaderMap::new(); - for (key, val) in headers { - let hkey = HeaderName::from_str(key.as_ref()).into_lua_err()?; - let hval = HeaderValue::from_bytes(val.as_ref()).into_lua_err()?; - map.insert(hkey, hval); - } - self.builder = self.builder.default_headers(map); - Ok(self) - } - - pub fn build(self) -> LuaResult { - let client = self.builder.build().into_lua_err()?; - Ok(NetClient(client)) - } -} - -#[derive(Debug, Clone)] -pub struct NetClient(reqwest::Client); - -impl NetClient { - pub fn request(&self, method: Method, url: U) -> RequestBuilder { - self.0.request(method, url) - } -} - -impl LuaUserData for NetClient {} diff --git a/src/old/lua/net/config.rs b/src/old/lua/net/config.rs deleted file mode 100644 index 59c1e3e..0000000 --- a/src/old/lua/net/config.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::collections::HashMap; - -use mlua::prelude::*; - -use reqwest::Method; - -// Net request config - -#[derive(Debug, Clone)] -pub struct RequestConfigOptions { - pub decompress: bool, -} - -impl Default for RequestConfigOptions { - fn default() -> Self { - Self { decompress: true } - } -} - -impl<'lua> FromLua<'lua> for RequestConfigOptions { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - // Nil means default options, table means custom options - if let LuaValue::Nil = value { - return Ok(Self::default()); - } else if let LuaValue::Table(tab) = value { - // Extract flags - let decompress = match tab.raw_get::<_, Option>("decompress") { - Ok(decomp) => Ok(decomp.unwrap_or(true)), - Err(_) => Err(LuaError::RuntimeError( - "Invalid option value for 'decompress' in request config options".to_string(), - )), - }?; - return Ok(Self { decompress }); - } - // Anything else is invalid - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "RequestConfigOptions", - message: Some(format!( - "Invalid request config options - expected table or nil, got {}", - value.type_name() - )), - }) - } -} - -#[derive(Debug, Clone)] -pub struct RequestConfig<'a> { - pub url: String, - pub method: Method, - pub query: HashMap, LuaString<'a>>, - pub headers: HashMap, LuaString<'a>>, - pub body: Option>, - pub options: RequestConfigOptions, -} - -impl<'lua> FromLua<'lua> for RequestConfig<'lua> { - fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult { - // If we just got a string we assume its a GET request to a given url - if let LuaValue::String(s) = value { - return Ok(Self { - url: s.to_string_lossy().to_string(), - method: Method::GET, - query: HashMap::new(), - headers: HashMap::new(), - body: None, - options: Default::default(), - }); - } - // If we got a table we are able to configure the entire request - if let LuaValue::Table(tab) = value { - // Extract url - let url = match tab.raw_get::<_, LuaString>("url") { - Ok(config_url) => Ok(config_url.to_string_lossy().to_string()), - Err(_) => Err(LuaError::RuntimeError( - "Missing 'url' in request config".to_string(), - )), - }?; - // Extract method - let method = match tab.raw_get::<_, LuaString>("method") { - Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(), - Err(_) => "GET".to_string(), - }; - // Extract query - let query = match tab.raw_get::<_, LuaTable>("query") { - Ok(config_headers) => { - let mut lua_headers = HashMap::new(); - for pair in config_headers.pairs::() { - let (key, value) = pair?.to_owned(); - lua_headers.insert(key, value); - } - lua_headers - } - Err(_) => HashMap::new(), - }; - // Extract headers - let headers = match tab.raw_get::<_, LuaTable>("headers") { - Ok(config_headers) => { - let mut lua_headers = HashMap::new(); - for pair in config_headers.pairs::() { - let (key, value) = pair?.to_owned(); - lua_headers.insert(key, value); - } - lua_headers - } - Err(_) => HashMap::new(), - }; - // Extract body - let body = match tab.raw_get::<_, LuaString>("body") { - Ok(config_body) => Some(config_body.as_bytes().to_owned()), - Err(_) => None, - }; - // Convert method string into proper enum - let method = method.trim().to_ascii_uppercase(); - let method = match method.as_ref() { - "GET" => Ok(Method::GET), - "POST" => Ok(Method::POST), - "PUT" => Ok(Method::PUT), - "DELETE" => Ok(Method::DELETE), - "HEAD" => Ok(Method::HEAD), - "OPTIONS" => Ok(Method::OPTIONS), - "PATCH" => Ok(Method::PATCH), - _ => Err(LuaError::RuntimeError(format!( - "Invalid request config method '{}'", - &method - ))), - }?; - // Parse any extra options given - let options = match tab.raw_get::<_, LuaValue>("options") { - Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?, - Err(_) => RequestConfigOptions::default(), - }; - // All good, validated and we got what we need - return Ok(Self { - url, - method, - query, - headers, - body, - options, - }); - }; - // Anything else is invalid - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "RequestConfig", - message: Some(format!( - "Invalid request config - expected string or table, got {}", - value.type_name() - )), - }) - } -} - -// Net serve config - -pub struct ServeConfig<'a> { - pub handle_request: LuaFunction<'a>, - pub handle_web_socket: Option>, -} - -impl<'lua> FromLua<'lua> for ServeConfig<'lua> { - fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult { - let message = match &value { - LuaValue::Function(f) => { - return Ok(ServeConfig { - handle_request: f.clone(), - handle_web_socket: None, - }) - } - LuaValue::Table(t) => { - let handle_request: Option = t.raw_get("handleRequest")?; - let handle_web_socket: Option = t.raw_get("handleWebSocket")?; - if handle_request.is_some() || handle_web_socket.is_some() { - return Ok(ServeConfig { - handle_request: handle_request.unwrap_or_else(|| { - let chunk = r#" - return { - status = 426, - body = "Upgrade Required", - headers = { - Upgrade = "websocket", - }, - } - "#; - lua.load(chunk) - .into_function() - .expect("Failed to create default http responder function") - }), - handle_web_socket, - }); - } else { - Some("Missing handleRequest and / or handleWebSocket".to_string()) - } - } - _ => None, - }; - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "ServeConfig", - message, - }) - } -} diff --git a/src/old/lua/net/mod.rs b/src/old/lua/net/mod.rs deleted file mode 100644 index c52b780..0000000 --- a/src/old/lua/net/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod client; -mod config; -mod response; -mod server; -mod websocket; - -pub use client::{NetClient, NetClientBuilder}; -pub use config::{RequestConfig, ServeConfig}; -pub use response::{NetServeResponse, NetServeResponseKind}; -pub use server::{NetLocalExec, NetService}; -pub use websocket::NetWebSocket; diff --git a/src/old/lua/net/response.rs b/src/old/lua/net/response.rs deleted file mode 100644 index fa2e748..0000000 --- a/src/old/lua/net/response.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::collections::HashMap; - -use hyper::{Body, Response}; -use mlua::prelude::*; - -#[derive(Debug, Clone, Copy)] -pub enum NetServeResponseKind { - PlainText, - Table, -} - -#[derive(Debug, Clone)] -pub struct NetServeResponse { - kind: NetServeResponseKind, - status: u16, - headers: HashMap>, - body: Option>, -} - -impl NetServeResponse { - pub fn into_response(self) -> LuaResult> { - Ok(match self.kind { - NetServeResponseKind::PlainText => Response::builder() - .status(200) - .header("Content-Type", "text/plain") - .body(Body::from(self.body.unwrap())) - .into_lua_err()?, - NetServeResponseKind::Table => { - let mut response = Response::builder(); - for (key, value) in self.headers { - response = response.header(&key, value); - } - response - .status(self.status) - .body(Body::from(self.body.unwrap_or_default())) - .into_lua_err()? - } - }) - } -} - -impl<'lua> FromLua<'lua> for NetServeResponse { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - match value { - // Plain strings from the handler are plaintext responses - LuaValue::String(s) => Ok(Self { - kind: NetServeResponseKind::PlainText, - status: 200, - headers: HashMap::new(), - body: Some(s.as_bytes().to_vec()), - }), - // Tables are more detailed responses with potential status, headers, body - LuaValue::Table(t) => { - let status: Option = t.get("status")?; - let headers: Option = t.get("headers")?; - let body: Option = t.get("body")?; - - let mut headers_map = HashMap::new(); - if let Some(headers) = headers { - for pair in headers.pairs::() { - let (h, v) = pair?; - headers_map.insert(h, v.as_bytes().to_vec()); - } - } - - let body_bytes = body.map(|s| s.as_bytes().to_vec()); - - Ok(Self { - kind: NetServeResponseKind::Table, - status: status.unwrap_or(200), - headers: headers_map, - body: body_bytes, - }) - } - // Anything else is an error - value => Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "NetServeResponse", - message: None, - }), - } - } -} - -impl<'lua> IntoLua<'lua> for NetServeResponse { - fn into_lua(self, lua: &'lua Lua) -> LuaResult> { - if self.headers.len() > i32::MAX as usize { - return Err(LuaError::ToLuaConversionError { - from: "NetServeResponse", - to: "table", - message: Some("Too many header values".to_string()), - }); - } - let body = self.body.map(|b| lua.create_string(b)).transpose()?; - let headers = lua.create_table_with_capacity(0, self.headers.len())?; - for (key, value) in self.headers { - headers.set(key, lua.create_string(&value)?)?; - } - let table = lua.create_table_with_capacity(0, 3)?; - table.set("status", self.status)?; - table.set("headers", headers)?; - table.set("body", body)?; - table.set_readonly(true); - Ok(LuaValue::Table(table)) - } -} diff --git a/src/old/lua/net/server.rs b/src/old/lua/net/server.rs deleted file mode 100644 index 807a0f2..0000000 --- a/src/old/lua/net/server.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::{ - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -use mlua::prelude::*; - -use hyper::{body::to_bytes, server::conn::AddrStream, service::Service}; -use hyper::{Body, Request, Response}; -use hyper_tungstenite::{is_upgrade_request as is_ws_upgrade_request, upgrade as ws_upgrade}; -use tokio::task; - -use crate::lune_temp::{ - lua::table::TableBuilder, - lua::task::{TaskScheduler, TaskSchedulerAsyncExt, TaskSchedulerScheduleExt}, -}; - -use super::{NetServeResponse, NetWebSocket}; - -// Hyper service implementation for net, lots of boilerplate here -// but make_svc and make_svc_function do not work for what we need - -pub struct NetServiceInner( - &'static Lua, - Arc, - Arc>, -); - -impl Service> for NetServiceInner { - type Response = Response; - type Error = LuaError; - type Future = Pin>>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, mut req: Request) -> Self::Future { - let lua = self.0; - if self.2.is_some() && is_ws_upgrade_request(&req) { - // Websocket upgrade request + websocket handler exists, - // we should now upgrade this connection to a websocket - // and then call our handler with a new socket object - let kopt = self.2.clone(); - let key = kopt.as_ref().as_ref().unwrap(); - let handler: LuaFunction = lua.registry_value(key).expect("Missing websocket handler"); - let (response, ws) = ws_upgrade(&mut req, None).expect("Failed to upgrade websocket"); - // This should be spawned as a registered task, otherwise - // the scheduler may exit early and cancel this even though what - // we want here is a long-running task that keeps the program alive - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler"); - let task = sched.register_background_task(); - task::spawn_local(async move { - // Create our new full websocket object, then - // schedule our handler to get called asap - let ws = ws.await.into_lua_err()?; - let sock = NetWebSocket::new(ws).into_lua_table(lua)?; - let sched = lua - .app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler"); - let result = sched.schedule_blocking( - lua.create_thread(handler)?, - LuaMultiValue::from_vec(vec![LuaValue::Table(sock)]), - ); - task.unregister(Ok(())); - result - }); - Box::pin(async move { Ok(response) }) - } else { - // Got a normal http request or no websocket handler - // exists, just call the http request handler - let key = self.1.clone(); - let (parts, body) = req.into_parts(); - Box::pin(async move { - // Convert request body into bytes, extract handler - let bytes = to_bytes(body).await.into_lua_err()?; - let handler: LuaFunction = lua.registry_value(&key)?; - // Create a readonly table for the request query params - let query_params = TableBuilder::new(lua)? - .with_values( - parts - .uri - .query() - .unwrap_or_default() - .split('&') - .filter_map(|q| q.split_once('=')) - .collect(), - )? - .build_readonly()?; - // Do the same for headers - let header_map = TableBuilder::new(lua)? - .with_values( - parts - .headers - .iter() - .map(|(name, value)| { - (name.to_string(), value.to_str().unwrap().to_string()) - }) - .collect(), - )? - .build_readonly()?; - // Create a readonly table with request info to pass to the handler - let request = TableBuilder::new(lua)? - .with_value("path", parts.uri.path())? - .with_value("query", query_params)? - .with_value("method", parts.method.as_str())? - .with_value("headers", header_map)? - .with_value("body", lua.create_string(&bytes)?)? - .build_readonly()?; - let response: LuaResult = handler.call(request); - // Send below errors to task scheduler so that they can emit properly - let lua_error = match response { - Ok(r) => match r.into_response() { - Ok(res) => return Ok(res), - Err(err) => err, - }, - Err(err) => err, - }; - lua.app_data_ref::<&TaskScheduler>() - .expect("Missing task scheduler") - .forward_lua_error(lua_error); - Ok(Response::builder() - .status(500) - .body(Body::from("Internal Server Error")) - .unwrap()) - }) - } - } -} - -pub struct NetService( - &'static Lua, - Arc, - Arc>, -); - -impl NetService { - pub fn new( - lua: &'static Lua, - callback_http: LuaRegistryKey, - callback_websocket: Option, - ) -> Self { - Self(lua, Arc::new(callback_http), Arc::new(callback_websocket)) - } -} - -impl Service<&AddrStream> for NetService { - type Response = NetServiceInner; - type Error = hyper::Error; - type Future = Pin>>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: &AddrStream) -> Self::Future { - let lua = self.0; - let key1 = self.1.clone(); - let key2 = self.2.clone(); - Box::pin(async move { Ok(NetServiceInner(lua, key1, key2)) }) - } -} - -#[derive(Clone, Copy, Debug)] -pub struct NetLocalExec; - -impl hyper::rt::Executor for NetLocalExec -where - F: std::future::Future + 'static, // not requiring `Send` -{ - fn execute(&self, fut: F) { - task::spawn_local(fut); - } -} diff --git a/src/old/lua/net/websocket.rs b/src/old/lua/net/websocket.rs deleted file mode 100644 index ece0ce9..0000000 --- a/src/old/lua/net/websocket.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::{cell::Cell, sync::Arc}; - -use hyper::upgrade::Upgraded; -use mlua::prelude::*; - -use futures_util::{ - stream::{SplitSink, SplitStream}, - SinkExt, StreamExt, -}; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - net::TcpStream, - sync::Mutex as AsyncMutex, -}; - -use hyper_tungstenite::{ - tungstenite::{ - protocol::{frame::coding::CloseCode as WsCloseCode, CloseFrame as WsCloseFrame}, - Message as WsMessage, - }, - WebSocketStream, -}; -use tokio_tungstenite::MaybeTlsStream; - -use crate::lune_temp::lua::table::TableBuilder; - -const WEB_SOCKET_IMPL_LUA: &str = r#" -return freeze(setmetatable({ - close = function(...) - return close(websocket, ...) - end, - send = function(...) - return send(websocket, ...) - end, - next = function(...) - return next(websocket, ...) - end, -}, { - __index = function(self, key) - if key == "closeCode" then - return close_code(websocket) - end - end, -})) -"#; - -#[derive(Debug)] -pub struct NetWebSocket { - close_code: Arc>>, - read_stream: Arc>>>, - write_stream: Arc, WsMessage>>>, -} - -impl Clone for NetWebSocket { - fn clone(&self) -> Self { - Self { - close_code: Arc::clone(&self.close_code), - read_stream: Arc::clone(&self.read_stream), - write_stream: Arc::clone(&self.write_stream), - } - } -} - -impl NetWebSocket -where - T: AsyncRead + AsyncWrite + Unpin, -{ - pub fn new(value: WebSocketStream) -> Self { - let (write, read) = value.split(); - - Self { - close_code: Arc::new(Cell::new(None)), - read_stream: Arc::new(AsyncMutex::new(read)), - write_stream: Arc::new(AsyncMutex::new(write)), - } - } - - fn into_lua_table_with_env<'lua>( - lua: &'lua Lua, - env: LuaTable<'lua>, - ) -> LuaResult> { - lua.load(WEB_SOCKET_IMPL_LUA) - .set_name("websocket") - .set_environment(env) - .eval() - } -} - -type NetWebSocketStreamClient = MaybeTlsStream; -impl NetWebSocket { - pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult { - let socket_env = TableBuilder::new(lua)? - .with_value("websocket", self)? - .with_function("close_code", close_code::)? - .with_async_function("close", close::)? - .with_async_function("send", send::)? - .with_async_function("next", next::)? - .with_value( - "setmetatable", - lua.named_registry_value::("tab.setmeta")?, - )? - .with_value( - "freeze", - lua.named_registry_value::("tab.freeze")?, - )? - .build_readonly()?; - Self::into_lua_table_with_env(lua, socket_env) - } -} - -type NetWebSocketStreamServer = Upgraded; -impl NetWebSocket { - pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult { - let socket_env = TableBuilder::new(lua)? - .with_value("websocket", self)? - .with_function("close_code", close_code::)? - .with_async_function("close", close::)? - .with_async_function("send", send::)? - .with_async_function("next", next::)? - .with_value( - "setmetatable", - lua.named_registry_value::("tab.setmeta")?, - )? - .with_value( - "freeze", - lua.named_registry_value::("tab.freeze")?, - )? - .build_readonly()?; - Self::into_lua_table_with_env(lua, socket_env) - } -} - -impl LuaUserData for NetWebSocket {} - -fn close_code<'lua, T>( - _lua: &'lua Lua, - socket: LuaUserDataRef<'lua, NetWebSocket>, -) -> LuaResult> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - Ok(match socket.close_code.get() { - Some(code) => LuaValue::Number(code as f64), - None => LuaValue::Nil, - }) -} - -async fn close<'lua, T>( - _lua: &'lua Lua, - (socket, code): (LuaUserDataRef<'lua, NetWebSocket>, Option), -) -> LuaResult<()> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let mut ws = socket.write_stream.lock().await; - - ws.send(WsMessage::Close(Some(WsCloseFrame { - code: match code { - Some(code) if (1000..=4999).contains(&code) => WsCloseCode::from(code), - Some(code) => { - return Err(LuaError::RuntimeError(format!( - "Close code must be between 1000 and 4999, got {code}" - ))) - } - None => WsCloseCode::Normal, - }, - reason: "".into(), - }))) - .await - .into_lua_err()?; - - let res = ws.close(); - res.await.into_lua_err() -} - -async fn send<'lua, T>( - _lua: &'lua Lua, - (socket, string, as_binary): ( - LuaUserDataRef<'lua, NetWebSocket>, - LuaString<'lua>, - Option, - ), -) -> LuaResult<()> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let msg = if matches!(as_binary, Some(true)) { - WsMessage::Binary(string.as_bytes().to_vec()) - } else { - let s = string.to_str().into_lua_err()?; - WsMessage::Text(s.to_string()) - }; - let mut ws = socket.write_stream.lock().await; - ws.send(msg).await.into_lua_err() -} - -async fn next<'lua, T>( - lua: &'lua Lua, - socket: LuaUserDataRef<'lua, NetWebSocket>, -) -> LuaResult> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let mut ws = socket.read_stream.lock().await; - let item = ws.next().await.transpose().into_lua_err(); - let msg = match item { - Ok(Some(WsMessage::Close(msg))) => { - if let Some(msg) = &msg { - socket.close_code.replace(Some(msg.code.into())); - } - Ok(Some(WsMessage::Close(msg))) - } - val => val, - }?; - while let Some(msg) = &msg { - let msg_string_opt = match msg { - WsMessage::Binary(bin) => Some(lua.create_string(bin)?), - WsMessage::Text(txt) => Some(lua.create_string(txt)?), - // Stop waiting for next message if we get a close message - WsMessage::Close(_) => return Ok(LuaValue::Nil), - // Ignore ping/pong/frame messages, they are handled by tungstenite - _ => None, - }; - if let Some(msg_string) = msg_string_opt { - return Ok(LuaValue::String(msg_string)); - } - } - Ok(LuaValue::Nil) -} diff --git a/src/old/lua/process/mod.rs b/src/old/lua/process/mod.rs deleted file mode 100644 index b74cd00..0000000 --- a/src/old/lua/process/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::process::ExitStatus; - -use mlua::prelude::*; -use tokio::{io, process::Child, task}; - -mod tee_writer; -use tee_writer::AsyncTeeWriter; - -pub async fn pipe_and_inherit_child_process_stdio( - mut child: Child, -) -> LuaResult<(ExitStatus, Vec, Vec)> { - let mut child_stdout = child.stdout.take().unwrap(); - let mut child_stderr = child.stderr.take().unwrap(); - - /* - NOTE: We do not need to register these - independent tasks spawning in the scheduler - - This function is only used by `process.spawn` which in - turn registers a task with the scheduler that awaits this - */ - - let stdout_thread = task::spawn(async move { - let mut stdout = io::stdout(); - let mut tee = AsyncTeeWriter::new(&mut stdout); - - io::copy(&mut child_stdout, &mut tee).await.into_lua_err()?; - - Ok::<_, LuaError>(tee.into_vec()) - }); - - let stderr_thread = task::spawn(async move { - let mut stderr = io::stderr(); - let mut tee = AsyncTeeWriter::new(&mut stderr); - - io::copy(&mut child_stderr, &mut tee).await.into_lua_err()?; - - Ok::<_, LuaError>(tee.into_vec()) - }); - - let status = child.wait().await.expect("Child process failed to start"); - - let stdout_buffer = stdout_thread.await.expect("Tee writer for stdout errored"); - let stderr_buffer = stderr_thread.await.expect("Tee writer for stderr errored"); - - Ok::<_, LuaError>((status, stdout_buffer?, stderr_buffer?)) -} diff --git a/src/old/lua/process/tee_writer.rs b/src/old/lua/process/tee_writer.rs deleted file mode 100644 index fee7776..0000000 --- a/src/old/lua/process/tee_writer.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{ - io::Write, - pin::Pin, - task::{Context, Poll}, -}; - -use pin_project::pin_project; -use tokio::io::{self, AsyncWrite}; - -#[pin_project] -pub struct AsyncTeeWriter<'a, W> -where - W: AsyncWrite + Unpin, -{ - #[pin] - writer: &'a mut W, - buffer: Vec, -} - -impl<'a, W> AsyncTeeWriter<'a, W> -where - W: AsyncWrite + Unpin, -{ - pub fn new(writer: &'a mut W) -> Self { - Self { - writer, - buffer: Vec::new(), - } - } - - pub fn into_vec(self) -> Vec { - self.buffer - } -} - -impl<'a, W> AsyncWrite for AsyncTeeWriter<'a, W> -where - W: AsyncWrite + Unpin, -{ - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - let mut this = self.project(); - match this.writer.as_mut().poll_write(cx, buf) { - Poll::Ready(res) => { - this.buffer - .write_all(buf) - .expect("Failed to write to internal tee buffer"); - Poll::Ready(res) - } - Poll::Pending => Poll::Pending, - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().writer.as_mut().poll_flush(cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().writer.as_mut().poll_shutdown(cx) - } -} diff --git a/src/old/lua/serde/compress_decompress.rs b/src/old/lua/serde/compress_decompress.rs deleted file mode 100644 index 85e7ac5..0000000 --- a/src/old/lua/serde/compress_decompress.rs +++ /dev/null @@ -1,162 +0,0 @@ -use lz4_flex::{compress_prepend_size, decompress_size_prepended}; -use mlua::prelude::*; -use tokio::{ - io::{copy, BufReader}, - task, -}; - -use async_compression::{ - tokio::bufread::{ - BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder, - }, - Level::Best as CompressionQuality, -}; - -#[derive(Debug, Clone, Copy)] -pub enum CompressDecompressFormat { - Brotli, - GZip, - LZ4, - ZLib, -} - -#[allow(dead_code)] -impl CompressDecompressFormat { - pub fn detect_from_bytes(bytes: impl AsRef<[u8]>) -> Option { - match bytes.as_ref() { - // https://github.com/PSeitz/lz4_flex/blob/main/src/frame/header.rs#L28 - b if b.len() >= 4 - && matches!( - u32::from_le_bytes(b[0..4].try_into().unwrap()), - 0x184D2204 | 0x184C2102 - ) => - { - Some(Self::LZ4) - } - // https://github.com/dropbox/rust-brotli/blob/master/src/enc/brotli_bit_stream.rs#L2805 - b if b.len() >= 4 - && matches!( - b[0..3], - [0xE1, 0x97, 0x81] | [0xE1, 0x97, 0x82] | [0xE1, 0x97, 0x80] - ) => - { - Some(Self::Brotli) - } - // https://github.com/rust-lang/flate2-rs/blob/main/src/gz/mod.rs#L135 - b if b.len() >= 3 && matches!(b[0..3], [0x1F, 0x8B, 0x08]) => Some(Self::GZip), - // https://stackoverflow.com/a/43170354 - b if b.len() >= 2 - && matches!( - b[0..2], - [0x78, 0x01] | [0x78, 0x5E] | [0x78, 0x9C] | [0x78, 0xDA] - ) => - { - Some(Self::ZLib) - } - _ => None, - } - } - - pub fn detect_from_header_str(header: impl AsRef) -> Option { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives - match header.as_ref().to_ascii_lowercase().trim() { - "br" | "brotli" => Some(Self::Brotli), - "deflate" => Some(Self::ZLib), - "gz" | "gzip" => Some(Self::GZip), - _ => None, - } - } -} - -impl<'lua> FromLua<'lua> for CompressDecompressFormat { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - if let LuaValue::String(s) = &value { - match s.to_string_lossy().to_ascii_lowercase().trim() { - "brotli" => Ok(Self::Brotli), - "gzip" => Ok(Self::GZip), - "lz4" => Ok(Self::LZ4), - "zlib" => Ok(Self::ZLib), - kind => Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "CompressDecompressFormat", - message: Some(format!( - "Invalid format '{kind}', valid formats are: brotli, gzip, lz4, zlib" - )), - }), - } - } else { - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "CompressDecompressFormat", - message: None, - }) - } - } -} - -pub async fn compress<'lua>( - format: CompressDecompressFormat, - source: impl AsRef<[u8]>, -) -> LuaResult> { - if let CompressDecompressFormat::LZ4 = format { - let source = source.as_ref().to_vec(); - return task::spawn_blocking(move || compress_prepend_size(&source)) - .await - .into_lua_err(); - } - - let mut bytes = Vec::new(); - let reader = BufReader::new(source.as_ref()); - - match format { - CompressDecompressFormat::Brotli => { - let mut encoder = BrotliEncoder::with_quality(reader, CompressionQuality); - copy(&mut encoder, &mut bytes).await?; - } - CompressDecompressFormat::GZip => { - let mut encoder = GzipEncoder::with_quality(reader, CompressionQuality); - copy(&mut encoder, &mut bytes).await?; - } - CompressDecompressFormat::ZLib => { - let mut encoder = ZlibEncoder::with_quality(reader, CompressionQuality); - copy(&mut encoder, &mut bytes).await?; - } - CompressDecompressFormat::LZ4 => unreachable!(), - } - - Ok(bytes) -} - -pub async fn decompress<'lua>( - format: CompressDecompressFormat, - source: impl AsRef<[u8]>, -) -> LuaResult> { - if let CompressDecompressFormat::LZ4 = format { - let source = source.as_ref().to_vec(); - return task::spawn_blocking(move || decompress_size_prepended(&source)) - .await - .into_lua_err()? - .into_lua_err(); - } - - let mut bytes = Vec::new(); - let reader = BufReader::new(source.as_ref()); - - match format { - CompressDecompressFormat::Brotli => { - let mut decoder = BrotliDecoder::new(reader); - copy(&mut decoder, &mut bytes).await?; - } - CompressDecompressFormat::GZip => { - let mut decoder = GzipDecoder::new(reader); - copy(&mut decoder, &mut bytes).await?; - } - CompressDecompressFormat::ZLib => { - let mut decoder = ZlibDecoder::new(reader); - copy(&mut decoder, &mut bytes).await?; - } - CompressDecompressFormat::LZ4 => unreachable!(), - } - - Ok(bytes) -} diff --git a/src/old/lua/serde/encode_decode.rs b/src/old/lua/serde/encode_decode.rs deleted file mode 100644 index edbcc1b..0000000 --- a/src/old/lua/serde/encode_decode.rs +++ /dev/null @@ -1,133 +0,0 @@ -use mlua::prelude::*; - -use serde_json::Value as JsonValue; -use serde_yaml::Value as YamlValue; -use toml::Value as TomlValue; - -const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new() - .set_array_metatable(false) - .serialize_none_to_null(false) - .serialize_unit_to_null(false); - -const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new() - .deny_recursive_tables(false) - .deny_unsupported_types(true); - -#[derive(Debug, Clone, Copy)] -pub enum EncodeDecodeFormat { - Json, - Yaml, - Toml, -} - -impl<'lua> FromLua<'lua> for EncodeDecodeFormat { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - if let LuaValue::String(s) = &value { - match s.to_string_lossy().to_ascii_lowercase().trim() { - "json" => Ok(Self::Json), - "yaml" => Ok(Self::Yaml), - "toml" => Ok(Self::Toml), - kind => Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "EncodeDecodeFormat", - message: Some(format!( - "Invalid format '{kind}', valid formats are: json, yaml, toml" - )), - }), - } - } else { - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "EncodeDecodeFormat", - message: None, - }) - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct EncodeDecodeConfig { - pub format: EncodeDecodeFormat, - pub pretty: bool, -} - -impl EncodeDecodeConfig { - pub fn serialize_to_string<'lua>( - self, - lua: &'lua Lua, - value: LuaValue<'lua>, - ) -> LuaResult> { - let bytes = match self.format { - EncodeDecodeFormat::Json => { - let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?; - if self.pretty { - serde_json::to_vec_pretty(&serialized).into_lua_err()? - } else { - serde_json::to_vec(&serialized).into_lua_err()? - } - } - EncodeDecodeFormat::Yaml => { - let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?; - let mut writer = Vec::with_capacity(128); - serde_yaml::to_writer(&mut writer, &serialized).into_lua_err()?; - writer - } - EncodeDecodeFormat::Toml => { - let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?; - let s = if self.pretty { - toml::to_string_pretty(&serialized).into_lua_err()? - } else { - toml::to_string(&serialized).into_lua_err()? - }; - s.as_bytes().to_vec() - } - }; - lua.create_string(bytes) - } - - pub fn deserialize_from_string<'lua>( - self, - lua: &'lua Lua, - string: LuaString<'lua>, - ) -> LuaResult> { - let bytes = string.as_bytes(); - match self.format { - EncodeDecodeFormat::Json => { - let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?; - lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS) - } - EncodeDecodeFormat::Yaml => { - let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?; - lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS) - } - EncodeDecodeFormat::Toml => { - if let Ok(s) = string.to_str() { - let value: TomlValue = toml::from_str(s).into_lua_err()?; - lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS) - } else { - Err(LuaError::RuntimeError( - "TOML must be valid utf-8".to_string(), - )) - } - } - } - } -} - -impl From for EncodeDecodeConfig { - fn from(format: EncodeDecodeFormat) -> Self { - Self { - format, - pretty: false, - } - } -} - -impl From<(EncodeDecodeFormat, bool)> for EncodeDecodeConfig { - fn from(value: (EncodeDecodeFormat, bool)) -> Self { - Self { - format: value.0, - pretty: value.1, - } - } -} diff --git a/src/old/lua/serde/mod.rs b/src/old/lua/serde/mod.rs deleted file mode 100644 index ab554ef..0000000 --- a/src/old/lua/serde/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod compress_decompress; -mod encode_decode; - -pub use compress_decompress::{compress, decompress, CompressDecompressFormat}; -pub use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat}; diff --git a/src/old/lua/stdio/formatting.rs b/src/old/lua/stdio/formatting.rs deleted file mode 100644 index 02dd286..0000000 --- a/src/old/lua/stdio/formatting.rs +++ /dev/null @@ -1,473 +0,0 @@ -use std::fmt::Write; - -use console::{colors_enabled, set_colors_enabled, style, Style}; -use mlua::prelude::*; -use once_cell::sync::Lazy; - -use crate::lune_temp::lua::task::TaskReference; - -const MAX_FORMAT_DEPTH: usize = 4; - -const INDENT: &str = " "; - -pub const STYLE_RESET_STR: &str = "\x1b[0m"; - -// Colors -pub static COLOR_BLACK: Lazy