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;