@bookshop/hugo-engine 3.14.0 → 3.15.0-alpha.3
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.
- package/LICENSE +21 -0
- package/full-hugo-renderer/hugo_renderer.wasm.gz +0 -0
- package/package.json +7 -8
- package/.npmrc +0 -1
- package/.nyc_output/132e7b4a-afdf-418e-b365-774d529faa43.json +0 -1
- package/.nyc_output/2c506fb0-f1f8-47d7-af8c-ccdd651ee51d.json +0 -1
- package/.nyc_output/5e37d726-0046-4e64-9faa-5f344cb00c97.json +0 -1
- package/.nyc_output/95826c24-bb14-4904-85da-cb2e4024b1a9.json +0 -1
- package/.nyc_output/b070f025-72c1-460d-b0a9-3b685ed91a62.json +0 -1
- package/.nyc_output/processinfo/132e7b4a-afdf-418e-b365-774d529faa43.json +0 -1
- package/.nyc_output/processinfo/2c506fb0-f1f8-47d7-af8c-ccdd651ee51d.json +0 -1
- package/.nyc_output/processinfo/5e37d726-0046-4e64-9faa-5f344cb00c97.json +0 -1
- package/.nyc_output/processinfo/95826c24-bb14-4904-85da-cb2e4024b1a9.json +0 -1
- package/.nyc_output/processinfo/b070f025-72c1-460d-b0a9-3b685ed91a62.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/full-hugo-renderer/build.sh +0 -34
- package/full-hugo-renderer/go.mod +0 -109
- package/full-hugo-renderer/go.sum +0 -780
- package/full-hugo-renderer/hugo_renderer.wasm +0 -0
- package/full-hugo-renderer/main.go +0 -219
- package/full-hugo-renderer/wasm_exec.js +0 -568
- package/lib/engine.test.js +0 -82
- package/lib/engine__test__.js +0 -461
- package/lib/hugoIdentifierParser.test.js +0 -110
- package/lib/translateTextTemplate.test.js +0 -229
- package/main.test.js +0 -5
- package/prep__test__.js +0 -5
package/lib/engine__test__.js
DELETED
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
// import compressedHugoWasm from "../full-hugo-renderer/hugo_renderer.wasm.gz";
|
|
2
|
-
import { gunzipSync } from 'fflate';
|
|
3
|
-
import translateTextTemplate from './translateTextTemplate.js';
|
|
4
|
-
import { IdentifierParser } from './hugoIdentifierParser.js';
|
|
5
|
-
|
|
6
|
-
const sleep = (ms = 0) => {
|
|
7
|
-
return new Promise(r => setTimeout(r, ms));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const dig = (obj, path) => {
|
|
11
|
-
if (typeof path === 'string') path = path.replace(/\[(\d+)]/g, '.$1').split('.');
|
|
12
|
-
obj = obj[path.shift()];
|
|
13
|
-
if (obj && path.length) return dig(obj, path);
|
|
14
|
-
return obj;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let mapHugoVariablesToParams = (obj, path) => {
|
|
18
|
-
let resolved = {};
|
|
19
|
-
for (let [k, v] of Object.entries(obj)) {
|
|
20
|
-
if (k.startsWith('$')) {
|
|
21
|
-
const remapped = `__bksh_${k.substring(1)}`;
|
|
22
|
-
obj[remapped] = v;
|
|
23
|
-
resolved[k] = `${path}.${remapped}`;
|
|
24
|
-
} else {
|
|
25
|
-
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
26
|
-
resolved = { ...resolved, ...mapHugoVariablesToParams(v, `${path}.${k}`) }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return resolved
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class Engine {
|
|
34
|
-
constructor(options) {
|
|
35
|
-
options = {
|
|
36
|
-
name: "Hugo",
|
|
37
|
-
files: {},
|
|
38
|
-
...options,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
this.key = 'hugo';
|
|
42
|
-
this.name = options.name;
|
|
43
|
-
this.files = options.files;
|
|
44
|
-
this.extraFiles = options.extraFiles;
|
|
45
|
-
this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
|
|
46
|
-
this.synthetic = options.synthetic ?? false;
|
|
47
|
-
|
|
48
|
-
if (!this.synthetic) {
|
|
49
|
-
this.initializeHugo();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async initializeHugo() {
|
|
54
|
-
const templates = {
|
|
55
|
-
"layouts/partials/bookshop.html": (await import("../bookshop-hugo-templates/bookshop.html")).default,
|
|
56
|
-
"layouts/partials/bookshop_scss.html": (await import("../bookshop-hugo-templates/bookshop_scss.html")).default,
|
|
57
|
-
"layouts/partials/bookshop_partial.html": (await import("../bookshop-hugo-templates/bookshop_partial.html")).default,
|
|
58
|
-
"layouts/partials/bookshop_component_browser.html": (await import("../bookshop-hugo-templates/bookshop_component_browser.html")).default,
|
|
59
|
-
"layouts/partials/bookshop_bindings.html": (await import("../bookshop-hugo-templates/bookshop_bindings.html")).default,
|
|
60
|
-
"layouts/partials/_bookshop/helpers/component.html": (await import("../bookshop-hugo-templates/helpers/component.html")).default,
|
|
61
|
-
"layouts/partials/_bookshop/helpers/component_key.html": (await import("../bookshop-hugo-templates/helpers/component_key.html")).default,
|
|
62
|
-
"layouts/partials/_bookshop/helpers/flat_component_key.html": (await import("../bookshop-hugo-templates/helpers/flat_component_key.html")).default,
|
|
63
|
-
"layouts/partials/_bookshop/helpers/partial.html": (await import("../bookshop-hugo-templates/helpers/partial.html")).default,
|
|
64
|
-
"layouts/partials/_bookshop/helpers/partial_key.html": (await import("../bookshop-hugo-templates/helpers/partial_key.html")).default,
|
|
65
|
-
"layouts/partials/_bookshop/errors/bad_bookshop_tag.html": (await import("../bookshop-hugo-templates/errors/bad_bookshop_tag.html")).default,
|
|
66
|
-
"layouts/partials/_bookshop/errors/err.html": (await import("../bookshop-hugo-templates/errors/err.html")).default,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
templates["config.toml"] = "params.env_bookshop_live = true\nmarkup.goldmark.renderer.unsafe = true";
|
|
70
|
-
|
|
71
|
-
// When this script is run locally, the hugo wasm is loaded as binary rather than output as a file.
|
|
72
|
-
if (compressedHugoWasm?.constructor === Uint8Array) {
|
|
73
|
-
await this.initializeInlineHugo();
|
|
74
|
-
} else {
|
|
75
|
-
await this.initializeLocalCompressedHugo(true);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// TODO: Tidy
|
|
79
|
-
const mappedFiles = {};
|
|
80
|
-
for (const file of Object.entries(this.files)) {
|
|
81
|
-
mappedFiles[`layouts/partials/bookshop/${file[0]}`] = translateTextTemplate(file[1], {});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
|
|
85
|
-
const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
|
|
86
|
-
const extraSuccess = window["writeHugoFiles"](JSON.stringify(this.extraFiles));
|
|
87
|
-
|
|
88
|
-
// BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
|
|
89
|
-
// which doesn't yet make sense to me.
|
|
90
|
-
// If these files are created for the first time in render(), the site doesn't
|
|
91
|
-
// appear to build (need to extract some logs from hugo to determine why).
|
|
92
|
-
// Perplexingly, writing then building in eval() appears to have been working
|
|
93
|
-
// fine in the same case.
|
|
94
|
-
// My suspicion would be something around our changeEvents() handling
|
|
95
|
-
// in the Hugo builder, but I don't know why that wouldn't affect eval()... 🤷♂️
|
|
96
|
-
// For now, the load-bearing stubs will live in this Hugo initialization.
|
|
97
|
-
window.writeHugoFiles(JSON.stringify({
|
|
98
|
-
"layouts/index.html": "Uninitialized Layout",
|
|
99
|
-
"content/_index.md": "{ \”initialized\": false }\n"
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async initializeLocalCompressedHugo(usePrefetch) {
|
|
104
|
-
try {
|
|
105
|
-
|
|
106
|
-
let prefetched = {};
|
|
107
|
-
if (typeof window !== "undefined" && window?.CloudCannon && window?.CloudCannon?.prefetchedFiles) {
|
|
108
|
-
prefetched = await window.CloudCannon.prefetchedFiles?.();
|
|
109
|
-
this.availablePrefetchKeys = Object.keys(prefetched);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const go = new Go();
|
|
113
|
-
const compressedWasmOrigin = this.origin.replace(/\/[^\.\/]+\.(min\.)?js/, compressedHugoWasm.replace(/^\./, ''));
|
|
114
|
-
const compressedWasmPath = (new URL(compressedWasmOrigin)).pathname;
|
|
115
|
-
|
|
116
|
-
let compressedBuffer;
|
|
117
|
-
if (usePrefetch && prefetched[compressedWasmPath]) {
|
|
118
|
-
compressedBuffer = await prefetched[compressedWasmPath]?.arrayBuffer();
|
|
119
|
-
this.loadedFrom = "prefetch";
|
|
120
|
-
} else {
|
|
121
|
-
const compressedResponse = await fetch(compressedWasmOrigin);
|
|
122
|
-
compressedBuffer = await compressedResponse.arrayBuffer();
|
|
123
|
-
this.loadedFrom = "network";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const renderer = gunzipSync(new Uint8Array(compressedBuffer));
|
|
127
|
-
|
|
128
|
-
// Check the magic word at the start of the buffer as a way to verify the CDN download worked.
|
|
129
|
-
const isWasm = ([...renderer.slice(0, 4)]).map(g => g.toString(16).padStart(2, '0')).join('') === "0061736d";
|
|
130
|
-
if (!isWasm) throw "Not WASM";
|
|
131
|
-
|
|
132
|
-
const compressedResult = await WebAssembly.instantiate(renderer, go.importObject);
|
|
133
|
-
go.run(compressedResult.instance);
|
|
134
|
-
} catch (e) {
|
|
135
|
-
console.error("Couldn't load the local compressed Hugo WASM");
|
|
136
|
-
console.error(e);
|
|
137
|
-
|
|
138
|
-
// If our prefetch fails, fall back to loading the wasm ourselves
|
|
139
|
-
if (usePrefetch) {
|
|
140
|
-
await this.initializeLocalCompressedHugo(false);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async initializeInlineHugo() {
|
|
146
|
-
const go = new Go();
|
|
147
|
-
const buffer = compressedHugoWasm.buffer;
|
|
148
|
-
const renderer = gunzipSync(new Uint8Array(buffer));
|
|
149
|
-
const result = await WebAssembly.instantiate(renderer, go.importObject);
|
|
150
|
-
go.run(result.instance);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
getSharedKey(name) {
|
|
154
|
-
return `shared/hugo/${name}.hugo.html`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
getShared(name) {
|
|
158
|
-
const key = this.getSharedKey(name);
|
|
159
|
-
return this.files?.[key];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
getComponentKey(name) {
|
|
163
|
-
const base = name.split("/").reverse()[0];
|
|
164
|
-
return `components/${name}/${base}.hugo.html`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
getFlatComponentKey(name) {
|
|
168
|
-
return `components/${name}.hugo.html`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
getComponent(name) {
|
|
172
|
-
const key = this.getComponentKey(name);
|
|
173
|
-
const flatKey = this.getFlatComponentKey(name);
|
|
174
|
-
return this.files?.[key] ?? this.files?.[flatKey];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
hasComponent(name) {
|
|
178
|
-
const key = this.getComponentKey(name);
|
|
179
|
-
const flatKey = this.getFlatComponentKey(name);
|
|
180
|
-
return !!(this.files?.[key] ?? this.files?.[flatKey]);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
hasShared(name) {
|
|
184
|
-
const key = this.getSharedKey(name);
|
|
185
|
-
return !!this.files?.[key];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
resolveComponentType(name) {
|
|
189
|
-
if (this.hasComponent(name)) return 'component';
|
|
190
|
-
if (this.hasShared(name)) return 'shared';
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
transformData(data) {
|
|
195
|
-
return {
|
|
196
|
-
Params: data
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async storeMeta(meta = {}) {
|
|
201
|
-
while (!window.writeHugoFiles) {
|
|
202
|
-
await sleep(100);
|
|
203
|
-
};
|
|
204
|
-
window.writeHugoFiles(JSON.stringify({
|
|
205
|
-
"config.toml": [
|
|
206
|
-
meta.baseurl ? `baseURL = ${meta.baseurl}` : "",
|
|
207
|
-
meta.copyright ? `copyright = ${meta.copyright}` : "",
|
|
208
|
-
meta.title ? `title = ${meta.title}` : "",
|
|
209
|
-
"params.env_bookshop_live = true",
|
|
210
|
-
"markup.goldmark.renderer.unsafe = true"
|
|
211
|
-
].join('\n')
|
|
212
|
-
}));
|
|
213
|
-
const err = window.initHugoConfig();
|
|
214
|
-
if (err) {
|
|
215
|
-
console.error(err);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// window.loadHugoBookshopMeta(JSON.stringify(meta));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async storeInfo(info = {}) {
|
|
222
|
-
while (!window.writeHugoFiles) {
|
|
223
|
-
await sleep(100);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const data = info?.data;
|
|
227
|
-
if (!data || typeof data !== "object") return;
|
|
228
|
-
const files = {};
|
|
229
|
-
for (const [file, contents] of Object.entries(data)) {
|
|
230
|
-
files[`data/${file}.json`] = JSON.stringify(contents);
|
|
231
|
-
}
|
|
232
|
-
window.writeHugoFiles(JSON.stringify(files));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Tries to parse an error message and patch up the components
|
|
237
|
-
* in a rudimentary way before a rebuild.
|
|
238
|
-
*/
|
|
239
|
-
async componentQuack(error_string = "", log_messages = []) {
|
|
240
|
-
try {
|
|
241
|
-
const component_regex = /execute of template failed: template: ([^:]+):\d/ig;
|
|
242
|
-
let file_stack = [...error_string.matchAll(component_regex)].map(([, file]) => `layouts/${file}`);
|
|
243
|
-
if (file_stack.length) {
|
|
244
|
-
const deepest_errored_component = file_stack[file_stack.length - 1];
|
|
245
|
-
const error_chunks = error_string.split("execute of template failed:");
|
|
246
|
-
const error_msg = error_chunks[error_chunks.length - 1] ?? "template error";
|
|
247
|
-
window.writeHugoFiles(JSON.stringify({
|
|
248
|
-
[deepest_errored_component]: [
|
|
249
|
-
`<div style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
|
|
250
|
-
`Failed to render ${deepest_errored_component}. <br/>`,
|
|
251
|
-
`<pre style="margin-top: 10px; background-color: lightcoral; border: solid 1px black; white-space: pre-line;">`,
|
|
252
|
-
`<code style="font-family: monospace; color: black;">${error_msg.replace(/</, '<')}</code></pre>`,
|
|
253
|
-
`</div>`
|
|
254
|
-
].join('\n')
|
|
255
|
-
}));
|
|
256
|
-
return deepest_errored_component;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const error_logs = log_messages.filter(log => log.startsWith("ERROR")).join("\n");
|
|
260
|
-
const missing_regex = /Component "([^"]+)" does not exist/ig;
|
|
261
|
-
file_stack = [...error_logs.matchAll(missing_regex)].map(([, file]) => {
|
|
262
|
-
let filename = file.split('/').pop();
|
|
263
|
-
return `layouts/partials/bookshop/components/${file}/${filename}.hugo.html`;
|
|
264
|
-
});
|
|
265
|
-
if (file_stack.length) {
|
|
266
|
-
const deepest_errored_component = file_stack[file_stack.length - 1];
|
|
267
|
-
window.writeHugoFiles(JSON.stringify({
|
|
268
|
-
[deepest_errored_component]: [
|
|
269
|
-
`<div class="bookshop_error" style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
|
|
270
|
-
`Failed to find component ${deepest_errored_component}`,
|
|
271
|
-
`</div>`
|
|
272
|
-
].join('\n')
|
|
273
|
-
}));
|
|
274
|
-
return deepest_errored_component;
|
|
275
|
-
}
|
|
276
|
-
} catch (e) {
|
|
277
|
-
console.error(`ComponentQuack failed to patch things up: ${e}`);
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async render(target, name, props, globals, logger) {
|
|
283
|
-
while (!window.buildHugo) {
|
|
284
|
-
logger?.log?.(`Waiting for the Hugo WASM to be available...`);
|
|
285
|
-
await sleep(100);
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
let writeFiles = {};
|
|
289
|
-
|
|
290
|
-
if (this.hasComponent(name)) {
|
|
291
|
-
writeFiles["layouts/index.html"] = `{{ partial "bookshop" (slice "${name}" .Params.component) }}`
|
|
292
|
-
} else if (this.hasShared(name)) {
|
|
293
|
-
writeFiles["layouts/index.html"] = `{{ partial "bookshop_partial" (slice "${name}" .Params.component) }}`
|
|
294
|
-
} else {
|
|
295
|
-
console.warn(`[hugo-engine] No component found for ${name}`);
|
|
296
|
-
return "";
|
|
297
|
-
}
|
|
298
|
-
logger?.log?.(`Going to render ${name}, with layout:`);
|
|
299
|
-
logger?.log?.(writeFiles["layouts/index.html"]);
|
|
300
|
-
|
|
301
|
-
if (!globals || typeof globals !== "object") globals = {};
|
|
302
|
-
props = {
|
|
303
|
-
...globals, ...props,
|
|
304
|
-
env_bookshop_live: true
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// If we have assigned a root scope we need to pass that in as the context
|
|
308
|
-
if (props["."]) props = props["."];
|
|
309
|
-
writeFiles["content/_index.md"] = JSON.stringify({
|
|
310
|
-
component: props
|
|
311
|
-
}, null, 2) + "\n";
|
|
312
|
-
window.writeHugoFiles(JSON.stringify(writeFiles));
|
|
313
|
-
|
|
314
|
-
window.hugo_wasm_logging = [];
|
|
315
|
-
let render_attempts = 1;
|
|
316
|
-
let buildError = window.buildHugo();
|
|
317
|
-
while (buildError && render_attempts < 5) {
|
|
318
|
-
console.warn(`Hit a build error when rendering Hugo:\n${window.hugo_wasm_logging.map(l => ` ${l}`).join('\n')}`);
|
|
319
|
-
if (this.componentQuack(buildError, window.hugo_wasm_logging) === null) {
|
|
320
|
-
// Can't find a template to overwrite and re-render
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
// Try render again with the problem template stubbed out
|
|
324
|
-
window.hugo_wasm_logging = [];
|
|
325
|
-
buildError = window.buildHugo();
|
|
326
|
-
render_attempts += 1;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (buildError) {
|
|
330
|
-
console.error(buildError);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const output = window.readHugoFiles(JSON.stringify([
|
|
335
|
-
"public/index.html"
|
|
336
|
-
]));
|
|
337
|
-
|
|
338
|
-
target.innerHTML = output["public/index.html"];
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async eval(str, props = [{}], logger) {
|
|
343
|
-
if (!this.synthetic) {
|
|
344
|
-
while (!window.buildHugo) await sleep(10);
|
|
345
|
-
}
|
|
346
|
-
let props_obj = props.reduce((a, b) => { return { ...a, ...b } });
|
|
347
|
-
let full_props = props_obj;
|
|
348
|
-
str = str.trim();
|
|
349
|
-
|
|
350
|
-
// We can return early if we're evaluating a literal string or digit
|
|
351
|
-
if (/^".*"$|^`.*`$|^\d+(\.\d+)?$|^true$|^false$/.test(str)) {
|
|
352
|
-
logger?.log?.(`Unwrapping the string ${str}`);
|
|
353
|
-
try {
|
|
354
|
-
if (/^`.*`$/.test(str)) {
|
|
355
|
-
return JSON.parse(str.replace(/^`|`$/g, '"'));
|
|
356
|
-
}
|
|
357
|
-
return JSON.parse(str);
|
|
358
|
-
} catch (e) {
|
|
359
|
-
logger?.log?.(`Was not valid JSON for some reason. Moving on...`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// We're capable of looking up a simple variable
|
|
364
|
-
// (and it's hard to pass to wasm since we store variables on the context)
|
|
365
|
-
if (/^\$/.test(str)) {
|
|
366
|
-
logger?.log?.(`Trying to short circuit to return a variable`);
|
|
367
|
-
if (props_obj[str]) return props_obj[str];
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// If we have assigned a root scope we need to pass that in as the context
|
|
371
|
-
if (props_obj["."]) {
|
|
372
|
-
logger?.log?.(`Nesting the props object into its dot scope`);
|
|
373
|
-
props_obj = props_obj["."];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (str === ".") {
|
|
377
|
-
logger?.log?.(`Short circuiting dot notation to return the scope`);
|
|
378
|
-
return props_obj;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
let normalized = this.normalize(str);
|
|
382
|
-
if (typeof normalized === 'object') {
|
|
383
|
-
logger?.log?.(`Digging into the object ${JSON.stringify(normalized)}`);
|
|
384
|
-
const process = async (obj) => {
|
|
385
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
386
|
-
if (typeof v === "string") {
|
|
387
|
-
logger?.log?.(`Evaluating the inner ${k}: ${v}`);
|
|
388
|
-
obj[k] = await this.eval(v, [props_obj], logger?.nested?.());
|
|
389
|
-
} else if (typeof v === "object") {
|
|
390
|
-
logger?.log?.(`Processing the inner object ${k}`);
|
|
391
|
-
process(v);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
await process(normalized);
|
|
396
|
-
return normalized;
|
|
397
|
-
}
|
|
398
|
-
if (/^\w+(\.\w+)*$/.test(normalized)) {
|
|
399
|
-
logger?.log?.(`Trying to short circuit to return the dot notation ${normalized}`);
|
|
400
|
-
// We're capable of looking up a simple dot notation access
|
|
401
|
-
const result = dig(props_obj, normalized);
|
|
402
|
-
if (result !== undefined) return result
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Rewrite array.0 into index array 0
|
|
406
|
-
str = str.replace(/(.*)\.(\d+)$/, (_, obj, index) => {
|
|
407
|
-
return `index (${obj}) ${index}`;
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
const variable_pairs = mapHugoVariablesToParams(full_props, ".Params.full_props");
|
|
411
|
-
const variable_decl = Object.entries(variable_pairs).map(([k, v]) => {
|
|
412
|
-
return `{{ ${k} := ${v} }}`
|
|
413
|
-
}).join('');
|
|
414
|
-
|
|
415
|
-
const assignments = Object.entries(props_obj).filter(([key]) => key.startsWith('$')).map(([key, value]) => {
|
|
416
|
-
if (Array.isArray(value)) {
|
|
417
|
-
return `{{ ${key} := index ( \`{"a": ${JSON.stringify(value)}}\` | transform.Unmarshal ) "a" }}`
|
|
418
|
-
} else if (typeof value === 'object') {
|
|
419
|
-
return `{{ ${key} := \`${JSON.stringify(value)}\` | transform.Unmarshal }}`
|
|
420
|
-
} else {
|
|
421
|
-
return `{{ ${key} := ${JSON.stringify(value)} }}`
|
|
422
|
-
}
|
|
423
|
-
}).join('');
|
|
424
|
-
const eval_str = `${variable_decl}{{ with .Params.props }}${assignments}{{ jsonify (${str}) }}{{ end }}`;
|
|
425
|
-
|
|
426
|
-
if (this.synthetic) {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
window.writeHugoFiles(JSON.stringify({
|
|
431
|
-
"layouts/index.html": eval_str,
|
|
432
|
-
"content/_index.md": JSON.stringify({ props: props_obj, full_props: full_props }, null, 2)
|
|
433
|
-
}));
|
|
434
|
-
|
|
435
|
-
const buildError = window.buildHugo();
|
|
436
|
-
if (buildError) {
|
|
437
|
-
console.warn(buildError);
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const output = window.readHugoFiles(JSON.stringify([
|
|
442
|
-
"public/index.html"
|
|
443
|
-
]))["public/index.html"];
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
return JSON.parse(output);
|
|
447
|
-
} catch (e) {
|
|
448
|
-
logger?.log?.(`Error evaluating \`${str}\` in the Hugo engine`);
|
|
449
|
-
logger?.log?.(output);
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
normalize(str) {
|
|
455
|
-
return (new IdentifierParser(str)).build();
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
loader() {
|
|
459
|
-
// esbuild loader if required
|
|
460
|
-
}
|
|
461
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
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
|
-
let ident = `(index (.items) 5)`;
|
|
70
|
-
let output = (new IdentifierParser(ident)).build();
|
|
71
|
-
t.deepEqual(output, `items.5`);
|
|
72
|
-
|
|
73
|
-
ident = `(index .array 5)`;
|
|
74
|
-
output = (new IdentifierParser(ident)).build();
|
|
75
|
-
t.deepEqual(output, `array.5`);
|
|
76
|
-
|
|
77
|
-
ident = `(index $variable 5)`;
|
|
78
|
-
output = (new IdentifierParser(ident)).build();
|
|
79
|
-
t.deepEqual(output, `$variable.5`);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
test(`Converts a dot index into a JS object property`, async t => {
|
|
84
|
-
let ident = `(index (.) 5)`;
|
|
85
|
-
let output = (new IdentifierParser(ident)).build();
|
|
86
|
-
t.deepEqual(output, `5`);
|
|
87
|
-
|
|
88
|
-
ident = `(index . 1234)`;
|
|
89
|
-
output = (new IdentifierParser(ident)).build();
|
|
90
|
-
t.deepEqual(output, `1234`);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test(`Multiline dicts and slices`, async t => {
|
|
94
|
-
const ident = `(dict
|
|
95
|
-
"contents"
|
|
96
|
-
(slice 1 "2"
|
|
97
|
-
(dict
|
|
98
|
-
"a" .Params.b)
|
|
99
|
-
) "len" (
|
|
100
|
-
len
|
|
101
|
-
.some_array
|
|
102
|
-
))`;
|
|
103
|
-
const output = (new IdentifierParser(ident)).build();
|
|
104
|
-
t.deepEqual(output, {
|
|
105
|
-
"contents": ["1", `"2"`, {
|
|
106
|
-
"a": "Params.b"
|
|
107
|
-
}],
|
|
108
|
-
"len": `(\n len\n .some_array\n)`
|
|
109
|
-
});
|
|
110
|
-
});
|