diff --git a/.lune/hello_lune.luau b/.lune/hello_lune.luau index 5055a27..9248f52 100644 --- a/.lune/hello_lune.luau +++ b/.lune/hello_lune.luau @@ -169,7 +169,36 @@ assert(apiResponse.body == "bar", "Invalid json response") print("Got valid JSON response with changes applied") --[==[ - Example #8 + EXAMPLE #8 + + Using the console library to print pretty +]==] + +print("\nPrinting with pretty colors and auto-formatting 🎨") + +console.setColor("blue") +print(string.rep("—", 22)) +console.resetColor() + +console.info("API response:", apiResponse) +console.warn({ + Oh = { + No = { + TooMuch = { + Nesting = { + "Will not print", + }, + }, + }, + }, +}) + +console.setColor("blue") +print(string.rep("—", 22)) +console.resetColor() + +--[==[ + EXAMPLE #9 Saying goodbye 😔 ]==] diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae7d17..4938726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 assert(apiResponse.body == "bar", "Invalid json response") ``` +- Added console logging & coloring functions under `console` + + This piece of code: + + ```lua + local tab = { Integer = 1234, Hello = { "World" } } + console.log(tab) + ``` + + Will print the following formatted text to the console, **_with syntax highlighting_**: + + ```lua + { + Integer = 1234, + Hello = { + "World", + } + } + ``` + + Additional utility functions exist with the same behavior but that also print out a colored + tag together with any data given to them: `console.info`, `console.warn`, `console.error` - + These print out prefix tags `[INFO]`, `[WARN]`, `[ERROR]` in blue, orange, and red, respectively. + ### Changed - The `json` api is now part of `net` diff --git a/README.md b/README.md index 889598d..67a2cee 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,27 @@ Check out the examples of how to write a script in the [.lune](.lune) folder !
🔎 Full list of APIs -### **`fs`** - Filesystem +
+console - Logging & formatting + +```lua +type console = { + resetColor: () -> (), + setColor: (color: "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white") -> (), + resetStyle: () -> (), + setStyle: (color: "bold" | "dim") -> (), + format: (...any) -> (string), + log: (...any) -> (), + info: (...any) -> (), + warn: (...any) -> (), + error: (...any) -> (), +} +``` + +
+ +
+fs - Filesystem ```lua type fs = { @@ -47,7 +67,10 @@ type fs = { } ``` -### **`net`** - Networking +
+ +
+net - Networking ```lua type net = { @@ -68,7 +91,10 @@ type net = { } ``` -### **`process`** - Current process & child processes +
+ +
+process - Current process & child processes ```lua type process = { @@ -87,6 +113,8 @@ type process = {
+
+
🔀 Example translation from Bash diff --git a/lune.yml b/lune.yml index 97698ce..93ad358 100644 --- a/lune.yml +++ b/lune.yml @@ -1,6 +1,30 @@ # Lune v0.0.2 --- globals: + # Console + console.resetColor: + console.setColor: + args: + - type: string + console.resetStyle: + console.setStyle: + args: + - type: string + console.format: + args: + - type: "..." + console.log: + args: + - type: "..." + console.info: + args: + - type: "..." + console.warn: + args: + - type: "..." + console.error: + args: + - type: "..." # FS (filesystem) fs.readFile: args: diff --git a/luneTypes.d.luau b/luneTypes.d.luau index 611bc98..df43337 100644 --- a/luneTypes.d.luau +++ b/luneTypes.d.luau @@ -1,5 +1,17 @@ -- Lune v0.0.2 +declare console: { + resetColor: () -> (), + setColor: (color: "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white") -> (), + resetStyle: () -> (), + setStyle: (color: "bold" | "dim") -> (), + format: (...any) -> (string), + log: (...any) -> (), + info: (...any) -> (), + warn: (...any) -> (), + error: (...any) -> (), +} + declare fs: { readFile: (path: string) -> string, readDir: (path: string) -> { string }, diff --git a/src/cli.rs b/src/cli.rs index 6875925..f01abc0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,7 +7,7 @@ use clap::{CommandFactory, Parser}; use mlua::{Lua, MultiValue, Result, ToLua}; use crate::{ - lune::{fs::LuneFs, net::LuneNet, process::LuneProcess}, + lune::{console::LuneConsole, fs::LuneFs, net::LuneNet, process::LuneProcess}, utils::GithubClient, }; @@ -101,6 +101,7 @@ impl Cli { // Create a new lua state and add in all lune globals let lua = Lua::new(); let globals = lua.globals(); + globals.set("console", LuneConsole::new())?; globals.set("fs", LuneFs::new())?; globals.set("net", LuneNet::new())?; globals.set("process", LuneProcess::new())?; diff --git a/src/lune/console.rs b/src/lune/console.rs new file mode 100644 index 0000000..12e9567 --- /dev/null +++ b/src/lune/console.rs @@ -0,0 +1,240 @@ +use std::{ + fmt::Write, + io::{self, Write as IoWrite}, +}; + +use mlua::{Lua, MultiValue, Result, UserData, UserDataMethods, Value}; + +const MAX_FORMAT_DEPTH: usize = 4; + +const INDENT: &str = " "; + +const COLOR_RESET: &str = "\x1B[0m"; +const COLOR_BLACK: &str = "\x1B[30m"; +const COLOR_RED: &str = "\x1B[31m"; +const COLOR_GREEN: &str = "\x1B[32m"; +const COLOR_YELLOW: &str = "\x1B[33m"; +const COLOR_BLUE: &str = "\x1B[34m"; +const COLOR_PURPLE: &str = "\x1B[35m"; +const COLOR_CYAN: &str = "\x1B[36m"; +const COLOR_WHITE: &str = "\x1B[37m"; + +const STYLE_RESET: &str = "\x1B[22m"; +const STYLE_BOLD: &str = "\x1B[1m"; +const STYLE_DIM: &str = "\x1B[2m"; + +pub struct LuneConsole(); + +impl LuneConsole { + pub fn new() -> Self { + Self() + } +} + +impl UserData for LuneConsole { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("resetColor", console_reset_color); + methods.add_function("setColor", console_set_color); + methods.add_function("resetStyle", console_reset_style); + methods.add_function("setStyle", console_set_style); + methods.add_function("format", console_format); + methods.add_function("log", console_log); + methods.add_function("info", console_info); + methods.add_function("warn", console_warn); + methods.add_function("error", console_error); + } +} + +fn flush_stdout() -> Result<()> { + io::stdout().flush().map_err(mlua::Error::external) +} + +fn can_be_plain_lua_table_key(s: &mlua::String) -> bool { + let str = s.to_string_lossy().to_string(); + let first_char = str.chars().next().unwrap(); + if first_char.is_alphabetic() { + str.chars().all(|c| c == '_' || c.is_alphanumeric()) + } else { + false + } +} + +fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) -> anyhow::Result<()> { + // TODO: Handle tables with cyclic references + // TODO: Handle other types like function, userdata, ... + match &value { + Value::Nil => write!(buffer, "nil")?, + Value::Boolean(true) => write!(buffer, "{}true{}", COLOR_YELLOW, COLOR_RESET)?, + Value::Boolean(false) => write!(buffer, "{}false{}", COLOR_YELLOW, COLOR_RESET)?, + Value::Number(n) => write!(buffer, "{}{}{}", COLOR_BLUE, n, COLOR_RESET)?, + Value::Integer(i) => write!(buffer, "{}{}{}", COLOR_BLUE, i, COLOR_RESET)?, + Value::String(s) => write!( + buffer, + "{}\"{}\"{}", + COLOR_GREEN, + s.to_string_lossy() + .replace('"', r#"\""#) + .replace('\n', r#"\n"#), + COLOR_RESET + )?, + Value::Table(ref tab) => { + if depth >= MAX_FORMAT_DEPTH { + write!(buffer, "{}{{ ... }}{}", STYLE_DIM, STYLE_RESET)?; + } else { + let depth_indent = INDENT.repeat(depth); + write!(buffer, "{}{{{}", STYLE_DIM, STYLE_RESET)?; + for pair in tab.clone().pairs::() { + let (key, value) = pair?; + match &key { + Value::String(s) if can_be_plain_lua_table_key(s) => write!( + buffer, + "\n{}{}{} {}={} ", + depth_indent, + INDENT, + s.to_string_lossy(), + STYLE_DIM, + STYLE_RESET + )?, + _ => { + write!(buffer, "\n{}{}[", depth_indent, INDENT)?; + pretty_format_value(buffer, &key, depth)?; + write!(buffer, "] {}={} ", STYLE_DIM, STYLE_RESET)?; + } + } + pretty_format_value(buffer, &value, depth + 1)?; + write!(buffer, "{},{}", STYLE_DIM, STYLE_RESET)?; + } + write!(buffer, "\n{}{}}}{}", depth_indent, STYLE_DIM, STYLE_RESET)?; + } + } + _ => write!(buffer, "?")?, + } + Ok(()) +} + +fn pretty_format_multi_value(multi: &MultiValue) -> Result { + let mut buffer = String::new(); + let mut counter = 0; + for value in multi { + counter += 1; + if let Value::String(s) = value { + write!(buffer, "{}", s.to_string_lossy()).map_err(mlua::Error::external)? + } else { + pretty_format_value(&mut buffer, value, 0).map_err(mlua::Error::external)?; + } + if counter < multi.len() { + write!(&mut buffer, " ").map_err(mlua::Error::external)?; + } + } + Ok(buffer) +} + +fn print_style>(s: S) -> Result<()> { + print!( + "{}", + match s.as_ref() { + "reset" => STYLE_RESET, + "bold" => STYLE_BOLD, + "dim" => STYLE_DIM, + _ => { + return Err(mlua::Error::RuntimeError(format!( + "The style '{}' is not a valid style name", + s.as_ref() + ))); + } + } + ); + flush_stdout()?; + Ok(()) +} + +fn print_color>(s: S) -> Result<()> { + 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(mlua::Error::RuntimeError(format!( + "The color '{}' is not a valid color name", + s.as_ref() + ))); + } + } + ); + flush_stdout()?; + Ok(()) +} + +fn console_reset_color(_: &Lua, _: ()) -> Result<()> { + print_color("reset")?; + flush_stdout()?; + Ok(()) +} + +fn console_set_color(_: &Lua, color: String) -> Result<()> { + print_color(color.trim().to_ascii_lowercase())?; + Ok(()) +} + +fn console_reset_style(_: &Lua, _: ()) -> Result<()> { + print_style("reset")?; + flush_stdout()?; + Ok(()) +} + +fn console_set_style(_: &Lua, style: String) -> Result<()> { + print_style(style.trim().to_ascii_lowercase())?; + Ok(()) +} + +fn console_format(_: &Lua, args: MultiValue) -> Result { + pretty_format_multi_value(&args) +} + +fn console_log(_: &Lua, args: MultiValue) -> Result<()> { + let s = pretty_format_multi_value(&args)?; + println!("{}", s); + flush_stdout()?; + Ok(()) +} + +fn console_info(_: &Lua, args: MultiValue) -> Result<()> { + print!( + "{}{}[INFO]{}{} ", + STYLE_BOLD, COLOR_CYAN, COLOR_RESET, STYLE_RESET + ); + let s = pretty_format_multi_value(&args)?; + println!("{}", s); + flush_stdout()?; + Ok(()) +} + +fn console_warn(_: &Lua, args: MultiValue) -> Result<()> { + print!( + "{}{}[WARN]{}{} ", + STYLE_BOLD, COLOR_YELLOW, COLOR_RESET, STYLE_RESET + ); + let s = pretty_format_multi_value(&args)?; + println!("{}", s); + flush_stdout()?; + Ok(()) +} + +fn console_error(_: &Lua, args: MultiValue) -> Result<()> { + eprint!( + "{}{}[ERROR]{}{} ", + STYLE_BOLD, COLOR_RED, COLOR_RESET, STYLE_RESET + ); + let s = pretty_format_multi_value(&args)?; + eprintln!("{}", s); + flush_stdout()?; + Ok(()) +} diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 653afb6..79f3e23 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,3 +1,4 @@ +pub mod console; pub mod fs; pub mod net; pub mod process;