Skip to content

Utility function to handle empty query results and templates #1386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Library/Std/Pages/Maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!"}
15 changes: 4 additions & 11 deletions Library/Std/Template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -60,4 +53,4 @@ function template.fromPage(name, raw)
end
return template.new(templateText), fm.frontmatter
end
```
```
4 changes: 4 additions & 0 deletions common/space_lua/lua.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
16 changes: 16 additions & 0 deletions common/space_lua/stdlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
luaCall,
LuaEnv,
luaGet,
luaKeys,
LuaMultiRes,
LuaRuntimeError,
type LuaTable,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -233,5 +248,6 @@ export function luaBuildStandardEnv() {
// Non-standard
env.set("each", eachFunction);
env.set("spacelua", spaceluaApi);
env.set("some", someFunction);
return env;
}
27 changes: 27 additions & 0 deletions common/space_lua/stdlib/global_test.lua
Original file line number Diff line number Diff line change
@@ -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")
48 changes: 32 additions & 16 deletions website/API/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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")`.
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
```
4 changes: 2 additions & 2 deletions website/API/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 2 additions & 0 deletions website/Space Lua.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
7 changes: 7 additions & 0 deletions website/Space Lua/Lua Integrated Query.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down