mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Respect tostring metamethods when printing
This commit is contained in:
parent
a82624023f
commit
9b568aa8ec
5 changed files with 266 additions and 1 deletions
|
@ -2,6 +2,10 @@
|
|||
run-test TEST_NAME:
|
||||
cargo run -- "tests/{{TEST_NAME}}"
|
||||
|
||||
# Run an individual file using the Lune CLI
|
||||
run-file FILE_NAME:
|
||||
cargo run -- "{{FILE_NAME}}"
|
||||
|
||||
# Run tests for the Lune library
|
||||
test:
|
||||
cargo test --package lune -- --test-threads 1
|
||||
|
|
225
.lune/hello_lune.luau
Normal file
225
.lune/hello_lune.luau
Normal file
|
@ -0,0 +1,225 @@
|
|||
--[[
|
||||
EXAMPLE #1
|
||||
|
||||
Using arguments given to the program
|
||||
]]
|
||||
|
||||
if #process.args > 0 then
|
||||
print("Got arguments:")
|
||||
print(process.args)
|
||||
if #process.args > 3 then
|
||||
error("Too many arguments!")
|
||||
end
|
||||
else
|
||||
print("Got no arguments ☹️")
|
||||
end
|
||||
|
||||
--[[
|
||||
EXAMPLE #2
|
||||
|
||||
Using the stdio library to prompt for terminal input
|
||||
]]
|
||||
|
||||
local text = stdio.prompt("text", "Please write some text")
|
||||
|
||||
print("You wrote '" .. text .. "'!")
|
||||
|
||||
local confirmed = stdio.prompt("confirm", "Please confirm that you wrote some text")
|
||||
if confirmed == false then
|
||||
error("You didn't confirm!")
|
||||
else
|
||||
print("Confirmed!")
|
||||
end
|
||||
|
||||
--[[
|
||||
EXAMPLE #3
|
||||
|
||||
Get & set environment variables
|
||||
|
||||
Checks if environment variables are empty or not,
|
||||
prints out ❌ if empty and ✅ if they have a value
|
||||
]]
|
||||
|
||||
print("Reading current environment 🔎")
|
||||
|
||||
-- Environment variables can be read directly
|
||||
assert(process.env.PATH ~= nil, "Missing PATH")
|
||||
assert(process.env.PWD ~= nil, "Missing PWD")
|
||||
|
||||
-- And they can also be accessed using Luau's generalized iteration (but not pairs())
|
||||
for key, value in process.env do
|
||||
local box = if value and value ~= "" then "✅" else "❌"
|
||||
print(string.format("[%s] %s", box, key))
|
||||
end
|
||||
|
||||
--[[
|
||||
EXAMPLE #4
|
||||
|
||||
Spawning concurrent tasks
|
||||
|
||||
These tasks will run at the same time as other Lua code which lets you do primitive multitasking
|
||||
]]
|
||||
|
||||
task.spawn(function()
|
||||
print("Spawned a task that will run instantly but not block")
|
||||
task.wait(5)
|
||||
end)
|
||||
|
||||
print("Spawning a delayed task that will run in 5 seconds")
|
||||
task.delay(5, function()
|
||||
print("...")
|
||||
task.wait(1)
|
||||
print("Hello again!")
|
||||
task.wait(1)
|
||||
print("Goodbye again! 🌙")
|
||||
end)
|
||||
|
||||
--[[
|
||||
EXAMPLE #5
|
||||
|
||||
Read files in the current directory
|
||||
|
||||
This prints out directory & file names with some fancy icons
|
||||
]]
|
||||
|
||||
print("Reading current dir 🗂️")
|
||||
local entries = fs.readDir(".")
|
||||
|
||||
-- NOTE: We have to do this outside of the sort function
|
||||
-- to avoid yielding across the metamethod boundary, all
|
||||
-- of the filesystem APIs are asynchronous and yielding
|
||||
local entryIsDir = {}
|
||||
for _, entry in entries do
|
||||
entryIsDir[entry] = fs.isDir(entry)
|
||||
end
|
||||
|
||||
-- Sort prioritizing directories first, then alphabetically
|
||||
table.sort(entries, function(entry0, entry1)
|
||||
if entryIsDir[entry0] ~= entryIsDir[entry1] then
|
||||
return entryIsDir[entry0]
|
||||
end
|
||||
return entry0 < entry1
|
||||
end)
|
||||
|
||||
-- Make sure we got some known files that should always exist
|
||||
assert(table.find(entries, "Cargo.toml") ~= nil, "Missing Cargo.toml")
|
||||
assert(table.find(entries, "Cargo.lock") ~= nil, "Missing Cargo.lock")
|
||||
|
||||
-- Print the pretty stuff
|
||||
for _, entry in entries do
|
||||
if fs.isDir(entry) then
|
||||
print("📁 " .. entry)
|
||||
else
|
||||
print("📄 " .. entry)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
EXAMPLE #6
|
||||
|
||||
Call out to another program / executable
|
||||
|
||||
You can also get creative and combine this with example #6 to spawn several programs at the same time!
|
||||
]]
|
||||
|
||||
print("Sending 4 pings to google 🌏")
|
||||
local result = process.spawn("ping", {
|
||||
"google.com",
|
||||
"-c 4",
|
||||
})
|
||||
|
||||
--[[
|
||||
EXAMPLE #7
|
||||
|
||||
Using the result of a spawned process, exiting the process
|
||||
|
||||
This looks scary with lots of weird symbols, but, it's just some Lua-style pattern matching
|
||||
to parse the lines of "min/avg/max/stddev = W/X/Y/Z ms" that the ping program outputs to us
|
||||
]]
|
||||
|
||||
if result.ok then
|
||||
assert(#result.stdout > 0, "Result output was empty")
|
||||
local min, avg, max, stddev = string.match(
|
||||
result.stdout,
|
||||
"min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms"
|
||||
)
|
||||
print(string.format("Minimum ping time: %.3fms", assert(tonumber(min))))
|
||||
print(string.format("Maximum ping time: %.3fms", assert(tonumber(max))))
|
||||
print(string.format("Average ping time: %.3fms", assert(tonumber(avg))))
|
||||
print(string.format("Standard deviation: %.3fms", assert(tonumber(stddev))))
|
||||
else
|
||||
print("Failed to send ping to google!")
|
||||
print(result.stderr)
|
||||
process.exit(result.code)
|
||||
end
|
||||
|
||||
--[[
|
||||
EXAMPLE #8
|
||||
|
||||
Using the built-in networking library, encoding & decoding json
|
||||
]]
|
||||
|
||||
print("Sending PATCH request to web API 📤")
|
||||
local apiResult = net.request({
|
||||
url = "https://jsonplaceholder.typicode.com/posts/1",
|
||||
method = "PATCH",
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
},
|
||||
body = net.jsonEncode({
|
||||
title = "foo",
|
||||
body = "bar",
|
||||
}),
|
||||
})
|
||||
|
||||
if not apiResult.ok then
|
||||
print("Failed to send network request!")
|
||||
print(string.format("%d (%s)", apiResult.statusCode, apiResult.statusMessage))
|
||||
print(apiResult.body)
|
||||
process.exit(1)
|
||||
end
|
||||
|
||||
type ApiResponse = {
|
||||
id: number,
|
||||
title: string,
|
||||
body: string,
|
||||
userId: number,
|
||||
}
|
||||
|
||||
local apiResponse: ApiResponse = net.jsonDecode(apiResult.body)
|
||||
assert(apiResponse.title == "foo", "Invalid json response")
|
||||
assert(apiResponse.body == "bar", "Invalid json response")
|
||||
print("Got valid JSON response with changes applied")
|
||||
|
||||
--[[
|
||||
EXAMPLE #9
|
||||
|
||||
Using the stdio library to print pretty
|
||||
]]
|
||||
|
||||
print("Printing with pretty colors and auto-formatting 🎨")
|
||||
|
||||
print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset"))
|
||||
|
||||
printinfo("API response:", apiResponse)
|
||||
warn({
|
||||
Oh = {
|
||||
No = {
|
||||
TooMuch = {
|
||||
Nesting = {
|
||||
"Will not print",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset"))
|
||||
|
||||
--[[
|
||||
EXAMPLE #10
|
||||
|
||||
Saying goodbye 😔
|
||||
]]
|
||||
|
||||
print("Goodbye, lune! 🌙")
|
|
@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issues with CFrame math operations
|
||||
|
|
|
@ -261,7 +261,7 @@ print("Printing with pretty colors and auto-formatting 🎨")
|
|||
|
||||
print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset"))
|
||||
|
||||
info("API response:", apiResponse)
|
||||
printinfo("API response:", apiResponse)
|
||||
warn({
|
||||
Oh = {
|
||||
No = {
|
||||
|
|
|
@ -127,6 +127,8 @@ pub fn pretty_format_value(
|
|||
LuaValue::Table(ref tab) => {
|
||||
if depth >= MAX_FORMAT_DEPTH {
|
||||
write!(buffer, "{}", STYLE_DIM.apply_to("{ ... }"))?;
|
||||
} else if let Some(s) = call_table_tostring_metamethod(tab) {
|
||||
write!(buffer, "{s}")?;
|
||||
} else {
|
||||
let mut is_empty = false;
|
||||
let depth_indent = INDENT.repeat(depth);
|
||||
|
@ -172,6 +174,8 @@ pub fn pretty_format_value(
|
|||
// to lua and pretend to be normal lua
|
||||
// threads for compatibility purposes
|
||||
write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?
|
||||
} else if let Some(s) = call_userdata_tostring_metamethod(u) {
|
||||
write!(buffer, "{s}")?
|
||||
} else {
|
||||
write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))?
|
||||
}
|
||||
|
@ -433,3 +437,31 @@ fn fix_error_nitpicks(full_message: String) -> String {
|
|||
"'[C]' - function require",
|
||||
)
|
||||
}
|
||||
|
||||
fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
||||
let f = match tab.get_metatable() {
|
||||
None => None,
|
||||
Some(meta) => match meta.get::<_, LuaFunction>(LuaMetaMethod::ToString.name()) {
|
||||
Ok(method) => Some(method),
|
||||
Err(_) => None,
|
||||
},
|
||||
}?;
|
||||
match f.call::<_, String>(()) {
|
||||
Ok(res) => Some(res),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
||||
let f = match tab.get_metatable() {
|
||||
Err(_) => None,
|
||||
Ok(meta) => match meta.get::<_, LuaFunction>(LuaMetaMethod::ToString.name()) {
|
||||
Ok(method) => Some(method),
|
||||
Err(_) => None,
|
||||
},
|
||||
}?;
|
||||
match f.call::<_, String>(()) {
|
||||
Ok(res) => Some(res),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue