diff --git a/crates/lune-std-stdio/src/lib.rs b/crates/lune-std-stdio/src/lib.rs index a4f77514..0f666b7f 100644 --- a/crates/lune-std-stdio/src/lib.rs +++ b/crates/lune-std-stdio/src/lib.rs @@ -1,12 +1,16 @@ #![allow(clippy::cargo_common_metadata)] +use std::sync::LazyLock; use lune_utils::fmt::{pretty_format_multi_value, ValueFormatConfig}; use mlua::prelude::*; use mlua_luau_scheduler::LuaSpawnExt; -use tokio::io::{stderr, stdin, stdout, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{ + stderr, stdin, stdout, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, Stdin, +}; use lune_utils::TableBuilder; +use tokio::sync::Mutex; mod prompt; mod style_and_color; @@ -18,6 +22,9 @@ const FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new() .with_max_depth(4) .with_colors_enabled(false); +static STDIN_BUFFER_READER: LazyLock>> = + LazyLock::new(|| Mutex::new(BufReader::new(stdin()))); + /** Creates the `stdio` standard library module. @@ -33,6 +40,7 @@ pub fn module(lua: &Lua) -> LuaResult { .with_async_function("write", stdio_write)? .with_async_function("ewrite", stdio_ewrite)? .with_async_function("readToEnd", stdio_read_to_end)? + .with_async_function("readLine", stdio_read_line)? .with_async_function("prompt", stdio_prompt)? .build_readonly() } @@ -63,21 +71,21 @@ async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> { Ok(()) } -/* - FUTURE: Figure out how to expose some kind of "readLine" function using a buffered reader. - - This is a bit tricky since we would want to be able to use **both** readLine and readToEnd - in the same script, doing something like readLine, readLine, readToEnd from lua, and - having that capture the first two lines and then read the rest of the input. -*/ - async fn stdio_read_to_end(lua: &Lua, (): ()) -> LuaResult { let mut input = Vec::new(); - let mut stdin = stdin(); - stdin.read_to_end(&mut input).await?; + let mut buffer = STDIN_BUFFER_READER.lock().await; + buffer.get_mut().read_to_end(&mut input).await?; lua.create_string(&input) } +async fn stdio_read_line(lua: &Lua, (): ()) -> LuaResult { + let mut input = String::new(); + let mut buffer = STDIN_BUFFER_READER.lock().await; + buffer.read_line(&mut input).await?; + let parsed = input.trim_end_matches('\n').trim_end_matches('\r'); + lua.create_string(parsed) +} + async fn stdio_prompt(lua: &Lua, options: PromptOptions) -> LuaResult { lua.spawn_blocking(move || prompt(options)) .await diff --git a/tests/stdio/readLine.luau b/tests/stdio/readLine.luau new file mode 100644 index 00000000..ba993bc9 --- /dev/null +++ b/tests/stdio/readLine.luau @@ -0,0 +1,21 @@ +local stdio = require("@lune/stdio") + +local function linePrefix(prefix:string) + print("-------") + stdio.write(prefix) + local t = stdio.readLine() + print("-------") + return t +end + +local function toEndPrefix(prefix:string) + print("-------") + stdio.write(prefix) + local t = stdio.readToEnd() + print("-------") + return t +end + +print(linePrefix("READLINE> ")) +print(linePrefix("READLINE> ")) +print(toEndPrefix("READTOEND> ")) diff --git a/types/stdio.luau b/types/stdio.luau index e6e88a44..fc37db2d 100644 --- a/types/stdio.luau +++ b/types/stdio.luau @@ -59,8 +59,10 @@ end stdio.write("All on the same line") stdio.ewrite("\nAnd some error text, too") - -- Reading the entire input from stdin - local input = stdio.readToEnd() + -- Reading from stdin, either line-by-line or the entire input + local firstLine = stdio.readLine() + local secondLine = stdio.readLine() + local remaining = stdio.readToEnd() ``` ]=] local stdio = {} @@ -158,4 +160,16 @@ function stdio.readToEnd(): string return nil :: any end +--[=[ + @within Stdio + @tag must_use + + Reads a single line from stdin. + + @return The input from stdin +]=] +function stdio.readLine(): string + return nil :: any +end + return stdio