Lua: Reimplement `template` and `config` in Space Lua, distributed with Library/Std
parent
d72983bde5
commit
517cfb209a
|
@ -16,7 +16,6 @@ import { tableApi } from "$common/space_lua/stdlib/table.ts";
|
||||||
import { osApi } from "$common/space_lua/stdlib/os.ts";
|
import { osApi } from "$common/space_lua/stdlib/os.ts";
|
||||||
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
||||||
import { spaceLuaApi } from "$common/space_lua/stdlib/space_lua.ts";
|
import { spaceLuaApi } from "$common/space_lua/stdlib/space_lua.ts";
|
||||||
import { templateApi } from "$common/space_lua/stdlib/template.ts";
|
|
||||||
import { mathApi } from "$common/space_lua/stdlib/math.ts";
|
import { mathApi } from "$common/space_lua/stdlib/math.ts";
|
||||||
import { parse } from "$common/space_lua/parse.ts";
|
import { parse } from "$common/space_lua/parse.ts";
|
||||||
import { evalStatement } from "$common/space_lua/eval.ts";
|
import { evalStatement } from "$common/space_lua/eval.ts";
|
||||||
|
@ -192,6 +191,6 @@ export function luaBuildStandardEnv() {
|
||||||
// Non-standard
|
// Non-standard
|
||||||
env.set("each", eachFunction);
|
env.set("each", eachFunction);
|
||||||
env.set("space_lua", spaceLuaApi);
|
env.set("space_lua", spaceLuaApi);
|
||||||
env.set("template", templateApi);
|
// env.set("template", templateApi);
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import {
|
|
||||||
type ILuaFunction,
|
|
||||||
jsToLuaValue,
|
|
||||||
LuaBuiltinFunction,
|
|
||||||
LuaTable,
|
|
||||||
} from "$common/space_lua/runtime.ts";
|
|
||||||
import { interpolateLuaString } from "$common/space_lua/stdlib/space_lua.ts";
|
|
||||||
|
|
||||||
export const templateApi = new LuaTable({
|
|
||||||
each: new LuaBuiltinFunction(
|
|
||||||
async (sf, tbl: LuaTable | any[], fn: ILuaFunction): Promise<string> => {
|
|
||||||
const result = [];
|
|
||||||
if (tbl instanceof LuaTable) {
|
|
||||||
tbl = tbl.toJSArray();
|
|
||||||
}
|
|
||||||
for (const item of tbl) {
|
|
||||||
result.push(await fn.call(sf, item));
|
|
||||||
}
|
|
||||||
return result.join("");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new: new LuaBuiltinFunction(
|
|
||||||
(_sf, template: string): ILuaFunction => {
|
|
||||||
const lines = template.split("\n").map((line) =>
|
|
||||||
line.replace(/^\s{4}/, "")
|
|
||||||
);
|
|
||||||
const processed = lines.join("\n");
|
|
||||||
return new LuaBuiltinFunction(
|
|
||||||
async (sf, env: LuaTable | any) => {
|
|
||||||
if (!(env instanceof LuaTable)) {
|
|
||||||
env = jsToLuaValue(env);
|
|
||||||
}
|
|
||||||
return await interpolateLuaString(sf, processed, env);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { FileMeta } from "@silverbulletmd/silverbullet/types";
|
||||||
import type { SysCallMapping, System } from "../system.ts";
|
import type { SysCallMapping, System } from "../system.ts";
|
||||||
|
|
||||||
export default function assetSyscalls(system: System<any>): SysCallMapping {
|
export default function assetSyscalls(system: System<any>): SysCallMapping {
|
||||||
|
@ -7,5 +8,28 @@ export default function assetSyscalls(system: System<any>): SysCallMapping {
|
||||||
name,
|
name,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
"asset.listFiles": (_ctx, plugName: string): FileMeta[] => {
|
||||||
|
const assets = system.loadedPlugs.get(plugName)!.assets!;
|
||||||
|
const fileNames = assets.listFiles();
|
||||||
|
return fileNames.map((name) => ({
|
||||||
|
name,
|
||||||
|
contentType: assets.getMimeType(name),
|
||||||
|
created: assets.getMtime(name),
|
||||||
|
lastModified: assets.getMtime(name),
|
||||||
|
size: -1,
|
||||||
|
perm: "ro",
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
"asset.getFileMeta": (_ctx, plugName: string, name: string): FileMeta => {
|
||||||
|
const assets = system.loadedPlugs.get(plugName)!.assets!;
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
contentType: assets.getMimeType(name),
|
||||||
|
created: assets.getMtime(name),
|
||||||
|
lastModified: assets.getMtime(name),
|
||||||
|
size: -1,
|
||||||
|
perm: "ro",
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { base64DecodeDataUrl } from "../../lib/crypto.ts";
|
import { base64DecodeDataUrl } from "../../lib/crypto.ts";
|
||||||
import { syscall } from "../syscall.ts";
|
import { syscall } from "../syscall.ts";
|
||||||
|
import type { FileMeta } from "@silverbulletmd/silverbullet/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an asset embedded in a plug (via the `assets` field in the plug manifest).
|
* Reads an asset embedded in a plug (via the `assets` field in the plug manifest).
|
||||||
|
@ -21,3 +22,14 @@ export async function readAsset(
|
||||||
return dataUrl;
|
return dataUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listFiles(plugName: string): Promise<FileMeta[]> {
|
||||||
|
return await syscall("asset.listFiles", plugName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileMeta(
|
||||||
|
plugName: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<FileMeta> {
|
||||||
|
return await syscall("asset.getFileMeta", plugName, name);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#meta
|
||||||
|
|
||||||
|
Config library for defining and getting config values
|
||||||
|
|
||||||
|
```space-lua
|
||||||
|
-- priority: 100
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
local config_values = {}
|
||||||
|
local config_schema = {}
|
||||||
|
|
||||||
|
function config.define(key, schema)
|
||||||
|
config_schema[key] = schema or true
|
||||||
|
end
|
||||||
|
|
||||||
|
function config.set(key, value)
|
||||||
|
local schema = config_schema[key]
|
||||||
|
if schema == nil then
|
||||||
|
error("Config key not defined: " .. key)
|
||||||
|
end
|
||||||
|
if schema != true then
|
||||||
|
result = jsonschema.validate_object(schema, value)
|
||||||
|
if result != nil then
|
||||||
|
error("Validation error (" .. key .. "): " .. result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
config_values[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
function config.get(key)
|
||||||
|
return config_values[key]
|
||||||
|
end
|
||||||
|
```
|
|
@ -0,0 +1,38 @@
|
||||||
|
#meta
|
||||||
|
|
||||||
|
Implements useful template functions
|
||||||
|
|
||||||
|
```space-lua
|
||||||
|
-- priority: 100
|
||||||
|
-- Template library for working with templates and iterables
|
||||||
|
template = {}
|
||||||
|
|
||||||
|
-- Iterates over a table/array and applies a function to each element,
|
||||||
|
-- concatenating the results
|
||||||
|
function template.each(tbl, fn)
|
||||||
|
local result = {}
|
||||||
|
for _, item in ipairs(tbl) do
|
||||||
|
table.insert(result, fn(item))
|
||||||
|
end
|
||||||
|
return table.concat(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Creates a new template function from a string template
|
||||||
|
function template.new(template_str)
|
||||||
|
-- Preprocess: strip indentation
|
||||||
|
local lines = {}
|
||||||
|
local split_lines = string.split(template_str, "\n")
|
||||||
|
for _, line in ipairs(split_lines) do
|
||||||
|
line = string.gsub(line, "^ ", "")
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
template_str = table.concat(lines, "\n")
|
||||||
|
return function(obj)
|
||||||
|
return space_lua.interpolate(template_str, obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("template loaded!!")
|
||||||
|
|
||||||
|
|
||||||
|
```
|
|
@ -1,4 +1,37 @@
|
||||||
name: core
|
name: core
|
||||||
|
assets:
|
||||||
|
- "Library/Std/*"
|
||||||
|
functions:
|
||||||
|
init:
|
||||||
|
path: ./std.ts:init
|
||||||
|
env: server
|
||||||
|
events:
|
||||||
|
- system:ready
|
||||||
|
readFile:
|
||||||
|
path: ./std.ts:readFile
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "^Library/Std/.+"
|
||||||
|
operation: readFile
|
||||||
|
writeFile:
|
||||||
|
path: ./std.ts:writeFile
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "^Library/Std/.+"
|
||||||
|
operation: writeFile
|
||||||
|
deleteFile:
|
||||||
|
path: ./std.ts:deleteFile
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "^Library/Std/.+"
|
||||||
|
operation: deleteFile
|
||||||
|
getFileMeta:
|
||||||
|
path: ./std.ts:getFileMeta
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "^Library/Std/.+"
|
||||||
|
operation: getFileMeta
|
||||||
|
listFiles:
|
||||||
|
path: ./std.ts:listFiles
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "^Library/Std/.+"
|
||||||
|
operation: listFiles
|
||||||
config:
|
config:
|
||||||
# Built-in schemas
|
# Built-in schemas
|
||||||
schema.tag:
|
schema.tag:
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
asset,
|
||||||
|
datastore,
|
||||||
|
mq,
|
||||||
|
system,
|
||||||
|
} from "@silverbulletmd/silverbullet/syscalls";
|
||||||
|
import type { FileMeta } from "@silverbulletmd/silverbullet/types";
|
||||||
|
|
||||||
|
export async function listFiles(): Promise<FileMeta[]> {
|
||||||
|
return await asset.listFiles("core");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readFile(
|
||||||
|
name: string,
|
||||||
|
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
||||||
|
const text = await asset.readAsset("core", name, "utf8");
|
||||||
|
return {
|
||||||
|
data: new TextEncoder().encode(text),
|
||||||
|
meta: await asset.getFileMeta("core", name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeFile(): Promise<FileMeta> {
|
||||||
|
throw new Error("Writing std files not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteFile(): Promise<void> {
|
||||||
|
throw new Error("Deleting std files not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFileMeta(name: string): Promise<FileMeta> {
|
||||||
|
return asset.getFileMeta("core", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stdLibCacheKey = ["stdLibCache"];
|
||||||
|
|
||||||
|
type StdLibCache = Record<string, number>; // page name -> last modified time
|
||||||
|
|
||||||
|
export async function init() {
|
||||||
|
let stdLibCache: StdLibCache | undefined = await datastore.get(
|
||||||
|
stdLibCacheKey,
|
||||||
|
);
|
||||||
|
if (!stdLibCache) {
|
||||||
|
stdLibCache = {};
|
||||||
|
}
|
||||||
|
// Iterate over the current file listing, check if any new files have been added, removed or modified
|
||||||
|
const newListing = await listFiles();
|
||||||
|
// First check for files that were removed
|
||||||
|
for (const cachedFile of Object.keys(stdLibCache)) {
|
||||||
|
if (!newListing.find((f) => f.name === cachedFile)) {
|
||||||
|
console.log(`Clearing index for removed file ${cachedFile}`);
|
||||||
|
await system.invokeFunction("index.clearDSIndex", cachedFile);
|
||||||
|
delete stdLibCache[cachedFile];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check for new/modified files
|
||||||
|
for (const file of newListing) {
|
||||||
|
const lastModified = file.lastModified;
|
||||||
|
|
||||||
|
// Check if file is new or modified compared to cache
|
||||||
|
if (!stdLibCache[file.name] || stdLibCache[file.name] !== lastModified) {
|
||||||
|
// console.log(`Queuing for indexing ${file.name}`);
|
||||||
|
await system.invokeFunction("index.clearDSIndex", file.name);
|
||||||
|
await mq.send("indexQueue", file.name);
|
||||||
|
stdLibCache[file.name] = lastModified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updated cache
|
||||||
|
await datastore.set(stdLibCacheKey, stdLibCache);
|
||||||
|
}
|
|
@ -80,11 +80,14 @@ export default function reducer(
|
||||||
currPageMeta = pageMeta;
|
currPageMeta = pageMeta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
allPages: action.allPages,
|
allPages: action.allPages,
|
||||||
currentPageMeta: currPageMeta,
|
|
||||||
};
|
};
|
||||||
|
if (currPageMeta) {
|
||||||
|
newState.currentPageMeta = currPageMeta;
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
}
|
}
|
||||||
case "start-navigate": {
|
case "start-navigate": {
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue