@silverbulletmd/silverbullet 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- package/plugs/builtin_plugs.ts +14 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { LuaBuiltinFunction, LuaTable } from "../runtime.ts";
|
|
2
|
+
|
|
3
|
+
const ONE_DAY = 1000 * 60 * 60 * 24;
|
|
4
|
+
const ONE_WEEK = ONE_DAY * 7;
|
|
5
|
+
|
|
6
|
+
function weekNumber(
|
|
7
|
+
d: Date,
|
|
8
|
+
utc: boolean,
|
|
9
|
+
weekStartDay: number,
|
|
10
|
+
iso: boolean,
|
|
11
|
+
): number {
|
|
12
|
+
const year = utc ? d.getUTCFullYear() : d.getFullYear();
|
|
13
|
+
const month = utc ? d.getUTCMonth() : d.getMonth();
|
|
14
|
+
const day = utc ? d.getUTCDate() : d.getDate();
|
|
15
|
+
const date = new Date(Date.UTC(year, month, day));
|
|
16
|
+
|
|
17
|
+
if (iso) {
|
|
18
|
+
const target = new Date(date);
|
|
19
|
+
target.setUTCDate(
|
|
20
|
+
target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
|
|
24
|
+
const weekStart = new Date(yearStart);
|
|
25
|
+
weekStart.setUTCDate(
|
|
26
|
+
yearStart.getUTCDate() - ((yearStart.getUTCDay() + 6) % 7),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return 1 + Math.floor((target.getTime() - weekStart.getTime()) / ONE_WEEK);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const yearStart = new Date(Date.UTC(year, 0, 1));
|
|
33
|
+
const startDay = yearStart.getUTCDay();
|
|
34
|
+
const offset = (7 + (startDay - weekStartDay)) % 7;
|
|
35
|
+
const firstWeekStart = new Date(yearStart);
|
|
36
|
+
|
|
37
|
+
firstWeekStart.setUTCDate(yearStart.getUTCDate() + (7 - offset) % 7);
|
|
38
|
+
|
|
39
|
+
if (date < firstWeekStart) return 0;
|
|
40
|
+
return 1 +
|
|
41
|
+
Math.floor((date.getTime() - firstWeekStart.getTime()) / ONE_WEEK);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function dayOfYear(d: Date, utc: boolean): number {
|
|
45
|
+
const year = utc ? d.getUTCFullYear() : d.getFullYear();
|
|
46
|
+
const start = new Date(Date.UTC(year, 0, 0));
|
|
47
|
+
|
|
48
|
+
const current = utc
|
|
49
|
+
? new Date(
|
|
50
|
+
Date.UTC(
|
|
51
|
+
year,
|
|
52
|
+
d.getUTCMonth(),
|
|
53
|
+
d.getUTCDate(),
|
|
54
|
+
d.getUTCHours(),
|
|
55
|
+
d.getUTCMinutes(),
|
|
56
|
+
d.getUTCSeconds(),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
: new Date(
|
|
60
|
+
Date.UTC(
|
|
61
|
+
year,
|
|
62
|
+
d.getMonth(),
|
|
63
|
+
d.getDate(),
|
|
64
|
+
d.getHours(),
|
|
65
|
+
d.getMinutes(),
|
|
66
|
+
d.getSeconds(),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return Math.floor((current.getTime() - start.getTime()) / ONE_DAY);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isoWeekYear(d: Date, utc: boolean): number {
|
|
74
|
+
const year = utc ? d.getUTCFullYear() : d.getFullYear();
|
|
75
|
+
const month = utc ? d.getUTCMonth() : d.getMonth();
|
|
76
|
+
const day = utc ? d.getUTCDate() : d.getDate();
|
|
77
|
+
const target = new Date(Date.UTC(year, month, day));
|
|
78
|
+
|
|
79
|
+
target.setUTCDate(
|
|
80
|
+
target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return target.getUTCFullYear();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isDST(d: Date): boolean {
|
|
87
|
+
const jan = new Date(d.getFullYear(), 0, 1);
|
|
88
|
+
|
|
89
|
+
return d.getTimezoneOffset() < jan.getTimezoneOffset();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function yr(d: Date, utc: boolean): number {
|
|
93
|
+
return utc ? d.getUTCFullYear() : d.getFullYear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function mo(d: Date, utc: boolean): number {
|
|
97
|
+
return utc ? d.getUTCMonth() : d.getMonth();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function da(d: Date, utc: boolean): number {
|
|
101
|
+
return utc ? d.getUTCDate() : d.getDate();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hr(d: Date, utc: boolean): number {
|
|
105
|
+
return utc ? d.getUTCHours() : d.getHours();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function mi(d: Date, utc: boolean): number {
|
|
109
|
+
return utc ? d.getUTCMinutes() : d.getMinutes();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sc(d: Date, utc: boolean): number {
|
|
113
|
+
return utc ? d.getUTCSeconds() : d.getSeconds();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function wd(d: Date, utc: boolean): number {
|
|
117
|
+
return utc ? d.getUTCDay() : d.getDay();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pad2(n: number): string {
|
|
121
|
+
return n.toString().padStart(2, "0");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function pad3(n: number): string {
|
|
125
|
+
return n.toString().padStart(3, "0");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function dateTable(d: Date, utc: boolean): LuaTable {
|
|
129
|
+
const tbl = new LuaTable({
|
|
130
|
+
year: yr(d, utc),
|
|
131
|
+
month: mo(d, utc) + 1,
|
|
132
|
+
day: da(d, utc),
|
|
133
|
+
hour: hr(d, utc),
|
|
134
|
+
min: mi(d, utc),
|
|
135
|
+
sec: sc(d, utc),
|
|
136
|
+
wday: wd(d, utc) + 1,
|
|
137
|
+
yday: dayOfYear(d, utc),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!utc) {
|
|
141
|
+
tbl.rawSet("isdst", isDST(d));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return tbl;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build the specifier map for a given `Date` and `utc` flag.
|
|
148
|
+
// Returns a record mapping single-char specifier to its output string.
|
|
149
|
+
function buildSpecMap(
|
|
150
|
+
d: Date,
|
|
151
|
+
utc: boolean,
|
|
152
|
+
): Record<string, () => string> {
|
|
153
|
+
const h = () => hr(d, utc);
|
|
154
|
+
const h12 = () => h() % 12 || 12;
|
|
155
|
+
const dow = () => wd(d, utc);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
// Date
|
|
159
|
+
"Y": () => yr(d, utc).toString(),
|
|
160
|
+
"y": () => pad2(yr(d, utc) % 100),
|
|
161
|
+
"C": () => pad2(Math.floor(yr(d, utc) / 100)),
|
|
162
|
+
"m": () => pad2(mo(d, utc) + 1),
|
|
163
|
+
"d": () => pad2(da(d, utc)),
|
|
164
|
+
"e": () => da(d, utc).toString().padStart(2, " "),
|
|
165
|
+
"j": () => pad3(dayOfYear(d, utc)),
|
|
166
|
+
|
|
167
|
+
// Time
|
|
168
|
+
"H": () => pad2(h()),
|
|
169
|
+
"I": () => pad2(h12()),
|
|
170
|
+
"M": () => pad2(mi(d, utc)),
|
|
171
|
+
"S": () => pad2(sc(d, utc)),
|
|
172
|
+
"p": () => h() >= 12 ? "PM" : "AM",
|
|
173
|
+
|
|
174
|
+
// Weekday
|
|
175
|
+
"A": () =>
|
|
176
|
+
d.toLocaleString("en-US", {
|
|
177
|
+
weekday: "long",
|
|
178
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
179
|
+
}),
|
|
180
|
+
"a": () =>
|
|
181
|
+
d.toLocaleString("en-US", {
|
|
182
|
+
weekday: "short",
|
|
183
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
184
|
+
}),
|
|
185
|
+
"w": () => dow().toString(),
|
|
186
|
+
"u": () => (dow() === 0 ? 7 : dow()).toString(),
|
|
187
|
+
|
|
188
|
+
// Month name
|
|
189
|
+
"b": () =>
|
|
190
|
+
d.toLocaleString("en-US", {
|
|
191
|
+
month: "short",
|
|
192
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
193
|
+
}),
|
|
194
|
+
"h": () =>
|
|
195
|
+
d.toLocaleString("en-US", {
|
|
196
|
+
month: "short",
|
|
197
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
198
|
+
}),
|
|
199
|
+
"B": () =>
|
|
200
|
+
d.toLocaleString("en-US", {
|
|
201
|
+
month: "long",
|
|
202
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
203
|
+
}),
|
|
204
|
+
|
|
205
|
+
// Week number
|
|
206
|
+
"U": () => pad2(weekNumber(d, utc, 0, false)),
|
|
207
|
+
"W": () => pad2(weekNumber(d, utc, 1, false)),
|
|
208
|
+
"V": () => pad2(weekNumber(d, utc, 1, true)),
|
|
209
|
+
"G": () => isoWeekYear(d, utc).toString(),
|
|
210
|
+
"g": () => pad2(isoWeekYear(d, utc) % 100),
|
|
211
|
+
|
|
212
|
+
// Composite specifiers
|
|
213
|
+
"c": () =>
|
|
214
|
+
d.toLocaleString("en-US", {
|
|
215
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
216
|
+
}),
|
|
217
|
+
"x": () =>
|
|
218
|
+
d.toLocaleDateString("en-US", {
|
|
219
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
220
|
+
}),
|
|
221
|
+
"X": () =>
|
|
222
|
+
d.toLocaleTimeString("en-US", {
|
|
223
|
+
...(utc ? { timeZone: "UTC" } : {}),
|
|
224
|
+
}),
|
|
225
|
+
"D": () =>
|
|
226
|
+
`${pad2(mo(d, utc) + 1)}/${pad2(da(d, utc))}/${pad2(yr(d, utc) % 100)}`,
|
|
227
|
+
"F": () =>
|
|
228
|
+
`${yr(d, utc).toString()}-${pad2(mo(d, utc) + 1)}-${pad2(da(d, utc))}`,
|
|
229
|
+
"R": () => `${pad2(h())}:${pad2(mi(d, utc))}`,
|
|
230
|
+
"T": () => `${pad2(h())}:${pad2(mi(d, utc))}:${pad2(sc(d, utc))}`,
|
|
231
|
+
"r": () =>
|
|
232
|
+
`${pad2(h12())}:${pad2(mi(d, utc))}:${pad2(sc(d, utc))} ${
|
|
233
|
+
h() >= 12 ? "PM" : "AM"
|
|
234
|
+
}`,
|
|
235
|
+
|
|
236
|
+
// Epoch
|
|
237
|
+
"s": () => Math.floor(d.getTime() / 1000).toString(),
|
|
238
|
+
|
|
239
|
+
// Whitespace
|
|
240
|
+
"n": () => "\n",
|
|
241
|
+
"t": () => "\t",
|
|
242
|
+
|
|
243
|
+
// Timezone
|
|
244
|
+
"Z": () => {
|
|
245
|
+
if (utc) return "UTC";
|
|
246
|
+
const match = d.toTimeString().match(/\((.*)\)/);
|
|
247
|
+
return match ? match[1] : "";
|
|
248
|
+
},
|
|
249
|
+
"z": () => {
|
|
250
|
+
if (utc) return "+0000";
|
|
251
|
+
const offset = -d.getTimezoneOffset();
|
|
252
|
+
const sign = offset >= 0 ? "+" : "-";
|
|
253
|
+
const abs = Math.abs(offset);
|
|
254
|
+
return `${sign}${pad2(Math.floor(abs / 60))}${pad2(abs % 60)}`;
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// Literal
|
|
258
|
+
"%": () => "%",
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function luaFormatTime(fmt: string, d: Date, utc: boolean): string {
|
|
263
|
+
const specs = buildSpecMap(d, utc);
|
|
264
|
+
|
|
265
|
+
let out = "";
|
|
266
|
+
let i = 0;
|
|
267
|
+
|
|
268
|
+
while (i < fmt.length) {
|
|
269
|
+
if (fmt[i] !== "%") {
|
|
270
|
+
out += fmt[i];
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
i++; // skip '%'
|
|
275
|
+
if (i >= fmt.length) {
|
|
276
|
+
throw new Error("invalid conversion specifier '%'");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ch = fmt[i];
|
|
280
|
+
const fn = specs[ch];
|
|
281
|
+
if (!fn) {
|
|
282
|
+
throw new Error(`invalid conversion specifier '%${ch}'`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
out += fn();
|
|
286
|
+
i++;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const osApi = new LuaTable({
|
|
293
|
+
time: new LuaBuiltinFunction((_sf, tbl?: LuaTable) => {
|
|
294
|
+
if (tbl) {
|
|
295
|
+
if (!tbl.has("year")) {
|
|
296
|
+
throw new Error("time(): year is required");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!tbl.has("month")) {
|
|
300
|
+
throw new Error("time(): month is required");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!tbl.has("day")) {
|
|
304
|
+
throw new Error("time(): day is required");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const year = tbl.get("year");
|
|
308
|
+
const month = tbl.get("month");
|
|
309
|
+
const day = tbl.get("day");
|
|
310
|
+
const hour = tbl.get("hour") ?? 12;
|
|
311
|
+
const min = tbl.get("min") ?? 0;
|
|
312
|
+
const sec = tbl.get("sec") ?? 0;
|
|
313
|
+
const date = new Date(year, month - 1, day, hour, min, sec);
|
|
314
|
+
|
|
315
|
+
return Math.floor(date.getTime() / 1000);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return Math.floor(Date.now() / 1000);
|
|
319
|
+
}),
|
|
320
|
+
|
|
321
|
+
// Returns the difference, from time `t1` to time `t2` in seconds
|
|
322
|
+
// In POSIX and some other systems, this value is exactly $t2-t1$.
|
|
323
|
+
difftime: new LuaBuiltinFunction((_sf, t2: number, t1: number): number => {
|
|
324
|
+
return t2 - t1;
|
|
325
|
+
}),
|
|
326
|
+
|
|
327
|
+
// Returns a string or a table containing date and time, formatted
|
|
328
|
+
// according to the given string format.
|
|
329
|
+
//
|
|
330
|
+
// If format starts with '!', the date is formatted in UTC.
|
|
331
|
+
//
|
|
332
|
+
// If format is "*t" (or "!*t"), returns a table with fields:
|
|
333
|
+
//
|
|
334
|
+
// - `year`,
|
|
335
|
+
// - `month` (1-12),
|
|
336
|
+
// - `day` (1-31),
|
|
337
|
+
// - `hour` (0-23),
|
|
338
|
+
// - `min` (0-59),
|
|
339
|
+
// - `sec` (0-61),
|
|
340
|
+
// - `wday` (1-7, Sunday is 1),
|
|
341
|
+
// - `yday` (1-366), and
|
|
342
|
+
// - `isdst` (boolean).
|
|
343
|
+
//
|
|
344
|
+
// Otherwise, format specifiers follow ISO C `strftime`.
|
|
345
|
+
//
|
|
346
|
+
// If format is absent, it defaults to `%c`.
|
|
347
|
+
date: new LuaBuiltinFunction(
|
|
348
|
+
(_sf, format?: string, timestamp?: number) => {
|
|
349
|
+
let fmt = format ?? "%c";
|
|
350
|
+
let utc = false;
|
|
351
|
+
|
|
352
|
+
if (fmt.startsWith("!")) {
|
|
353
|
+
utc = true;
|
|
354
|
+
fmt = fmt.slice(1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const d = timestamp !== undefined && timestamp !== null
|
|
358
|
+
? new Date(timestamp * 1000)
|
|
359
|
+
: new Date();
|
|
360
|
+
|
|
361
|
+
if (fmt === "*t") {
|
|
362
|
+
return dateTable(d, utc);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return luaFormatTime(fmt, d, utc);
|
|
366
|
+
},
|
|
367
|
+
),
|
|
368
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { parseExpressionString } from "../parse.ts";
|
|
2
|
+
import type { LuaExpression } from "../ast.ts";
|
|
3
|
+
import { evalExpression } from "../eval.ts";
|
|
4
|
+
import {
|
|
5
|
+
LuaBuiltinFunction,
|
|
6
|
+
LuaEnv,
|
|
7
|
+
LuaRuntimeError,
|
|
8
|
+
type LuaStackFrame,
|
|
9
|
+
LuaTable,
|
|
10
|
+
luaToString,
|
|
11
|
+
luaValueToJS,
|
|
12
|
+
singleResult,
|
|
13
|
+
} from "../runtime.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Helper function to create an augmented environment
|
|
21
|
+
*/
|
|
22
|
+
function createAugmentedEnv(
|
|
23
|
+
sf: LuaStackFrame,
|
|
24
|
+
envAugmentation?: LuaTable,
|
|
25
|
+
): LuaEnv {
|
|
26
|
+
const globalEnv = sf.threadLocal.get("_GLOBAL");
|
|
27
|
+
if (!globalEnv) {
|
|
28
|
+
throw new Error("_GLOBAL not defined");
|
|
29
|
+
}
|
|
30
|
+
const env = new LuaEnv(globalEnv);
|
|
31
|
+
if (envAugmentation) {
|
|
32
|
+
env.setLocal("_", envAugmentation);
|
|
33
|
+
for (const key of envAugmentation.keys()) {
|
|
34
|
+
env.setLocal(key, envAugmentation.rawGet(key));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return env;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Interpolates a string with lua expressions and returns the result.
|
|
42
|
+
*
|
|
43
|
+
* @param sf - The current space_lua state.
|
|
44
|
+
* @param template - The template string to interpolate.
|
|
45
|
+
* @param envAugmentation - An optional environment to augment the global environment with.
|
|
46
|
+
* @returns The interpolated string.
|
|
47
|
+
*/
|
|
48
|
+
export async function interpolateLuaString(
|
|
49
|
+
sf: LuaStackFrame,
|
|
50
|
+
template: string,
|
|
51
|
+
envAugmentation?: LuaTable,
|
|
52
|
+
): Promise<string> {
|
|
53
|
+
let result = "";
|
|
54
|
+
let currentIndex = 0;
|
|
55
|
+
|
|
56
|
+
while (true) {
|
|
57
|
+
const startIndex = template.indexOf("${", currentIndex);
|
|
58
|
+
if (startIndex === -1) {
|
|
59
|
+
result += template.slice(currentIndex);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
result += template.slice(currentIndex, startIndex);
|
|
64
|
+
|
|
65
|
+
// Find matching closing brace by counting nesting
|
|
66
|
+
let nestLevel = 1;
|
|
67
|
+
let endIndex = startIndex + 2;
|
|
68
|
+
while (nestLevel > 0 && endIndex < template.length) {
|
|
69
|
+
if (template[endIndex] === "{") {
|
|
70
|
+
nestLevel++;
|
|
71
|
+
} else if (template[endIndex] === "}") {
|
|
72
|
+
nestLevel--;
|
|
73
|
+
}
|
|
74
|
+
if (nestLevel > 0) {
|
|
75
|
+
endIndex++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (nestLevel > 0) {
|
|
80
|
+
throw new LuaRuntimeError("Unclosed interpolation expression", sf);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const expr = template.slice(startIndex + 2, endIndex);
|
|
84
|
+
try {
|
|
85
|
+
const parsedExpr = parseExpressionString(expr);
|
|
86
|
+
const env = createAugmentedEnv(sf, envAugmentation);
|
|
87
|
+
// Do `luaToString` before `luaValueToJS` to preserve tagged float
|
|
88
|
+
// formatting.
|
|
89
|
+
const luaResult = singleResult(await evalExpression(parsedExpr, env, sf));
|
|
90
|
+
result += await luaToString(luaResult);
|
|
91
|
+
} catch (e: any) {
|
|
92
|
+
throw new LuaRuntimeError(
|
|
93
|
+
`Error evaluating "${expr}": ${e.message}`,
|
|
94
|
+
sf,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
currentIndex = endIndex + 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const spaceluaApi = new LuaTable({
|
|
105
|
+
/**
|
|
106
|
+
* Parses a lua expression and returns the parsed expression.
|
|
107
|
+
*
|
|
108
|
+
* @param sf - The current space_lua state.
|
|
109
|
+
* @param luaExpression - The lua expression to parse.
|
|
110
|
+
* @returns The parsed expression.
|
|
111
|
+
*/
|
|
112
|
+
parseExpression: new LuaBuiltinFunction(
|
|
113
|
+
(_sf, luaExpression: string) => {
|
|
114
|
+
return parseExpressionString(luaExpression);
|
|
115
|
+
},
|
|
116
|
+
),
|
|
117
|
+
/**
|
|
118
|
+
* Evaluates a parsed lua expression and returns the result.
|
|
119
|
+
*
|
|
120
|
+
* @param sf - The current space_lua state.
|
|
121
|
+
* @param parsedExpr - The parsed lua expression to evaluate.
|
|
122
|
+
* @param envAugmentation - An optional environment to augment the global environment with.
|
|
123
|
+
* @returns The result of the evaluated expression.
|
|
124
|
+
*/
|
|
125
|
+
evalExpression: new LuaBuiltinFunction(
|
|
126
|
+
async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => {
|
|
127
|
+
const env = createAugmentedEnv(sf, envAugmentation);
|
|
128
|
+
return luaValueToJS(await evalExpression(parsedExpr, env, sf), sf);
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
/**
|
|
132
|
+
* Interpolates a string with lua expressions and returns the result.
|
|
133
|
+
*/
|
|
134
|
+
interpolate: new LuaBuiltinFunction(
|
|
135
|
+
(sf, template: string, envAugmentation?: LuaTable) => {
|
|
136
|
+
return interpolateLuaString(sf, template, envAugmentation);
|
|
137
|
+
},
|
|
138
|
+
),
|
|
139
|
+
/**
|
|
140
|
+
* Returns your SilverBullet instance's base URL, or `undefined` when run on the server
|
|
141
|
+
*/
|
|
142
|
+
baseUrl: new LuaBuiltinFunction(
|
|
143
|
+
() => {
|
|
144
|
+
// Deal with Deno
|
|
145
|
+
if (typeof location === "undefined") {
|
|
146
|
+
return null;
|
|
147
|
+
} else {
|
|
148
|
+
//NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
|
|
149
|
+
return document.baseURI.replace(/\/*$/, "");
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
),
|
|
153
|
+
});
|