@@ -0,0 +1,37 @@
|
||||
import { assertEquals } from "../test_deps.ts";
|
||||
import { traverseAndRewriteJSON } from "./json.ts";
|
||||
|
||||
Deno.test("traverseAndRewrite should recursively traverse and rewrite object properties", () => {
|
||||
const bufArray = new Uint8Array([1, 2, 3]);
|
||||
const obj = {
|
||||
foo: "bar",
|
||||
list: ["hello", { sup: "world" }],
|
||||
nested: {
|
||||
baz: "qux",
|
||||
},
|
||||
special: {
|
||||
value: () => {
|
||||
return bufArray;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const rewritten = traverseAndRewriteJSON(obj, (val) => {
|
||||
if (typeof val?.value === "function") {
|
||||
return val.value();
|
||||
}
|
||||
if (typeof val === "string") {
|
||||
return val.toUpperCase();
|
||||
}
|
||||
return val;
|
||||
});
|
||||
|
||||
assertEquals(rewritten, {
|
||||
foo: "BAR",
|
||||
list: ["HELLO", { sup: "WORLD" }],
|
||||
nested: {
|
||||
baz: "QUX",
|
||||
},
|
||||
special: bufArray,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Traverses and rewrites an object recursively.
|
||||
*
|
||||
* @param obj - The object to traverse and rewrite.
|
||||
* @param rewrite - The function to apply for rewriting each value.
|
||||
* @returns The rewritten object.
|
||||
*/
|
||||
export function traverseAndRewriteJSON(
|
||||
obj: any,
|
||||
rewrite: (val: any) => any,
|
||||
): any {
|
||||
// Apply rewrite to object as a whole
|
||||
obj = rewrite(obj);
|
||||
// Recurse down if this is an array or a "plain object"
|
||||
if (
|
||||
obj && Array.isArray(obj) ||
|
||||
(typeof obj === "object" && obj.constructor === Object)
|
||||
) {
|
||||
const keys = Object.keys(obj);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
obj[key] = traverseAndRewriteJSON(obj[key], rewrite);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
+8
-1
@@ -25,7 +25,10 @@ import {
|
||||
xmlLanguage,
|
||||
yamlLanguage,
|
||||
} from "./deps.ts";
|
||||
import { highlightingQueryParser } from "./markdown_parser/parser.ts";
|
||||
import {
|
||||
expressionParser,
|
||||
highlightingQueryParser,
|
||||
} from "./markdown_parser/parser.ts";
|
||||
|
||||
export const builtinLanguages: Record<string, Language> = {
|
||||
"meta": StreamLanguage.define(yamlLanguage),
|
||||
@@ -81,6 +84,10 @@ export const builtinLanguages: Record<string, Language> = {
|
||||
name: "query",
|
||||
parser: highlightingQueryParser,
|
||||
}),
|
||||
"expression": LRLanguage.define({
|
||||
name: "expression",
|
||||
parser: expressionParser,
|
||||
}),
|
||||
};
|
||||
|
||||
export function languageFor(name: string): Language | null {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
@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"> ( kw<"each"> | kw<"all"> )? 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 }
|
||||
}
|
||||
@@ -13,6 +13,6 @@ export const parser = LRParser.deserialize({
|
||||
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}],
|
||||
specialized: [{term: 9, get: (value) => spec_Identifier[value] || -1}],
|
||||
tokenPrec: 431
|
||||
})
|
||||
|
||||
@@ -13,6 +13,6 @@ export const parser = LRParser.deserialize({
|
||||
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!P~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![~#r#s$x~$}O!`~~%QWOr$}rs%js$Ip$}$Ip$Iq%j$Iq$Ir%j$Ir;'S$};'S;=`%o<%lO$}~%oOY~~%rP;=`<%l$}~%zO!c~~&PO!V~~&UO!W~~&ZO!a~~&`O!d~~&eO!S~~&jO!e~~&oO!U~U&vX!bS]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!X~!_!`)q~)vO!Y~~){P!Z~#r#s*O~*TO!_~~*YP!^~!_!`*]~*bO!]~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*{V+iP!QQ!}#O+lT+oTO#P+l#P#Q,O#Q;'S+l;'S;=`,Z<%lO+lT,RP#P#Q,UT,ZOxTT,^P;=`<%l+l~,fO!T~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]},
|
||||
specialized: [{term: 5, get: value => spec_Identifier[value] || -1}],
|
||||
specialized: [{term: 5, get: (value) => spec_Identifier[value] || -1}],
|
||||
tokenPrec: 798
|
||||
})
|
||||
|
||||
@@ -136,3 +136,9 @@ Deno.test("Test command link arguments", () => {
|
||||
const args2 = findNodeOfType(commands[1], "CommandLinkArgs");
|
||||
assertEquals(args2!.children![0].text, '"other", "args", 123');
|
||||
});
|
||||
|
||||
Deno.test("Test template directives", () => {
|
||||
const lang = buildMarkdown([]);
|
||||
const tree = parse(lang, `Hello there {{name}}!`);
|
||||
console.log("Template directive", JSON.stringify(tree, null, 2));
|
||||
});
|
||||
|
||||
@@ -135,6 +135,38 @@ const CommandLink: MarkdownConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
export const templateDirectiveRegex = /^\{\{([^\}]+)\}\}/;
|
||||
|
||||
const TemplateDirective: MarkdownConfig = {
|
||||
defineNodes: [
|
||||
{ name: "TemplateDirective", style: t.monospace },
|
||||
{ name: "TemplateDirectiveMark", style: t.monospace },
|
||||
],
|
||||
parseInline: [
|
||||
{
|
||||
name: "TemplateDirective",
|
||||
parse(cx, next, pos) {
|
||||
let match: RegExpMatchArray | null;
|
||||
if (
|
||||
next != 123 /* '{' */ ||
|
||||
!(match = templateDirectiveRegex.exec(cx.slice(pos, cx.end)))
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
const fullMatch = match[0];
|
||||
const endPos = pos + fullMatch.length;
|
||||
return cx.addElement(
|
||||
cx.elt("TemplateDirective", pos, endPos, [
|
||||
cx.elt("TemplateDirectiveMark", pos, pos + 2),
|
||||
cx.elt("TemplateDirectiveMark", endPos - 2, endPos),
|
||||
]),
|
||||
);
|
||||
},
|
||||
after: "Emphasis",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" };
|
||||
|
||||
export const Highlight: MarkdownConfig = {
|
||||
@@ -175,6 +207,8 @@ export const highlightingQueryParser = queryParser.configure({
|
||||
],
|
||||
});
|
||||
|
||||
export { parser as expressionParser } from "./parse-expression.js";
|
||||
|
||||
export const attributeStartRegex = /^\[([\w\$]+)(::?\s*)/;
|
||||
|
||||
export const Attribute: MarkdownConfig = {
|
||||
@@ -355,6 +389,7 @@ export default function buildMarkdown(mdExtensions: MDExt[]): Language {
|
||||
TaskList,
|
||||
Comment,
|
||||
Highlight,
|
||||
TemplateDirective,
|
||||
Strikethrough,
|
||||
Table,
|
||||
...mdExtensions.map(mdExtensionSyntaxConfig),
|
||||
|
||||
@@ -40,5 +40,17 @@ export function handlebarHelpers() {
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
return niceDate(nextWeek);
|
||||
},
|
||||
ifEq: function (v1: any, v2: any, options: any) {
|
||||
if (v1 === v2) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
},
|
||||
ifNeq: function (v1: any, v2: any, options: any) {
|
||||
if (v1 !== v2) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,12 +4,20 @@ import { SpacePrimitives } from "./spaces/space_primitives.ts";
|
||||
import { expandPropertyNames } from "$sb/lib/json.ts";
|
||||
import type { BuiltinSettings } from "../web/types.ts";
|
||||
|
||||
/**
|
||||
* Runs a function safely by catching any errors and logging them to the console.
|
||||
* @param fn - The function to run.
|
||||
*/
|
||||
export function safeRun(fn: () => Promise<void>) {
|
||||
fn().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current platform is Mac-like (Mac, iPhone, iPod, iPad).
|
||||
* @returns A boolean indicating if the platform is Mac-like.
|
||||
*/
|
||||
export function isMacLike() {
|
||||
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
||||
}
|
||||
@@ -17,6 +25,11 @@ export function isMacLike() {
|
||||
// TODO: This is naive, may be better to use a proper parser
|
||||
const yamlSettingsRegex = /```yaml([^`]+)```/;
|
||||
|
||||
/**
|
||||
* Parses YAML settings from a Markdown string.
|
||||
* @param settingsMarkdown - The Markdown string containing the YAML settings.
|
||||
* @returns An object representing the parsed YAML settings.
|
||||
*/
|
||||
export function parseYamlSettings(settingsMarkdown: string): {
|
||||
[key: string]: any;
|
||||
} {
|
||||
@@ -35,6 +48,12 @@ export function parseYamlSettings(settingsMarkdown: string): {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the settings and index page exist in the given space.
|
||||
* If they don't exist, default settings and index page will be created.
|
||||
* @param space - The SpacePrimitives object representing the space.
|
||||
* @returns A promise that resolves to the built-in settings.
|
||||
*/
|
||||
export async function ensureSettingsAndIndex(
|
||||
space: SpacePrimitives,
|
||||
): Promise<BuiltinSettings> {
|
||||
|
||||
Reference in New Issue
Block a user