Migrate console global to stdio

This commit is contained in:
Filip Tibell 2023-02-06 00:13:12 -05:00
parent 420a861061
commit bbf1c9f4f7
No known key found for this signature in database
19 changed files with 435 additions and 299 deletions

View file

@ -7,14 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added a new global `stdio` which replaces `console` and adds a couple new functions:
- `write` writes a string directly to stdout, without any newlines
- `ewrite` writes a string directly to stderr, without any newlines
### Changed ### Changed
- Migrated `console.setColor/resetColor` and `console.setStyle/resetStyle` to `stdio.color` and `stdio.style` to allow for more flexibility in custom printing using ANSI color codes. Check the documentation for new usage and behavior.
- Migrated the pretty-printing and formatting behavior of `console.log/info/warn/error` to the standard Luau printing functions. - Migrated the pretty-printing and formatting behavior of `console.log/info/warn/error` to the standard Luau printing functions.
### Removed ### Removed
- Removed printing functions `console.log/info/warn/error` in favor of regular global functions for printing. - Removed printing functions `console.log/info/warn/error` in favor of regular global functions for printing.
### Fixed
- Fixed scripts hanging indefinitely on error
## `0.2.2` - February 5th, 2023 ## `0.2.2` - February 5th, 2023
### Added ### Added

33
Cargo.lock generated
View file

@ -117,6 +117,19 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "console"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.42.0",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -136,6 +149,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -450,6 +469,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.139" version = "0.2.139"
@ -518,7 +543,9 @@ name = "lune"
version = "0.2.2" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"console",
"hyper", "hyper",
"lazy_static",
"mlua", "mlua",
"os_str_bytes", "os_str_bytes",
"reqwest", "reqwest",
@ -1134,6 +1161,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"

View file

