parent
1a1b134ffa
commit
b39d79ef04
@ -0,0 +1,12 @@ |
|||||||
|
{ |
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", |
||||||
|
"runtime.version": "Lua 5.5", |
||||||
|
"runtime.builtin": { |
||||||
|
"io": "disable", |
||||||
|
"debug": "disable", |
||||||
|
"package": "disable" |
||||||
|
}, |
||||||
|
"runtime.path": ["lua/?.lua", "lua/?/init.lua"], |
||||||
|
"workspace.checkThirdParty": false, |
||||||
|
"workspace.ignoreDir": ["target", "tmp"] |
||||||
|
} |
||||||
@ -0,0 +1,256 @@ |
|||||||
|
-- Weather forecast demo — a small app that exercises most of the `a` stdlib: |
||||||
|
-- |
||||||
|
-- tui prompts (text w/ suggestions, select, confirm), styled markup, |
||||||
|
-- custom presets, escape for untrusted text in markup, message & |
||||||
|
-- outline blocks, raw output with <clear=line> redraws, |
||||||
|
-- setTitle, setClipboard |
||||||
|
-- http getJSON with a timeout, non-2xx handling |
||||||
|
-- task.join a spinner animating *while* the request is in flight |
||||||
|
-- table map / ifilter / ifilterMap / min / max / mean / concat |
||||||
|
-- math round / clamp / scale |
||||||
|
-- utils try, dump |
||||||
|
-- log diagnostics on stderr (run with RUST_LOG=info to see them) |
||||||
|
-- |
||||||
|
-- Data comes from https://wttr.in (no API key). The app prompts for a city, |
||||||
|
-- shows current conditions and a 3-day forecast, then lets you drill into a |
||||||
|
-- day's hourly detail. Esc backs out of any prompt. |
||||||
|
-- |
||||||
|
-- Run with: cargo run -- lua/weather.lua |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Styling: custom presets keep the markup below readable |
||||||
|
---------------------------------------------------------------------- |
||||||
|
tui.addPreset("accent", "cyan;b") |
||||||
|
tui.addPreset("dim", "gray") |
||||||
|
tui.addPreset("freezing", "#7ecbff") |
||||||
|
tui.addPreset("chilly", "#b0e0ff") |
||||||
|
tui.addPreset("mild", "green") |
||||||
|
tui.addPreset("warm", "yellow") |
||||||
|
tui.addPreset("hot", "#ff5f2e;b") |
||||||
|
|
||||||
|
-- Pick a preset name for a temperature (thresholds are °C). |
||||||
|
local function tempPreset(celsius) |
||||||
|
if celsius <= 0 then return "freezing" |
||||||
|
elseif celsius <= 10 then return "chilly" |
||||||
|
elseif celsius <= 22 then return "mild" |
||||||
|
elseif celsius <= 30 then return "warm" |
||||||
|
else return "hot" end |
||||||
|
end |
||||||
|
|
||||||
|
-- "<mild>17°C</>" — a temperature with its color; `shown` is the display |
||||||
|
-- value (may be °F), `celsius` drives the color choice. |
||||||
|
local function fmtTemp(shown, celsius, suffix) |
||||||
|
local p = tempPreset(celsius) |
||||||
|
return ("<%s>%d%s</%s>"):format(p, math.round(shown), suffix, p) |
||||||
|
end |
||||||
|
|
||||||
|
-- A fixed-width bar: `lo..hi` is the full scale, `a..b` the filled span. |
||||||
|
-- math.scale maps temperatures onto columns; clamp=true guards outliers. |
||||||
|
local function spanBar(lo, hi, a, b, width) |
||||||
|
local from = math.round(math.scale(a, lo, hi, 1, width, true)) |
||||||
|
local to = math.round(math.scale(b, lo, hi, 1, width, true)) |
||||||
|
from, to = math.clamp(from, 1, width), math.clamp(to, 1, width) |
||||||
|
return ("·"):rep(from - 1) .. ("█"):rep(to - from + 1) .. ("·"):rep(width - to) |
||||||
|
end |
||||||
|
|
||||||
|
-- A 0-100% meter, colored by how full it is. |
||||||
|
local function pctBar(pct, width) |
||||||
|
local filled = math.round(math.scale(pct, 0, 100, 0, width, true)) |
||||||
|
local style = pct >= 60 and "accent" or "dim" |
||||||
|
return ("<%s>%s</%s><dim>%s</dim> %3d%%"):format( |
||||||
|
style, ("▪"):rep(filled), style, ("·"):rep(width - filled), pct) |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Fetching: task.join runs the HTTP request and a spinner concurrently |
||||||
|
---------------------------------------------------------------------- |
||||||
|
local FRAMES = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" } |
||||||
|
|
||||||
|
local function fetchWeather(city) |
||||||
|
local url = "https://wttr.in/" .. city:gsub(" ", "+") .. "?format=j1" |
||||||
|
log.info("GET", url) |
||||||
|
|
||||||
|
-- The city is user input headed for styled markup: escape it so a name |
||||||
|
-- like "<error>" (or a stray backslash) can't derail the tags around it. |
||||||
|
local cityLabel = tui.escape(city) |
||||||
|
|
||||||
|
local data, err |
||||||
|
local done = false |
||||||
|
task.join( |
||||||
|
function() |
||||||
|
-- utils.try traps transport errors (DNS, TLS, timeout) instead |
||||||
|
-- of aborting; an HTTP error status is not an error and is |
||||||
|
-- reported through resp.status / resp.ok. |
||||||
|
local resp |
||||||
|
resp, err = utils.try(function() |
||||||
|
return http.get(url, { timeout = 20 }) |
||||||
|
end) |
||||||
|
if resp and not resp.ok then |
||||||
|
err = ("wttr.in answered HTTP %d — probably not a place it knows"):format(resp.status) |
||||||
|
elseif resp then |
||||||
|
data, err = utils.try(resp.json) |
||||||
|
end |
||||||
|
done = true |
||||||
|
end, |
||||||
|
function() |
||||||
|
-- The spinner keeps animating because http.getJSON suspends |
||||||
|
-- instead of blocking. <clear=line> redraws in place. |
||||||
|
local i = 1 |
||||||
|
while not done do |
||||||
|
tui.raw(tui.styled(("<clear=line><accent>%s</accent> asking wttr.in about <b>%s</b>…") |
||||||
|
:format(FRAMES[i], cityLabel))) |
||||||
|
i = i % #FRAMES + 1 |
||||||
|
os.sleep(0.08) |
||||||
|
end |
||||||
|
tui.raw(tui.styled("<clear=line>")) |
||||||
|
end |
||||||
|
) |
||||||
|
return data, err |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Rendering |
||||||
|
---------------------------------------------------------------------- |
||||||
|
local BAR_W = 24 |
||||||
|
|
||||||
|
local function dayLabel(isoDate) |
||||||
|
local y, m, d = isoDate:match("(%d+)-(%d+)-(%d+)") |
||||||
|
return os.date("%a %b %e", os.time({ year = y, month = m, day = d, hour = 12 })) |
||||||
|
end |
||||||
|
|
||||||
|
local function showCurrent(data, T, W) |
||||||
|
local cur = data.current_condition[1] |
||||||
|
local area = data.nearest_area[1] |
||||||
|
local place = ("%s, %s"):format(area.areaName[1].value, area.country[1].value) |
||||||
|
log.debug("nearest_area:", utils.dump(area)) |
||||||
|
|
||||||
|
tui.block((" %s (%s, %s)"):format(place, area.latitude, area.longitude), |
||||||
|
{ style = "black;on-cyan", padding = true }) |
||||||
|
|
||||||
|
local t, feels = tonumber(cur["temp_" .. T]), tonumber(cur["FeelsLike" .. T]) |
||||||
|
local tC = tonumber(cur.temp_C) |
||||||
|
-- weatherDesc is free text from the API — escape it like any input. |
||||||
|
print(tui.styled((" %s <b>%s</b> <dim>(feels like %s)</dim>") |
||||||
|
:format(fmtTemp(t, tC, "°" .. T), tui.escape(cur.weatherDesc[1].value), |
||||||
|
fmtTemp(feels, tonumber(cur.FeelsLikeC), "°" .. T)))) |
||||||
|
print(tui.styled((" wind <b>%s %s</b> %s · pressure <b>%s hPa</b> · UV <b>%s</b>") |
||||||
|
:format(cur["windspeed" .. W], W == "Kmph" and "km/h" or "mph", |
||||||
|
cur.winddir16Point, cur.pressure, cur.uvIndex))) |
||||||
|
print(tui.styled(" humidity " .. pctBar(tonumber(cur.humidity), BAR_W))) |
||||||
|
print() |
||||||
|
end |
||||||
|
|
||||||
|
local function showForecast(data, T) |
||||||
|
-- Every hourly temperature across all days — table.map preserves the |
||||||
|
-- sequence, table.min/max aggregate it — fixes one scale for the bars. |
||||||
|
local allTemps = {} |
||||||
|
for _, day in ipairs(data.weather) do |
||||||
|
for _, h in ipairs(day.hourly) do |
||||||
|
table.insert(allTemps, tonumber(h["temp" .. T])) |
||||||
|
end |
||||||
|
end |
||||||
|
local lo, hi = table.min(allTemps), table.max(allTemps) |
||||||
|
|
||||||
|
print(tui.styled((" <u>3-day forecast</u> <dim>(scale %d°%s … %d°%s)</dim>"):format(lo, T, hi, T))) |
||||||
|
for _, day in ipairs(data.weather) do |
||||||
|
local temps = table.map(day.hourly, function(h) return tonumber(h["temp" .. T]) end) |
||||||
|
local rain = math.round(table.mean( |
||||||
|
table.map(day.hourly, function(h) return tonumber(h.chanceofrain) end)) or 0) |
||||||
|
local dMin, dMax = table.min(temps), table.max(temps) |
||||||
|
print(tui.styled((" %-10s %s … %s <%s>%s</> rain %2d%% <dim>☀ %s → %s</dim>") |
||||||
|
:format(dayLabel(day.date), |
||||||
|
fmtTemp(dMin, tonumber(day.mintempC), "°"), |
||||||
|
fmtTemp(dMax, tonumber(day.maxtempC), "°"), |
||||||
|
tempPreset(tonumber(day.maxtempC)), spanBar(lo, hi, dMin, dMax, BAR_W), |
||||||
|
rain, day.astronomy[1].sunrise, day.astronomy[1].sunset))) |
||||||
|
end |
||||||
|
print() |
||||||
|
end |
||||||
|
|
||||||
|
local function showHourly(day, T, W) |
||||||
|
print(tui.styled(("\n <u>%s, hour by hour</u>"):format(dayLabel(day.date)))) |
||||||
|
for _, h in ipairs(day.hourly) do |
||||||
|
local time = ("%04d"):format(tonumber(h.time)):sub(1, 2) .. ":00" |
||||||
|
print(tui.styled((" <dim>%s</dim> %s %s <dim>wind %3s</dim> %-24s") |
||||||
|
:format(time, |
||||||
|
fmtTemp(tonumber(h["temp" .. T]), tonumber(h.tempC), "°"), |
||||||
|
pctBar(tonumber(h.chanceofrain), 10), |
||||||
|
h["windspeed" .. W], tui.escape(h.weatherDesc[1].value)))) |
||||||
|
end |
||||||
|
|
||||||
|
-- table.ifilterMap: keep only the rainy hours, mapped to "HH:00". |
||||||
|
local rainy = table.ifilterMap(day.hourly, function(h) |
||||||
|
if tonumber(h.chanceofrain) >= 60 then |
||||||
|
return ("%04d"):format(tonumber(h.time)):sub(1, 2) .. ":00" |
||||||
|
end |
||||||
|
end) |
||||||
|
if #rainy > 0 then |
||||||
|
tui.warning("Rain is likely around " .. table.concat(rainy, ", ") .. " — pack an umbrella.") |
||||||
|
else |
||||||
|
tui.success("No serious rain expected that day.") |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Main loop |
||||||
|
---------------------------------------------------------------------- |
||||||
|
tui.outlineInfo("Weather forecast, courtesy of wttr.in.\nEsc backs out of any prompt; Ctrl-C quits.", |
||||||
|
{ title = "a-weather" }) |
||||||
|
|
||||||
|
local units = tui.promptSelect("Units?", "metric", { |
||||||
|
options = { "metric", "imperial" }, |
||||||
|
help = "°C & km/h, or °F & mph", |
||||||
|
}) |
||||||
|
if units == nil then return end |
||||||
|
local T = units == "metric" and "C" or "F" -- temp field suffix in wttr JSON |
||||||
|
local W = units == "metric" and "Kmph" or "Miles" -- wind field suffix |
||||||
|
|
||||||
|
while true do |
||||||
|
local city = tui.promptText("City?", "Prague", { |
||||||
|
placeholder = "any city name wttr.in knows", |
||||||
|
suggestions = { "Prague", "Brno", "Berlin", "London", "Tokyo", "Reykjavik", "Dubai" }, |
||||||
|
}) |
||||||
|
if city == nil or city == "" then |
||||||
|
tui.comment("Nothing to look up — bye.") |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
tui.setTitle("weather: " .. city) -- restored automatically on exit |
||||||
|
|
||||||
|
local data, err = fetchWeather(city) |
||||||
|
if not data then |
||||||
|
tui.error({ "Weather lookup failed.", tostring(err) }) |
||||||
|
elseif not data.current_condition then |
||||||
|
-- wttr answers 200 with an error payload for unknown places |
||||||
|
tui.warning(("wttr.in has no forecast for %q — try another spelling."):format(city)) |
||||||
|
else |
||||||
|
print() |
||||||
|
showCurrent(data, T, W) |
||||||
|
showForecast(data, T) |
||||||
|
|
||||||
|
-- Drill into one day; options are built from the payload itself. |
||||||
|
local labels = table.map(data.weather, function(d) return dayLabel(d.date) end) |
||||||
|
local pick = tui.promptSelect("Hourly detail for which day?", labels[1], { |
||||||
|
options = labels, |
||||||
|
help = "Esc to skip", |
||||||
|
}) |
||||||
|
if pick ~= nil then |
||||||
|
local _, idx = table.find(labels, function(l) return l == pick end) |
||||||
|
showHourly(data.weather[idx], T, W) |
||||||
|
end |
||||||
|
|
||||||
|
-- One-line summary to the clipboard (OSC 52), if the user wants it. |
||||||
|
local cur = data.current_condition[1] |
||||||
|
local summary = ("%s: %s°%s, %s"):format(city, cur["temp_" .. T], T, cur.weatherDesc[1].value) |
||||||
|
if tui.confirm("Copy summary to clipboard?", false, { help = summary }) then |
||||||
|
tui.setClipboard(summary) |
||||||
|
tui.info("Copied: " .. summary) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
print() |
||||||
|
if not tui.confirm("Look up another city?", true) then |
||||||
|
tui.comment("Done. (Terminal title resets on exit.)") |
||||||
|
return |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
# Lua stubs for the `a` runtime environment |
||||||
|
|
||||||
|
`---@meta` type definitions of the **Rust-native** parts of the `a` stdlib, for |
||||||
|
[lua-language-server](https://github.com/LuaLS/lua-language-server) (completion, |
||||||
|
hover docs, static checking). Definitions only — these files are never executed |
||||||
|
and are not part of the runtime. |
||||||
|
|
||||||
|
The **Lua-implemented** parts of the stdlib need no stubs: `lua/stdlib/*.lua` |
||||||
|
are the actual sources and the language server reads them directly. The split: |
||||||
|
|
||||||
|
| Stub here | Covers | |
||||||
|
|----------------|---------------------------------------------------------------| |
||||||
|
| `tui.lua` | the whole `tui` module (fully native) | |
||||||
|
| `http.lua` | `http` requests, response shape, sessions | |
||||||
|
| `sqlite.lua` | `sqlite.connect` and the connection methods | |
||||||
|
| `log.lua` | `log.trace` … `log.error` | |
||||||
|
| `task.lua` | `task.join` | |
||||||
|
| `utils.lua` | native half: `NULL`, `isNull`, `dump`, `toJSON`, `fromJSON` (`try`/`tryn` live in `lua/stdlib/utils.lua`) | |
||||||
|
| `os.lua` | sandbox additions `os.microtime`, `os.sleep` | |
||||||
|
| `require.lua` | the sandboxed `require` (the stock `package` library is disabled in `.luarc.json`) | |
||||||
|
|
||||||
|
The sandbox itself is mirrored in the repo-root `.luarc.json`: `runtime.builtin` |
||||||
|
disables `io`, `debug` and `package`, which do not exist in scripts. |
||||||
|
lua-language-server cannot hide *individual* fields, so removed functions that |
||||||
|
live inside kept libraries (`os.execute`, `os.remove`, `os.exit`, `load`, |
||||||
|
`loadfile`, `dofile`, `string.dump`) still autocomplete — they raise at runtime, |
||||||
|
do not use them. |
||||||
|
|
||||||
|
Keep these files in sync with the real API; the prose reference is `docs/*.md`. |
||||||
@ -0,0 +1,133 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the `http` module: the global table, the response/opts/session |
||||||
|
-- classes, and the get/post/… shorthands (lua/stdlib/http.lua assigns those |
||||||
|
-- dynamically in a loop, invisible to the language server). request, getJSON, |
||||||
|
-- postJSON and session are NOT stubbed — they are annotated at their real |
||||||
|
-- definitions in lua/stdlib/http.lua. Reference: docs/http.md |
||||||
|
-- |
||||||
|
-- A non-2xx status is NOT an error (see resp.ok/resp.status); only failing to |
||||||
|
-- get a response at all (DNS, connection, TLS, timeout) raises. |
||||||
|
|
||||||
|
http = {} |
||||||
|
|
||||||
|
---@class http.Response |
||||||
|
---@field status integer HTTP status code, e.g. 200 |
||||||
|
---@field ok boolean true when status is 2xx |
||||||
|
---@field headers table<string, string> response headers, keyed by lowercase name |
||||||
|
---@field body string raw response body |
||||||
|
---@field json fun(): any parse body as JSON (JSON null becomes utils.NULL); raises on invalid JSON |
||||||
|
|
||||||
|
---@class http.Auth |
||||||
|
---@field username string |
||||||
|
---@field password string |
||||||
|
---@field scheme "basic"|"digest"? default "basic" |
||||||
|
|
||||||
|
---@class http.RequestOpts |
||||||
|
---@field headers table<string, string>? extra request headers |
||||||
|
---@field body string? raw request body (set Content-Type yourself) |
||||||
|
---@field json any? serialized to JSON; sets Content-Type: application/json |
||||||
|
---@field form table<string, any>? URL-encoded; sets application/x-www-form-urlencoded |
||||||
|
---@field cookies table<string, string>? cookies for this request |
||||||
|
---@field timeout number? per-request timeout in seconds (default 30) |
||||||
|
---@field auth http.Auth? basic or digest credentials |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.get(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.post(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.put(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.patch(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.delete(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function http.head(url, opts) end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Sessions: a cookie jar carried across requests |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---@class http.Session |
||||||
|
local Session = {} |
||||||
|
|
||||||
|
---@param method string |
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:request(method, url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:get(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:post(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:put(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:patch(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:delete(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:head(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return any body |
||||||
|
---@return http.Response resp |
||||||
|
function Session:getJSON(url, opts) end |
||||||
|
|
||||||
|
---@param url string |
||||||
|
---@param body any |
||||||
|
---@param opts http.RequestOpts? |
||||||
|
---@return http.Response |
||||||
|
function Session:postJSON(url, body, opts) end |
||||||
|
|
||||||
|
---The jar as a nested table, {domain = {name = value}}. |
||||||
|
---@return table<string, table<string, string>> |
||||||
|
function Session:cookies() end |
||||||
|
|
||||||
|
---Empty the in-memory jar. |
||||||
|
function Session:clearCookies() end |
||||||
|
|
||||||
|
---Write the jar to a JSONL file (one cookie per line). |
||||||
|
---@param path string |
||||||
|
function Session:save(path) end |
||||||
|
|
||||||
|
---Merge cookies from a JSONL file into the jar (matching names overwritten). |
||||||
|
---@param path string |
||||||
|
function Session:load(path) end |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
---@meta |
||||||
|
-- Type stubs for the native `log` module: level-tagged diagnostics on stderr. |
||||||
|
-- Visibility is controlled by RUST_LOG (default: warn and error only). |
||||||
|
-- Arguments are stringified like print and joined with tabs; use |
||||||
|
-- utils.dump(t) to log a table's contents. Reference: docs/log.md |
||||||
|
|
||||||
|
log = {} |
||||||
|
|
||||||
|
---Very fine-grained, step-by-step detail. Shown with RUST_LOG=trace. |
||||||
|
---@param ... any |
||||||
|
function log.trace(...) end |
||||||
|
|
||||||
|
---Developer diagnostics. Shown with RUST_LOG=debug. |
||||||
|
---@param ... any |
||||||
|
function log.debug(...) end |
||||||
|
|
||||||
|
---Normal high-level progress. Shown with RUST_LOG=info. |
||||||
|
---@param ... any |
||||||
|
function log.info(...) end |
||||||
|
|
||||||
|
---Something unexpected but recoverable. Shown by default. |
||||||
|
---@param ... any |
||||||
|
function log.warn(...) end |
||||||
|
|
||||||
|
---Alias of log.warn. |
||||||
|
---@param ... any |
||||||
|
function log.warning(...) end |
||||||
|
|
||||||
|
---A failure worth reporting. Shown by default. |
||||||
|
---@param ... any |
||||||
|
function log.error(...) end |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the `a` additions to the sandboxed `os` table. |
||||||
|
-- |
||||||
|
-- The sandbox trims `os` to its time-related functions — os.time, os.clock, |
||||||
|
-- os.date, os.difftime, os.getenv — plus the two additions below. The |
||||||
|
-- system-access functions (os.execute, os.exit, os.remove, os.rename, |
||||||
|
-- os.tmpname, os.setlocale) do NOT exist at runtime; lua-language-server |
||||||
|
-- cannot hide individual fields, so it still suggests them — do not use them. |
||||||
|
-- Reference: docs/os.md |
||||||
|
|
||||||
|
---Current time as a Unix timestamp in seconds, as a float with sub-second |
||||||
|
---precision (wall clock — may jump if the system clock is adjusted). Use the |
||||||
|
---difference of two readings for interval timing. |
||||||
|
---@return number timestamp |
||||||
|
function os.microtime() end |
||||||
|
|
||||||
|
---Async sleep: pause for `seconds` (a float, so 0.1 is 100 ms) by suspending |
||||||
|
---on the runtime instead of blocking the thread — sibling task.join tasks |
||||||
|
---keep running. Does not actually wait inside self-driven coroutines |
||||||
|
---(coroutine.wrap/resume); run those through task.join instead. |
||||||
|
---@param seconds number non-negative finite |
||||||
|
function os.sleep(seconds) end |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stub for the sandboxed `require`. The stock `package` library does not |
||||||
|
-- exist in `a` (it is disabled in .luarc.json, which also removes the stock |
||||||
|
-- `require` definition — this stub replaces it). Reference: docs/require.md |
||||||
|
-- |
||||||
|
-- Names are dot-separated (`require "lib.helper"` → lib/helper.lua or |
||||||
|
-- lib/helper/init.lua), resolved against the main script's directory and each |
||||||
|
-- entry of $A_INCLUDE_PATHS. Each module runs once; the result is cached. |
||||||
|
|
||||||
|
---Load a module. Returns the module's value and, unlike stock Lua's loader |
||||||
|
---data, the resolved file path as the second value. |
||||||
|
---@param modname string dot-separated module name |
||||||
|
---@return any module |
||||||
|
---@return string path the resolved file path |
||||||
|
function require(modname) end |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the `sqlite` module. The native core provides sqlite.connect |
||||||
|
-- and the connection methods; lua/stdlib/sqlite.lua adds the transaction |
||||||
|
-- helpers (typed here too, since the source defines them on a hidden method |
||||||
|
-- table). Reference: docs/sqlite.md |
||||||
|
-- |
||||||
|
-- Failures raise Lua errors (bad SQL, unbindable type, parameter mismatch, |
||||||
|
-- closed connection) — trap with pcall/utils.try where needed. |
||||||
|
|
||||||
|
sqlite = {} |
||||||
|
|
||||||
|
---Parameters bind positionally (array for `?`) or by name (string keys for |
||||||
|
---`:name`/`@name`/`$name`); mixing the two is an error. Bind utils.NULL for |
||||||
|
---SQL NULL — a literal nil cannot live in a table. |
||||||
|
---@alias sqlite.Params any[]|table<string, any> |
||||||
|
|
||||||
|
---@class sqlite.Connection |
||||||
|
local Connection = {} |
||||||
|
|
||||||
|
---Run a statement that changes data (INSERT, UPDATE, DELETE, CREATE, …). |
||||||
|
---@param sql string |
||||||
|
---@param params sqlite.Params? |
||||||
|
---@return integer changed number of rows changed |
||||||
|
function Connection:execute(sql, params) end |
||||||
|
|
||||||
|
---Run a SELECT and return all rows, each a table keyed by column name. |
||||||
|
---A NULL column is simply absent from its row table. |
||||||
|
---@param sql string |
||||||
|
---@param params sqlite.Params? |
||||||
|
---@return table[] rows |
||||||
|
function Connection:query(sql, params) end |
||||||
|
|
||||||
|
---Like query, but returns only the first row, or nil if nothing matched. |
||||||
|
---@param sql string |
||||||
|
---@param params sqlite.Params? |
||||||
|
---@return table? row |
||||||
|
function Connection:queryOne(sql, params) end |
||||||
|
|
||||||
|
---Rowid of the most recent successful INSERT on this connection. |
||||||
|
---@return integer |
||||||
|
function Connection:lastInsertRowid() end |
||||||
|
|
||||||
|
---Rows changed by the most recent INSERT/UPDATE/DELETE. |
||||||
|
---@return integer |
||||||
|
function Connection:changes() end |
||||||
|
|
||||||
|
---Release the connection eagerly (also happens on garbage collection). |
||||||
|
---Any further call on the connection raises. |
||||||
|
function Connection:close() end |
||||||
|
|
||||||
|
---Start a transaction (BEGIN). |
||||||
|
function Connection:begin() end |
||||||
|
|
||||||
|
---Commit the current transaction (COMMIT). |
||||||
|
function Connection:commit() end |
||||||
|
|
||||||
|
---Discard the current transaction (ROLLBACK). |
||||||
|
function Connection:rollback() end |
||||||
|
|
||||||
|
---Run fn inside a transaction: commit on success, roll back and re-raise on |
||||||
|
---error. fn's return values are passed through. |
||||||
|
---@param fn function |
||||||
|
---@return any ... |
||||||
|
function Connection:transaction(fn) end |
||||||
|
|
||||||
|
---Open a database file (created if missing), or ":memory:" for a private |
||||||
|
---in-memory database. |
||||||
|
---@param path string |
||||||
|
---@return sqlite.Connection |
||||||
|
function sqlite.connect(path) end |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the native `task` module. Reference: docs/concurrency.md |
||||||
|
|
||||||
|
task = {} |
||||||
|
|
||||||
|
---Run each function as its own coroutine, drive them concurrently on the |
||||||
|
---async runtime, and return each one's first result positionally once all |
||||||
|
---have finished. Async stdlib calls (os.sleep, http, sqlite, prompts) inside |
||||||
|
---the tasks suspend and overlap — wall-clock time is the longest task, not |
||||||
|
---the sum. If a task raises, the first error is re-raised. |
||||||
|
---@param ... fun(): any |
||||||
|
---@return any ... |
||||||
|
function task.join(...) end |
||||||
@ -0,0 +1,318 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the native `tui` module: interactive prompts, styled output, |
||||||
|
-- message blocks and terminal control. Always available as the global `tui`. |
||||||
|
-- Reference: docs/tui.md |
||||||
|
-- |
||||||
|
-- Shared prompt behavior: Esc returns nil; Ctrl-C raises "<fn>: interrupted"; |
||||||
|
-- prompting without a terminal raises "<fn>: not a terminal". Prompts are |
||||||
|
-- async — they suspend only the calling coroutine. |
||||||
|
|
||||||
|
tui = {} |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Prompts |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---@class tui.ConfirmOpts |
||||||
|
---@field help string? hint line shown below the prompt |
||||||
|
---@field placeholder string? dim hint shown while the input is empty |
||||||
|
|
||||||
|
---Yes/no question, answered with y/n. `default` is returned on plain Enter; |
||||||
|
---with nil the user must type an answer. Esc returns nil (distinct from false). |
||||||
|
---@param text string |
||||||
|
---@param default boolean? |
||||||
|
---@param opts tui.ConfirmOpts? |
||||||
|
---@return boolean? answer |
||||||
|
function tui.confirm(text, default, opts) end |
||||||
|
|
||||||
|
---@class tui.TextOpts |
||||||
|
---@field help string? |
||||||
|
---@field placeholder string? dim hint shown while the input is empty |
||||||
|
---@field default string? returned when the input is submitted empty (distinct from the pre-filled positional default) |
||||||
|
---@field minLen integer? length bound, enforced inline |
||||||
|
---@field maxLen integer? length bound, enforced inline |
||||||
|
---@field suggestions string[]? autocompletion candidates (substring filter, Tab completes) |
||||||
|
---@field pageSize integer? visible suggestion rows |
||||||
|
|
||||||
|
---One-line text input. The positional `default` is pre-filled, editable text. |
||||||
|
---@param text string |
||||||
|
---@param default string? |
||||||
|
---@param opts tui.TextOpts? |
||||||
|
---@return string? answer |
||||||
|
function tui.promptText(text, default, opts) end |
||||||
|
|
||||||
|
---@class tui.LongTextOpts |
||||||
|
---@field help string? |
||||||
|
---@field extension string? temp-file extension for editor syntax highlighting (default ".txt") |
||||||
|
---@field editor string? editor command instead of $VISUAL/$EDITOR |
||||||
|
|
||||||
|
---Multi-line input via an external editor ($VISUAL/$EDITOR) on a temp file. |
||||||
|
---@param text string |
||||||
|
---@param default string? text the editor buffer starts with |
||||||
|
---@param opts tui.LongTextOpts? |
||||||
|
---@return string? answer |
||||||
|
function tui.promptLongText(text, default, opts) end |
||||||
|
|
||||||
|
---@class tui.SelectOpts |
||||||
|
---@field options string[] the choices (required, non-empty) |
||||||
|
---@field help string? |
||||||
|
---@field multiple boolean? checkbox multi-select; result becomes string[] |
||||||
|
---@field pageSize integer? visible rows (default 7) |
||||||
|
---@field vimMode boolean? hjkl navigation |
||||||
|
---@field filter boolean? set false to disable type-to-filter |
||||||
|
---@field minSelected integer? multiple only, enforced inline |
||||||
|
---@field maxSelected integer? multiple only, enforced inline |
||||||
|
---@field allSelected boolean? multiple only: start with everything checked |
||||||
|
|
||||||
|
---Pick from a list (filterable). `default` is matched against the options by |
||||||
|
---value; a value not among them raises. With `opts.multiple` the default may |
||||||
|
---be a string or array of strings and the result is an array. |
||||||
|
---@param text string |
||||||
|
---@param default string|string[]|nil |
||||||
|
---@param opts tui.SelectOpts |
||||||
|
---@return string|string[]|nil answer |
||||||
|
function tui.promptSelect(text, default, opts) end |
||||||
|
|
||||||
|
---@class tui.SecretOpts |
||||||
|
---@field help string? |
||||||
|
---@field confirm boolean? ask twice and require both entries to match |
||||||
|
---@field display "masked"|"hidden"|"full"? echo mode (default "masked") |
||||||
|
---@field toggle boolean? allow Ctrl-R to reveal the input |
||||||
|
---@field minLen integer? enforced inline |
||||||
|
|
||||||
|
---Secret input. Deliberately has no default parameter. |
||||||
|
---@param text string |
||||||
|
---@param opts tui.SecretOpts? |
||||||
|
---@return string? answer |
||||||
|
function tui.promptSecret(text, opts) end |
||||||
|
|
||||||
|
---@class tui.NumberOpts |
||||||
|
---@field help string? |
||||||
|
---@field placeholder string? |
||||||
|
---@field min number? enforced inline |
||||||
|
---@field max number? enforced inline |
||||||
|
|
||||||
|
---Numeric input, re-prompted until it parses. `default` is returned on empty submit. |
||||||
|
---@param text string |
||||||
|
---@param default number? |
||||||
|
---@param opts tui.NumberOpts? |
||||||
|
---@return number? answer |
||||||
|
function tui.promptNumber(text, default, opts) end |
||||||
|
|
||||||
|
---Whole-number input, re-prompted until it parses. A fractional `default` |
||||||
|
---raises at call time. |
||||||
|
---@param text string |
||||||
|
---@param default integer? |
||||||
|
---@param opts tui.NumberOpts? |
||||||
|
---@return integer? answer |
||||||
|
function tui.promptInt(text, default, opts) end |
||||||
|
|
||||||
|
---@class tui.DateOpts |
||||||
|
---@field help string? |
||||||
|
---@field min string? earliest selectable date, "YYYY-MM-DD" |
||||||
|
---@field max string? latest selectable date, "YYYY-MM-DD" |
||||||
|
---@field weekStart string? first day of the week, e.g. "monday" (default Sunday) |
||||||
|
|
||||||
|
---Interactive calendar. Dates cross the API as "YYYY-MM-DD" strings. |
||||||
|
---@param text string |
||||||
|
---@param default string? initially selected date, "YYYY-MM-DD" |
||||||
|
---@param opts tui.DateOpts? |
||||||
|
---@return string? date |
||||||
|
function tui.promptDate(text, default, opts) end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Styled output |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---Render HTML-like style tags (`<info>`, `<fg=#c0392b;options=bold>`, shorthand |
||||||
|
---`<red;on-white;b>`, `<href=URL>`, action tags like `<clear=line>`) into a |
||||||
|
---string with ANSI escapes. Degrades to plain text when piped or NO_COLOR is |
||||||
|
---set. `</>` closes the innermost style; invalid tags pass through literally. |
||||||
|
---@param markup string |
||||||
|
---@return string |
||||||
|
function tui.styled(markup) end |
||||||
|
|
||||||
|
---Backslash-escape the markup metacharacters (`<`, `>`, `\`) so untrusted |
||||||
|
---text — user input, API data — can be embedded in a `tui.styled` format |
||||||
|
---string and come out verbatim, without breaking the surrounding markup. |
||||||
|
---@param text string |
||||||
|
---@return string |
||||||
|
function tui.escape(text) end |
||||||
|
|
||||||
|
---Register a custom tag for `tui.styled` and block styles. The spec uses tag |
||||||
|
---syntax: attributes ("fg=white;bg=magenta"), shorthand ("i;bold"), or another |
||||||
|
---preset name. Re-registering overwrites; presets may shadow built-ins. |
||||||
|
---@param name string identifier-like tag name |
||||||
|
---@param style string |
||||||
|
function tui.addPreset(name, style) end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Message blocks (print directly to stdout, plain-text message) |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---@class tui.BlockOpts |
||||||
|
---@field label string? the "[LABEL]" text |
||||||
|
---@field style string? tag-body syntax: preset name, spec or shorthand |
||||||
|
---@field prefix string? prepended to every line (default " ") |
||||||
|
---@field padding boolean? blank styled line above and below |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.success(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.error(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.warning(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.caution(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.info(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.note(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.comment(message, opts) end |
||||||
|
|
||||||
|
---Generic block: everything comes from opts. |
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.BlockOpts? |
||||||
|
function tui.block(message, opts) end |
||||||
|
|
||||||
|
---@class tui.OutlineOpts |
||||||
|
---@field title string? shown in the top border |
||||||
|
---@field style string? border color, tag-body syntax |
||||||
|
---@field padding boolean? blank line above/below the content inside the box (default true) |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineSuccess(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineError(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineWarning(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineNote(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineInfo(message, opts) end |
||||||
|
|
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineCaution(message, opts) end |
||||||
|
|
||||||
|
---Generic outline block. |
||||||
|
---@param message string|string[] |
||||||
|
---@param opts tui.OutlineOpts? |
||||||
|
function tui.outlineBlock(message, opts) end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Low-level output |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---Write arguments to stdout with no newline and no separator (each converted |
||||||
|
---with tostring), then flush. |
||||||
|
---@param ... any |
||||||
|
function tui.raw(...) end |
||||||
|
|
||||||
|
---@class tui.ScreenInfo |
||||||
|
---@field width integer? terminal width in cells; nil when there is no terminal |
||||||
|
---@field height integer? terminal height in cells; nil when there is no terminal |
||||||
|
---@field tty boolean whether stdout is a terminal |
||||||
|
---@field colors boolean whether styled output will actually emit colors |
||||||
|
|
||||||
|
---Facts about the terminal, for scripts that render their own UI. |
||||||
|
---@return tui.ScreenInfo |
||||||
|
function tui.screenInfo() end |
||||||
|
|
||||||
|
---------------------------------------------------------------------- |
||||||
|
-- Terminal control (no-ops without a terminal; bad arguments always raise; |
||||||
|
-- alternate screen / cursor / scroll region / title restored on exit) |
||||||
|
---------------------------------------------------------------------- |
||||||
|
|
||||||
|
---Absolute cursor move, 1-based. nil for one axis keeps it. |
||||||
|
---@param row integer? |
||||||
|
---@param col integer? |
||||||
|
function tui.moveTo(row, col) end |
||||||
|
|
||||||
|
---Relative cursor move: positive dx right, positive dy down; nil counts as 0. |
||||||
|
---@param dx integer? |
||||||
|
---@param dy integer? |
||||||
|
function tui.move(dx, dy) end |
||||||
|
|
||||||
|
---Save the cursor position (one slot). |
||||||
|
function tui.saveCursor() end |
||||||
|
|
||||||
|
---Restore the saved cursor position. |
||||||
|
function tui.restoreCursor() end |
||||||
|
|
||||||
|
---Ask the terminal where the cursor is (1-based). Returns nil when there is |
||||||
|
---no terminal or it does not answer. Async, serialized against prompts. |
||||||
|
---@return integer? row |
||||||
|
---@return integer? col |
||||||
|
function tui.cursorPos() end |
||||||
|
|
||||||
|
---Hide (false) or show (true or no argument) the cursor. |
||||||
|
---@param visible boolean? |
||||||
|
function tui.showCursor(visible) end |
||||||
|
|
||||||
|
---@class tui.CursorStyleOpts |
||||||
|
---@field blink boolean? |
||||||
|
|
||||||
|
---Set the cursor shape; no arguments reset to the terminal default. |
||||||
|
---@param style "block"|"underline"|"bar"|nil |
||||||
|
---@param opts tui.CursorStyleOpts? |
||||||
|
function tui.cursorStyle(style, opts) end |
||||||
|
|
||||||
|
---Erase the current line. mode: 0/nil = whole (cursor to column 1), |
||||||
|
----1 = up to the cursor, 1 = cursor to end (cursor stays). |
||||||
|
---@param mode integer? |
||||||
|
function tui.clearLine(mode) end |
||||||
|
|
||||||
|
---Erase the screen. mode: 0/nil = whole (cursor home), -1 = above the cursor, |
||||||
|
---1 = below the cursor (cursor stays). |
||||||
|
---@param mode integer? |
||||||
|
function tui.clearScreen(mode) end |
||||||
|
|
||||||
|
---Switch to (true) or back from (false) the alternate screen buffer. |
||||||
|
---@param on boolean |
||||||
|
function tui.altScreen(on) end |
||||||
|
|
||||||
|
---Restrict scrolling to rows top..bottom (1-based, inclusive); moves the |
||||||
|
---cursor home. No arguments reset to the full screen. |
||||||
|
---@param top integer? |
||||||
|
---@param bottom integer? |
||||||
|
function tui.scrollRegion(top, bottom) end |
||||||
|
|
||||||
|
---Scroll the scroll region: positive n up, negative down. Cursor stays. |
||||||
|
---@param n integer |
||||||
|
function tui.scroll(n) end |
||||||
|
|
||||||
|
---Set the terminal window title (restored on exit). Control characters raise. |
||||||
|
---@param text string |
||||||
|
function tui.setTitle(text) end |
||||||
|
|
||||||
|
---Copy text into the system clipboard via the terminal (OSC 52). Write-only. |
||||||
|
---@param text string |
||||||
|
function tui.setClipboard(text) end |
||||||
|
|
||||||
|
---Soft-reset the terminal (DECSTR) without clearing the screen. |
||||||
|
function tui.reset() end |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
---@meta |
||||||
|
---@diagnostic disable: missing-return |
||||||
|
-- Type stubs for the NATIVE half of the `utils` module: the JSON codec, the |
||||||
|
-- NULL sentinel and the pretty-printer. The Lua-implemented half (utils.try, |
||||||
|
-- utils.tryn) lives in lua/stdlib/utils.lua and needs no stub. |
||||||
|
-- Reference: docs/utils.md |
||||||
|
|
||||||
|
utils = {} |
||||||
|
|
||||||
|
---Sentinel standing for an explicit null in tables (a Lua nil would mean "key |
||||||
|
---absent" — assigning nil deletes the key). Produced by fromJSON for JSON |
||||||
|
---null; serialized back to null by toJSON and the http helpers; binds SQL |
||||||
|
---NULL in sqlite. Compare with utils.isNull. |
||||||
|
---@type userdata |
||||||
|
utils.NULL = nil |
||||||
|
|
||||||
|
---Serialize a Lua value to a JSON string. A table with keys exactly 1..#t |
||||||
|
---becomes an array, any other table an object; utils.NULL becomes null. |
||||||
|
---Raises on NaN/infinity, values with no JSON form, or nesting beyond 64. |
||||||
|
---@param value any |
||||||
|
---@param pretty boolean? indented multi-line output (default compact) |
||||||
|
---@return string |
||||||
|
function utils.toJSON(value, pretty) end |
||||||
|
|
||||||
|
---Parse a JSON string into a Lua value. JSON null decodes to utils.NULL (not |
||||||
|
---nil), so nulls inside arrays do not create gaps. Invalid JSON raises. |
||||||
|
---@param str string |
||||||
|
---@return any |
||||||
|
function utils.fromJSON(str) end |
||||||
|
|
||||||
|
---True if value is the utils.NULL sentinel. isNull(nil) is false. |
||||||
|
---@param value any |
||||||
|
---@return boolean |
||||||
|
function utils.isNull(value) end |
||||||
|
|
||||||
|
---Render any Lua value as a readable string, for logging and inspection. |
||||||
|
---Cycles print as <circular>; nesting is capped at 20 levels. |
||||||
|
---@param value any |
||||||
|
---@return string |
||||||
|
function utils.dump(value) end |
||||||
Loading…
Reference in new issue