diff --git a/Library/Std/Pages/Maintenance.md b/Library/Std/Pages/Maintenance.md index 92c8fd8fc..7a42af82b 100644 --- a/Library/Std/Pages/Maintenance.md +++ b/Library/Std/Pages/Maintenance.md @@ -5,18 +5,18 @@ We would like to keep our space clean. These are some tools that help you do tha # Aspiring pages This shows page links (max 20 to keep things sensible) that link to a page that does not (yet) exist. These could be broken links or just pages _aspiring_ to be created. -${template.each(query[[ +${some(template.each(query[[ from index.tag "aspiring-page" limit 20 ]], template.new[==[ * [[${ref}]]: broken link to [[${name}]] -]==], "No aspiring pages, all good!")} +]==])) or "No aspiring pages, all good!"} # Conflicting copies These are pages that have conflicting copies (as a result of sync). Have a look at them as well as their original (non-conflicting) versions and decide which one to keep. -${template.each(query[[ +${some(template.each(query[[ from index.tag "page" where name:find("%.conflicted%.") ]], template.new[==[ * [[${name:gsub("%.conflicted%..+$", "")}]]: conflict copy [[${name}]] -]==], "No conflicting pages!")} +]==]) or "No conflicting pages!"} diff --git a/Library/Std/Template.md b/Library/Std/Template.md index f497e6201..e8efd5764 100644 --- a/Library/Std/Template.md +++ b/Library/Std/Template.md @@ -10,20 +10,13 @@ template = template or {} templates = {} -- Iterates over a table/array and applies a function to each element, --- concatenating the results. The last, optional argument is output for empty table -function template.each(tbl, fn, empty) - local empty = empty or "" - +-- concatenating the results +function template.each(tbl, fn) local result = {} for _, item in ipairs(tbl) do table.insert(result, fn(item)) end - - if #result == 0 then - return empty - else - return table.concat(result) - end + return table.concat(result) end -- Creates a new template function from a string template @@ -60,4 +53,4 @@ function template.fromPage(name, raw) end return template.new(templateText), fm.frontmatter end -``` +``` \ No newline at end of file diff --git a/common/space_lua/lua.test.ts b/common/space_lua/lua.test.ts index de910660e..2c51a85d5 100644 --- a/common/space_lua/lua.test.ts +++ b/common/space_lua/lua.test.ts @@ -38,6 +38,10 @@ Deno.test("[Lua] JS tests", async () => { await runLuaTest("./stdlib/js_test.lua"); }); +Deno.test("[Lua] Global functions tests", async () => { + await runLuaTest("./stdlib/global_test.lua"); +}); + async function runLuaTest(luaPath: string) { const luaFile = await Deno.readTextFile( fileURLToPath(new URL(luaPath, import.meta.url)), diff --git a/common/space_lua/stdlib.ts b/common/space_lua/stdlib.ts index cb1261c82..361ce47a5 100644 --- a/common/space_lua/stdlib.ts +++ b/common/space_lua/stdlib.ts @@ -4,6 +4,7 @@ import { luaCall, LuaEnv, luaGet, + luaKeys, LuaMultiRes, LuaRuntimeError, type LuaTable, @@ -203,6 +204,20 @@ const dofileFunction = new LuaBuiltinFunction(async (sf, filename: string) => { } }); +const someFunction = new LuaBuiltinFunction(async (_sf, value: any) => { + switch (await luaTypeOf(value)) { + case "number": + if (!isFinite(value)) return null; + break; + case "string": + if (value.trim() === "") return null; + break; + case "table": + if (luaKeys(value).length === 0) return null; + } + return value; +}); + export function luaBuildStandardEnv() { const env = new LuaEnv(); // Top-level builtins @@ -233,5 +248,6 @@ export function luaBuildStandardEnv() { // Non-standard env.set("each", eachFunction); env.set("spacelua", spaceluaApi); + env.set("some", someFunction); return env; } diff --git a/common/space_lua/stdlib/global_test.lua b/common/space_lua/stdlib/global_test.lua new file mode 100644 index 000000000..e2ef5318e --- /dev/null +++ b/common/space_lua/stdlib/global_test.lua @@ -0,0 +1,27 @@ +local function assertEqual(a, b) + if a ~= b then + error("Assertion failed: " .. a .. " is not equal to " .. b) + end +end + +-- Logic values, shouldn't be affected +assertEqual(some(nil), nil) +assertEqual(some(true), true) +assertEqual(some(false), false) + +-- Numbers +assertEqual(some(1), 1) +assertEqual(some(0), 0) -- 0 is not falsy unlike C +assertEqual(some(-1), -1) +assertEqual(some(1/0), nil) -- inf +assertEqual(some(0/0), nil) -- nan + +-- Strings +assertEqual(some("foo bar"), "foo bar") +assertEqual(some(""), nil) +assertEqual(some(" \n"), nil) + +-- Tables +assertEqual(some({}), nil) +assertEqual(some({"baz"})[1], "baz") -- compare an element to ensure passthrough +assertEqual(some({foo="bar"})["foo"], "bar") diff --git a/website/API/global.md b/website/API/global.md index 19f8a4dbe..0d50a6aa2 100644 --- a/website/API/global.md +++ b/website/API/global.md @@ -49,21 +49,6 @@ end -- city New York ``` -## each -Returns an iterator for array-like tables that iterates over values only (without indices). - -Example: -```lua -local fruits = {"apple", "banana", "orange"} -for fruit in each(fruits) do - print(fruit) -end --- Output: --- apple --- banana --- orange -``` - ## unpack Unpacks a table into individual values. @@ -202,4 +187,35 @@ print(t.foo) -- prints: "bar" ``` ## dofile(path) -Loads a Lua file from a path in your space, e.g. if you uploaded a `test.lua` file, you can load it with `dofile("test.lua")`. \ No newline at end of file +Loads a Lua file from a path in your space, e.g. if you uploaded a `test.lua` file, you can load it with `dofile("test.lua")`. + +# Non-standard Extensions +## each +Returns an iterator for array-like tables that iterates over values only (without indices). + +Example: +```lua +local fruits = {"apple", "banana", "orange"} +for fruit in each(fruits) do + print(fruit) +end +-- Output: +-- apple +-- banana +-- orange +``` + +## some +Returns nil if the value is empty, otherwise returns the value unchanged. Empty tables, strings containing only whitespace, `inf` and `nan` numeric value are considered empty. + +Example: +```lua +print(some("hello")) -- hello +print(some("")) -- nil +print(some(" ")) -- nil +print(some({})) -- nil +print(some(0)) -- 0 +print(some(1/0)) -- nil + +print(some({}) or "empty") -- empty +``` diff --git a/website/API/template.md b/website/API/template.md index 8cfbec2e8..90c85dbeb 100644 --- a/website/API/template.md +++ b/website/API/template.md @@ -13,8 +13,8 @@ examples.sayHello = template.new[==[Hello ${name}!]==] And its use: ${examples.sayHello {name="Pete"}} -## template.each(collection, template, empty="") -Iterates over a collection and renders a template for each item. Optionally specify output for an empty collection. +## template.each(collection, template) +Iterates over a collection and renders a template for each item. Example: diff --git a/website/Space Lua.md b/website/Space Lua.md index cba31673b..0e00ebce8 100644 --- a/website/Space Lua.md +++ b/website/Space Lua.md @@ -57,6 +57,8 @@ A new syntax introduced with Space Lua is the `${lua expression}` syntax that yo For example: 10 + 2 = ${adder(10, 2)} (Alt-click, or select to see the expression) is using the just defined `adder` function to this rather impressive calculation. +Note that you can have the text {lua expression} in your page and it won't be evaluated. So writing `${"$"}{lua expression}` can be used to “delay” evaluation by one pass, for example to escape expressions in templated content. + ## Queries Space Lua has a feature called [[Space Lua/Lua Integrated Query]], which integrate SQL-like queries into Lua. Here’s a small example querying the last 3 modifies pages: diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index d031f79b9..a0dcb432f 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -28,6 +28,13 @@ ${query[[ limit 3 ]]} +Note that the query returns a regular Lua table, so it can be part of a bigger expression: + +${some(query[[ + from p = index.tag "page" + limit 0 +]]) or "Matched no pages"} + # Clauses Here are the clauses that are currently supported: