@bookshop/hugo-engine 2.4.0-hugo.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.nyc_output/50eb2ebd-c797-4d3f-8df7-a707c1b211eb.json +1 -0
  2. package/.nyc_output/86f85916-4f58-43da-8c32-c1ec29ca783e.json +1 -0
  3. package/.nyc_output/a27680c8-8858-4ede-921d-6e6617f42a0b.json +1 -0
  4. package/.nyc_output/b5f86652-cbe8-44f9-a36c-d5721dfe5bbe.json +1 -0
  5. package/.nyc_output/ecca87c1-5774-4962-864a-2e4537aa9915.json +1 -0
  6. package/.nyc_output/processinfo/50eb2ebd-c797-4d3f-8df7-a707c1b211eb.json +1 -0
  7. package/.nyc_output/processinfo/86f85916-4f58-43da-8c32-c1ec29ca783e.json +1 -0
  8. package/.nyc_output/processinfo/a27680c8-8858-4ede-921d-6e6617f42a0b.json +1 -0
  9. package/.nyc_output/processinfo/b5f86652-cbe8-44f9-a36c-d5721dfe5bbe.json +1 -0
  10. package/.nyc_output/processinfo/ecca87c1-5774-4962-864a-2e4537aa9915.json +1 -0
  11. package/.nyc_output/processinfo/index.json +1 -0
  12. package/build.js +1 -0
  13. package/hugo-renderer/build.sh +16 -0
  14. package/hugo-renderer/deps/bookshop_modified_deps.go +6 -0
  15. package/hugo-renderer/go.mod +55 -0
  16. package/hugo-renderer/go.sum +886 -0
  17. package/hugo-renderer/helpers/bookshop_modified_content.go +64 -0
  18. package/hugo-renderer/helpers/bookshop_modified_general.go +33 -0
  19. package/hugo-renderer/hugo_renderer.wasm +0 -0
  20. package/hugo-renderer/main.go +28 -0
  21. package/hugo-renderer/tpl/bookshop_engine/bookshop_func_importer.go +34 -0
  22. package/hugo-renderer/tpl/bookshop_engine/bookshop_render.go +64 -0
  23. package/hugo-renderer/tpl/bookshop_library/bookshop_components.go +107 -0
  24. package/hugo-renderer/tpl/cast/cast.go +63 -0
  25. package/hugo-renderer/tpl/cast/init.go +57 -0
  26. package/hugo-renderer/tpl/collections/append.go +37 -0
  27. package/hugo-renderer/tpl/collections/apply.go +131 -0
  28. package/hugo-renderer/tpl/collections/collections.go +767 -0
  29. package/hugo-renderer/tpl/collections/complement.go +55 -0
  30. package/hugo-renderer/tpl/collections/index.go +133 -0
  31. package/hugo-renderer/tpl/collections/init.go +214 -0
  32. package/hugo-renderer/tpl/collections/merge.go +126 -0
  33. package/hugo-renderer/tpl/collections/reflect_helpers.go +216 -0
  34. package/hugo-renderer/tpl/collections/sort.go +183 -0
  35. package/hugo-renderer/tpl/collections/symdiff.go +68 -0
  36. package/hugo-renderer/tpl/collections/where.go +517 -0
  37. package/hugo-renderer/tpl/compare/compare.go +324 -0
  38. package/hugo-renderer/tpl/compare/init.go +85 -0
  39. package/hugo-renderer/tpl/crypto/crypto.go +109 -0
  40. package/hugo-renderer/tpl/crypto/init.go +65 -0
  41. package/hugo-renderer/tpl/debug/debug.go +40 -0
  42. package/hugo-renderer/tpl/debug/init.go +45 -0
  43. package/hugo-renderer/tpl/encoding/encoding.go +90 -0
  44. package/hugo-renderer/tpl/encoding/init.go +59 -0
  45. package/hugo-renderer/tpl/fmt/fmt.go +64 -0
  46. package/hugo-renderer/tpl/fmt/init.go +77 -0
  47. package/hugo-renderer/tpl/inflect/inflect.go +79 -0
  48. package/hugo-renderer/tpl/inflect/init.go +60 -0
  49. package/hugo-renderer/tpl/internal/bookshop_modified_templatefuncsRegistry.go +88 -0
  50. package/hugo-renderer/tpl/math/init.go +134 -0
  51. package/hugo-renderer/tpl/math/math.go +164 -0
  52. package/hugo-renderer/tpl/math/round.go +61 -0
  53. package/hugo-renderer/tpl/partials/init.go +55 -0
  54. package/hugo-renderer/tpl/partials/partials.go +86 -0
  55. package/hugo-renderer/tpl/path/init.go +60 -0
  56. package/hugo-renderer/tpl/path/path.go +159 -0
  57. package/hugo-renderer/tpl/reflect/init.go +51 -0
  58. package/hugo-renderer/tpl/reflect/reflect.go +36 -0
  59. package/hugo-renderer/tpl/safe/init.go +80 -0
  60. package/hugo-renderer/tpl/safe/safe.go +72 -0
  61. package/hugo-renderer/tpl/strings/init.go +229 -0
  62. package/hugo-renderer/tpl/strings/regexp.go +125 -0
  63. package/hugo-renderer/tpl/strings/strings.go +500 -0
  64. package/hugo-renderer/tpl/strings/truncate.go +157 -0
  65. package/hugo-renderer/wasm_exec.js +636 -0
  66. package/lib/builder.js +21 -0
  67. package/lib/engine.js +134 -0
  68. package/lib/engine.test.js +5 -0
  69. package/lib/hugoIdentifierParser.js +324 -0
  70. package/lib/hugoIdentifierParser.test.js +72 -0
  71. package/lib/translateTextTemplate.js +58 -0
  72. package/lib/translateTextTemplate.test.js +39 -0
  73. package/main.js +1 -0
  74. package/main.test.js +5 -0
  75. package/package.json +32 -0
