mikel 0.10.0 → 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 +65 -22
- package/index.js +85 -117
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,27 +340,6 @@ The `@last` variable allows to check if the current iteration using the `#each`
|
|
|
340
340
|
{{#each items}}{{@index}}:{{.}} {{#unless @last}},{{/unless}}{{/each}}
|
|
341
341
|
```
|
|
342
342
|
|
|
343
|
-
### Custom runtime variables
|
|
344
|
-
|
|
345
|
-
> Added in `v0.5.0`.
|
|
346
|
-
|
|
347
|
-
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.
|
|
348
|
-
|
|
349
|
-
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.
|
|
350
|
-
|
|
351
|
-
Example:
|
|
352
|
-
|
|
353
|
-
```javascript
|
|
354
|
-
const result = m("Hello, {{@customVariable}}!", {}, {
|
|
355
|
-
variables: {
|
|
356
|
-
customVariable: "World",
|
|
357
|
-
},
|
|
358
|
-
});
|
|
359
|
-
console.log(result); // --> 'Hello, World!'
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
In this example, the custom data variable `customVariable` is defined with the value `"World"`, and it can be accessed in the template using `@customVariable`.
|
|
363
|
-
|
|
364
343
|
### Functions
|
|
365
344
|
|
|
366
345
|
> Added in `v0.8.0`.
|
|
@@ -403,7 +382,6 @@ Render the given template string with the provided data object and options.
|
|
|
403
382
|
- `data` (object): the data object containing the values to render.
|
|
404
383
|
- `options` (object): an object containing the following optional values:
|
|
405
384
|
- `partials` (object): an object containing the available partials.
|
|
406
|
-
- `variables` (object): an object containing custom data variables.
|
|
407
385
|
- `helpers` (object): an object containing custom helpers.
|
|
408
386
|
- `functions` (object): and object containing custom functions.
|
|
409
387
|
|
|
@@ -420,6 +398,71 @@ const result = mikel("Hello, {{name}}!", data);
|
|
|
420
398
|
console.log(result); // Output: "Hello, World!"
|
|
421
399
|
```
|
|
422
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
|
+
|
|
423
466
|
### `mikel.escape(str)`
|
|
424
467
|
|
|
425
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 || {}) : [])
|
|
@@ -94,88 +44,106 @@ const defaultHelpers = {
|
|
|
94
44
|
"with": (value, opt) => opt.fn(value),
|
|
95
45
|
};
|
|
96
46
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
110
|
-
const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
111
|
-
const j = i + 1;
|
|
112
|
-
output.push(helpers[t](...args.map(v => parse(v, context, vars)), {
|
|
113
|
-
context: context,
|
|
114
|
-
fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
|
|
115
|
-
i = compile(tokens, blockOutput, blockContext, partials, helpers, {...vars, ...blockVars, root: vars.root}, fn, j, t);
|
|
116
|
-
return blockOutput.join("");
|
|
117
|
-
},
|
|
118
|
-
}));
|
|
119
|
-
// Make sure that this block is executed at least once
|
|
120
|
-
if (i + 1 === j) {
|
|
121
|
-
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]);
|
|
122
59
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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);
|
|
129
68
|
const j = i + 1;
|
|
130
|
-
(
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
133
80
|
}
|
|
134
|
-
else {
|
|
135
|
-
const
|
|
136
|
-
|
|
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
|
+
}
|
|
137
95
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
101
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
}
|
|
149
107
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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;
|
|
154
113
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
114
|
+
else {
|
|
115
|
+
output.push(escape(get(context, tokens[i].trim())));
|
|
116
|
+
}
|
|
117
|
+
i = i + 1;
|
|
159
118
|
}
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
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
|
+
});
|
|
163
135
|
};
|
|
164
136
|
|
|
165
137
|
// @description main compiler function
|
|
166
|
-
const mikel = (
|
|
167
|
-
|
|
168
|
-
const helpers = Object.assign({}, defaultHelpers, opt.helpers || {});
|
|
169
|
-
const variables = Object.assign({}, opt.variables || {}, {root: context});
|
|
170
|
-
compile(str.split(tags), output, context, partials, helpers, variables, opt.functions || {}, 0, "");
|
|
171
|
-
return output.join("");
|
|
138
|
+
const mikel = (template = "", data = {}, options = {}) => {
|
|
139
|
+
return create(template, options)(data);
|
|
172
140
|
};
|
|
173
141
|
|
|
174
142
|
// @description assign utilities
|
|
143
|
+
mikel.create = create;
|
|
175
144
|
mikel.escape = escape;
|
|
176
145
|
mikel.get = get;
|
|
177
146
|
mikel.parse = parse;
|
|
178
|
-
mikel.yaml = yamlParser;
|
|
179
147
|
mikel.frontmatter = frontmatter;
|
|
180
148
|
|
|
181
149
|
export default mikel;
|