= {}): string {
if (!node) {
return build;
}
@@ -135,18 +138,32 @@ export class Engine {
this._execute(node.consequent!, value, '') :
this._execute(node.alternative!, value, ''))
}
- case 'Data':
- console.log('ref', node.key, data)
- if (node.key == ".") {
+ case 'Range':
+ const strippedListKey = node.listKey!.substring(1);
+ const list: any[] = data[strippedListKey] ?? []
+ let unwrapped = list.reduce((s, d) => {
+ const subBuild = node.placeholders!.map(p => this._execute(p, d, '', data)).join('')
+ return s + subBuild;
+ }, '');
+ return build + unwrapped;
+ case 'Data':
+ let source = data;
+ let key = node.key!;
+ if (key.startsWith('$')) {
+ source = parentData;
+ key = key.substring(1);
+ }
+ if (key == ".") {
if (typeof data === 'string') {
- return build + data
+ return build + source
} else {
- return build + JSON.stringify(data);
+ return build + JSON.stringify(source);
}
} else {
- if (typeof data === 'object') {
- const inner = data[node.key!.substring(1)]
+ if (typeof source === 'object') {
+ const strippedKey = key.substring(1)
+ const inner = source[strippedKey]
if (typeof inner === 'string') {
return build + inner
}
@@ -184,6 +201,7 @@ function _parse(text: string, root: Node): Node {
let prevScope = root;
let scope = root;
let inConditional = false;
+ let inRange = false;
let textNode: Node = {
type: 'Text',
@@ -193,6 +211,19 @@ function _parse(text: string, root: Node): Node {
let altPath = false;
+
+ const pushScope = (node: Node) => {
+ switch (scope.type) {
+ case 'Container':
+ case 'Range':
+ scope.placeholders!.push(node)
+ break;
+ case 'Conditional':
+ const placeholders = (altPath) ? scope.alternative!.placeholders! : scope.consequent!.placeholders!;
+ placeholders.push(node);
+ }
+ }
+
while (i < text.length) {
textNode.endIndex = i;
@@ -211,14 +242,7 @@ function _parse(text: string, root: Node): Node {
}
textNode.text = text.substring(textNode.startIndex!, textNode.endIndex!)
- switch (scope.type) {
- case 'Container':
- scope.placeholders!.push(textNode)
- break;
- case 'Conditional':
- const placeholders = (altPath) ? scope.alternative!.placeholders! : scope.consequent!.placeholders!;
- placeholders.push(textNode);
- }
+ pushScope(textNode);
textNode = {
type: 'Text',
@@ -237,38 +261,29 @@ function _parse(text: string, root: Node): Node {
inConditional = false;
scope = prevScope;
altPath = false;
+ } else if (inRange) {
+ inRange = false;
+ scope = prevScope;
} else {
- switch (scope.type) {
- case 'Container':
- scope.placeholders!.push(command)
- break;
- case 'Conditional':
- const placeholders = (altPath) ? scope.alternative!.placeholders! : scope.consequent!.placeholders!;
- placeholders.push(command);
- break;
- }
+ pushScope(command)
}
break;
case 'else':
altPath = true;
break;
}
- }
- else if (command.type == 'Conditional') {
+ } else if (command.type == 'Conditional') {
inConditional = true;
scope.placeholders!.push(command)
prevScope = scope;
scope = command!;
+ } else if (command.type == 'Range') {
+ inRange = true;
+ scope.placeholders!.push(command)
+ prevScope = scope;
+ scope = command!;
} else {
- switch (scope.type) {
- case 'Container':
- scope.placeholders!.push(command)
- break;
- case 'Conditional':
- const placeholders = (altPath) ? scope.alternative!.placeholders! : scope.consequent!.placeholders!;
- placeholders.push(command);
- break;
- }
+ pushScope(command)
}
}
i++;
@@ -312,17 +327,17 @@ function parseCommand(text: string, i: number, j: number): Node {
view = text.substring(i, j);
}
- if (view.startsWith('.')) {
+ if (view.startsWith('.') || view.startsWith('$')) {
node = parseDataText(view);
}
-
+
if (view.startsWith('if') || view.trim().startsWith('with')) {
node = parseConditionalText(text, i, j);
}
- if (view == 'else' || view == 'end') {
+ if (view.startsWith('else') || view.startsWith('end')) {
node.type = 'Marker';
- node.markerType = view;
+ node.markerType = view.trim() as 'else' | 'end';
}
if (view.startsWith("define")) {
@@ -342,6 +357,15 @@ function parseCommand(text: string, i: number, j: number): Node {
}
node.reference = name;
}
+
+ if (view.startsWith("range")) {
+ node.type = 'Range';
+ let anchor = view.split(' ')[1];
+
+ node.listKey = anchor.trim();
+ node.placeholders = [];
+ }
+
return node;
};
@@ -406,6 +430,12 @@ function printNode(node: Node, offset='') {
console.log(offset + 'Else:\n')
printNode(node.alternative!, offset + '\t')
break;
+ case 'Range':
+ console.log(`${offset}${node.type} [with=${node.listKey}]`);
+ for (let p of node.placeholders ?? []) {
+ printNode(p, offset + '|__');
+ }
+ break;
case 'Container':
console.log(offset + node.type)
for (let p of node.placeholders ?? []) {
@@ -423,56 +453,6 @@ function printNode(node: Node, offset='') {
}
}
-/**
- * Stopgap :(
- */
-export default abstract class Yuck {
- static async renderDiscoverPage() {
- const discover = await Bun.file('public/pages/discover.html').text();
- const header = await Bun.file('public/templates/header.html').text();
-
- const headerHtml = header
- .replaceAll(/{{\s*[^}]*?}}/g, '');
-
- const discoverHtml = discover
- .replace('{{ template "header" . }}', headerHtml)
- .replaceAll(/{{\s*[^}]*?}}/g, '')
-
- return discoverHtml;
- }
-
- static async renderMoviePage(
- id: number,
- title: string,
- releaseYear: string,
- voteAverage: string,
- overview: string,
- posterPath: string
- ) {
- const header = await Bun.file('public/templates/header.html').text();
- const card = await Bun.file('public/templates/card.html').text();
-
- const headerHtml = header
- .replaceAll(/{{\s*[^}]*?}}/g, '');
-
- const cardHtml = card
- .replaceAll('{{ .ID }}', id.toString())
- .replaceAll('{{ .Title }}', title)
- .replaceAll('{{ .ReleaseDate }}', releaseYear)
- .replaceAll('{{ .VoteAverage }}', voteAverage)
- .replaceAll('{{ .Overview }}', overview)
- .replaceAll('{{ .PosterPath }}', posterPath)
- .replaceAll(/{{\s*[^}]*?}}/g, '')
-
- return [headerHtml, cardHtml].join("");
- }
-
- static async renderProviderPage() {
- // NAH ima implement that in the engine
- return `[look at me im a bunch of provider logos. yippeeee]
`
- }
-}
-
const instance = new Engine();
export {
instance
diff --git a/src/util/goop.ts b/src/util/goop.ts
index ea5ae2a..9a219fd 100644
--- a/src/util/goop.ts
+++ b/src/util/goop.ts
@@ -3,6 +3,33 @@
*
* This is just cause I gotta stick to not touching Public templates
*/
-export default function ungoop(obj: object) {
- // TODO. will prob just use the type system
-}
+
+type Camel =
+ S extends "id" ? "ID" :
+ S extends `${infer H}.${infer T}` ? `${H}${Capitalize>}` :
+ S extends `${infer H}_${infer T}` ? `${H}${Capitalize>}` : S;
+
+type Pascal = S extends "id" ? "ID" : Capitalize>;
+
+export type Camelize =
+ T extends readonly (infer U)[] ? Camelize[] :
+ T extends object ? { [K in keyof T as Pascal]: Camelize } :
+ T;
+
+const pascalKey = (k: string) =>
+ k === "id"
+ ? "ID"
+ : k
+ .replace(/[._](\w)/g, (_, c) => c.toUpperCase())
+ .replace(/^\w/, (c) => c.toUpperCase());
+
+export default function ungoop(obj: T): Camelize {
+ return (
+ Array.isArray(obj) ? obj.map(ungoop)
+ : obj && typeof obj === "object"
+ ? Object.fromEntries(
+ Object.entries(obj).map(([k, v]) => [pascalKey(k), ungoop(v)]),
+ )
+ : obj
+ ) as Camelize;
+}
\ No newline at end of file