Complete redo of content indexing and querying (#517)
Complete redo of data store Introduces live queries and live templates
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
+14
-4
@@ -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 {
|
||||
+31
-25
@@ -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");
|
||||
}
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user