diff --git a/src/util/gin.ts b/src/util/gin.ts index db5de44..0b9d36a 100644 --- a/src/util/gin.ts +++ b/src/util/gin.ts @@ -1,17 +1,3 @@ - -const example = -`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' @@ -39,13 +25,11 @@ type Node = { parent?: Node; }; -function parse(text: string): Node { - let root: Node = { - type: 'Container', - placeholders: [] - }; - return _parse(text, root); -} +type Template = { + name: string; + root: Node; +}; + function _parse(text: string, root: Node): Node { if (!text || text.length < 1) { @@ -130,7 +114,7 @@ function _parse(text: string, root: Node): Node { if (textNode.startIndex! < textNode.endIndex!) { textNode.parent = scope; - textNode.text = text.substring(textNode.startIndex!, textNode.endIndex!) + textNode.text = text.substring(textNode.startIndex!, textNode.endIndex! + 1) scope.placeholders!.push(textNode); } @@ -223,35 +207,97 @@ function parseDataText(text: string): Node { }; } -function printNode(node: Node, offset='') { +function _execute(node: Node, data: Record | string, build: string): string { + if (!node) { + return build; + } + 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 + '|__'); + return build + node.placeholders!.map(p => _execute(p, data, '')).join(''); + case 'Conditional': + const condEval = _execute(node.condition!, data, ''); + let value = condEval; + try { + value = JSON.parse(value); + } catch (e) {} + if (node.conditionType == 'if') { + // BREH JS BS (without the parenthesis it includes build in its ternary condition -.-) + return build + (!!value ? + _execute(node.consequent!, data, '') : + _execute(node.alternative!, data, '')) + } else { + // BREH JS BS + return build + (!!value ? + _execute(node.consequent!, value, '') : + _execute(node.alternative!, value, '')) } - break; case 'Data': - console.log(`${offset}${node.type} [key=${node.key}]`); - break; + if (node.key == ".") { + if (typeof data === 'string') { + return build + data + } else { + return build + JSON.stringify(data); + } + } else { + if (typeof data === 'object') { + const inner = data[node.key!.substring(1)] + if (typeof inner === 'string') { + return build + inner + } + return build + JSON.stringify(inner); + } + throw new Error('huh?') + } case 'Text': - console.log(`${offset}${node.type} [text=${JSON.stringify(node.text)}]`); - break; + return node.text!; default: - console.log(`${offset}${node.type}`); + return build + '[NOT_IMPLEMENTED]' } } -printNode(parse(example)); +export namespace Gin { + export function parse(text: string): Node { + let root: Node = { + type: 'Container', + placeholders: [] + }; + return _parse(text, root); + }; + + export 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)}]`); + break; + default: + console.log(`${offset}${node.type}`); + } + } + + export function execute(template: Template, data: Record) { + return _execute(template.root, data, ''); + } +}; + /** * Stopgap :( */ diff --git a/test/util/gin.test.ts b/test/util/gin.test.ts new file mode 100644 index 0000000..cadd526 --- /dev/null +++ b/test/util/gin.test.ts @@ -0,0 +1,81 @@ +import { describe, it, expect } from 'bun:test' +import { Gin } from '../../src/util/gin' + +describe('Gin template porting test', () => { + it('parse', async () => { + const example = + `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`; + + const template = { + name: 'example', + root: Gin.parse(example) + }; + + const data = [ + { + Name: 'Aunt Mildred', + Gift: 'bone china tea set', + Attended: true, + }, + { + Name: 'Uncle John', + Gift: 'moleskin pants', + Attended: false, + }, + { + Name: 'Cousin Rodney', + Gift: '', + Attended: false, + }, + ] + + const expected = + `Dear Aunt Mildred, + +It was a pleasure to see you at the wedding. +Thank you for the lovely bone china tea set. + +Best wishes, +Josie + +Dear Uncle John, + +It is a shame you couldn't make it to the wedding. +Thank you for the lovely moleskin pants. + +Best wishes, +Josie + +Dear Cousin Rodney, + +It is a shame you couldn't make it to the wedding. + +Best wishes, +Josie` + + const actual = data.map(r => Gin.execute(template, r)).join('\n'); + console.log(actual) + expect(actual).toEqual(expected); + }) + + // it('parseGlob', async () => { + // const gin = new Gin(); + // const paths = await gin.parseGlob('public/**/*html'); + // }) + + // it('parseHTML', async () => { + // const gin = new Gin(); + // const text = await gin.parseHTML('public/pages/movie.html'); + // console.log(text); + // }) +}) \ No newline at end of file