mikel 0.9.2 → 0.11.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/README.md +82 -22
- package/index.js +86 -117
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,6 +219,23 @@ The `ne` helper renders the block only if the two values provided as argument ar
|
|
|
219
219
|
console.log(m(`{{#ne name "bob"}}Not bob{{/ne}}`, {name: "John"})); // --> 'Not bob'
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
#### with
|
|
223
|
+
|
|
224
|
+
> Added in `v0.10.0`.
|
|
225
|
+
|
|
226
|
+
The `with` helper allows to change the data context of the block.
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
const data = {
|
|
230
|
+
autor: {
|
|
231
|
+
name: "Bob",
|
|
232
|
+
email: "bob@email.com",
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
console.log(m("{{#with autor}}{{name}} <{{email}}>{{/with}}", data)); // --> 'Bob <bob@email.com>'
|
|
237
|
+
```
|
|
238
|
+
|
|
222
239
|
### Custom Helpers
|
|
223
240
|
|
|
224
241
|
> Added in `v0.5.0`.
|
|
@@ -323,27 +340,6 @@ The `@last` variable allows to check if the current iteration using the `#each`
|
|
|
323
340
|
{{#each items}}{{@index}}:{{.}} {{#unless @last}},{{/unless}}{{/each}}
|
|
324
341
|
```
|
|
325
342
|
|
|
326
|
-
### Custom runtime variables
|
|
327
|
-
|
|
328
|
-
> Added in `v0.5.0`.
|
|
329
|
-
|
|
330
|
-
Mikel allows users to define custom data variables, providing enhanced flexibility and customization options for templates. These custom data variables can be accessed within the template using the `@` character.
|
|
331
|
-
|
|
332
|
-
Custom data variables should be provided in the `options.variables` field of the options object when rendering a template. Each custom data variable should be defined as a key-value pair, where the key represents the variable name and the value represents the data associated with that variable.
|
|
333
|
-
|
|
334
|
-
Example:
|
|
335
|
-
|
|
336
|
-
```javascript
|
|
337
|
-
const result = m("Hello, {{@customVariable}}!", {}, {
|
|
338
|
-
variables: {
|
|
339
|
-
customVariable: "World",
|
|
340
|
-
},
|
|
341
|
-
});
|
|
342
|
-
console.log(result); // --> 'Hello, World!'
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
In this example, the custom data variable `customVariable` is defined with the value `"World"`, and it can be accessed in the template using `@customVariable`.
|
|
346
|
-
|
|
347
343
|
### Functions
|
|
348
344
|
|
|
349
345
|
> Added in `v0.8.0`.
|
|
@@ -386,7 +382,6 @@ Render the given template string with the provided data object and options.
|
|
|
386
382
|
- `data` (object): the data object containing the values to render.
|
|
387
383
|
- `options` (object): an object containing the following optional values:
|
|
388
384
|
- `partials` (object): an object containing the available partials.
|
|
389
|
-
- `variables` (object): an object containing custom data variables.
|
|
390
385
|
- `helpers` (object): an object containing custom helpers.
|
|
391
386
|
- `functions` (object): and object containing custom functions.
|
|
392
387
|
|
|
@@ -403,6 +398,71 @@ const result = mikel("Hello, {{name}}!", data);
|
|
|
403
398
|
console.log(result); // Output: "Hello, World!"
|
|
404
399
|
```
|
|
405
400
|
|
|
401
|
+
### `mikel.create(template [, options])`
|
|
402
|
+
|
|
403
|
+
Allows to create an isolated instance of mikel, useful when you want to compile the same template using different data. The `template` argument is the template string, and the optional `options` argument is the same options object that you can pass to `mikel` method.
|
|
404
|
+
|
|
405
|
+
It returns a function that you can call with the data to compile the template.
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
import mikel from "mikel";
|
|
409
|
+
|
|
410
|
+
const template = mikel.create("Hello, {{name}}!");
|
|
411
|
+
|
|
412
|
+
console.log(template({name: "Bob"})); // --> "Hello, Bob!"
|
|
413
|
+
console.log(template({name: "Susan"})); // --> "Hello, Susan!"
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
It also exposes the following additional methods:
|
|
417
|
+
|
|
418
|
+
#### `template.addHelper(helperName, helperFn)`
|
|
419
|
+
|
|
420
|
+
Allows to register a new helper instead of using the `options` object.
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
template.addHelper("foo", () => { ... });
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### `template.removeHelper(helperName)`
|
|
427
|
+
|
|
428
|
+
Removes a previously added helper.
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
template.removeHelper("foo");
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### `template.addPartial(partialName, partialCode)`
|
|
435
|
+
|
|
436
|
+
Registers a new partial instead of using the `options` object.
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
template.addPartial("bar", " ... ");
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
#### `template.removePartial(partialName)`
|
|
443
|
+
|
|
444
|
+
Removes a previously added partial.
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
template.removePartial("bar");
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### `template.addFunction(fnName, fn)`
|
|
451
|
+
|
|
452
|
+
Registers a new function instead of using the `options` object.
|
|
453
|
+
|
|
454
|
+
```javascript
|
|
455
|
+
template.addFunction("foo", () => "...");
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### `template.removeFunction(fnName)`
|
|
459
|
+
|
|
460
|
+
Removes a previously added function.
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
template.removeFunction("foo");
|
|
464
|
+
```
|
|
465
|
+
|
|
406
466
|
### `mikel.escape(str)`
|
|
407
467
|
|
|
408
468
|
This function converts special HTML characters `&`, `<`, `>`, `"`, and `'` to their corresponding HTML entities.
|
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const tags = /\{\{|\}\}/;
|
|
2
|
-
const endl = "\n";
|
|
3
2
|
const escapedChars = {
|
|
4
3
|
"&": "&",
|
|
5
4
|
"<": "<",
|
|
@@ -19,56 +18,6 @@ const parse = (v, context = {}, vars = {}) => {
|
|
|
19
18
|
return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || ".");
|
|
20
19
|
};
|
|
21
20
|
|
|
22
|
-
// @description tiny yaml parser
|
|
23
|
-
const yamlParser = (str = "") => {
|
|
24
|
-
const lines = str.split(endl), result = {}, levels = [{ctx: result, indent: 0}];
|
|
25
|
-
let level = 0, i = 0;
|
|
26
|
-
while (i < lines.length) {
|
|
27
|
-
const line = lines[i] || "";
|
|
28
|
-
if (!!line.trim() && !line.trim().startsWith("#")) {
|
|
29
|
-
const indent = (line.match(/^( *)/m)?.[0] || "").length;
|
|
30
|
-
while(level > 0 && indent < levels[level].indent) {
|
|
31
|
-
levels.pop();
|
|
32
|
-
level = level - 1;
|
|
33
|
-
}
|
|
34
|
-
const isArrayItem = line.trim().startsWith("-");
|
|
35
|
-
// TODO: improve the regex to capture the line structure
|
|
36
|
-
let [_, key, value] = (line.trim().match(/^([^:"]*):(.*)$/m) || ["", line]).map(v => v.trim());
|
|
37
|
-
// Check if is a new item of the array
|
|
38
|
-
if (isArrayItem) {
|
|
39
|
-
key = key.replace(/^(- *)/m, "");
|
|
40
|
-
if (typeof value === "undefined") {
|
|
41
|
-
value = key;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const newIndent = (line.slice(0, line.indexOf(key))).length;
|
|
45
|
-
levels[level].ctx.push({});
|
|
46
|
-
levels.push({ctx: levels[level].ctx[levels[level].ctx.length - 1], indent: newIndent});
|
|
47
|
-
level = level + 1;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Check for empty value --> entering into a nested object or array
|
|
51
|
-
if (!value) {
|
|
52
|
-
const nextLine = lines[i + 1] || "";
|
|
53
|
-
const nextIndent = (nextLine.match(/^( *)/m)?.[0] || "").length;
|
|
54
|
-
levels[level].ctx[key] = nextLine.trim().startsWith("-") ? [] : {};
|
|
55
|
-
if (nextIndent > levels[level].indent) {
|
|
56
|
-
levels.push({ctx: levels[level].ctx[key], indent: nextIndent});
|
|
57
|
-
level = level + 1;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else if(value && Array.isArray(levels[level].ctx)) {
|
|
61
|
-
levels[level].ctx.push(JSON.parse(value));
|
|
62
|
-
}
|
|
63
|
-
else if (value) {
|
|
64
|
-
levels[level].ctx[key] = JSON.parse(value);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
i = i + 1;
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
21
|
// @description tiny front-matter parser
|
|
73
22
|
const frontmatter = (str = "", parser = null) => {
|
|
74
23
|
let body = (str || "").trim(), data = {};
|
|
@@ -76,11 +25,12 @@ const frontmatter = (str = "", parser = null) => {
|
|
|
76
25
|
if (matches?.length === 2 && matches[0].index === 0) {
|
|
77
26
|
const front = body.substring(0 + matches[0][1].length, matches[1].index).trim();
|
|
78
27
|
body = body.substring(matches[1].index + matches[1][1].length).trim();
|
|
79
|
-
data = typeof parser === "function" ? parser(front) :
|
|
28
|
+
data = typeof parser === "function" ? parser(front) : front;
|
|
80
29
|
}
|
|
81
30
|
return {body, data};
|
|
82
31
|
};
|
|
83
32
|
|
|
33
|
+
// @description default helpers
|
|
84
34
|
const defaultHelpers = {
|
|
85
35
|
"each": (value, opt) => {
|
|
86
36
|
return (typeof value === "object" ? Object.entries(value || {}) : [])
|
|
@@ -91,90 +41,109 @@ const defaultHelpers = {
|
|
|
91
41
|
"unless": (value, opt) => !!!value ? opt.fn(opt.context) : "",
|
|
92
42
|
"eq": (a, b, opt) => a === b ? opt.fn(opt.context) : "",
|
|
93
43
|
"ne": (a, b, opt) => a !== b ? opt.fn(opt.context) : "",
|
|
44
|
+
"with": (value, opt) => opt.fn(value),
|
|
94
45
|
};
|
|
95
46
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
109
|
-
const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
110
|
-
const j = i + 1;
|
|
111
|
-
output.push(helpers[t](...args.map(v => parse(v, context, vars)), {
|
|
112
|
-
context: context,
|
|
113
|
-
fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
|
|
114
|
-
i = compile(tokens, blockOutput, blockContext, partials, helpers, {...vars, ...blockVars, root: vars.root}, fn, j, t);
|
|
115
|
-
return blockOutput.join("");
|
|
116
|
-
},
|
|
117
|
-
}));
|
|
118
|
-
// Make sure that this block is executed at least once
|
|
119
|
-
if (i + 1 === j) {
|
|
120
|
-
i = compile(tokens, [], {}, {}, helpers, {}, {}, j, t);
|
|
47
|
+
// @description create a new instance of mikel
|
|
48
|
+
const create = (template = "", options = {}) => {
|
|
49
|
+
// initialize internal context
|
|
50
|
+
const helpers = Object.assign({}, defaultHelpers, options?.helpers || {});
|
|
51
|
+
const partials = options?.partials || {};
|
|
52
|
+
const functions = options?.functions || {};
|
|
53
|
+
// internal method to compile the template
|
|
54
|
+
const compile = (tokens, output, context, vars, index = 0, section = "") => {
|
|
55
|
+
let i = index;
|
|
56
|
+
while (i < tokens.length) {
|
|
57
|
+
if (i % 2 === 0) {
|
|
58
|
+
output.push(tokens[i]);
|
|
121
59
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
60
|
+
else if (tokens[i].startsWith("@")) {
|
|
61
|
+
output.push(get(vars, tokens[i].slice(1).trim() ?? "_") ?? "");
|
|
62
|
+
}
|
|
63
|
+
else if (tokens[i].startsWith("!")) {
|
|
64
|
+
output.push(get(context, tokens[i].slice(1).trim()));
|
|
65
|
+
}
|
|
66
|
+
else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
67
|
+
const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
128
68
|
const j = i + 1;
|
|
129
|
-
(
|
|
130
|
-
|
|
131
|
-
|
|
69
|
+
output.push(helpers[t](...args.map(v => parse(v, context, vars)), {
|
|
70
|
+
context: context,
|
|
71
|
+
fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
|
|
72
|
+
i = compile(tokens, blockOutput, blockContext, {...vars, ...blockVars, root: vars.root}, j, t);
|
|
73
|
+
return blockOutput.join("");
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
76
|
+
// Make sure that this block is executed at least once
|
|
77
|
+
if (i + 1 === j) {
|
|
78
|
+
i = compile(tokens, [], {}, {}, j, t);
|
|
79
|
+
}
|
|
132
80
|
}
|
|
133
|
-
else {
|
|
134
|
-
const
|
|
135
|
-
|
|
81
|
+
else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
|
|
82
|
+
const t = tokens[i].slice(1).trim();
|
|
83
|
+
const value = get(context, t);
|
|
84
|
+
const negate = tokens[i].startsWith("^");
|
|
85
|
+
if (!negate && value && Array.isArray(value)) {
|
|
86
|
+
const j = i + 1;
|
|
87
|
+
(value.length > 0 ? value : [""]).forEach(item => {
|
|
88
|
+
i = compile(tokens, value.length > 0 ? output : [], item, vars, j, t);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const includeOutput = (!negate && !!value) || (negate && !!!value);
|
|
93
|
+
i = compile(tokens, includeOutput ? output : [], context, vars, i + 1, t);
|
|
94
|
+
}
|
|
136
95
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
96
|
+
else if (tokens[i].startsWith(">")) {
|
|
97
|
+
const [t, v] = tokens[i].slice(1).trim().split(" ");
|
|
98
|
+
if (typeof partials[t] === "string") {
|
|
99
|
+
compile(partials[t].split(tags), output, v ? get(context, v) : context, vars, 0, "");
|
|
100
|
+
}
|
|
142
101
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
102
|
+
else if (tokens[i].startsWith("=")) {
|
|
103
|
+
const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
104
|
+
if (typeof functions[t] === "function") {
|
|
105
|
+
output.push(functions[t](...args.map(v => parse(v, context, vars))) || "");
|
|
106
|
+
}
|
|
148
107
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
108
|
+
else if (tokens[i].startsWith("/")) {
|
|
109
|
+
if (tokens[i].slice(1).trim() !== section) {
|
|
110
|
+
throw new Error(`Unmatched section end: {{${tokens[i]}}}`);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
153
113
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
114
|
+
else {
|
|
115
|
+
output.push(escape(get(context, tokens[i].trim())));
|
|
116
|
+
}
|
|
117
|
+
i = i + 1;
|
|
158
118
|
}
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
119
|
+
return i;
|
|
120
|
+
};
|
|
121
|
+
// entry method to compile the template with the provided data object
|
|
122
|
+
const compileTemplate = (data = {}, output = []) => {
|
|
123
|
+
compile(template.split(tags), output, data, {root: data}, 0, "");
|
|
124
|
+
return output.join("");
|
|
125
|
+
};
|
|
126
|
+
// assign api methods and return method to compile the template
|
|
127
|
+
return Object.assign(compileTemplate, {
|
|
128
|
+
addHelper: (name, fn) => helpers[name] = fn,
|
|
129
|
+
removeHelper: name => delete helpers[name],
|
|
130
|
+
addFunction: (name, fn) => functions[name] = fn,
|
|
131
|
+
removeFunction: name => delete functions[name],
|
|
132
|
+
addPartial: (name, partial) => partials[name] = partial,
|
|
133
|
+
removePartial: name => delete partials[name],
|
|
134
|
+
});
|
|
162
135
|
};
|
|
163
136
|
|
|
164
137
|
// @description main compiler function
|
|
165
|
-
const mikel = (
|
|
166
|
-
|
|
167
|
-
const helpers = Object.assign({}, defaultHelpers, opt.helpers || {});
|
|
168
|
-
const variables = Object.assign({}, opt.variables || {}, {root: context});
|
|
169
|
-
compile(str.split(tags), output, context, partials, helpers, variables, opt.functions || {}, 0, "");
|
|
170
|
-
return output.join("");
|
|
138
|
+
const mikel = (template = "", data = {}, options = {}) => {
|
|
139
|
+
return create(template, options)(data);
|
|
171
140
|
};
|
|
172
141
|
|
|
173
142
|
// @description assign utilities
|
|
143
|
+
mikel.create = create;
|
|
174
144
|
mikel.escape = escape;
|
|
175
145
|
mikel.get = get;
|
|
176
146
|
mikel.parse = parse;
|
|
177
|
-
mikel.yaml = yamlParser;
|
|
178
147
|
mikel.frontmatter = frontmatter;
|
|
179
148
|
|
|
180
149
|
export default mikel;
|