mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Improve formatting / printing of userdata and tables with __type and / or __tostring metamethods
This commit is contained in:
parent
0efc2c565b
commit
1fb1d3e7b5
7 changed files with 121 additions and 50 deletions
|
@ -81,7 +81,7 @@ impl LuaUserData for LuaCaptures {
|
||||||
|
|
||||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("RegexCaptures({})", this.num_captures()))
|
Ok(format!("{}", this.num_captures()))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ impl LuaUserData for LuaMatch {
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("RegexMatch({})", this.slice()))
|
Ok(this.slice().to_string())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl LuaUserData for LuaRegex {
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("Regex({})", this.inner.as_str()))
|
Ok(this.inner.as_str().to_string())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::fmt::ErrorComponents;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
metamethods::{call_table_tostring_metamethod, call_userdata_tostring_metamethod},
|
metamethods::{
|
||||||
|
call_table_tostring_metamethod, call_userdata_tostring_metamethod,
|
||||||
|
get_table_type_metavalue, get_userdata_type_metavalue,
|
||||||
|
},
|
||||||
style::{COLOR_CYAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_YELLOW},
|
style::{COLOR_CYAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_YELLOW},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,19 +61,39 @@ pub(crate) fn format_value_styled(value: &LuaValue, prefer_plain: bool) -> Strin
|
||||||
LuaValue::Function(_) => COLOR_MAGENTA.apply_to("<function>").to_string(),
|
LuaValue::Function(_) => COLOR_MAGENTA.apply_to("<function>").to_string(),
|
||||||
LuaValue::LightUserData(_) => COLOR_MAGENTA.apply_to("<pointer>").to_string(),
|
LuaValue::LightUserData(_) => COLOR_MAGENTA.apply_to("<pointer>").to_string(),
|
||||||
LuaValue::UserData(u) => {
|
LuaValue::UserData(u) => {
|
||||||
if let Some(s) = call_userdata_tostring_metamethod(u) {
|
let formatted = format_typename_and_tostringed(
|
||||||
s
|
"userdata",
|
||||||
} else {
|
get_userdata_type_metavalue(u),
|
||||||
COLOR_MAGENTA.apply_to("<userdata>").to_string()
|
call_userdata_tostring_metamethod(u),
|
||||||
}
|
);
|
||||||
|
COLOR_MAGENTA.apply_to(formatted).to_string()
|
||||||
}
|
}
|
||||||
LuaValue::Table(t) => {
|
LuaValue::Table(t) => {
|
||||||
if let Some(s) = call_table_tostring_metamethod(t) {
|
let formatted = format_typename_and_tostringed(
|
||||||
s
|
"table",
|
||||||
} else {
|
get_table_type_metavalue(t),
|
||||||
COLOR_MAGENTA.apply_to("<table>").to_string()
|
call_table_tostring_metamethod(t),
|
||||||
|
);
|
||||||
|
COLOR_MAGENTA.apply_to(formatted).to_string()
|
||||||
}
|
}
|
||||||
}
|
LuaValue::Error(e) => COLOR_MAGENTA
|
||||||
_ => COLOR_MAGENTA.apply_to("<?>").to_string(),
|
.apply_to(format!(
|
||||||
|
"<LuaError(\n{})>",
|
||||||
|
ErrorComponents::from(e.clone())
|
||||||
|
))
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_typename_and_tostringed(
|
||||||
|
fallback: &'static str,
|
||||||
|
typename: Option<String>,
|
||||||
|
tostringed: Option<String>,
|
||||||
|
) -> String {
|
||||||
|
match (typename, tostringed) {
|
||||||
|
(Some(typename), Some(tostringed)) => format!("<{typename}({tostringed})>"),
|
||||||
|
(Some(typename), None) => format!("<{typename}>"),
|
||||||
|
(None, Some(tostringed)) => format!("<{tostringed}>"),
|
||||||
|
(None, None) => format!("<{fallback}>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn get_table_type_metavalue<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
||||||
|
let s = tab
|
||||||
|
.get_metatable()?
|
||||||
|
.get::<_, LuaString>(LuaMetaMethod::Type.name())
|
||||||
|
.ok()?;
|
||||||
|
let s = s.to_str().ok()?;
|
||||||
|
Some(s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_userdata_type_metavalue<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
||||||
|
let s = tab
|
||||||
|
.get_metatable()
|
||||||
|
.ok()?
|
||||||
|
.get::<LuaString>(LuaMetaMethod::Type.name())
|
||||||
|
.ok()?;
|
||||||
|
let s = s.to_str().ok()?;
|
||||||
|
Some(s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
pub fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
||||||
let f = match tab.get_metatable() {
|
tab.get_metatable()?
|
||||||
None => None,
|
.get::<_, LuaFunction>(LuaMetaMethod::ToString.name())
|
||||||
Some(meta) => match meta.get::<_, LuaFunction>(LuaMetaMethod::ToString.name()) {
|
.ok()?
|
||||||
Ok(method) => Some(method),
|
.call(tab)
|
||||||
Err(_) => None,
|
.ok()
|
||||||
},
|
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
pub fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
||||||
let f = match tab.get_metatable() {
|
tab.get_metatable()
|
||||||
Err(_) => None,
|
.ok()?
|
||||||
Ok(meta) => match meta.get::<LuaFunction>(LuaMetaMethod::ToString.name()) {
|
.get::<LuaFunction>(LuaMetaMethod::ToString.name())
|
||||||
Ok(method) => Some(method),
|
.ok()?
|
||||||
Err(_) => None,
|
.call(tab)
|
||||||
},
|
.ok()
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
local regex = require("@lune/regex")
|
local regex = require("@lune/regex")
|
||||||
|
|
||||||
local re = regex.new("[0-9]+")
|
local re = regex.new("[0-9]+")
|
||||||
assert(tostring(re) == "Regex([0-9]+)")
|
assert(tostring(re) == "[0-9]+")
|
||||||
assert(typeof(re) == "Regex")
|
assert(typeof(re) == "Regex")
|
||||||
|
|
||||||
local mtch = re:find("1337 wow")
|
local mtch = re:find("1337 wow")
|
||||||
assert(tostring(mtch) == "RegexMatch(1337)")
|
assert(tostring(mtch) == "1337")
|
||||||
assert(typeof(mtch) == "RegexMatch")
|
assert(typeof(mtch) == "RegexMatch")
|
||||||
|
|
||||||
local re2 = regex.new("([0-9]+) ([0-9]+) wow! ([0-9]+) ([0-9]+)")
|
local re2 = regex.new("([0-9]+) ([0-9]+) wow! ([0-9]+) ([0-9]+)")
|
||||||
local captures = re2:captures("1337 125600 wow! 1984 0")
|
local captures = re2:captures("1337 125600 wow! 1984 0")
|
||||||
assert(tostring(captures) == "RegexCaptures(4)")
|
assert(tostring(captures) == "4")
|
||||||
assert(typeof(captures) == "RegexCaptures")
|
assert(typeof(captures) == "RegexCaptures")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local process = require("@lune/process")
|
local process = require("@lune/process")
|
||||||
|
local regex = require("@lune/regex")
|
||||||
local roblox = require("@lune/roblox")
|
local roblox = require("@lune/roblox")
|
||||||
local stdio = require("@lune/stdio")
|
local stdio = require("@lune/stdio")
|
||||||
|
|
||||||
|
@ -9,6 +10,13 @@ local function assertFormatting(errorMessage: string, formatted: string, expecte
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function assertContains(errorMessage: string, haystack: string, needle: string)
|
||||||
|
if string.find(haystack, needle) == nil then
|
||||||
|
stdio.ewrite(string.format("%s\nHaystack: %s\nNeedle: %s", errorMessage, needle, haystack))
|
||||||
|
process.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assertFormatting(
|
assertFormatting(
|
||||||
"Should add a single space between arguments",
|
"Should add a single space between arguments",
|
||||||
stdio.format("Hello", "world", "!"),
|
stdio.format("Hello", "world", "!"),
|
||||||
|
@ -47,25 +55,38 @@ assertFormatting(
|
||||||
|
|
||||||
local userdatas = {
|
local userdatas = {
|
||||||
Foo = newproxy(false),
|
Foo = newproxy(false),
|
||||||
Bar = (roblox :: any).Vector3.new(),
|
Bar = regex.new("TEST"),
|
||||||
|
Baz = (roblox :: any).Vector3.new(1, 2, 3),
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFormatting(
|
assertFormatting(
|
||||||
"Should format userdatas as their type (unknown userdata)",
|
"Should format userdatas as generic 'userdata' if unknown",
|
||||||
stdio.format(userdatas.Foo),
|
stdio.format(userdatas.Foo),
|
||||||
"<userdata>"
|
"<userdata>"
|
||||||
)
|
)
|
||||||
|
|
||||||
assertFormatting(
|
assertContains(
|
||||||
"Should format userdatas as their type (known userdata)",
|
"Should format userdatas with their type if they have a __type metafield",
|
||||||
stdio.format(userdatas.Bar),
|
stdio.format(userdatas.Bar),
|
||||||
"<Vector3>"
|
"Regex"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
"Should format userdatas with their type even if they have a __tostring metamethod",
|
||||||
|
stdio.format(userdatas.Baz),
|
||||||
|
"Vector3"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
"Should format userdatas with their tostringed value if they have a __tostring metamethod",
|
||||||
|
stdio.format(userdatas.Baz),
|
||||||
|
"1, 2, 3"
|
||||||
)
|
)
|
||||||
|
|
||||||
assertFormatting(
|
assertFormatting(
|
||||||
"Should format userdatas as their type in tables",
|
"Should format userdatas properly in tables",
|
||||||
stdio.format(userdatas),
|
stdio.format(userdatas),
|
||||||
"{\n Foo = <userdata>,\n Bar = <Vector3>,\n}"
|
"{\n Bar = <Regex(TEST)>,\n Baz = <Vector3(1, 2, 3)>,\n Foo = <userdata>,\n}"
|
||||||
)
|
)
|
||||||
|
|
||||||
local nested = {
|
local nested = {
|
||||||
|
@ -80,7 +101,24 @@ local nested = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(
|
assertContains(
|
||||||
string.find(stdio.format(nested), "Nesting = { ... }", 1, true) ~= nil,
|
"Should print 4 levels of nested tables before cutting off",
|
||||||
"Should print 4 levels of nested tables before cutting off"
|
stdio.format(nested),
|
||||||
|
"Nesting = { ... }"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local _, errorMessage = pcall(function()
|
||||||
|
local function innerInnerFn()
|
||||||
|
process.spawn("PROGRAM_THAT_DOES_NOT_EXIST")
|
||||||
|
end
|
||||||
|
local function innerFn()
|
||||||
|
innerInnerFn()
|
||||||
|
end
|
||||||
|
innerFn()
|
||||||
|
end)
|
||||||
|
|
||||||
|
stdio.ewrite(typeof(errorMessage))
|
||||||
|
|
||||||
|
assertContains("Should format errors similarly to userdata", stdio.format(errorMessage), "<LuaErr")
|
||||||
|
assertContains("Should format errors with stack begins", stdio.format(errorMessage), "Stack Begin")
|
||||||
|
assertContains("Should format errors with stack ends", stdio.format(errorMessage), "Stack End")
|
||||||
|
|
Loading…
Reference in a new issue