package/lib/engine.js ADDED
@@ -0,0 +1,134 @@
1
+ import hugoWasm from "../hugo-renderer/hugo_renderer.wasm";
2
+ import translateTextTemplate from './translateTextTemplate.js';
3
+ import { IdentifierParser } from './hugoIdentifierParser.js';
4
+
5
+ const sleep = (ms = 0) => {
6
+ return new Promise(r => setTimeout(r, ms));
7
+ }
8
+
9
+ const dig = (obj, path) => {
10
+ if (typeof path === 'string') path = path.replace(/\[(\d+)]/g, '.$1').split('.');
11
+ obj = obj[path.shift()];
12
+ if (obj && path.length) return dig(obj, path);
13
+ return obj;
14
+ }
15
+
16
+ export class Engine {
17
+ constructor(options) {
18
+ options = {
19
+ name: "Hugo",
20
+ files: {},
21
+ ...options,
22
+ };
23
+
24
+ this.key = 'hugo';
25
+ this.name = options.name;
26
+ this.files = options.files;
27
+
28
+ this.initializeHugo();
29
+ }
30
+
31
+ async initializeHugo() {
32
+ const scriptOrigin = document.currentScript?.src || `/bookshop.js`;
33
+ const wasmOrigin = scriptOrigin.replace(/\/[^\.\/]+\.js$/, hugoWasm.replace(/^\./, ''));
34
+ const go = new Go();
35
+ const response = await fetch(wasmOrigin);
36
+ const buffer = await response.arrayBuffer();
37
+ const result = await WebAssembly.instantiate(buffer, go.importObject);
38
+ go.run(result.instance)
39
+
40
+ // TODO: Tidy
41
+ const mappedFiles = {};
42
+ for (const file of Object.entries(this.files)) {
43
+ mappedFiles[file[0]] = {
44
+ contents: translateTextTemplate(file[1], {})
45
+ }
46
+ }
47
+
48
+ const success = window.loadHugoBookshopPartials(JSON.stringify(mappedFiles));
49
+ }
50
+
51
+ getShared(name) {
52
+ const key = `shared/hugo/${name}.hugo.html`
53
+ return this.files?.[key];
54
+ }
55
+
56
+ getComponentKey(name) {
57
+ const base = name.split("/").reverse()[0];
58
+ return `components/${name}/${base}.hugo.html`;
59
+ }
60
+
61
+ getComponent(name) {
62
+ const key = this.getComponentKey(name);
63
+ return this.files?.[key];
64
+ }
65
+
66
+ hasComponent(name) {
67
+ const key = this.getComponentKey(name);
68
+ return !!this.files?.[key];
69
+ }
70
+
71
+ resolveComponentType(name) {
72
+ if (this.getComponent(name)) return 'component';
73
+ if (this.getShared(name)) return 'shared';
74
+ return false;
75
+ }
76
+
77
+ transformData(data) {
78
+ return {
79
+ Params: data
80
+ };
81
+ }
82
+
83
+ async render(target, name, props, globals) {
84
+ while (!window.renderHugo) await sleep(10);
85
+
86
+ let source = this.getComponent(name);
87
+ // TODO: Remove the below check and update the live comments to denote shared
88
+ if (!source) source = this.getShared(name);
89
+ if (!source) {
90
+ console.warn(`[hugo-engine] No component found for ${name}`);
91
+ return "";
92
+ }
93
+ // TODO: this template already exists on the other side of the wasm bounary
94
+ source = translateTextTemplate(source, {});
95
+ if (!globals || typeof globals !== "object") globals = {};
96
+ props = { ...globals, ...props };
97
+ const output = window.renderHugo(source, JSON.stringify(props));
98
+ target.innerHTML = output;
99
+ }
100
+
101
+ async eval(str, props = [{}]) {
102
+ while (!window.renderHugo) await sleep(10);
103
+ const props_obj = props.reduce((a, b) => { return { ...b, ...a } });
104
+
105
+ // We're capable of looking up a simple variable
106
+ // (and it's hard to pass to wasm since we store variables on the context)
107
+ if (/^\$/.test(str)) {
108
+ return props_obj[str] ?? null;
109
+ }
110
+
111
+ // Rewrite array.0 into index array 0
112
+ str = str.replace(/(.*)\.(\d+)$/, (_, obj, index) => {
113
+ return `index (${obj}) ${index}`;
114
+ })
115
+
116
+ const eval_str = `{{ jsonify (${str}) }}`;
117
+ const output = window.renderHugo(eval_str, JSON.stringify(props_obj));
118
+
119
+ try {
120
+ return JSON.parse(output);
121
+ } catch (e) {
122
+ console.error(`Error evaluating \`${str}\` in the Hugo engine`, output);
123
+ return null;
124
+ }
125
+ }
126
+
127
+ normalize(str) {
128
+ return (new IdentifierParser(str)).build();
129
+ }
130
+
131
+ loader() {
132
+ // esbuild loader if required
133
+ }
134
+ }
@@ -0,0 +1,5 @@
1
+ import test from 'ava';
2
+
3
+ test("stub", async t => {
4
+ t.pass()
5
+ });
@@ -0,0 +1,324 @@
1
+ const TOKENS = {
2
+ DELIM: /"|'|`/,
3
+ ESCAPE: /\\/,
4
+ SPACE: /\s/,
5
+ INSCOPE: /\(/,
6
+ OUTSCOPE: /\)/,
7
+ SCOPE: /\./,
8
+ }
9
+
10
+ /**
11
+ * Takes in an identifier string and returns a Javascript-friendly version
12
+ * Usually, this is in the form of convering a dict to an object
13
+ */
14
+ export class IdentifierParser {
15
+ constructor(input) {
16
+ this.input = input;
17
+ this.stream = [];
18
+ this.state = `START`;
19
+ this.deps = {};
20
+ this.output = this.input;
21
+ }
22
+
23
+ // Some short-circuits for easy to regex patterns to skip the parser
24
+ tryShortCircuit() {
25
+ // change the index function into Bookshop dot notation
26
+ const indexFunc = /^\s*\(\s*index\s+\(?\.?(.+?)\)?\s+(\d+)\s*\)\s*$/
27
+ if (indexFunc.test(this.input)) {
28
+ const [, variable, index] = this.input.match(indexFunc);
29
+ return `${variable}.${index}`;
30
+ }
31
+
32
+ // strip the leading . from basic variable references
33
+ const variableDot = /^\s*\.([^\.\s])/;
34
+ if (/^\s*\./.test(this.input)) {
35
+ return this.input.replace(/^\s*\.([^\.\s])/, '$1');
36
+ }
37
+
38
+ return null;
39
+ }
40
+
41
+ build() {
42
+ let transformedStr = this.tryShortCircuit();
43
+ if (transformedStr) return transformedStr;
44
+
45
+ this.stream = this.input.split('');
46
+ while (this.stream.length && this.state !== `BREAK`) {
47
+ this.process(this.stream.shift());
48
+ }
49
+ // Flush any lingering value
50
+ this.process(' ');
51
+
52
+ return this.output;
53
+ }
54
+
55
+ process(t) {
56
+ switch (this.state) {
57
+ case `START`:
58
+ return this.processSTART(t);
59
+ case `FUNC`:
60
+ return this.processFUNC(t);
61
+ case `DICT_IDENT`:
62
+ return this.processDICT_IDENT(t);
63
+ case `DICT_VALUE`:
64
+ return this.processDICT_VALUE(t);
65
+ case `SLICE`:
66
+ return this.processSLICE(t);
67
+ }
68
+ }
69
+
70
+ processSTART(t) {
71
+ if (TOKENS.SPACE.test(t)) { return };
72
+
73
+ if (!TOKENS.INSCOPE.test(t)) {
74
+ this.state = `BREAK`;
75
+ return;
76
+ };
77
+
78
+ this.state = `FUNC`;
79
+ }
80
+
81
+ processFUNC(t) {
82
+ if (TOKENS.SPACE.test(t) && !this.deps.started) { return };
83
+ this.deps.func = this.deps.func || '';
84
+ this.deps.started = true;
85
+
86
+ // ↓
87
+ // (dict "a" "b")
88
+ if (TOKENS.SPACE.test(t)) {
89
+ switch (this.deps.func) {
90
+ case "dict":
91
+ this.state = `DICT_IDENT`;
92
+ this.output = {};
93
+ this.deps = {};
94
+ return;
95
+ case "slice":
96
+ this.state = `SLICE`;
97
+ this.output = [];
98
+ this.deps = {};
99
+ return;
100
+ default:
101
+ this.state = `BREAK`;
102
+ return;
103
+ }
104
+ }
105
+
106
+ // ↓↓↓↓
107
+ // (dict "key" "value")
108
+ this.deps.func += t;
109
+ }
110
+
111
+ processDICT_IDENT(t) {
112
+ // skip leading whitespace
113
+ if (TOKENS.SPACE.test(t) && !this.deps.started) { return };
114
+ this.deps.identifier = this.deps.identifier || '';
115
+ this.deps.started = true;
116
+
117
+ // ↓
118
+ // (dict "key" "value")
119
+ if (TOKENS.DELIM.test(t) && !this.deps.delim) {
120
+ return this.deps.delim = new RegExp(t);
121
+ }
122
+
123
+ if (TOKENS.OUTSCOPE.test(t)) {
124
+ if (this.deps.identifier.length) {
125
+ throw new Error(`Tried to parse a bad dict: ${this.input}`);
126
+ }
127
+ return this.state = 'BREAK';
128
+ }
129
+
130
+ // ↓
131
+ // (dict key "value")
132
+ if (!this.deps.delim) {
133
+ throw new Error(`Tried to parse a bad dict: ${this.input}`);
134
+ }
135
+
136
+ // ↓
137
+ // (dict "ke\"y" "value")
138
+ if (this.deps.escape) {
139
+ this.deps.identifier += t;
140
+ this.deps.escape = false;
141
+ return;
142
+ }
143
+
144
+ // ↓
145
+ // (dict "key" "value")
146
+ if (this.deps.delim && this.deps.delim.test(t)) {
147
+ this.state = 'DICT_VALUE';
148
+ this.deps = { identifier: this.deps.identifier };
149
+ return;
150
+ }
151
+
152
+ // ↓
153
+ // (dict "ke\"y" "value")
154
+ if (TOKENS.ESCAPE.test(t)) {
155
+ return this.deps.escape = true;
156
+ }
157
+
158
+ // ↓↓↓
159
+ // (dict "key" "value")
160
+ this.deps.identifier += t;
161
+ this.deps.escape = false;
162
+ }
163
+
164
+ processDICT_VALUE(t) {
165
+ // skip leading whitespace
166
+ if (TOKENS.SPACE.test(t) && !this.deps.started) { return };
167
+ this.deps.value = this.deps.value || '';
168
+ this.deps.started = true;
169
+
170
+ // ↓
171
+ // (dict "key" "va\"lue")
172
+ if (this.deps.escape) {
173
+ this.deps.value += t;
174
+ this.deps.escape = false;
175
+ return;
176
+ }
177
+
178
+ // ↓
179
+ // (dict "key" "va\"lue")
180
+ if (TOKENS.ESCAPE.test(t)) {
181
+ this.deps.escape = true;
182
+ return;
183
+ }
184
+
185
+ // ↓↓↓↓↓↓↓
186
+ // (dict "key" "value")
187
+ this.deps.value += t;
188
+
189
+ if (!this.deps.delim) {
190
+ // ↓
191
+ // (dict "key" "value")
192
+ if (TOKENS.DELIM.test(t)) {
193
+ return this.deps.delim = new RegExp(t);
194
+ }
195
+ // ↓
196
+ // (dict "key" (slice 1 2 3))
197
+ if (TOKENS.INSCOPE.test(t)) {
198
+ return this.deps.delim = TOKENS.OUTSCOPE;
199
+ }
200
+
201
+ this.deps.delim = TOKENS.SPACE
202
+
203
+ // ↓
204
+ // (dict "key" .variable)
205
+ if (!TOKENS.SPACE.test(t)) {
206
+ return;
207
+ }
208
+ }
209
+
210
+ // ↓
211
+ // (dict "key" (slice 1 2 3))
212
+ if (this.deps.delimDepth && this.deps.delim.test(t)) {
213
+ return this.deps.delimDepth -= 1;
214
+ }
215
+
216
+ // ↓
217
+ // (dict "key" .var)
218
+ if (!this.deps.delimDepth && this.deps.delim !== TOKENS.OUTSCOPE && TOKENS.OUTSCOPE.test(t)) {
219
+ if (this.deps.delim !== TOKENS.OUTSCOPE) this.deps.value = this.deps.value.replace(/.$/, '');
220
+ this.output[this.deps.identifier] = (new IdentifierParser(this.deps.value)).build();
221
+ this.state = 'BREAK';
222
+ this.deps = {};
223
+ return;
224
+ }
225
+
226
+ // ↓ ↓
227
+ // (dict "key" .var "key2" (len .arr))
228
+ if (this.deps.delim.test(t)) {
229
+ if (this.deps.delim === TOKENS.SPACE) this.deps.value = this.deps.value.replace(/.$/, '');
230
+ this.output[this.deps.identifier] = (new IdentifierParser(this.deps.value)).build();
231
+ this.state = 'DICT_IDENT';
232
+ this.deps = {};
233
+ return;
234
+ }
235
+
236
+ // Handed nested parentheses
237
+ // ↓
238
+ // (dict "key" (slice 1 2 3))
239
+ if (this.deps.delim === TOKENS.OUTSCOPE && TOKENS.INSCOPE.test(t)) {
240
+ this.deps.delimDepth = this.deps.delimDepth || 0;
241
+ this.deps.delimDepth += 1;
242
+ }
243
+ }
244
+
245
+ processSLICE(t) {
246
+ // skip leading whitespace
247
+ if (TOKENS.SPACE.test(t) && !this.deps.started) { return };
248
+ this.deps.value = this.deps.value || '';
249
+ this.deps.started = true;
250
+
251
+ // ↓
252
+ // (slice "value" "va\"lue")
253
+ if (this.deps.escape) {
254
+ this.deps.value += t;
255
+ this.deps.escape = false;
256
+ return;
257
+ }
258
+
259
+ // ↓
260
+ // (slice "value" "va\"lue")
261
+ if (TOKENS.ESCAPE.test(t)) {
262
+ this.deps.escape = true;
263
+ return;
264
+ }
265
+
266
+ // ↓↓↓↓↓↓↓ ↓↓↓↓↓↓
267
+ // (slice "value" .value)
268
+ this.deps.value += t;
269
+
270
+ if (!this.deps.delim) {
271
+ // ↓
272
+ // (slice "value")
273
+ if (TOKENS.DELIM.test(t)) {
274
+ return this.deps.delim = new RegExp(t);
275
+ }
276
+ // ↓
277
+ // (slice "value" (slice 1 2 3))
278
+ if (TOKENS.INSCOPE.test(t)) {
279
+ return this.deps.delim = TOKENS.OUTSCOPE;
280
+ }
281
+
282
+ this.deps.delim = TOKENS.SPACE
283
+
284
+ // ↓
285
+ // (slice "value" .variable)
286
+ if (!TOKENS.SPACE.test(t)) {
287
+ return;
288
+ }
289
+ }
290
+
291
+ // ↓
292
+ // (slice "value" (slice 1 2 3))
293
+ if (this.deps.delimDepth && this.deps.delim.test(t)) {
294
+ return this.deps.delimDepth -= 1;
295
+ }
296
+
297
+ // ↓
298
+ // (slice "value" .var)
299
+ if (!this.deps.delimDepth && TOKENS.OUTSCOPE.test(t)) {
300
+ this.deps.value = this.deps.value.replace(/.$/, '')
301
+ this.output.push((new IdentifierParser(this.deps.value)).build());
302
+ this.state = 'BREAK';
303
+ this.deps = {};
304
+ return;
305
+ }
306
+
307
+ // ↓ ↓
308
+ // (slice "value" .val2 3)
309
+ if (this.deps.delim.test(t)) {
310
+ if (this.deps.delim === TOKENS.SPACE) this.deps.value = this.deps.value.replace(/.$/, '');
311
+ this.output.push((new IdentifierParser(this.deps.value)).build());
312
+ this.deps = {};
313
+ return;
314
+ }
315
+
316
+ // Handed nested parentheses
317
+ // ↓
318
+ // (slice "value" (slice 1 2 3))
319
+ if (this.deps.delim === TOKENS.OUTSCOPE && TOKENS.INSCOPE.test(t)) {
320
+ this.deps.delimDepth = this.deps.delimDepth || 0;
321
+ this.deps.delimDepth += 1;
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,72 @@
1
+ import test from 'ava';
2
+ import { IdentifierParser } from './hugoIdentifierParser.js';
3
+
4
+ test(`Dict with parameter`, async t => {
5
+ const ident = `(dict "contents" .Params.contents)`;
6
+ const output = (new IdentifierParser(ident)).build();
7
+ t.deepEqual(output, {
8
+ "contents": "Params.contents"
9
+ });
10
+ });
11
+
12
+ test(`Dict with string`, async t => {
13
+ const ident = `(dict "title" "text")`;
14
+ const output = (new IdentifierParser(ident)).build();
15
+ t.deepEqual(output, {
16
+ "title": `"text"`
17
+ });
18
+ });
19
+
20
+ test(`Dict with parentheses`, async t => {
21
+ const ident = `(dict "length" (len .arr))`;
22
+ const output = (new IdentifierParser(ident)).build();
23
+ t.deepEqual(output, {
24
+ "length": "(len .arr)"
25
+ });
26
+ });
27
+
28
+
29
+ test(`Larger dict`, async t => {
30
+ const ident = `(dict "title" "text" "contents" .Params.contents "number" 5)`;
31
+ const output = (new IdentifierParser(ident)).build();
32
+ t.deepEqual(output, {
33
+ "title": `"text"`,
34
+ "contents": "Params.contents",
35
+ "number": "5"
36
+ });
37
+ });
38
+
39
+ test(`Slice`, async t => {
40
+ const ident = `(slice "text" .Params.contents 5)`;
41
+ const output = (new IdentifierParser(ident)).build();
42
+ t.deepEqual(output, [`"text"`, "Params.contents", "5"]);
43
+ });
44
+
45
+ test(`Unknown functions untouched`, async t => {
46
+ const ident = `(len .array)`;
47
+ const output = (new IdentifierParser(ident)).build();
48
+ t.deepEqual(output, `(len .array)`);
49
+ });
50
+
51
+ test(`Values untouched`, async t => {
52
+ const ident = `"Something"`;
53
+ const output = (new IdentifierParser(ident)).build();
54
+ t.deepEqual(output, `"Something"`);
55
+ });
56
+
57
+ test(`Nested dicts and slices`, async t => {
58
+ const ident = `(dict "contents" (slice 1 "2" (dict "a" .Params.b)) "len" (len .some_array))`;
59
+ const output = (new IdentifierParser(ident)).build();
60
+ t.deepEqual(output, {
61
+ "contents": ["1", `"2"`, {
62
+ "a": "Params.b"
63
+ }],
64
+ "len": "(len .some_array)"
65
+ });
66
+ });
67
+
68
+ test(`Converts an index into dot notation`, async t => {
69
+ const ident = `(index (.items) 5)`;
70
+ const output = (new IdentifierParser(ident)).build();
71
+ t.deepEqual(output, `items.5`);
72
+ });
@@ -0,0 +1,58 @@
1
+ import { Tokenizer } from 'liquidjs';
2
+
3
+ const tokens = {
4
+ END: /{{\s*end\s*}}/,
5
+ LOOP: /{{\s*range\s+(.+?)\s*}}/,
6
+ ASSIGN: /{{\s*(\$\S+)\s+:=\s+(.+?)\s*}}/,
7
+ BOOKSHOP: /{{\s*partial\s+"bookshop(?:_partial)?"\s+\(\s*slice\s+"(.+?)" (.+?)\s*\)\s*}}/,
8
+ }
9
+
10
+ /**
11
+ * Parse a go text/template using the liquidjs parser
12
+ * that we already have in the bundle.
13
+ * All go templating tags will come through as value tokens.
14
+ */
15
+ const rewriteTag = function (token, src, liveMarkup) {
16
+ let raw = token.getText();
17
+
18
+ // Skip non-value tags
19
+ if (token.kind !== 8) return src;
20
+ if (tokens.END.test(raw)) return src;
21
+
22
+ if (liveMarkup && tokens.LOOP.test(raw)) {
23
+ let [, iterator] = raw.match(tokens.LOOP);
24
+ raw = [`{{ $bookshop__live__iterator := 0 }}`,
25
+ `${raw}`,
26
+ `{{ (printf \`<!--bookshop-live context(.: (index (${iterator}) %d))-->\` $bookshop__live__iterator) | safeHTML }}`,
27
+ `{{ $bookshop__live__iterator = (add $bookshop__live__iterator 1) }}`
28
+ ].join('')
29
+ } else if (liveMarkup && tokens.ASSIGN.test(raw)) {
30
+ let [, identifier, value] = raw.match(tokens.ASSIGN);
31
+ raw = `${raw}{{ \`<!--bookshop-live context(${identifier}: ${value})-->\` | safeHTML }}`
32
+ } else if (liveMarkup && tokens.BOOKSHOP.test(raw)) {
33
+ let [, name, params] = raw.match(tokens.BOOKSHOP);
34
+ raw = `{{ \`<!--bookshop-live name(${name}) params(.: ${params})-->\` | safeHTML }}${raw}{{ \`<!--bookshop-live end-->\` | safeHTML }}`
35
+ }
36
+
37
+ return [
38
+ src.substr(0, token.begin),
39
+ raw,
40
+ src.substr(token.end)
41
+ ].join('');
42
+ }
43
+
44
+ export default function (text, opts) {
45
+ opts = {
46
+ liveMarkup: true,
47
+ ...opts
48
+ }
49
+ text = text.toString();
50
+ const tokenizer = new Tokenizer(text);
51
+ const output = tokenizer.readTopLevelTokens();
52
+
53
+ output.reverse().forEach(tag => {
54
+ text = rewriteTag(tag, text, opts.liveMarkup);
55
+ });
56
+
57
+ return text;
58
+ };
@@ -0,0 +1,39 @@
1
+ import test from 'ava';
2
+ import translateTextTemplate from './translateTextTemplate.js';
3
+
4
+ test("add live markup to bookshop tags", t => {
5
+ const input = `{{ partial "bookshop" (slice "content" (dict "content_html" .Params.note_html "type" "note")) }}`;
6
+ const expected = [
7
+ `{{ \`<!--bookshop-live name(content) params(.: (dict "content_html" .Params.note_html "type" "note"))-->\` | safeHTML }}`,
8
+ `{{ partial "bookshop" (slice "content" (dict "content_html" .Params.note_html "type" "note")) }}`,
9
+ `{{ \`<!--bookshop-live end-->\` | safeHTML }}`
10
+ ].join('');
11
+ t.is(translateTextTemplate(input, {}), expected);
12
+ });
13
+
14
+ test("add live markup to bookshop_partial tags", t => {
15
+ const input = `{{ partial "bookshop_partial" (slice "helper" (dict "text" "input")) }}`;
16
+ const expected = [
17
+ `{{ \`<!--bookshop-live name(helper) params(.: (dict "text" "input"))-->\` | safeHTML }}`,
18
+ `{{ partial "bookshop_partial" (slice "helper" (dict "text" "input")) }}`,
19
+ `{{ \`<!--bookshop-live end-->\` | safeHTML }}`
20
+ ].join('');
21
+ t.is(translateTextTemplate(input, {}), expected);
22
+ });
23
+
24
+ test("add live markup to assigns", t => {
25
+ const input = `{{ $a := .b }}`;
26
+ const expected = `{{ $a := .b }}{{ \`<!--bookshop-live context($a: .b)-->\` | safeHTML }}`;
27
+ t.is(translateTextTemplate(input, {}), expected);
28
+ });
29
+
30
+ test("add live markup to loops", t => {
31
+ const input = `{{ range .items }}<p>{{ . }}</p>{{ end }}`;
32
+ const expected = [`{{ $bookshop__live__iterator := 0 }}`,
33
+ `{{ range .items }}`,
34
+ `{{ (printf \`<!--bookshop-live context(.: (index (.items) %d))-->\` $bookshop__live__iterator) | safeHTML }}`,
35
+ `{{ $bookshop__live__iterator = (add $bookshop__live__iterator 1) }}`,
36
+ `<p>{{ . }}</p>{{ end }}`
37
+ ].join('');
38
+ t.is(translateTextTemplate(input, {}), expected);
39
+ });
package/main.js ADDED
@@ -0,0 +1 @@
1
+ export {Engine} from './lib/engine.js';
package/main.test.js ADDED
@@ -0,0 +1,5 @@
1
+ import test from 'ava';
2
+
3
+ test("stub", async t => {
4
+ t.pass()
5
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@bookshop/hugo-engine",
3
+ "packageManager": "yarn@3.0.0",
4
+ "version": "2.4.0-hugo.0",
5
+ "description": "Bookshop frontend Hugo renderer",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "main.js",
9
+ "exports": {
10
+ ".": "./main.js",
11
+ "./build": "./build.js"
12
+ },
13
+ "scripts": {
14
+ "test": "nyc ava -v"
15
+ },
16
+ "author": "@bglw",
17
+ "license": "MIT",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "devDependencies": {
22
+ "ava": "^3.15.0",
23
+ "nyc": "^15.1.0"
24
+ },
25
+ "dependencies": {
26
+ "@bookshop/helpers": "2.4.0-hugo.0",
27
+ "esbuild": "^0.13.10"
28
+ },
29
+ "engines": {
30
+ "node": ">=14.16"
31
+ }
32
+ }