@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.
- package/.nyc_output/50eb2ebd-c797-4d3f-8df7-a707c1b211eb.json +1 -0
- package/.nyc_output/86f85916-4f58-43da-8c32-c1ec29ca783e.json +1 -0
- package/.nyc_output/a27680c8-8858-4ede-921d-6e6617f42a0b.json +1 -0
- package/.nyc_output/b5f86652-cbe8-44f9-a36c-d5721dfe5bbe.json +1 -0
- package/.nyc_output/ecca87c1-5774-4962-864a-2e4537aa9915.json +1 -0
- package/.nyc_output/processinfo/50eb2ebd-c797-4d3f-8df7-a707c1b211eb.json +1 -0
- package/.nyc_output/processinfo/86f85916-4f58-43da-8c32-c1ec29ca783e.json +1 -0
- package/.nyc_output/processinfo/a27680c8-8858-4ede-921d-6e6617f42a0b.json +1 -0
- package/.nyc_output/processinfo/b5f86652-cbe8-44f9-a36c-d5721dfe5bbe.json +1 -0
- package/.nyc_output/processinfo/ecca87c1-5774-4962-864a-2e4537aa9915.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/build.js +1 -0
- package/hugo-renderer/build.sh +16 -0
- package/hugo-renderer/deps/bookshop_modified_deps.go +6 -0
- package/hugo-renderer/go.mod +55 -0
- package/hugo-renderer/go.sum +886 -0
- package/hugo-renderer/helpers/bookshop_modified_content.go +64 -0
- package/hugo-renderer/helpers/bookshop_modified_general.go +33 -0
- package/hugo-renderer/hugo_renderer.wasm +0 -0
- package/hugo-renderer/main.go +28 -0
- package/hugo-renderer/tpl/bookshop_engine/bookshop_func_importer.go +34 -0
- package/hugo-renderer/tpl/bookshop_engine/bookshop_render.go +64 -0
- package/hugo-renderer/tpl/bookshop_library/bookshop_components.go +107 -0
- package/hugo-renderer/tpl/cast/cast.go +63 -0
- package/hugo-renderer/tpl/cast/init.go +57 -0
- package/hugo-renderer/tpl/collections/append.go +37 -0
- package/hugo-renderer/tpl/collections/apply.go +131 -0
- package/hugo-renderer/tpl/collections/collections.go +767 -0
- package/hugo-renderer/tpl/collections/complement.go +55 -0
- package/hugo-renderer/tpl/collections/index.go +133 -0
- package/hugo-renderer/tpl/collections/init.go +214 -0
- package/hugo-renderer/tpl/collections/merge.go +126 -0
- package/hugo-renderer/tpl/collections/reflect_helpers.go +216 -0
- package/hugo-renderer/tpl/collections/sort.go +183 -0
- package/hugo-renderer/tpl/collections/symdiff.go +68 -0
- package/hugo-renderer/tpl/collections/where.go +517 -0
- package/hugo-renderer/tpl/compare/compare.go +324 -0
- package/hugo-renderer/tpl/compare/init.go +85 -0
- package/hugo-renderer/tpl/crypto/crypto.go +109 -0
- package/hugo-renderer/tpl/crypto/init.go +65 -0
- package/hugo-renderer/tpl/debug/debug.go +40 -0
- package/hugo-renderer/tpl/debug/init.go +45 -0
- package/hugo-renderer/tpl/encoding/encoding.go +90 -0
- package/hugo-renderer/tpl/encoding/init.go +59 -0
- package/hugo-renderer/tpl/fmt/fmt.go +64 -0
- package/hugo-renderer/tpl/fmt/init.go +77 -0
- package/hugo-renderer/tpl/inflect/inflect.go +79 -0
- package/hugo-renderer/tpl/inflect/init.go +60 -0
- package/hugo-renderer/tpl/internal/bookshop_modified_templatefuncsRegistry.go +88 -0
- package/hugo-renderer/tpl/math/init.go +134 -0
- package/hugo-renderer/tpl/math/math.go +164 -0
- package/hugo-renderer/tpl/math/round.go +61 -0
- package/hugo-renderer/tpl/partials/init.go +55 -0
- package/hugo-renderer/tpl/partials/partials.go +86 -0
- package/hugo-renderer/tpl/path/init.go +60 -0
- package/hugo-renderer/tpl/path/path.go +159 -0
- package/hugo-renderer/tpl/reflect/init.go +51 -0
- package/hugo-renderer/tpl/reflect/reflect.go +36 -0
- package/hugo-renderer/tpl/safe/init.go +80 -0
- package/hugo-renderer/tpl/safe/safe.go +72 -0
- package/hugo-renderer/tpl/strings/init.go +229 -0
- package/hugo-renderer/tpl/strings/regexp.go +125 -0
- package/hugo-renderer/tpl/strings/strings.go +500 -0
- package/hugo-renderer/tpl/strings/truncate.go +157 -0
- package/hugo-renderer/wasm_exec.js +636 -0
- package/lib/builder.js +21 -0
- package/lib/engine.js +134 -0
- package/lib/engine.test.js +5 -0
- package/lib/hugoIdentifierParser.js +324 -0
- package/lib/hugoIdentifierParser.test.js +72 -0
- package/lib/translateTextTemplate.js +58 -0
- package/lib/translateTextTemplate.test.js +39 -0
- package/main.js +1 -0
- package/main.test.js +5 -0
- 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,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
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
|
+
}
|