@ -35,10 +35,10 @@ More examples of how to write Lune scripts can be found in the [examples](.lune/
<details> <details>
<summary><b>🔎 List of APIs</b></summary> <summary><b>🔎 List of APIs</b></summary>
`console` - Logging & formatting <br />
`fs` - Filesystem <br /> `fs` - Filesystem <br />
`net` - Networking <br /> `net` - Networking <br />
`process` - Current process & child processes <br /> `process` - Current process & child processes <br />
`stdio` - Standard input / output & utility functions <br />
`task` - Task scheduler & thread spawning <br /> `task` - Task scheduler & thread spawning <br />
Documentation for individual members and types can be found using your editor of choice and [Luau LSP](https://github.com/JohnnyMorganz/luau-lsp). Documentation for individual members and types can be found using your editor of choice and [Luau LSP](https://github.com/JohnnyMorganz/luau-lsp).

View file

@ -1,19 +1,6 @@
# Lune v0.2.2 # Lune v0.2.2
--- ---
globals: globals:
# Console
console.resetStyle:
args: []
console.setStyle:
args:
- required: false
type: string
- required: false
type: string
console.format:
must_use: true
args:
- type: "..."
# FS (filesystem) # FS (filesystem)
fs.readFile: fs.readFile:
must_use: true must_use: true
@ -80,6 +67,25 @@ globals:
type: table type: table
- required: false - required: false
type: table type: table
# Stdio
stdio.color:
must_use: true
args:
- type: string
stdio.style:
must_use: true
args:
- type: string
console.format:
must_use: true
args:
- type: "..."
stdio.write:
args:
- type: string
stdio.ewrite:
args:
- type: string
# Task # Task
task.cancel: task.cancel:
args: args:

View file

@ -1,61 +1,4 @@
{ {
"@roblox/global/console": {
"code_sample": "",
"documentation": "Logging & formatting",
"keys": {
"console": "@roblox/global/console.console"
},
"learn_more_link": ""
},
"@roblox/global/console.format": {
"code_sample": "",
"documentation": "Formats arguments into a human-readable string with syntax highlighting for tables.",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/console.format/param/0",
"name": "..."
}
],
"returns": [
"@roblox/global/console.format/return/0"
]
},
"@roblox/global/console.format/param/0": {
"documentation": "The values to format"
},
"@roblox/global/console.format/return/0": {
"documentation": "The formatted string"
},
"@roblox/global/console.resetStyle": {
"code_sample": "",
"documentation": "Resets the current persistent output color and style.",
"learn_more_link": "",
"params": [],
"returns": []
},
"@roblox/global/console.setStyle": {
"code_sample": "",
"documentation": "Sets the current persistent output color and / or style.",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/console.setStyle/param/0",
"name": "color"
},
{
"documentation": "@roblox/global/console.setStyle/param/1",
"name": "style"
}
],
"returns": []
},
"@roblox/global/console.setStyle/param/0": {
"documentation": "The color to set"
},
"@roblox/global/console.setStyle/param/1": {
"documentation": "The style to set"
},
"@roblox/global/fs": { "@roblox/global/fs": {
"code_sample": "", "code_sample": "",
"documentation": "Filesystem", "documentation": "Filesystem",
@ -386,6 +329,94 @@
"@roblox/global/process.spawn/return/0": { "@roblox/global/process.spawn/return/0": {
"documentation": "A dictionary representing the result of the child process" "documentation": "A dictionary representing the result of the child process"
}, },
"@roblox/global/stdio": {
"code_sample": "",
"documentation": "Standard input / output & utility functions",
"keys": {
"stdio": "@roblox/global/stdio.stdio"
},
"learn_more_link": ""
},
"@roblox/global/stdio.color": {
"code_sample": "",
"documentation": "Return an ANSI string that can be used to modify the persistent output color.\n\nPass `\"reset\"` to get a string that can reset the persistent output color.\n\n### Example usage\n\n```lua\nstdio.write(stdio.color(\"red\"))\nprint(\"This text will be red\")\nstdio.write(stdio.color(\"reset\"))\nprint(\"This text will be normal\")\n```",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/stdio.color/param/0",
"name": "color"
}
],
"returns": []
},
"@roblox/global/stdio.color/param/0": {
"documentation": "The color to use"
},
"@roblox/global/stdio.ewrite": {
"code_sample": "",
"documentation": "Writes a string directly to stderr, without any newline.",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/stdio.ewrite/param/0",
"name": "s"
}
],
"returns": []
},
"@roblox/global/stdio.ewrite/param/0": {
"documentation": "The string to write to stderr"
},
"@roblox/global/stdio.format": {
"code_sample": "",
"documentation": "Formats arguments into a human-readable string with syntax highlighting for tables.",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/stdio.format/param/0",
"name": "..."
}
],
"returns": [
"@roblox/global/stdio.format/return/0"
]
},
"@roblox/global/stdio.format/param/0": {
"documentation": "The values to format"
},
"@roblox/global/stdio.format/return/0": {
"documentation": "The formatted string"
},
"@roblox/global/stdio.style": {
"code_sample": "",
"documentation": "Return an ANSI string that can be used to modify the persistent output style.\n\nPass `\"reset\"` to get a string that can reset the persistent output style.\n\n### Example usage\n\n```lua\nstdio.write(stdio.style(\"bold\"))\nprint(\"This text will be bold\")\nstdio.write(stdio.style(\"reset\"))\nprint(\"This text will be normal\")\n```",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/stdio.style/param/0",
"name": "style"
}
],
"returns": []
},
"@roblox/global/stdio.style/param/0": {
"documentation": "The style to use"
},
"@roblox/global/stdio.write": {
"code_sample": "",
"documentation": "Writes a string directly to stdout, without any newline.",
"learn_more_link": "",
"params": [
{
"documentation": "@roblox/global/stdio.write/param/0",
"name": "s"
}
],
"returns": []
},
"@roblox/global/stdio.write/param/0": {
"documentation": "The string to write to stdout"
},
"@roblox/global/task": { "@roblox/global/task": {
"code_sample": "", "code_sample": "",
"documentation": "Task scheduler & thread spawning", "documentation": "Task scheduler & thread spawning",

View file

@ -1,40 +1,5 @@
-- Lune v0.2.2 -- Lune v0.2.2
type ConsoleColor = "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white"
type ConsoleStyle = "bold" | "dim"
--[=[
@class console
Logging & formatting
]=]
declare console: {
--[=[
@within console
Resets the current persistent output color and style.
]=]
resetStyle: () -> (),
--[=[
@within console
Sets the current persistent output color and / or style.
@param color The color to set
@param style The style to set
]=]
setStyle: (color: ConsoleColor?, style: ConsoleStyle?) -> (),
--[=[
@within console
Formats arguments into a human-readable string with syntax highlighting for tables.
@param ... The values to format
@return The formatted string
]=]
format: (...any) -> (string),
}
--[=[ --[=[
@class fs @class fs
@ -317,6 +282,77 @@ declare process: {
}, },
} }
--[=[
@class stdio
Standard input / output & utility functions
]=]
declare stdio: {
--[=[
@within stdio
Return an ANSI string that can be used to modify the persistent output color.
Pass `"reset"` to get a string that can reset the persistent output color.
### Example usage
```lua
stdio.write(stdio.color("red"))
print("This text will be red")
stdio.write(stdio.color("reset"))
print("This text will be normal")
```
@param color The color to use
]=]
color: (color: "reset" | "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white") -> (),
--[=[
@within stdio
Return an ANSI string that can be used to modify the persistent output style.
Pass `"reset"` to get a string that can reset the persistent output style.
### Example usage
```lua
stdio.write(stdio.style("bold"))
print("This text will be bold")
stdio.write(stdio.style("reset"))
print("This text will be normal")
```
@param style The style to use
]=]
style: (style: "reset" | "bold" | "dim") -> (),
--[=[
@within stdio
Formats arguments into a human-readable string with syntax highlighting for tables.
@param ... The values to format
@return The formatted string
]=]
format: (...any) -> (string),
--[=[
@within stdio
Writes a string directly to stdout, without any newline.
@param s The string to write to stdout
]=]
write: (s: string) -> (),
--[=[
@within stdio
Writes a string directly to stderr, without any newline.
@param s The string to write to stderr
]=]
ewrite: (s: string) -> (),
}
--[=[ --[=[
@class task @class task

View file

@ -20,6 +20,8 @@ serde.workspace = true
tokio.workspace = true tokio.workspace = true
reqwest.workspace = true reqwest.workspace = true
console = "0.15.5"
lazy_static = "1.4.0"
os_str_bytes = "6.4.1" os_str_bytes = "6.4.1"
hyper = { version = "0.14.24", features = ["full"] } hyper = { version = "0.14.24", features = ["full"] }

View file

@ -1,30 +0,0 @@
use mlua::prelude::*;
use crate::utils::{
formatting::{pretty_format_multi_value, print_color, print_reset, print_style},
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<()> {
lua.globals().raw_set(
"console",
TableBuilder::new(lua)?
.with_function("resetStyle", |_, _: ()| print_reset())?
.with_function(
"setStyle",
|_, (color, style): (Option<String>, Option<String>)| {
if let Some(color) = color {
print_color(color)?;
}
if let Some(style) = style {
print_style(style)?;
}
Ok(())
},
)?
.with_function("format", |_, args: LuaMultiValue| {
pretty_format_multi_value(&args)
})?
.build_readonly()?,
)
}

View file

@ -1,24 +1,24 @@
mod console;
mod fs; mod fs;
mod net; mod net;
mod process; mod process;
mod require; mod require;
mod stdio;
mod task; mod task;
// Global tables // Global tables
pub use console::create as create_console;
pub use fs::create as create_fs; pub use fs::create as create_fs;
pub use net::create as create_net; pub use net::create as create_net;
pub use process::create as create_process; pub use process::create as create_process;
pub use require::create as create_require; pub use require::create as create_require;
pub use stdio::create as create_stdio;
pub use task::create as create_task; pub use task::create as create_task;
// Individual top-level global values // Individual top-level global values
use mlua::prelude::*; use mlua::prelude::*;
use crate::utils::formatting::pretty_format_multi_value; use crate::utils::formatting::{format_label, pretty_format_multi_value};
pub fn create_top_level(lua: &Lua) -> LuaResult<()> { pub fn create_top_level(lua: &Lua) -> LuaResult<()> {
let globals = lua.globals(); let globals = lua.globals();
@ -42,28 +42,40 @@ pub fn create_top_level(lua: &Lua) -> LuaResult<()> {
globals.raw_set( globals.raw_set(
"info", "info",
lua.create_function(|lua, args: LuaMultiValue| { lua.create_function(|lua, args: LuaMultiValue| {
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?; let print: LuaFunction = lua.named_registry_value("print")?;
print.call(formatted)?; print.call(format!(
"{}\n{}",
format_label("info"),
pretty_format_multi_value(&args)?
))?;
Ok(()) Ok(())
})?, })?,
)?; )?;
globals.raw_set( globals.raw_set(
"warn", "warn",
lua.create_function(|lua, args: LuaMultiValue| { lua.create_function(|lua, args: LuaMultiValue| {
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?; let print: LuaFunction = lua.named_registry_value("print")?;
print.call(formatted)?; print.call(format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
))?;
Ok(()) Ok(())
})?, })?,
)?; )?;
globals.raw_set( globals.raw_set(
"error", "error",
lua.create_function(|lua, (arg, level): (LuaValue, Option<u32>)| { lua.create_function(|lua, (arg, level): (LuaValue, Option<u32>)| {
let multi = arg.to_lua_multi(lua)?;
let formatted = pretty_format_multi_value(&multi)?;
let error: LuaFunction = lua.named_registry_value("error")?; let error: LuaFunction = lua.named_registry_value("error")?;
error.call((formatted, level))?; let multi = arg.to_lua_multi(lua)?;
error.call((
format!(
"{}\n{}",
format_label("error"),
pretty_format_multi_value(&multi)?
),
level,
))?;
Ok(()) Ok(())
})?, })?,
)?; )?;

View file

@ -0,0 +1,35 @@
use mlua::prelude::*;
use crate::utils::{
formatting::{
format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str,
},
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<()> {
lua.globals().raw_set(
"stdio",
TableBuilder::new(lua)?
.with_function("color", |_, color: String| {
let ansi_string = format_style(style_from_color_str(&color)?);
Ok(ansi_string)
})?
.with_function("style", |_, style: String| {
let ansi_string = format_style(style_from_style_str(&style)?);
Ok(ansi_string)
})?
.with_function("format", |_, args: LuaMultiValue| {
pretty_format_multi_value(&args)
})?
.with_function("write", |_, s: String| {
print!("{s}");
Ok(())
})?
.with_function("ewrite", |_, s: String| {
eprint!("{s}");
Ok(())
})?
.build_readonly()?,
)
}

View file

@ -8,7 +8,7 @@ pub(crate) mod utils;
use crate::{ use crate::{
globals::{ globals::{
create_console, create_fs, create_net, create_process, create_require, create_task, create_fs, create_net, create_process, create_require, create_stdio, create_task,
create_top_level, create_top_level,
}, },
utils::formatting::pretty_format_luau_error, utils::formatting::pretty_format_luau_error,
@ -16,11 +16,11 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LuneGlobal { pub enum LuneGlobal {
Console,
Fs, Fs,
Net, Net,
Process, Process,
Require, Require,
Stdio,
Task, Task,
TopLevel, TopLevel,
} }
@ -28,11 +28,11 @@ pub enum LuneGlobal {
impl LuneGlobal { impl LuneGlobal {
pub fn get_all() -> Vec<Self> { pub fn get_all() -> Vec<Self> {
vec![ vec![
Self::Console,
Self::Fs, Self::Fs,
Self::Net, Self::Net,
Self::Process, Self::Process,
Self::Require, Self::Require,
Self::Stdio,
Self::Task, Self::Task,
Self::TopLevel, Self::TopLevel,
] ]
@ -85,11 +85,11 @@ impl Lune {
// Add in wanted lune globals // Add in wanted lune globals
for global in &self.globals { for global in &self.globals {
match &global { match &global {
LuneGlobal::Console => create_console(&lua)?,
LuneGlobal::Fs => create_fs(&lua)?, LuneGlobal::Fs => create_fs(&lua)?,
LuneGlobal::Net => create_net(&lua)?, LuneGlobal::Net => create_net(&lua)?,
LuneGlobal::Process => create_process(&lua, self.args.clone())?, LuneGlobal::Process => create_process(&lua, self.args.clone())?,
LuneGlobal::Require => create_require(&lua)?, LuneGlobal::Require => create_require(&lua)?,
LuneGlobal::Stdio => create_stdio(&lua)?,
LuneGlobal::Task => create_task(&lua)?, LuneGlobal::Task => create_task(&lua)?,
LuneGlobal::TopLevel => create_top_level(&lua)?, LuneGlobal::TopLevel => create_top_level(&lua)?,
} }
@ -150,7 +150,7 @@ impl Lune {
LuneMessage::LuaError(e) => { LuneMessage::LuaError(e) => {
eprintln!("{}", pretty_format_luau_error(&e)); eprintln!("{}", pretty_format_luau_error(&e));
got_error = true; got_error = true;
task_count += 1; task_count -= 1;
} }
}; };
// If there are no tasks left running, it is now // If there are no tasks left running, it is now
@ -180,6 +180,8 @@ mod tests {
use std::{env::set_current_dir, path::PathBuf, process::ExitCode}; use std::{env::set_current_dir, path::PathBuf, process::ExitCode};
use anyhow::Result; use anyhow::Result;
use console::set_colors_enabled;
use console::set_colors_enabled_stderr;
use tokio::fs::read_to_string; use tokio::fs::read_to_string;
use crate::Lune; use crate::Lune;
@ -191,6 +193,10 @@ mod tests {
$( $(
#[tokio::test] #[tokio::test]
async fn $name() -> Result<ExitCode> { async fn $name() -> Result<ExitCode> {
// Disable styling for stdout and stderr since
// some tests rely on output not being styled
set_colors_enabled(false);
set_colors_enabled_stderr(false);
// NOTE: This path is relative to the lib // NOTE: This path is relative to the lib
// package, not the cwd or workspace root, // package, not the cwd or workspace root,
// so we need to cd to the repo root first // so we need to cd to the repo root first
@ -218,8 +224,6 @@ mod tests {
} }
run_tests! { run_tests! {
console_format: "console/format",
console_set_style: "console/set_style",
fs_files: "fs/files", fs_files: "fs/files",
fs_dirs: "fs/dirs", fs_dirs: "fs/dirs",
net_request_codes: "net/request/codes", net_request_codes: "net/request/codes",
@ -238,6 +242,11 @@ mod tests {
require_nested: "require/tests/nested", require_nested: "require/tests/nested",
require_parents: "require/tests/parents", require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings", require_siblings: "require/tests/siblings",
stdio_format: "stdio/format",
stdio_color: "stdio/color",
stdio_style: "stdio/style",
stdio_write: "stdio/write",
stdio_ewrite: "stdio/ewrite",
task_cancel: "task/cancel", task_cancel: "task/cancel",
task_defer: "task/defer", task_defer: "task/defer",
task_delay: "task/delay", task_delay: "task/delay",

View file

@ -1,32 +1,28 @@
use std::{ use std::fmt::Write;
fmt::Write as _,
io::{self, Write as _},
};
use console::{style, Style};
use lazy_static::lazy_static;
use mlua::prelude::*; use mlua::prelude::*;
const MAX_FORMAT_DEPTH: usize = 4; const MAX_FORMAT_DEPTH: usize = 4;
const INDENT: &str = " "; const INDENT: &str = " ";
// TODO: Use some crate for this instead pub const STYLE_RESET_STR: &str = "\x1b[0m";
pub const COLOR_RESET: &str = if cfg!(test) { "" } else { "\x1B[0m" }; lazy_static! {
pub const COLOR_BLACK: &str = if cfg!(test) { "" } else { "\x1B[30m" }; // Colors
pub const COLOR_RED: &str = if cfg!(test) { "" } else { "\x1B[31m" }; pub static ref COLOR_BLACK: Style = Style::new().black();
pub const COLOR_GREEN: &str = if cfg!(test) { "" } else { "\x1B[32m" }; pub static ref COLOR_RED: Style = Style::new().red();
pub const COLOR_YELLOW: &str = if cfg!(test) { "" } else { "\x1B[33m" }; pub static ref COLOR_GREEN: Style = Style::new().green();
pub const COLOR_BLUE: &str = if cfg!(test) { "" } else { "\x1B[34m" }; pub static ref COLOR_YELLOW: Style = Style::new().yellow();
pub const COLOR_PURPLE: &str = if cfg!(test) { "" } else { "\x1B[35m" }; pub static ref COLOR_BLUE: Style = Style::new().blue();
pub const COLOR_CYAN: &str = if cfg!(test) { "" } else { "\x1B[36m" }; pub static ref COLOR_PURPLE: Style = Style::new().magenta();
pub const COLOR_WHITE: &str = if cfg!(test) { "" } else { "\x1B[37m" }; pub static ref COLOR_CYAN: Style = Style::new().cyan();
pub static ref COLOR_WHITE: Style = Style::new().white();
pub const STYLE_RESET: &str = if cfg!(test) { "" } else { "\x1B[22m" }; // Styles
pub const STYLE_BOLD: &str = if cfg!(test) { "" } else { "\x1B[1m" }; pub static ref STYLE_BOLD: Style = Style::new().bold();
pub const STYLE_DIM: &str = if cfg!(test) { "" } else { "\x1B[2m" }; pub static ref STYLE_DIM: Style = Style::new().dim();
pub fn flush_stdout() -> LuaResult<()> {
io::stdout().flush().map_err(LuaError::external)
} }
fn can_be_plain_lua_table_key(s: &LuaString) -> bool { fn can_be_plain_lua_table_key(s: &LuaString) -> bool {
@ -41,74 +37,68 @@ fn can_be_plain_lua_table_key(s: &LuaString) -> bool {
pub fn format_label<S: AsRef<str>>(s: S) -> String { pub fn format_label<S: AsRef<str>>(s: S) -> String {
format!( format!(
"{}[{}{}{}{}]{} ", "{}{}{} ",
STYLE_BOLD, style("[").bold(),
match s.as_ref().to_ascii_lowercase().as_str() { match s.as_ref().to_ascii_lowercase().as_str() {
"info" => COLOR_BLUE, "info" => style("INFO").blue(),
"warn" => COLOR_YELLOW, "warn" => style("WARN").yellow(),
"error" => COLOR_RED, "error" => style("ERROR").red(),
_ => COLOR_WHITE, _ => style(""),
}, },
s.as_ref().to_ascii_uppercase(), style("]").bold()
COLOR_RESET,
STYLE_BOLD,
STYLE_RESET
) )
} }
pub fn print_label<S: AsRef<str>>(s: S) -> LuaResult<()> { pub fn format_style(style: Option<&'static Style>) -> String {
print!("{}", format_label(s)); if cfg!(test) {
flush_stdout()?; "".to_string()
Ok(()) } else if let Some(style) = style {
// HACK: We have no direct way of referencing the ansi color code
// of the style that console::Style provides, and we also know for
// sure that styles always include the reset sequence at the end
style
.apply_to("")
.to_string()
.strip_suffix(STYLE_RESET_STR)
.unwrap()
.to_string()
} else {
STYLE_RESET_STR.to_string()
}
} }
pub fn print_style<S: AsRef<str>>(s: S) -> LuaResult<()> { pub fn style_from_color_str<S: AsRef<str>>(s: S) -> LuaResult<Option<&'static Style>> {
print!( Ok(match s.as_ref() {
"{}", "reset" => None,
match s.as_ref() { "black" => Some(&COLOR_BLACK),
"reset" => STYLE_RESET, "red" => Some(&COLOR_RED),
"bold" => STYLE_BOLD, "green" => Some(&COLOR_GREEN),
"dim" => STYLE_DIM, "yellow" => Some(&COLOR_YELLOW),
_ => { "blue" => Some(&COLOR_BLUE),
return Err(LuaError::RuntimeError(format!( "purple" => Some(&COLOR_PURPLE),
"The style '{}' is not a valid style name", "cyan" => Some(&COLOR_CYAN),
s.as_ref() "white" => Some(&COLOR_WHITE),
)));
}
}
);
flush_stdout()?;
Ok(())
}
pub fn print_reset() -> LuaResult<()> {
print_style("reset")?;
Ok(())
}
pub fn print_color<S: AsRef<str>>(s: S) -> LuaResult<()> {
print!(
"{}",
match s.as_ref() {
"reset" => COLOR_RESET,
"black" => COLOR_BLACK,
"red" => COLOR_RED,
"green" => COLOR_GREEN,
"yellow" => COLOR_YELLOW,
"blue" => COLOR_BLUE,
"purple" => COLOR_PURPLE,
"cyan" => COLOR_CYAN,
"white" => COLOR_WHITE,
_ => { _ => {
return Err(LuaError::RuntimeError(format!( return Err(LuaError::RuntimeError(format!(
"The color '{}' is not a valid color name", "The color '{}' is not a valid color name",
s.as_ref() s.as_ref()
))); )));
} }
})
} }
);
flush_stdout()?; pub fn style_from_style_str<S: AsRef<str>>(s: S) -> LuaResult<Option<&'static Style>> {
Ok(()) Ok(match s.as_ref() {
"reset" => None,
"bold" => Some(&STYLE_BOLD),
"dim" => Some(&STYLE_DIM),
_ => {
return Err(LuaError::RuntimeError(format!(
"The style '{}' is not a valid style name",
s.as_ref()
)));
}
})
} }
pub fn pretty_format_value( pub fn pretty_format_value(
@ -119,65 +109,66 @@ pub fn pretty_format_value(
// TODO: Handle tables with cyclic references // TODO: Handle tables with cyclic references
match &value { match &value {
LuaValue::Nil => write!(buffer, "nil")?, LuaValue::Nil => write!(buffer, "nil")?,
LuaValue::Boolean(true) => write!(buffer, "{COLOR_YELLOW}true{COLOR_RESET}")?, LuaValue::Boolean(true) => write!(buffer, "{}", COLOR_YELLOW.apply_to("true"))?,
LuaValue::Boolean(false) => write!(buffer, "{COLOR_YELLOW}false{COLOR_RESET}")?, LuaValue::Boolean(false) => write!(buffer, "{}", COLOR_YELLOW.apply_to("false"))?,
LuaValue::Number(n) => write!(buffer, "{COLOR_CYAN}{n}{COLOR_RESET}")?, LuaValue::Number(n) => write!(buffer, "{}", COLOR_CYAN.apply_to(format!("{n}")))?,
LuaValue::Integer(i) => write!(buffer, "{COLOR_CYAN}{i}{COLOR_RESET}")?, LuaValue::Integer(i) => write!(buffer, "{}", COLOR_CYAN.apply_to(format!("{i}")))?,
LuaValue::String(s) => write!( LuaValue::String(s) => write!(
buffer, buffer,
"{}\"{}\"{}", "\"{}\"",
COLOR_GREEN, COLOR_GREEN.apply_to(
s.to_string_lossy() s.to_string_lossy()
.replace('"', r#"\""#) .replace('"', r#"\""#)
.replace('\r', r#"\r"#) .replace('\r', r#"\r"#)
.replace('\n', r#"\n"#), .replace('\n', r#"\n"#)
COLOR_RESET )
)?, )?,
LuaValue::Table(ref tab) => { LuaValue::Table(ref tab) => {
if depth >= MAX_FORMAT_DEPTH { if depth >= MAX_FORMAT_DEPTH {
write!(buffer, "{STYLE_DIM}{{ ... }}{STYLE_RESET}")?; write!(buffer, "{}", STYLE_DIM.apply_to("{ ... }"))?;
} else { } else {
let mut is_empty = false; let mut is_empty = false;
let depth_indent = INDENT.repeat(depth); let depth_indent = INDENT.repeat(depth);
write!(buffer, "{STYLE_DIM}{{{STYLE_RESET}")?; write!(buffer, "{}", STYLE_DIM.apply_to("{"))?;
for pair in tab.clone().pairs::<LuaValue, LuaValue>() { for pair in tab.clone().pairs::<LuaValue, LuaValue>() {
let (key, value) = pair.unwrap(); let (key, value) = pair.unwrap();
match &key { match &key {
LuaValue::String(s) if can_be_plain_lua_table_key(s) => write!( LuaValue::String(s) if can_be_plain_lua_table_key(s) => write!(
buffer, buffer,
"\n{}{}{} {}={} ", "\n{}{}{} {} ",
depth_indent, depth_indent,
INDENT, INDENT,
s.to_string_lossy(), s.to_string_lossy(),
STYLE_DIM, STYLE_DIM.apply_to("=")
STYLE_RESET
)?, )?,
_ => { _ => {
write!(buffer, "\n{depth_indent}{INDENT}[")?; write!(buffer, "\n{depth_indent}{INDENT}[")?;
pretty_format_value(buffer, &key, depth)?; pretty_format_value(buffer, &key, depth)?;
write!(buffer, "] {STYLE_DIM}={STYLE_RESET} ")?; write!(buffer, "] {} ", STYLE_DIM.apply_to("="))?;
} }
} }
pretty_format_value(buffer, &value, depth + 1)?; pretty_format_value(buffer, &value, depth + 1)?;
write!(buffer, "{STYLE_DIM},{STYLE_RESET}")?; write!(buffer, "{}", STYLE_DIM.apply_to(","))?;
is_empty = false; is_empty = false;
} }
if is_empty { if is_empty {
write!(buffer, " {STYLE_DIM}}}{STYLE_RESET}")?; write!(buffer, "{}", STYLE_DIM.apply_to(" }"))?;
} else { } else {
write!(buffer, "\n{depth_indent}{STYLE_DIM}}}{STYLE_RESET}")?; write!(buffer, "\n{}", STYLE_DIM.apply_to("}"))?;
} }
} }
} }
LuaValue::Vector(x, y, z) => { LuaValue::Vector(x, y, z) => write!(
write!(buffer, "{COLOR_PURPLE}<vector({x}, {y}, {z})>{COLOR_RESET}",)? buffer,
} "{}",
LuaValue::Thread(_) => write!(buffer, "{COLOR_PURPLE}<thread>{COLOR_RESET}")?, COLOR_PURPLE.apply_to(format!("<vector({x}, {y}, {z})>"))
LuaValue::Function(_) => write!(buffer, "{COLOR_PURPLE}<function>{COLOR_RESET}")?, )?,
LuaValue::Thread(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?,
LuaValue::Function(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<function>"))?,
LuaValue::UserData(_) | LuaValue::LightUserData(_) => { LuaValue::UserData(_) | LuaValue::LightUserData(_) => {
write!(buffer, "{COLOR_PURPLE}<userdata>{COLOR_RESET}")? write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))?
} }
_ => write!(buffer, "?")?, _ => write!(buffer, "{}", STYLE_DIM.apply_to("?"))?,
} }
Ok(()) Ok(())
} }
@ -200,8 +191,8 @@ pub fn pretty_format_multi_value(multi: &LuaMultiValue) -> LuaResult<String> {
} }
pub fn pretty_format_luau_error(e: &LuaError) -> String { pub fn pretty_format_luau_error(e: &LuaError) -> String {
let stack_begin = format!("[{}Stack Begin{}]", COLOR_BLUE, COLOR_RESET); let stack_begin = format!("[{}]", COLOR_BLUE.apply_to("Stack Begin"));
let stack_end = format!("[{}Stack End{}]", COLOR_BLUE, COLOR_RESET); let stack_end = format!("[{}]", COLOR_BLUE.apply_to("Stack End"));
let err_string = match e { let err_string = match e {
LuaError::RuntimeError(e) => { LuaError::RuntimeError(e) => {
// Remove unnecessary prefix // Remove unnecessary prefix

View file

@ -1,35 +0,0 @@
local STYLES_VALID = { "bold", "dim" }
local STYLES_INVALID = { "", "*bold*", "dimm", "megabright", "cheerful", "sad", " " }
local COLORS_VALID = { "black", "red", "green", "yellow", "blue", "purple", "cyan", "white" }
local COLORS_INVALID = { "", "gray", "grass", "red?", "super red", " ", "none" }
-- Test colors
for _, color in COLORS_VALID do
if not pcall(console.setStyle, color :: any, nil) then
error(string.format("Setting color failed for color '%s'", color))
end
end
for _, color in COLORS_INVALID do
if pcall(console.setStyle, color :: any, nil) then
console.resetStyle()
error(string.format("Setting color should have failed for color '%s' but succeeded", color))
end
end
-- Test styles
for _, style in STYLES_VALID do
if not pcall(console.setStyle, nil, style :: any) then
error(string.format("Setting style failed for style '%s'", style))
end
end
for _, style in STYLES_INVALID do
if pcall(console.setStyle, nil, style :: any) then
console.resetStyle()
error(string.format("Setting style should have failed for style '%s' but succeeded", style))
end
end

15
tests/stdio/color.luau Normal file
View file

@ -0,0 +1,15 @@
local COLORS_VALID =
{ "reset", "black", "red", "green", "yellow", "blue", "purple", "cyan", "white" }
local COLORS_INVALID = { "", "gray", "grass", "red?", "super red", " ", "none" }
for _, color in COLORS_VALID do
stdio.color(color :: any)
stdio.color("reset")
end
for _, color in COLORS_INVALID do
if pcall(stdio.color, color :: any) then
stdio.color("reset")
error(string.format("Setting color should have failed for color '%s' but succeeded", color))
end
end

3
tests/stdio/ewrite.luau Normal file
View file

@ -0,0 +1,3 @@
stdio.ewrite("Hello, stderr!")
process.exit(0)

View file

@ -1,10 +1,10 @@
assert( assert(
console.format("Hello", "world", "!") == "Hello world !", stdio.format("Hello", "world", "!") == "Hello world !",
"Format should add a single space between arguments" "Format should add a single space between arguments"
) )
assert( assert(
console.format({ Hello = "World" }) == '{\n Hello = "World",\n}', stdio.format({ Hello = "World" }) == '{\n Hello = "World",\n}',
"Format should print out proper tables" "Format should print out proper tables"
) )
@ -21,6 +21,6 @@ local nested = {
} }
assert( assert(
string.find(console.format(nested), "Nesting = { ... }", 1, true) ~= nil, string.find(stdio.format(nested), "Nesting = { ... }", 1, true) ~= nil,
"Format should print 4 levels of nested tables before cutting off" "Format should print 4 levels of nested tables before cutting off"
) )

14
tests/stdio/style.luau Normal file
View file

@ -0,0 +1,14 @@
local STYLES_VALID = { "reset", "bold", "dim" }
local STYLES_INVALID = { "", "*bold*", "dimm", "megabright", "cheerful", "sad", " " }
for _, style in STYLES_VALID do
stdio.style(style :: any)
stdio.style("reset")
end
for _, style in STYLES_INVALID do
if pcall(stdio.style, style :: any) then
stdio.style("reset")
error(string.format("Setting style should have failed for style '%s' but succeeded", style))
end
end

3
tests/stdio/write.luau Normal file
View file

@ -0,0 +1,3 @@
stdio.write("Hello, stdout!")
process.exit(0)

View file

@ -5,7 +5,7 @@ return function(index: number, type: string, value: any)
"Expected argument #%d to be of type %s, got %s", "Expected argument #%d to be of type %s, got %s",
index, index,
type, type,
console.format(value) stdio.format(value)
) )
) )
end end