From e1c997616c69d120f507b7453f2392d3f2f6c3d2 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Thu, 6 Feb 2025 11:58:27 +0100 Subject: [PATCH] Support ${lua expression} in templates --- common/syscalls/lua.ts | 23 +++++++++++++++++++++-- common/template/render.ts | 32 +------------------------------- plug-api/syscalls/lua.ts | 6 ++++++ plugs/markdown/api.ts | 24 +++++++++++++++++++++++- plugs/template/util.ts | 30 ++++++++++++++++++++++++++++++ server/server_system.ts | 2 +- web/client_system.ts | 2 +- web/cm_plugins/lua_widget.ts | 2 +- 8 files changed, 84 insertions(+), 37 deletions(-) diff --git a/common/syscalls/lua.ts b/common/syscalls/lua.ts index f4971e36..dfb0884a 100644 --- a/common/syscalls/lua.ts +++ b/common/syscalls/lua.ts @@ -1,10 +1,29 @@ import type { SysCallMapping } from "$lib/plugos/system.ts"; -import { parse } from "../space_lua/parse.ts"; +import { evalExpression } from "$common/space_lua/eval.ts"; +import { parse, parseExpressionString } from "../space_lua/parse.ts"; +import type { CommonSystem } from "$common/common_system.ts"; +import { LuaStackFrame, luaValueToJS } from "$common/space_lua/runtime.ts"; -export function luaSyscalls(): SysCallMapping { +export function luaSyscalls(commonSystem: CommonSystem): SysCallMapping { return { "lua.parse": (_ctx, code: string) => { return parse(code); }, + /** + * Evaluates a Lua expression and returns the result as a JavaScript value + * @param _ctx + * @param expression + * @returns + */ + "lua.evalExpression": (_ctx, expression: string) => { + const ast = parseExpressionString(expression); + return luaValueToJS( + evalExpression( + ast, + commonSystem.spaceLuaEnv.env, + LuaStackFrame.lostFrame, + ), + ); + }, }; } diff --git a/common/template/render.ts b/common/template/render.ts index d0bc9328..c0808724 100644 --- a/common/template/render.ts +++ b/common/template/render.ts @@ -2,8 +2,7 @@ import type { AST } from "../../plug-api/lib/tree.ts"; import { evalQueryExpression } from "@silverbulletmd/silverbullet/lib/query_expression"; import { expressionToKvQueryExpression } from "../../plug-api/lib/parse_query.ts"; import type { FunctionMap } from "../../plug-api/types.ts"; -import { jsonToMDTable } from "../../plugs/template/util.ts"; -import { LuaTable } from "$common/space_lua/runtime.ts"; +import { renderExpressionResult } from "../../plugs/template/util.ts"; export async function renderTemplate( ast: AST, @@ -83,35 +82,6 @@ async function renderExpressionDirective( return renderExpressionResult(result); } -export function renderExpressionResult(result: any): string { - if (result instanceof LuaTable) { - result = result.toJS(); - } - if ( - Array.isArray(result) && result.length > 0 && typeof result[0] === "object" - ) { - // If result is an array of objects, render as a markdown table - try { - return jsonToMDTable(result); - } catch (e: any) { - console.error( - `Error rendering expression directive: ${e.message} for value ${ - JSON.stringify(result) - }`, - ); - return JSON.stringify(result); - } - } else if (typeof result === "object" && result.constructor === Object) { - // if result is a plain object, render as a markdown table - return jsonToMDTable([result]); - } else if (Array.isArray(result)) { - // Not-object array, let's render it as a markdown list - return result.map((item) => `- ${item}`).join("\n"); - } else { - return "" + result; - } -} - async function renderEachVarDirective( ast: AST, value: any[], diff --git a/plug-api/syscalls/lua.ts b/plug-api/syscalls/lua.ts index bd558f67..ce365554 100644 --- a/plug-api/syscalls/lua.ts +++ b/plug-api/syscalls/lua.ts @@ -6,3 +6,9 @@ export function parse( ): Promise { return syscall("lua.parse", code); } + +export function evalExpression( + expression: string, +): Promise { + return syscall("lua.evalExpression", expression); +} diff --git a/plugs/markdown/api.ts b/plugs/markdown/api.ts index 7614fc94..2ce256b0 100644 --- a/plugs/markdown/api.ts +++ b/plugs/markdown/api.ts @@ -4,7 +4,7 @@ import { renderToText, replaceNodesMatchingAsync, } from "../../plug-api/lib/tree.ts"; -import { codeWidget } from "@silverbulletmd/silverbullet/syscalls"; +import { codeWidget, lua } from "@silverbulletmd/silverbullet/syscalls"; import { parseMarkdown } from "../../plug-api/syscalls/markdown.ts"; import { type MarkdownRenderOptions, @@ -12,6 +12,8 @@ import { } from "./markdown_render.ts"; import { validatePageName } from "@silverbulletmd/silverbullet/lib/page_ref"; import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref"; +import type { LuaExpression } from "$common/space_lua/ast.ts"; +import { renderExpressionResult } from "../template/util.ts"; /** * Finds code widgets, runs their plug code to render and inlines their content in the parse tree @@ -102,6 +104,26 @@ export async function expandCodeWidgets( page, ); } + } else if (n.type === "LuaDirective") { + const expr: LuaExpression | null = findNodeOfType( + n, + "LuaExpressionDirective", + ) as LuaExpression | null; + if (!expr) { + return; + } + const exprText = renderToText(expr); + + let result = await lua.evalExpression(exprText); + + if (result.markdown) { + result = result.markdown; + } + + const markdown = renderExpressionResult(result); + + console.log("Expanding LuaDirective", exprText, result, markdown); + return await parseMarkdown(markdown); } }); return mdTree; diff --git a/plugs/template/util.ts b/plugs/template/util.ts index 0953c7c2..89e3d801 100644 --- a/plugs/template/util.ts +++ b/plugs/template/util.ts @@ -1,6 +1,7 @@ import type { PageMeta } from "../../plug-api/types.ts"; import { space, system, template } from "@silverbulletmd/silverbullet/syscalls"; import { cleanTemplate } from "./plug_api.ts"; +import { LuaTable } from "$common/space_lua/runtime.ts"; export function defaultJsonTransformer(v: any): string { if (v === undefined) { @@ -91,3 +92,32 @@ export async function renderQueryTemplate( config, }); } + +export function renderExpressionResult(result: any): string { + if (result instanceof LuaTable) { + result = result.toJS(); + } + if ( + Array.isArray(result) && result.length > 0 && typeof result[0] === "object" + ) { + // If result is an array of objects, render as a markdown table + try { + return jsonToMDTable(result); + } catch (e: any) { + console.error( + `Error rendering expression directive: ${e.message} for value ${ + JSON.stringify(result) + }`, + ); + return JSON.stringify(result); + } + } else if (typeof result === "object" && result.constructor === Object) { + // if result is a plain object, render as a markdown table + return jsonToMDTable([result]); + } else if (Array.isArray(result)) { + // Not-object array, let's render it as a markdown list + return result.map((item) => `- ${item}`).join("\n"); + } else { + return "" + result; + } +} diff --git a/server/server_system.ts b/server/server_system.ts index fb0ea486..0d6c32c5 100644 --- a/server/server_system.ts +++ b/server/server_system.ts @@ -139,7 +139,7 @@ export class ServerSystem extends CommonSystem { jsonschemaSyscalls(), indexSyscalls(this), commandSyscalls(this), - luaSyscalls(), + luaSyscalls(this), templateSyscalls(this.ds), dataStoreReadSyscalls(this.ds, this), codeWidgetSyscalls(codeWidgetHook), diff --git a/web/client_system.ts b/web/client_system.ts index c1647da1..d71b07fd 100644 --- a/web/client_system.ts +++ b/web/client_system.ts @@ -168,7 +168,7 @@ export class ClientSystem extends CommonSystem { jsonschemaSyscalls(), indexSyscalls(this), commandSyscalls(this), - luaSyscalls(), + luaSyscalls(this), this.client.syncMode // In sync mode handle locally ? mqSyscalls(this.mq) diff --git a/web/cm_plugins/lua_widget.ts b/web/cm_plugins/lua_widget.ts index bdfe4058..4a114a39 100644 --- a/web/cm_plugins/lua_widget.ts +++ b/web/cm_plugins/lua_widget.ts @@ -11,7 +11,7 @@ import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts"; import { renderToText } from "@silverbulletmd/silverbullet/lib/tree"; import { activeWidgets } from "./markdown_widget.ts"; import { attachWidgetEventHandlers } from "./widget_util.ts"; -import { renderExpressionResult } from "$common/template/render.ts"; +import { renderExpressionResult } from "../../plugs/template/util.ts"; export type LuaWidgetCallback = ( bodyText: string,