initial parser logic. still needs fixing for Text

This commit is contained in:
kree 2026-08-06 09:29:49 -04:00
parent 71acab3e54
commit b81ac470c5
2 changed files with 243 additions and 26 deletions

View file

@ -8,6 +8,7 @@ export const api = new Elysia({ prefix: '/api' })
const releaseYear = movie.release_date.substring(0, 4); const releaseYear = movie.release_date.substring(0, 4);
const voteAverage = movie.vote_average.toFixed(2); const voteAverage = movie.vote_average.toFixed(2);
// return Gin.render("card", movie);
return Yuck.renderMoviePage( return Yuck.renderMoviePage(
movie.id, movie.id,
movie.title, movie.title,
@ -22,6 +23,7 @@ export const api = new Elysia({ prefix: '/api' })
const releaseYear = movie.release_date.substring(0, 4); const releaseYear = movie.release_date.substring(0, 4);
const voteAverage = movie.vote_average.toFixed(2); const voteAverage = movie.vote_average.toFixed(2);
// return Gin.render("movie", movie);
return Yuck.renderMoviePage( return Yuck.renderMoviePage(
movie.id, movie.id,
movie.title, movie.title,
@ -33,5 +35,6 @@ export const api = new Elysia({ prefix: '/api' })
}) })
.get("providers/watch/:id", async ({ params: { id } }) => { .get("providers/watch/:id", async ({ params: { id } }) => {
const providers = await API.providers(parseInt(id)); const providers = await API.providers(parseInt(id));
// return Gin.render("providers", movie);
return Yuck.renderProviderPage() return Yuck.renderProviderPage()
}); });

View file

@ -1,39 +1,253 @@
// TODO: Finish template engine and parse the data like god intended
type GinNode = {
path: string; const example =
children: GinNode[]; `Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie`;
type Node = {
type: 'Text' | 'Data' | 'Conditional' | 'Marker' | 'Container' | 'Empty'
// Text
startIndex?: number;
endIndex?: number;
text?: string;
// Data
key?: string;
// Conditional
conditionType?: 'if' | 'with';
condition?: Node;
consequent?: Node;
alternative?: Node;
// Marker
markerType?: 'else' | 'end';
// Container
placeholders?: Node[],
// Scoping
parent?: Node;
};
function parse(text: string): Node {
let root: Node = {
type: 'Container',
placeholders: []
};
return _parse(text, root);
} }
class Gin { function _parse(text: string, root: Node): Node {
if (!text || text.length < 1) {
return root;
};
/** let i = 0;
* Preprocess template files from a given path into some data structure that tags them and notes substitutable areas const open = [];
* let prevScope = root;
* @param path let scope = root;
*/
setup(path: string) {}
/** let textNode: Node = {
* Creates HTML string from named template and supplied data type: 'Text',
* startIndex: 0,
* @param name endIndex: 0
* @param mappings
*/
render(name: string, data: Record<string, string | number>): string {
return '';
} }
/**
* Constructs actual dependency tree. Parsing is prob gonna be some recursive business let altPath = false;
* while (i < text.length) {
* @param path textNode.endIndex = i;
*/
private crawl(path: string): GinNode | undefined { if (text.substring(i, i+2) == '{{') {
return; open.push(i);
}
if (text.substring(i, i+2) == '}}') {
textNode.parent = scope;
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);
}
const j = open.pop()!;
textNode = {
type: 'Text',
startIndex: j+2,
endIndex: i
};
const command = parseCommand(text, j+2, i);
command.parent = scope;
if (command.type == 'Marker') {
switch (command.markerType) {
case 'end':
scope = prevScope;
altPath = false;
break;
case 'else':
altPath = true;
break;
}
}
else if (command.type == 'Conditional') {
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;
}
}
}
i++;
}
if (textNode.startIndex! < textNode.endIndex!) {
textNode.parent = scope;
textNode.text = text.substring(textNode.startIndex!, textNode.endIndex!)
scope.placeholders!.push(textNode);
}
return root;
}
function parseCommand(text: string, i: number, j: number): Node {
let node: Node = {
type: 'Empty'
};
if (i >= j) {
return node;
};
let view = text.substring(i, j);
// not supported
if (view.startsWith("- ")) {
i += 2;
view = text.substring(i, j);
}
// not supported
if (view.endsWith(" -")) {
j -= 2;
view = text.substring(i, j);
}
if (view.startsWith('.')) {
node = parseDataText(view);
}
if (view.startsWith('if') || view.trim().startsWith('with')) {
node = parseConditionalText(text, i, j);
}
if (view == 'else' || view == 'end') {
node.type = 'Marker';
node.markerType = view;
}
return node;
};
function parseConditionalText(text: string, i: number, j: number): Node {
const view = text.substring(i, j);
let node: Node = {
type: 'Conditional',
conditionType: view.startsWith('if') ? 'if' : 'with',
consequent: {
type: 'Container',
placeholders: []
},
alternative: {
type: 'Container',
placeholders: []
},
};
node.consequent!.parent = node;
node.alternative!.parent = node;
const pipelineStart = i + view.indexOf(' ') + 1;
const pipelineView = text.substring(pipelineStart, j)
if (pipelineView.startsWith('.')) {
node.condition = {
type: 'Data',
key: pipelineView,
parent: node,
}
} else {
node.condition = {
type: 'Text',
startIndex: pipelineStart,
endIndex: j,
text: pipelineView,
parent: node,
}
}
return node;
}
function parseDataText(text: string): Node {
return {
type: 'Data',
key: text.trim()
};
}
function printNode(node: Node, offset='') {
switch (node.type) {
case 'Conditional':
console.log(`${offset}${node.conditionType}`);
console.log(`${offset}Condition:\n`)
printNode(node.condition!, offset + '\t')
console.log(`${offset}Then:\n`)
printNode(node.consequent!, offset + '\t')
console.log(offset + 'Else:\n')
printNode(node.alternative!, offset + '\t')
break;
case 'Container':
console.log(offset + node.type)
for (let p of node.placeholders ?? []) {
printNode(p, offset + '|__');
}
break;
case 'Data':
console.log(`${offset}${node.type} [key=${node.key}]`);
break;
case 'Text':
console.log(`${offset}${node.type} [text=${JSON.stringify(node.text)}]`);
default:
console.log(`${offset}${node.type}`);
} }
} }
printNode(parse(example));
/** /**
* Stopgap :( * Stopgap :(
*/ */