Complete redo of content indexing and querying (#517)

Complete redo of data store
Introduces live queries and live templates
This commit is contained in:
Zef Hemel
2023-10-03 14:16:33 +02:00
committed by GitHub
parent 7af98e7c7b
commit 0313565610
200 changed files with 4675 additions and 4363 deletions
+134
View File
@@ -0,0 +1,134 @@
import { LRLanguage } from "@codemirror/language";
import {
cLanguage,
cppLanguage,
csharpLanguage,
dartLanguage,
htmlLanguage,
javaLanguage,
javascriptLanguage,
jsonLanguage,
kotlinLanguage,
Language,
objectiveCLanguage,
objectiveCppLanguage,
postgresqlLanguage,
protobufLanguage,
pythonLanguage,
rustLanguage,
scalaLanguage,
shellLanguage,
sqlLanguage,
StreamLanguage,
tomlLanguage,
typescriptLanguage,
xmlLanguage,
yamlLanguage,
} from "./deps.ts";
import { highlightingDirectiveParser } from "./markdown_parser/parser.ts";
const languageCache = new Map<string, Language>();
export function languageFor(name: string): Language | null {
if (languageCache.has(name)) {
return languageCache.get(name)!;
}
const language = languageLookup(name);
if (!language) {
return null;
}
languageCache.set(name, language);
return language;
}
function languageLookup(name: string): Language | null {
switch (name) {
case "meta":
case "yaml":
case "template":
case "embed":
case "data":
return StreamLanguage.define(yamlLanguage);
case "javascript":
case "js":
return javascriptLanguage;
case "typescript":
case "ts":
return typescriptLanguage;
case "sql":
return StreamLanguage.define(sqlLanguage);
case "postgresql":
case "pgsql":
case "postgres":
return StreamLanguage.define(postgresqlLanguage);
case "rust":
case "rs":
return StreamLanguage.define(rustLanguage);
case "css":
return StreamLanguage.define(sqlLanguage);
case "html":
return htmlLanguage;
case "python":
case "py":
return StreamLanguage.define(pythonLanguage);
case "protobuf":
case "proto":
return StreamLanguage.define(protobufLanguage);
case "shell":
case "sh":
case "bash":
case "zsh":
case "fish":
return StreamLanguage.define(shellLanguage);
case "swift":
return StreamLanguage.define(rustLanguage);
case "toml":
return StreamLanguage.define(tomlLanguage);
case "json":
return StreamLanguage.define(jsonLanguage);
case "xml":
return StreamLanguage.define(xmlLanguage);
case "c":
return StreamLanguage.define(cLanguage);
case "cpp":
case "c++":
case "cxx":
return StreamLanguage.define(cppLanguage);
case "java":
return StreamLanguage.define(javaLanguage);
case "csharp":
case "cs":
case "c#":
return StreamLanguage.define(csharpLanguage);
case "scala":
return StreamLanguage.define(scalaLanguage);
case "kotlin":
return StreamLanguage.define(kotlinLanguage);
case "objc":
case "objective-c":
case "objectivec":
return StreamLanguage.define(objectiveCLanguage);
case "objcpp":
case "objective-cpp":
case "objectivecpp":
case "objective-c++":
case "objectivec++":
return StreamLanguage.define(objectiveCppLanguage);
case "dart":
return StreamLanguage.define(dartLanguage);
case "query":
return LRLanguage.define({
name: "query",
parser: highlightingDirectiveParser,
});
default:
if (name.startsWith("#")) {
return StreamLanguage.define(yamlLanguage);
}
}
return null;
}
+20
View File
@@ -0,0 +1,20 @@
import { sleep } from "$sb/lib/async.ts";
import { assertEquals } from "../test_deps.ts";
import { LimitedMap } from "./limited_map.ts";
Deno.test("limited map", async () => {
const mp = new LimitedMap<string>(3);
mp.set("a", "a");
mp.set("b", "b");
mp.set("c", "c");
await sleep(2);
assertEquals(mp.get("a"), "a");
await sleep(2);
assertEquals(mp.get("b"), "b");
await sleep(2);
assertEquals(mp.get("c"), "c");
// Drops the first key
mp.set("d", "d");
await sleep(2);
assertEquals(mp.get("a"), undefined);
});
+50
View File
@@ -0,0 +1,50 @@
type LimitedMapRecord<V> = Record<string, { value: V; la: number }>;
export class LimitedMap<V> {
constructor(private maxSize: number, private map: LimitedMapRecord<V> = {}) {
}
set(key: string, value: V) {
if (Object.keys(this.map).length >= this.maxSize) {
// Remove the oldest key before adding a new one
const oldestKey = this.getOldestKey();
delete this.map[oldestKey!];
}
this.map[key] = { value, la: Date.now() };
}
get(key: string): V | undefined {
const entry = this.map[key];
if (entry) {
// Update the last accessed timestamp
entry.la = Date.now();
return entry.value;
}
return undefined;
}
remove(key: string) {
delete this.map[key];
}
toJSON() {
return this.map;
}
private getOldestKey(): string | undefined {
let oldestKey: string | undefined;
let oldestTimestamp: number | undefined;
for (const key in this.map) {
if (Object.prototype.hasOwnProperty.call(this.map, key)) {
const entry = this.map[key];
if (!oldestTimestamp || entry.la < oldestTimestamp) {
oldestKey = key;
oldestTimestamp = entry.la;
}
}
}
return oldestKey;
}
}
+1 -1
View File
@@ -22,5 +22,5 @@ export const AttributeNameTag = Tag.define();
export const AttributeValueTag = Tag.define();
export const TaskTag = Tag.define();
export const TaskMarkerTag = Tag.define();
export const TaskMarkTag = Tag.define();
export const TaskStateTag = Tag.define();
+119
View File
@@ -0,0 +1,119 @@
@top Program { Expression }
@precedence {
mulop @left
addop @left
binop @left
and @left
or @left
}
@skip {
space
}
commaSep<content> { content ("," content)* }
kw<term> { @specialize[@name={term}]<Identifier, term> }
Query {
TagIdentifier ( WhereClause | LimitClause | OrderClause | SelectClause | RenderClause )*
}
WhereClause { kw<"where"> Expression }
LimitClause { kw<"limit"> Expression }
OrderClause { Order commaSep<OrderBy> }
OrderBy { Expression OrderDirection? }
SelectClause { kw<"select"> commaSep<Select> }
RenderClause { kw<"render"> PageRef }
Select { Identifier | Expression kw<"as"> Identifier }
OrderDirection {
OrderKW
}
Value { Number | String | Bool | Regex | kw<"null"> | List }
Attribute {
LVal "." Identifier
}
Call {
Identifier "(" commaSep<Expression> ")" | Identifier "(" ")"
}
LVal {
Identifier
| Attribute
}
ParenthesizedExpression { "(" Expression ")" }
LogicalExpression {
Expression !and kw<"and"> Expression
| Expression !or kw<"or"> Expression
}
Expression {
Value
| LVal
| ParenthesizedExpression
| LogicalExpression
| BinExpression
| Call
}
BinExpression {
Expression !binop "<" Expression
| Expression !binop "<=" Expression
| Expression !binop "=" Expression
| Expression !binop "!=" Expression
| Expression !binop ">=" Expression
| Expression !binop ">" Expression
| Expression !binop "=~" Expression
| Expression !binop "!=~" Expression
| Expression !binop InKW Expression
| Expression !mulop "*" Expression
| Expression !mulop "/" Expression
| Expression !mulop "%" Expression
| Expression !addop "+" Expression
| Expression !addop "-" Expression
}
List { "[" commaSep<Expression> "]" }
Bool {
BooleanKW
}
@tokens {
space { std.whitespace+ }
TagIdentifier { @asciiLetter (@asciiLetter | @digit | "-" | "_" | "/" )* }
Identifier { @asciiLetter (@asciiLetter | @digit | "-" | "_")* }
String {
("\"" | "“" | "”") ![\"”“]* ("\"" | "“" | "”")
}
PageRef {
"[" "[" ![\]]* "]" "]"
}
Order { "order by" }
Regex { "/" ( ![/\\\n\r] | "\\" _ )* "/"? }
Number { std.digit+ }
BooleanKW { "true" | "false" }
InKW { "in" }
OrderKW { "asc" | "desc" }
@precedence { Order, BooleanKW, InKW, OrderKW, Identifier, Number }
}
+3 -3
View File
@@ -23,9 +23,9 @@ class MultiStatusTaskParser implements LeafBlockParser {
leaf,
cx.elt("Task", leaf.start, leaf.start + leaf.content.length, [
cx.elt("TaskState", leaf.start, leaf.start + 2 + this.status.length, [
cx.elt("TaskMarker", leaf.start, leaf.start + 1),
cx.elt("TaskMark", leaf.start, leaf.start + 1),
cx.elt(
"TaskMarker",
"TaskMark",
leaf.start + 1 + this.status.length,
leaf.start + 2 + this.status.length,
),
@@ -43,7 +43,7 @@ class MultiStatusTaskParser implements LeafBlockParser {
export const TaskList: MarkdownConfig = {
defineNodes: [
{ name: "Task", block: true, style: t.list },
{ name: "TaskMarker", style: t.atom },
{ name: "TaskMark", style: t.atom },
{ name: "TaskState", style: TaskStateTag },
],
parseBlock: [{
@@ -0,0 +1,18 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
const spec_Identifier = {__proto__:null,null:20, and:32, or:34}
export const parser = LRParser.deserialize({
version: 14,
states: "%jOVQPOOOOQO'#Cb'#CbOVQPO'#CgOOQO'#C_'#C_OqQQO'#C^O#UQQO'#ChOOQO'#Ch'#ChOVQPO'#CjQ$lQQOOOOQO'#C^'#C^O%pQQO'#CvO%}QPO,59RO&SQPO,59TO&XQPO,59[O&`QQO,59UOVQPO,59YOVQPO,59YOVQPO,59YOVQPO,59VOVQPO,59VOVQPO'#CqO&gQPO,59bOOQO1G.m1G.mOOQO1G.o1G.oO&rQPO1G.vOOQO1G.v1G.vOOQO1G.p1G.pO(RQQO1G.tOOQO1G.t1G.tO(]QQO1G.tO)tQQO1G.qO){QQO1G.qO*SQQO,59]OOQO-E6o-E6oOOQO7+$b7+$b",
stateData: "*g~OhOS~OSROTROVPOWROXTOYROiQOnVO~Om[O`QXaQXcQXfQXpQXqQXrQXsQXtQXuQXvQXwQXxQXyQXzQX{QX|QXkQXlQXoQX~On]O`[Xa[Xc[Xf[Xm[Xp[Xq[Xr[Xs[Xt[Xu[Xv[Xw[Xx[Xy[Xz[X{[X|[Xk[Xl[Xo[X~O`bOacOc_Op_Oq_Or_Os_Ot_Ou_Ov_Ow_Ox`Oy`Oz`O{aO|aO~OkdOljXojX~P$lOlfO~OXgO~OoiO~PVOojO~P$lOkdOljaoja~OorO~Ox`Oy`Oz`O`biabicbifbipbiqbirbisbitbiubivbiwbikbilbiobi~O{aO|aO~P&wO{bi|bi~P&wOc_Op_Oq_Or_Os_Ot_Ou_Ov_Ow_Ox`Oy`Oz`O{aO|aOa_if_ik_il_io_i~O`_i~P(gO`bO~P(gOkealeaoea~P$lOVcXSX~",
goto: "#hkPPl!YPP!fPPPP!f!r#O!Y!YPP!YP!Y#[PPPP#bQWOSYQ]Q^VQk_Ql`QmaQnbQocRpdeXOQV]_`abcdeROQV]_`abcdeSOQV]_`abcdeUOQV]_`abcdQeYRqeQZQRh]",
nodeNames: "⚠ Program Expression Value Number String Bool BooleanKW Regex Identifier null List LVal Attribute ParenthesizedExpression LogicalExpression and or BinExpression InKW Call",
maxTerm: 44,
skippedNodes: [0],
repeatNodeCount: 1,
tokenData: "/j~RuX^#fpq#fqr$Zrs$nuv%fxy%kyz%pz{%u{|%z|}&P}!O&U!O!P&Z!P!Q&`!Q![)Q!^!_)Y!_!`)g!`!a)t!c!}*R!}#O*g#P#Q*l#T#Y*R#Y#Z*q#Z#]*R#]#^-b#^#h*R#h#i.d#i#o*R#y#z#f$f$g#f#BY#BZ#f$IS$I_#f$Ip$Iq$n$Iq$Ir$n$I|$JO#f$JT$JU#f$KV$KW#f&FU&FV#f~#kYh~X^#fpq#f#y#z#f$f$g#f#BY#BZ#f$IS$I_#f$I|$JO#f$JT$JU#f$KV$KW#f&FU&FV#f~$^P!_!`$a~$fPs~#r#s$i~$nOw~~$qWOr$nrs%Zs$Ip$n$Ip$Iq%Z$Iq$Ir%Z$Ir;'S$n;'S;=`%`<%lO$n~%`OT~~%cP;=`<%l$n~%kOz~~%pOn~~%uOo~~%zOx~~&PO{~~&UOk~~&ZO|~~&`Om~R&gXyQWPOY'SZ]'S^!P'S!P!Q't!Q#O'S#O#P'y#P;'S'S;'S;=`(z<%lO'SP'XXWPOY'SZ]'S^!P'S!P!Q't!Q#O'S#O#P'y#P;'S'S;'S;=`(z<%lO'SP'yOWPP'|RO;'S'S;'S;=`(V;=`O'SP([YWPOY'SZ]'S^!P'S!P!Q't!Q#O'S#O#P'y#P;'S'S;'S;=`(z;=`<%l'S<%lO'SP(}P;=`<%l'S~)VPS~!Q![)Q~)_Pp~!_!`)b~)gOq~~)lPr~#r#s)o~)tOv~~)yPu~!_!`)|~*ROt~~*WTX~}!O*R!Q![*R!c!}*R#R#S*R#T#o*R~*lOi~~*qOl~~*vUX~}!O*R!Q![*R!c!}*R#R#S*R#T#U+Y#U#o*R~+_VX~}!O*R!Q![*R!c!}*R#R#S*R#T#`*R#`#a+t#a#o*R~+yVX~}!O*R!Q![*R!c!}*R#R#S*R#T#g*R#g#h,`#h#o*R~,eVX~}!O*R!Q![*R!c!}*R#R#S*R#T#X*R#X#Y,z#Y#o*R~-RTV~X~}!O*R!Q![*R!c!}*R#R#S*R#T#o*R~-gVX~}!O*R!Q![*R!c!}*R#R#S*R#T#b*R#b#c-|#c#o*R~.TTc~X~}!O*R!Q![*R!c!}*R#R#S*R#T#o*R~.iVX~}!O*R!Q![*R!c!}*R#R#S*R#T#f*R#f#g/O#g#o*R~/TVX~}!O*R!Q![*R!c!}*R#R#S*R#T#i*R#i#j,`#j#o*R",
tokenizers: [0, 1],
topRules: {"Program":[0,1]},
specialized: [{term: 9, get: value => spec_Identifier[value] || -1}],
tokenPrec: 431
})
@@ -0,0 +1,19 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Program = 1,
Expression = 2,
Value = 3,
Number = 4,
String = 5,
Bool = 6,
BooleanKW = 7,
Regex = 8,
Identifier = 9,
List = 11,
LVal = 12,
Attribute = 13,
ParenthesizedExpression = 14,
LogicalExpression = 15,
BinExpression = 18,
InKW = 19,
Call = 20
+10 -8
View File
@@ -1,16 +1,18 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
const spec_Identifier = {__proto__:null,where:12, null:28, and:40, or:42, limit:52, select:66, as:70, render:74}
export const parser = LRParser.deserialize({
version: 14,
states: "&`OVQPOOOmQQO'#C^QOQPOOOtQPO'#C`OyQPO'#ClO!OQPO'#CnO!TQPO'#CqO!YQPO'#CsOOQO'#Cv'#CvO!bQQO,58xO!iQQO'#CcO#WQQO'#CbOOQO,58z,58zOOQO,59W,59WO#oQQO,59YO$ZQQO'#D`OOQO,59],59]OOQO,59_,59_OOQO-E6t-E6tO$rQQO,58}OtQPO'#CxO%ZQQO,58|OOQO'#Cp'#CpOOQO1G.t1G.tO%rQPO'#CyO%wQQO,59zOOQO'#Cg'#CgO$rQQO'#CjOOQO'#Cd'#CdOOQO1G.i1G.iOOQO,59d,59dOOQO-E6v-E6vOOQO,59e,59eOOQO-E6w-E6wO&`QPO'#DRO&hQPO,59UO$rQQO'#CwO&mQPO,59mOOQO1G.p1G.pOOQO,59c,59cOOQO-E6u-E6u",
stateData: "&u~OpOS~ORPO~OTROaSOcTOfUOhVO~OnQX~P[ORYO~OX]O~OR^O~OR_O~OYaOiaO~OnQa~P[OqcOxcOycOzcO{cO|cO}cO!OcO!PcO~O_dOTUXaUXcUXfUXhUXnUX~O!QfO!RfOTbaabacbafbahbanba~OvhOT!SXa!SXc!SXf!SXh!SXn!SX~OXlOYlO[lO]lOrjOsjOtkO~O_dOTUaaUacUafUahUanUa~ORpO~OvhOT!Saa!Sac!Saf!Sah!San!Sa~OvtOwuX~OwvO~OvtOwua~O",
goto: "#g!TPP!UP!XP!]!`!fPP!oPP!oP!XP!XP!t!XP!XPP!w!}#T#ZPPPPPPP#aPPPPPPPPPPPP#dRQOTWPXR[RQZRRndQmcQrkRwtVlcktRg^QXPRbXQurRxuQeZRoeQi_RqiRskR`U",
nodeNames: "⚠ Program Query Name WhereClause Where LogicalExpr FilterExpr Value Number String Bool Regex Null List And LimitClause Limit OrderClause Order OrderDirection SelectClause Select RenderClause Render PageRef",
maxTerm: 50,
states: ")lOVQPOOOmQQO'#C^QOQPOOOtQQO'#CwOOQO'#DT'#DTO!`QQO,58xOtQQO'#C`OtQQO'#CuO!gQQO'#C|O#RQPO'#DQOOQO'#Cg'#CgOtQQO'#CkOOQO'#Cd'#CdO#WQSO'#CcO&_QSO'#ClOOQO'#Cl'#ClOtQQO'#CnO(VQSO'#CyOOQO'#Cc'#CcO(sQQO'#DpOOQO,59c,59cOOQO-E7R-E7RO)[QSO,58zO)rQSO,59aO*YQSO'#ClO*sQSO'#DOO*zQQO'#DqOOQO,59h,59hOOQO,59l,59lO+cQSO'#D]O+pQPO,59VO+uQQO,59XO+zQQO,59`O,RQSO,59YOtQQO,59^OtQQO,59^OtQQO,59^OOQO'#Cz'#CzOOQO,59e,59eOtQQO,59ZOtQQO,59ZOtQQO'#DVO,YQQO,5:[O,qQQO,59jO!gQQO'#DWO,vQQO,5:]OtQQO'#DUO-_QPO,59wOOQO1G.q1G.qOOQO1G.s1G.sO-jQPO1G.zOOQO1G.z1G.zOOQO1G.t1G.tO/`QSO1G.xOOQO1G.x1G.xO/jQSO1G.xO1hQSO1G.uO1oQSO1G.uOOQO,59q,59qOOQO-E7T-E7TOOQO1G/U1G/UOOQO,59r,59rOOQO-E7U-E7UO1vQSO,59pOOQO-E7S-E7SOOQO7+$f7+$f",
stateData: "2]~O}OS~ORPO~OUUOjVOlROqWOuXO~O{QX~P[OT^OX[OY[O[YO][O^[O!OZO!T`O~O{Qa~P[OThOX[OY[O[YO][O^[O!OZO!T`O~OvlO~O!SoOUVXdVXeVXgVXjVXlVXoVXqVXuVX{VX!QVX!VVX!WVX!XVX!YVX!ZVX![VX!]VX!^VX!_VX!`VX!aVX!bVX!cVXsVX!RVX!UVX~O!TpOd`Xe`Xg`X!S`X!V`X!W`X!X`X!Y`X!Z`X![`X!]`X!^`X!_`X!``X!a`X!b`X!c`Xs`X~OU`Xj`Xl`Xo`Xq`Xu`X{`X!Q`X!R`X!U`X~P%QOdwOexOgrO!VrO!WrO!XrO!YrO!ZrO![rO!]rO!^rO!_sO!`sO!asO!btO!ctO~OouOUmXjmXlmXqmXumX{mX!QmX~P'RO!QyOU!dXj!dXl!dXq!dXu!dX{!dX~OUSajSalSaqSauSa{Sa~P'ROUiajialiaqiauia{ia~P'ROUrXjrXlrXqrXurX{rX!QrX~P%QOs{O~P'RO!Q|OU!eXj!eXl!eXq!eXu!eX{!eX~O!Q!OO!R!PX!U!PX~P'RO!R!QO~OT!RO~O!U!TO~PtO!U!UO~P'RO!QyOU!daj!dal!daq!dau!da{!da~OT!^O~O!Q|OU!eaj!eal!eaq!eau!ea{!ea~O!Q!OO!R!Pa!U!Pa~O!U!cO~O!_sO!`sO!asOUfidfiefigfijfilfiofiqfiufi{fi!Qfi!Vfi!Wfi!Xfi!Yfi!Zfi![fi!]fi!^fisfi!Rfi!Ufi~O!btO!ctO~P-oO!bfi!cfi~P-oOgrO!VrO!WrO!XrO!YrO!ZrO![rO!]rO!^rO!_sO!`sO!asO!btO!ctOUciecijcilciociqciuci{ci!Qcisci!Rci!Uci~Odci~P/tOdwO~P/tO!Qxa!Rxa!Uxa~P'ROl[goTXo~",
goto: "&S!fPP!gP!jPP!n#gPP#xPPP#x$Z$l#g#gPP#gP#g!jP!jP$}%TP!jP%WP!jPP%^%d%j%pPPPP%vPPPPPPPPPPPPPPPPPP%|&PRQOTSPTSaRyQfUQgVSiW|SmZpQq`Q!VrQ!WsQ!XtQ!YwQ!ZxR!a!OobRUVWZ`prstwxy|!Oo[RUVWZ`prstwxy|!Oo]RUVWZ`prstwxy|!Oo_RUVWZ`prstwxy|!OQcRR![yRvaQjWR!_|QTPReTQ!PmR!b!PQzcR!]zQ}jR!`}QnZR!SpRdRRkW",
nodeNames: "⚠ Program Query TagIdentifier WhereClause Identifier where Expression Value Number String Bool BooleanKW Regex null List LVal Attribute ParenthesizedExpression LogicalExpression and or BinExpression InKW Call LimitClause limit OrderClause Order OrderBy OrderDirection OrderKW SelectClause select Select as RenderClause render PageRef",
maxTerm: 67,
skippedNodes: [0],
repeatNodeCount: 4,
tokenData: "C`~R|X^#{pq#{qr$prs%T|}%{}!O&Q!P!Q&c!Q![(a!^!_(i!_!`(v!`!a)T!c!}&Q!}#O)b#P#Q*_#R#S&Q#T#U*d#U#W&Q#W#X,y#X#Y&Q#Y#Z.u#Z#]&Q#]#^1V#^#`&Q#`#a2R#a#b&Q#b#c4f#c#d6b#d#f&Q#f#g9X#g#h<T#h#i?P#i#k&Q#k#l@{#l#o&Q#y#z#{$f$g#{#BY#BZ#{$IS$I_#{$Ip$Iq%T$Iq$Ir%T$I|$JO#{$JT$JU#{$KV$KW#{&FU&FV#{~$QYp~X^#{pq#{#y#z#{$f$g#{#BY#BZ#{$IS$I_#{$I|$JO#{$JT$JU#{$KV$KW#{&FU&FV#{~$sP!_!`$v~${Pz~#r#s%O~%TO!O~~%WWOr%Trs%ps$Ip%T$Ip$Iq%p$Iq$Ir%p$Ir;'S%T;'S;=`%u<%lO%T~%uOY~~%xP;=`<%l%T~&QOv~P&VSRP}!O&Q!c!}&Q#R#S&Q#T#o&Q~&hX[~OY&cZ]&c^!P&c!P!Q'T!Q#O&c#O#P'Y#P;'S&c;'S;=`(Z<%lO&c~'YO[~~']RO;'S&c;'S;=`'f;=`O&c~'kY[~OY&cZ]&c^!P&c!P!Q'T!Q#O&c#O#P'Y#P;'S&c;'S;=`(Z;=`<%l&c<%lO&c~(^P;=`<%l&c~(fPX~!Q![(a~(nPq~!_!`(q~(vOx~~({Py~#r#s)O~)TO}~~)YP|~!_!`)]~)bO{~R)gPtQ!}#O)jP)mTO#P)j#P#Q)|#Q;'S)j;'S;=`*X<%lO)jP*PP#P#Q*SP*XOiPP*[P;=`<%l)j~*dOw~R*iWRP}!O&Q!c!}&Q#R#S&Q#T#b&Q#b#c+R#c#g&Q#g#h+}#h#o&QR+WURP}!O&Q!c!}&Q#R#S&Q#T#W&Q#W#X+j#X#o&QR+qS_QRP}!O&Q!c!}&Q#R#S&Q#T#o&QR,SURP}!O&Q!c!}&Q#R#S&Q#T#V&Q#V#W,f#W#o&QR,mS!RQRP}!O&Q!c!}&Q#R#S&Q#T#o&QR-OURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y-b#Y#o&QR-gURP}!O&Q!c!}&Q#R#S&Q#T#g&Q#g#h-y#h#o&QR.OURP}!O&Q!c!}&Q#R#S&Q#T#V&Q#V#W.b#W#o&QR.iS!QQRP}!O&Q!c!}&Q#R#S&Q#T#o&QR.zTRP}!O&Q!c!}&Q#R#S&Q#T#U/Z#U#o&QR/`URP}!O&Q!c!}&Q#R#S&Q#T#`&Q#`#a/r#a#o&QR/wURP}!O&Q!c!}&Q#R#S&Q#T#g&Q#g#h0Z#h#o&QR0`URP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y0r#Y#o&QR0ySsQRP}!O&Q!c!}&Q#R#S&Q#T#o&QR1[URP}!O&Q!c!}&Q#R#S&Q#T#b&Q#b#c1n#c#o&QR1uS!PQRP}!O&Q!c!}&Q#R#S&Q#T#o&QR2WURP}!O&Q!c!}&Q#R#S&Q#T#]&Q#]#^2j#^#o&QR2oURP}!O&Q!c!}&Q#R#S&Q#T#a&Q#a#b3R#b#o&QR3WURP}!O&Q!c!}&Q#R#S&Q#T#]&Q#]#^3j#^#o&QR3oURP}!O&Q!c!}&Q#R#S&Q#T#h&Q#h#i4R#i#o&QR4YSaQRP}!O&Q!c!}&Q#R#S&Q#T#o&QR4kURP}!O&Q!c!}&Q#R#S&Q#T#i&Q#i#j4}#j#o&QR5SURP}!O&Q!c!}&Q#R#S&Q#T#`&Q#`#a5f#a#o&QR5kURP}!O&Q!c!}&Q#R#S&Q#T#`&Q#`#a5}#a#o&QR6USRP]Q}!O&Q!c!}&Q#R#S&Q#T#o&QR6gURP}!O&Q!c!}&Q#R#S&Q#T#f&Q#f#g6y#g#o&QR7OURP}!O&Q!c!}&Q#R#S&Q#T#W&Q#W#X7b#X#o&QR7gURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y7y#Y#o&QR8OURP}!O&Q!c!}&Q#R#S&Q#T#f&Q#f#g8b#g#o&QR8gTRPpq8v}!O&Q!c!}&Q#R#S&Q#T#o&QQ8yP#U#V8|Q9PP#m#n9SQ9XOcQR9^URP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y9p#Y#o&QR9uURP}!O&Q!c!}&Q#R#S&Q#T#b&Q#b#c:X#c#o&QR:^URP}!O&Q!c!}&Q#R#S&Q#T#W&Q#W#X:p#X#o&QR:uURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y;X#Y#o&QR;^URP}!O&Q!c!}&Q#R#S&Q#T#f&Q#f#g;p#g#o&QR;wSRPhQ}!O&Q!c!}&Q#R#S&Q#T#o&QR<YURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y<l#Y#o&QR<qURP}!O&Q!c!}&Q#R#S&Q#T#`&Q#`#a=T#a#o&QR=YURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y=l#Y#o&QR=qURP}!O&Q!c!}&Q#R#S&Q#T#V&Q#V#W>T#W#o&QR>YURP}!O&Q!c!}&Q#R#S&Q#T#h&Q#h#i>l#i#o&QR>sSRPfQ}!O&Q!c!}&Q#R#S&Q#T#o&QR?UURP}!O&Q!c!}&Q#R#S&Q#T#f&Q#f#g?h#g#o&QR?mURP}!O&Q!c!}&Q#R#S&Q#T#i&Q#i#j@P#j#o&QR@UURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#Y@h#Y#o&QR@oSrQRP}!O&Q!c!}&Q#R#S&Q#T#o&QRAQURP}!O&Q!c!}&Q#R#S&Q#T#[&Q#[#]Ad#]#o&QRAiURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#YA{#Y#o&QRBQURP}!O&Q!c!}&Q#R#S&Q#T#f&Q#f#gBd#g#o&QRBiURP}!O&Q!c!}&Q#R#S&Q#T#X&Q#X#YB{#Y#o&QRCSSRPTQ}!O&Q!c!}&Q#R#S&Q#T#o&Q",
tokenizers: [0, 1],
tokenData: "8k~RzX^#upq#uqr$jrs$}uv%uxy%zyz&Pz{&U{|&Z|}&`}!O&e!O!P&j!P!Q&o!Q![)a!^!_)i!_!`)v!`!a*T!c!}*b!}#O+d#P#Q,a#T#U,f#U#W*b#W#X.c#X#Y*b#Y#Z/S#Z#]*b#]#^2^#^#c*b#c#d3j#d#h*b#h#i7Z#i#o*b#y#z#u$f$g#u#BY#BZ#u$IS$I_#u$Ip$Iq$}$Iq$Ir$}$I|$JO#u$JT$JU#u$KV$KW#u&FU&FV#u~#zY}~X^#upq#u#y#z#u$f$g#u#BY#BZ#u$IS$I_#u$I|$JO#u$JT$JU#u$KV$KW#u&FU&FV#u~$mP!_!`$p~$uP!Y~#r#s$x~$}O!^~~%QWOr$}rs%js$Ip$}$Ip$Iq%j$Iq$Ir%j$Ir;'S$};'S;=`%o<%lO$}~%oOY~~%rP;=`<%l$}~%zO!a~~&PO!T~~&UO!U~~&ZO!_~~&`O!b~~&eO!Q~~&jO!c~~&oO!S~U&vX!`S]QOY'cZ]'c^!P'c!P!Q(T!Q#O'c#O#P(Y#P;'S'c;'S;=`)Z<%lO'cQ'hX]QOY'cZ]'c^!P'c!P!Q(T!Q#O'c#O#P(Y#P;'S'c;'S;=`)Z<%lO'cQ(YO]QQ(]RO;'S'c;'S;=`(f;=`O'cQ(kY]QOY'cZ]'c^!P'c!P!Q(T!Q#O'c#O#P(Y#P;'S'c;'S;=`)Z;=`<%l'c<%lO'cQ)^P;=`<%l'c~)fPX~!Q![)a~)nP!V~!_!`)q~)vO!W~~){P!X~#r#s*O~*TO!]~~*YP![~!_!`*]~*bO!Z~V*iURPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#o*bP+QURP}!O*{!P!Q*{!Q![*{!c!}*{#R#S*{#T#o*{R+iP!OQ!}#O+lP+oTO#P+l#P#Q,O#Q;'S+l;'S;=`,Z<%lO+lP,RP#P#Q,UP,ZOvPP,^P;=`<%l+l~,fO!R~V,mWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#g*b#g#h-V#h#o*bV-^WRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#V*b#V#W-v#W#o*bV.PURPoSTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#o*bV.jWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#X*b#X#Y,f#Y#o*bV/ZVRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#U/p#U#o*bV/wWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#`*b#`#a0a#a#o*bV0hWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#g*b#g#h1Q#h#o*bV1XWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#X*b#X#Y1q#Y#o*bV1zURP[QTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#o*bV2eWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#b*b#b#c2}#c#o*bV3WURPgSTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#o*bV3qWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#f*b#f#g4Z#g#o*bV4bWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#W*b#W#X4z#X#o*bV5RWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#X*b#X#Y5k#Y#o*bV5rWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#f*b#f#g6[#g#o*bV6cVRPTUpq6x}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#o*bU6{P#U#V7OU7RP#m#n7UU7ZOlUV7bWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#f*b#f#g7z#g#o*bV8RWRPTU}!O*b!P!Q*{!Q![*b!c!}*b#R#S*b#T#i*b#i#j1Q#j#o*b",
tokenizers: [0, 1, 2],
topRules: {"Program":[0,1]},
tokenPrec: 0
specialized: [{term: 5, get: value => spec_Identifier[value] || -1}],
tokenPrec: 787
})
+23 -18
View File
@@ -2,26 +2,31 @@
export const
Program = 1,
Query = 2,
Name = 3,
TagIdentifier = 3,
WhereClause = 4,
Where = 5,
LogicalExpr = 6,
FilterExpr = 7,
Identifier = 5,
Expression = 7,
Value = 8,
Number = 9,
String = 10,
Bool = 11,
Regex = 12,
Null = 13,
List = 14,
And = 15,
LimitClause = 16,
Limit = 17,
OrderClause = 18,
Order = 19,
OrderDirection = 20,
SelectClause = 21,
Select = 22,
RenderClause = 23,
Render = 24,
PageRef = 25
BooleanKW = 12,
Regex = 13,
List = 15,
LVal = 16,
Attribute = 17,
ParenthesizedExpression = 18,
LogicalExpression = 19,
BinExpression = 22,
InKW = 23,
Call = 24,
LimitClause = 25,
OrderClause = 27,
Order = 28,
OrderBy = 29,
OrderDirection = 30,
OrderKW = 31,
SelectClause = 32,
Select = 34,
RenderClause = 36,
PageRef = 38
-17
View File
@@ -73,23 +73,6 @@ Before
End
`;
Deno.test("Test directive parser", () => {
const lang = buildMarkdown([]);
let tree = parse(lang, directiveSample);
// console.log("tree", JSON.stringify(tree, null, 2));
assertEquals(renderToText(tree), directiveSample);
tree = parse(lang, nestedDirectiveExample);
// console.log("tree", JSON.stringify(tree, null, 2));
assertEquals(renderToText(tree), nestedDirectiveExample);
const orderByExample = `<!-- #query page order by lastModified -->
<!-- /query -->`;
tree = parse(lang, orderByExample);
console.log("Tree", JSON.stringify(tree, null, 2));
});
const inlineAttributeSample = `
Hello there [a link](http://zef.plus)
[age: 100]
+13 -4
View File
@@ -142,7 +142,7 @@ export const Highlight: MarkdownConfig = {
],
};
export const attributeStartRegex = /^\[(\w+)(::?\s*)/;
export const attributeStartRegex = /^\[([\w\$]+)(::?\s*)/;
export const Attribute: MarkdownConfig = {
defineNodes: [
@@ -252,16 +252,17 @@ const directiveStart = /^\s*<!--\s*#([a-z]+)\s*(.*?)-->\s*/;
const directiveEnd = /^\s*<!--\s*\/(.*?)-->\s*/;
import { parser as directiveParser } from "./parse-query.js";
import { parser as expressionParser } from "./parse-expression.js";
import { Table } from "./table_parser.ts";
const highlightingDirectiveParser = directiveParser.configure({
export const highlightingDirectiveParser = directiveParser.configure({
props: [
styleTags({
"Name": t.variableName,
"String": t.string,
"Number": t.number,
"PageRef": ct.WikiLinkTag,
"Where Limit Select Render Order OrderDirection And": t.keyword,
"where limit select render Order OrderKW and or as InKW": t.keyword,
}),
],
});
@@ -294,6 +295,14 @@ export const Directive: MarkdownConfig = {
cx.parsedPos + line.text.length + 1,
[cx.elt(queryParseTree, frontStart + fullMatch.indexOf(arg))],
));
} else if (directive === "eval") {
const expressionParseTree = expressionParser.parse(arg);
elts.push(cx.elt(
"DirectiveStart",
cx.parsedPos,
cx.parsedPos + line.text.length + 1,
[cx.elt(expressionParseTree, frontStart + fullMatch.indexOf(arg))],
));
} else {
elts.push(cx.elt(
"DirectiveStart",
@@ -432,7 +441,7 @@ export default function buildMarkdown(mdExtensions: MDExt[]): Language {
props: [
styleTags({
Task: ct.TaskTag,
TaskMarker: ct.TaskMarkerTag,
TaskMark: ct.TaskMarkTag,
Comment: ct.CommentTag,
"TableDelimiter SubscriptMark SuperscriptMark StrikethroughMark":
t.processingInstruction,
+85 -34
View File
@@ -1,58 +1,102 @@
@precedence { logic @left }
@top Program { Query }
@skip { space }
Query {
Name ( WhereClause | LimitClause | OrderClause | SelectClause | RenderClause )*
@precedence {
mulop @left
addop @left
binop @left
and @left
or @left
}
@skip {
space
}
commaSep<content> { content ("," content)* }
WhereClause { Where LogicalExpr }
LimitClause { Limit Number }
OrderClause { Order Name OrderDirection? }
SelectClause { Select commaSep<Name> }
RenderClause { Render (PageRef | String) }
kw<term> { @specialize[@name={term}]<Identifier, term> }
Query {
TagIdentifier ( WhereClause | LimitClause | OrderClause | SelectClause | RenderClause )*
}
WhereClause { kw<"where"> Expression }
LimitClause { kw<"limit"> Expression }
OrderClause { Order commaSep<OrderBy> }
OrderBy { Expression OrderDirection? }
SelectClause { kw<"select"> commaSep<Select> }
RenderClause { kw<"render"> PageRef }
Select { Identifier | Expression kw<"as"> Identifier }
OrderDirection {
"desc" | "asc"
OrderKW
}
Value { Number | String | Bool | Regex | Null | List }
Value { Number | String | Bool | Regex | kw<"null"> | List }
LogicalExpr { FilterExpr (And FilterExpr)* }
FilterExpr {
Name "<" Value
| Name "<=" Value
| Name "=" Value
| Name "!=" Value
| Name ">=" Value
| Name ">" Value
| Name "=~" Value
| Name "!=~" Value
| Name "in" Value
Attribute {
LVal "." Identifier
}
List { "[" commaSep<Value> "]" }
Call {
Identifier "(" commaSep<Expression> ")" | Identifier "(" ")"
}
LVal {
Identifier
| Attribute
}
ParenthesizedExpression { "(" Expression ")" }
LogicalExpression {
Expression !and kw<"and"> Expression
| Expression !or kw<"or"> Expression
}
Expression {
Value
| LVal
| ParenthesizedExpression
| LogicalExpression
| BinExpression
| Call
}
BinExpression {
Expression !binop "<" Expression
| Expression !binop "<=" Expression
| Expression !binop "=" Expression
| Expression !binop "!=" Expression
| Expression !binop ">=" Expression
| Expression !binop ">" Expression
| Expression !binop "=~" Expression
| Expression !binop "!=~" Expression
| Expression !binop InKW Expression
| Expression !mulop "*" Expression
| Expression !mulop "/" Expression
| Expression !mulop "%" Expression
| Expression !addop "+" Expression
| Expression !addop "-" Expression
}
List { "[" commaSep<Expression> "]" }
Bool {
"true" | "false"
BooleanKW
}
@tokens {
space { std.whitespace+ }
Name { (std.asciiLetter | "-" | "_")+ }
Where { "where" }
Order { "order by" }
Select { "select" }
Render { "render" }
Limit { "limit" }
And { "and" }
Null { "null" }
TagIdentifier { @asciiLetter (@asciiLetter | @digit | "-" | "_" | "/" )* }
Identifier { @asciiLetter (@asciiLetter | @digit | "-" | "_")* }
String {
("\"" | "“" | "”") ![\"”“]* ("\"" | "“" | "”")
@@ -60,9 +104,16 @@ Bool {
PageRef {
"[" "[" ![\]]* "]" "]"
}
Order { "order by" }
Regex { "/" ( ![/\\\n\r] | "\\" _ )* "/"? }
Number { std.digit+ }
// @precedence { Where, Sort, Select, Render, Limit, And, Null, Name }
BooleanKW { "true" | "false" }
InKW { "in" }
OrderKW { "asc" | "desc" }
@precedence { Order, BooleanKW, InKW, OrderKW, Identifier, Number }
}
@@ -1,9 +1,17 @@
import { indexedDB } from "https://deno.land/x/indexeddb@1.3.5/ponyfill_memory.ts";
import { IndexedDBSpacePrimitives } from "./indexeddb_space_primitives.ts";
import "https://esm.sh/fake-indexeddb@4.0.2/auto";
import { assertEquals } from "../../test_deps.ts";
import { DataStore } from "../../plugos/lib/datastore.ts";
import { IndexedDBKvPrimitives } from "../../plugos/lib/indexeddb_kv_primitives.ts";
import { DataStoreSpacePrimitives } from "./datastore_space_primitives.ts";
Deno.test("IndexedDBSpacePrimitives", async () => {
const space = new IndexedDBSpacePrimitives("test", indexedDB);
Deno.test("DataStoreSpacePrimitives", {
sanitizeResources: false,
sanitizeOps: false,
}, async () => {
const db = new IndexedDBKvPrimitives("test");
await db.init();
const space = new DataStoreSpacePrimitives(new DataStore(db));
const files = await space.fetchFileList();
assertEquals(files, []);
// Write text file
@@ -28,6 +36,8 @@ Deno.test("IndexedDBSpacePrimitives", async () => {
await space.deleteFile("test.bin");
assertEquals(await space.fetchFileList(), [fileMeta]);
db.close();
});
function stringToBytes(str: string): Uint8Array {
@@ -1,7 +1,7 @@
import type { SpacePrimitives } from "./space_primitives.ts";
import Dexie, { Table } from "dexie";
import { mime } from "../deps.ts";
import { FileMeta } from "$sb/types.ts";
import { DataStore } from "../../plugos/lib/datastore.ts";
export type FileContent = {
name: string;
@@ -9,34 +9,27 @@ export type FileContent = {
data: Uint8Array;
};
export class IndexedDBSpacePrimitives implements SpacePrimitives {
private db: Dexie;
filesMetaTable: Table<FileMeta, string>;
filesContentTable: Table<FileContent, string>;
const filesMetaPrefix = ["file", "meta"];
const filesContentPrefix = ["file", "content"];
export class DataStoreSpacePrimitives implements SpacePrimitives {
constructor(
dbName: string,
indexedDB?: any,
private ds: DataStore,
) {
this.db = new Dexie(dbName, {
indexedDB,
});
this.db.version(1).stores({
fileMeta: "name",
fileContent: "name",
});
this.filesMetaTable = this.db.table("fileMeta");
this.filesContentTable = this.db.table<FileContent, string>("fileContent");
}
fetchFileList(): Promise<FileMeta[]> {
return this.filesMetaTable.toArray();
async fetchFileList(): Promise<FileMeta[]> {
return (await this.ds.query<FileMeta>({ prefix: filesMetaPrefix }))
.map((kv) => kv.value);
}
async readFile(
name: string,
): Promise<{ data: Uint8Array; meta: FileMeta }> {
const fileContent = await this.filesContentTable.get(name);
const fileContent = await this.ds.get<FileContent>([
...filesContentPrefix,
name,
]);
if (!fileContent) {
throw new Error("Not found");
}
@@ -60,22 +53,35 @@ export class IndexedDBSpacePrimitives implements SpacePrimitives {
size: data.byteLength,
perm: suggestedMeta?.perm || "rw",
};
await this.filesContentTable.put({ name, data, meta });
await this.filesMetaTable.put(meta);
await this.ds.batchSet<FileMeta | FileContent>([
{
key: [...filesContentPrefix, name],
value: { name, data, meta },
},
{
key: [...filesMetaPrefix, name],
value: meta,
},
]);
return meta;
}
async deleteFile(name: string): Promise<void> {
const fileMeta = await this.filesMetaTable.get(name);
const fileMeta = await this.ds.get<FileMeta>([
...filesMetaPrefix,
name,
]);
if (!fileMeta) {
throw new Error("Not found");
}
await this.filesMetaTable.delete(name);
await this.filesContentTable.delete(name);
return this.ds.batchDelete([
[...filesMetaPrefix, name],
[...filesContentPrefix, name],
]);
}
async getFileMeta(name: string): Promise<FileMeta> {
const fileMeta = await this.filesMetaTable.get(name);
const fileMeta = await this.ds.get([...filesMetaPrefix, name]);
if (!fileMeta) {
throw new Error("Not found");
}
+1 -8
View File
@@ -93,14 +93,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
file.close();
// Fetch new metadata
const s = await Deno.stat(localPath);
return {
name: name,
size: s.size,
contentType: lookupContentType(name),
lastModified: s.mtime!.getTime(),
perm: "rw",
};
return this.getFileMeta(name);
} catch (e) {
console.error("Error while writing file", name, e);
throw Error(`Could not write ${name}`);
+12 -17
View File
@@ -47,7 +47,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
oldHash !== newHash
)
) {
this.dispatchEvent("file:changed", meta.name);
await this.dispatchEvent("file:changed", meta.name);
}
// Page found, not deleted
deletedFiles.delete(meta.name);
@@ -58,7 +58,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
for (const deletedFile of deletedFiles) {
delete this.spaceSnapshot[deletedFile];
this.dispatchEvent("file:deleted", deletedFile);
await this.dispatchEvent("file:deleted", deletedFile);
if (deletedFile.endsWith(".md")) {
const pageName = deletedFile.substring(0, deletedFile.length - 3);
@@ -66,7 +66,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
}
}
this.dispatchEvent("file:listed", newFileList);
await this.dispatchEvent("file:listed", newFileList);
this.alreadyFetching = false;
this.initialFileListLoad = false;
return newFileList;
@@ -93,7 +93,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
meta,
);
if (!selfUpdate) {
this.dispatchEvent("file:changed", name, true);
await this.dispatchEvent("file:changed", name, true);
}
this.spaceSnapshot[name] = newMeta.lastModified;
@@ -104,16 +104,11 @@ export class EventedSpacePrimitives implements SpacePrimitives {
const decoder = new TextDecoder("utf-8");
text = decoder.decode(data);
this.dispatchEvent("page:saved", pageName, newMeta)
.then(() => {
return this.dispatchEvent("page:index_text", {
name: pageName,
text,
});
})
.catch((e) => {
console.error("Error dispatching page:saved event", e);
});
await this.dispatchEvent("page:saved", pageName, newMeta);
await this.dispatchEvent("page:index_text", {
name: pageName,
text,
});
}
return newMeta;
}
@@ -134,9 +129,9 @@ export class EventedSpacePrimitives implements SpacePrimitives {
this.triggerEventsAndCache(name, newMeta.lastModified);
return newMeta;
} catch (e: any) {
console.log("Checking error", e, name);
// console.log("Checking error", e, name);
if (e.message === "Not found") {
this.dispatchEvent("file:deleted", name);
await this.dispatchEvent("file:deleted", name);
if (name.endsWith(".md")) {
const pageName = name.substring(0, name.length - 3);
await this.dispatchEvent("page:deleted", pageName);
@@ -154,6 +149,6 @@ export class EventedSpacePrimitives implements SpacePrimitives {
// await this.getPageMeta(name); // Check if page exists, if not throws Error
await this.wrapped.deleteFile(name);
delete this.spaceSnapshot[name];
this.dispatchEvent("file:deleted", name);
await this.dispatchEvent("file:deleted", name);
}
}
@@ -1,86 +0,0 @@
import { SpacePrimitives } from "./space_primitives.ts";
import type { SysCallMapping } from "../../plugos/system.ts";
import { FileMeta } from "$sb/types.ts";
// Enriches the file list listing with custom metadata from the page index
export class FileMetaSpacePrimitives implements SpacePrimitives {
constructor(
private wrapped: SpacePrimitives,
private indexSyscalls: SysCallMapping,
) {
}
async fetchFileList(): Promise<FileMeta[]> {
const files = await this.wrapped.fetchFileList();
// Enrich the file list with custom meta data (for pages)
const allFilesMap: Map<string, any> = new Map(
files.map((fm) => [fm.name, fm]),
);
for (
const { page, value } of await this.indexSyscalls["index.queryPrefix"](
{} as any,
"meta:",
)
) {
const p = allFilesMap.get(`${page}.md`);
if (p) {
for (const [k, v] of Object.entries(value)) {
if (
["name", "lastModified", "size", "perm", "contentType"].includes(k)
) {
continue;
}
p[k] = v;
}
}
}
return [...allFilesMap.values()];
}
readFile(
name: string,
): Promise<{ data: Uint8Array; meta: FileMeta }> {
return this.wrapped.readFile(name);
}
async getFileMeta(name: string): Promise<FileMeta> {
const meta = await this.wrapped.getFileMeta(name);
if (name.endsWith(".md")) {
const pageName = name.slice(0, -3);
const additionalMeta = await this.indexSyscalls["index.get"](
{} as any,
pageName,
"meta:",
);
if (additionalMeta) {
for (const [k, v] of Object.entries(additionalMeta)) {
if (
["name", "lastModified", "size", "perm", "contentType"].includes(k)
) {
continue;
}
meta[k] = v;
}
}
}
return meta;
}
writeFile(
name: string,
data: Uint8Array,
selfUpdate?: boolean,
meta?: FileMeta,
): Promise<FileMeta> {
return this.wrapped.writeFile(
name,
data,
selfUpdate,
meta,
);
}
deleteFile(name: string): Promise<void> {
return this.wrapped.deleteFile(name);
}
}
+40
View File
@@ -0,0 +1,40 @@
import { niceDate } from "$sb/lib/dates.ts";
export function handlebarHelpers() {
return {
json: (v: any) => JSON.stringify(v),
niceDate: (ts: any) => niceDate(new Date(ts)),
escapeRegexp: (ts: any) => {
return ts.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
},
replaceRegexp: (s: string, regexp: string, replacement: string) => {
return s.replace(new RegExp(regexp, "g"), replacement);
},
prefixLines: (v: string, prefix: string) =>
v.split("\n").map((l) => prefix + l).join("\n"),
substring: (s: string, from: number, to: number, elipsis = "") =>
s.length > to - from ? s.substring(from, to) + elipsis : s,
today: () => niceDate(new Date()),
tomorrow: () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return niceDate(tomorrow);
},
yesterday: () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return niceDate(yesterday);
},
lastWeek: () => {
const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);
return niceDate(lastWeek);
},
nextWeek: () => {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return niceDate(nextWeek);
},
};
}
+23
View File
@@ -0,0 +1,23 @@
import { SysCallMapping } from "../../plugos/system.ts";
import { handlebarHelpers } from "./handlebar_helpers.ts";
import Handlebars from "handlebars";
export function handlebarsSyscalls(): SysCallMapping {
return {
"handlebars.renderTemplate": (
_ctx,
template: string,
obj: any,
globals: Record<string, any> = {},
): string => {
const templateFn = Handlebars.compile(
template,
{ noEscape: true },
);
return templateFn(obj, {
helpers: handlebarHelpers(),
data: globals,
});
},
};
}
+20
View File
@@ -0,0 +1,20 @@
import { SysCallMapping } from "../../plugos/system.ts";
import { parse } from "../markdown_parser/parse_tree.ts";
import type { ParseTree } from "$sb/lib/tree.ts";
import { languageFor } from "../languages.ts";
export function languageSyscalls(): SysCallMapping {
return {
"language.parseLanguage": (
_ctx,
language: string,
code: string,
): ParseTree => {
const lang = languageFor(language);
if (!lang) {
throw new Error(`Unknown language ${language}`);
}
return parse(lang, code);
},
};
}
+12
View File
@@ -0,0 +1,12 @@
import { SysCallMapping } from "../../plugos/system.ts";
import { parse } from "../markdown_parser/parse_tree.ts";
import { Language } from "../../web/deps.ts";
import type { ParseTree } from "$sb/lib/tree.ts";
export function markdownSyscalls(lang: Language): SysCallMapping {
return {
"markdown.parseMarkdown": (_ctx, text: string): ParseTree => {
return parse(lang, text);
},
};
}
+16
View File
@@ -0,0 +1,16 @@
import { SysCallMapping } from "../../plugos/system.ts";
import { YAML } from "../../web/deps.ts";
export function yamlSyscalls(): SysCallMapping {
return {
"yaml.parse": (_ctx, text: string): any => {
return YAML.parse(text);
},
"yaml.stringify": (_ctx, obj: any): string => {
return YAML.stringify(obj, {
noArrayIndent: true,
noCompatMode: true,
});
},
};
}