mikel 0.28.0 → 0.30.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 +66 -6
- package/index.d.ts +4 -2
- package/index.js +69 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -433,10 +433,10 @@ const result = m(template, data, options);
|
|
|
433
433
|
console.log(result); // Output: "Hello, World!"
|
|
434
434
|
```
|
|
435
435
|
|
|
436
|
-
Custom helper functions receive a single object as argument, containing the following
|
|
436
|
+
Custom helper functions receive a single `params` object as argument, containing the following fields:
|
|
437
437
|
|
|
438
438
|
- `args`: an array containing the variables with the helper is called in the template.
|
|
439
|
-
- `
|
|
439
|
+
- `options`: an object containing the keyword arguments provided to the helper.
|
|
440
440
|
- `data`: the current data where the helper has been executed.
|
|
441
441
|
- `variables`: an object containing the runtime variables available in the current context (e.g., `@root`, `@index`, etc.).
|
|
442
442
|
- `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
|
|
@@ -562,10 +562,10 @@ Mikel allows users to define custom functions that can be used within templates
|
|
|
562
562
|
|
|
563
563
|
Functions should be provided in the `options.functions` field of the options object when rendering a template. Each function is defined by a name and a corresponding function that performs the desired operation.
|
|
564
564
|
|
|
565
|
-
Functions will receive a single object as argument, containing the following keys:
|
|
565
|
+
Functions will receive a single `params` object as argument, containing the following keys:
|
|
566
566
|
|
|
567
567
|
- `args`: an array containing the variables with the function is called in the template.
|
|
568
|
-
- `
|
|
568
|
+
- `options`: an object containing the keyword arguments provided to the function.
|
|
569
569
|
- `data`: the current data object where the function has been executed.
|
|
570
570
|
- `variables`: an object containing the runtime variables available in the current context (e.g., `@root`, `@index`, etc.).
|
|
571
571
|
|
|
@@ -592,7 +592,7 @@ console.log(result); // --> "My name is: John Doe"
|
|
|
592
592
|
|
|
593
593
|
#### Expand function arguments using the spread operator
|
|
594
594
|
|
|
595
|
-
> This feature was
|
|
595
|
+
> This feature was added in `v0.20.0`.
|
|
596
596
|
|
|
597
597
|
You can use the spread operator `...` to expand the arguments of a function. This allows you to pass an array of values as individual arguments to the function, or to pass an object as keyword arguments.
|
|
598
598
|
|
|
@@ -632,7 +632,7 @@ const data = {
|
|
|
632
632
|
const options = {
|
|
633
633
|
functions: {
|
|
634
634
|
fullName: params => {
|
|
635
|
-
return `${params.
|
|
635
|
+
return `${params.options.firstName} ${params.options.lastName}`;
|
|
636
636
|
}
|
|
637
637
|
},
|
|
638
638
|
};
|
|
@@ -641,6 +641,66 @@ const result = m("Users: {{=fullName ...user1}} and {{=fullName ...user2}}", dat
|
|
|
641
641
|
console.log(result); // --> "Users: John Doe and Alice Smith"
|
|
642
642
|
```
|
|
643
643
|
|
|
644
|
+
Of course, Jose — here’s a version of the **Subexpressions** documentation written to perfectly match the tone, structure, and formatting conventions of the current README.
|
|
645
|
+
It follows the same patterns: short intro, version note, examples, concise explanations, no extra fluff.
|
|
646
|
+
|
|
647
|
+
### Subexpressions
|
|
648
|
+
|
|
649
|
+
> Added in `v0.30.0`.
|
|
650
|
+
|
|
651
|
+
Subexpressions allow you to evaluate a function call inside another function call. They are written using parentheses, and can be used anywhere a normal function argument is allowed. Example:
|
|
652
|
+
|
|
653
|
+
```hbs
|
|
654
|
+
{{=sum (sum 3 4) 3}}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
In this example, the inner expression is evaluated first:
|
|
658
|
+
|
|
659
|
+
- `(sum 3 4)` → `7`
|
|
660
|
+
- `sum 7 3` → `10`
|
|
661
|
+
|
|
662
|
+
Result:
|
|
663
|
+
|
|
664
|
+
```
|
|
665
|
+
10
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
#### Nested subexpressions
|
|
669
|
+
|
|
670
|
+
Subexpressions can be nested to any depth:
|
|
671
|
+
|
|
672
|
+
```hbs
|
|
673
|
+
{{=sum (sum 1 (sum 2 3)) 4}}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### Using strings inside subexpressions
|
|
677
|
+
|
|
678
|
+
Strings behave the same way inside subexpressions, including quoted strings with spaces:
|
|
679
|
+
|
|
680
|
+
```hbs
|
|
681
|
+
{{=concat "Hello " (upper name)}}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
If `name = "world"`:
|
|
685
|
+
|
|
686
|
+
```
|
|
687
|
+
Hello WORLD
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
#### Variables inside subexpresspressions
|
|
691
|
+
|
|
692
|
+
You can reference variables or paths normally:
|
|
693
|
+
|
|
694
|
+
```hbs
|
|
695
|
+
{{=sum (sum price tax) shipping}}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
#### Limitations
|
|
699
|
+
|
|
700
|
+
- Subexpressions are currently supported **only for functions** (`{{=...}}`).
|
|
701
|
+
- Subexpressions inside helper arguments are not yet supported.
|
|
702
|
+
- Parentheses must be balanced; malformed expressions will throw an error.
|
|
703
|
+
|
|
644
704
|
|
|
645
705
|
## API
|
|
646
706
|
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type MikelHelper = (params: {
|
|
2
2
|
args: any[];
|
|
3
|
-
opt
|
|
3
|
+
opt?: Record<string, any>;
|
|
4
|
+
options: Record<string, any>;
|
|
4
5
|
tokens: string[];
|
|
5
6
|
data: Record<string, any>;
|
|
6
7
|
variables: Record<string, any>;
|
|
@@ -14,7 +15,8 @@ export type MikelPartial = {
|
|
|
14
15
|
|
|
15
16
|
export type MikelFunction = (params: {
|
|
16
17
|
args: any[];
|
|
17
|
-
opt
|
|
18
|
+
opt?: Record<string, any>;
|
|
19
|
+
options: Record<string,any>;
|
|
18
20
|
data: Record<string, any>;
|
|
19
21
|
variables: Record<string, any>;
|
|
20
22
|
}) => string | void;
|
package/index.js
CHANGED
|
@@ -26,29 +26,80 @@ const untokenize = (ts = [], s = "{{", e = "}}") => {
|
|
|
26
26
|
return ts.length > 0 ? ts.reduce((p, t, i) => p + (i % 2 === 0 ? e : s) + t) : "";
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// @description tokenize args
|
|
30
|
+
const tokenizeArgs = (str = "", tokens = [], strings = []) => {
|
|
31
|
+
let current = "", depth = 0;
|
|
32
|
+
// 1. replace strings
|
|
33
|
+
str = str.replace(/"([^"\\]|\\.)*"/g, (match) => {
|
|
34
|
+
const id = `__STR${strings.length}__`;
|
|
35
|
+
strings.push(match);
|
|
36
|
+
return id;
|
|
37
|
+
});
|
|
38
|
+
// 2. tokenize arguments
|
|
39
|
+
for (let i = 0; i < str.length; i++) {
|
|
40
|
+
const c = str[i];
|
|
41
|
+
if (c === "(") {
|
|
42
|
+
depth++;
|
|
43
|
+
current = current + c;
|
|
44
|
+
} else if (c === ")") {
|
|
45
|
+
depth--;
|
|
46
|
+
current = current + c;
|
|
47
|
+
} else if (c === " " && depth === 0) {
|
|
48
|
+
if (current.trim()) {
|
|
49
|
+
tokens.push(current.trim());
|
|
50
|
+
}
|
|
51
|
+
current = "";
|
|
52
|
+
} else {
|
|
53
|
+
current = current + c;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 3. add current token
|
|
57
|
+
if (current.trim()) {
|
|
58
|
+
tokens.push(current.trim());
|
|
59
|
+
}
|
|
60
|
+
// 4. replace strings back and return parsed tokens
|
|
61
|
+
return tokens.map(token => {
|
|
62
|
+
return token.replace(/__STR(\d+)__/g, (_, i) => strings[i]);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
29
66
|
// @description parse string arguments
|
|
30
|
-
const parseArgs = (str = "", data = {}, vars = {}, argv = [], opt = {}) => {
|
|
31
|
-
const [t, ...args] = str.trim()
|
|
67
|
+
const parseArgs = (str = "", data = {}, vars = {}, fns = {}, argv = [], opt = {}) => {
|
|
68
|
+
const [t, ...args] = tokenizeArgs(str.trim());
|
|
32
69
|
args.forEach(argStr => {
|
|
33
70
|
if (argStr.includes("=") && !argStr.startsWith(`"`)) {
|
|
34
71
|
const [k, v] = argStr.split("=");
|
|
35
|
-
opt[k] = parse(v, data, vars);
|
|
72
|
+
opt[k] = parse(v, data, vars, fns);
|
|
36
73
|
}
|
|
37
74
|
else if (argStr.startsWith("...")) {
|
|
38
|
-
const value = parse(argStr.replace(/^\.{3}/, ""), data, vars);
|
|
75
|
+
const value = parse(argStr.replace(/^\.{3}/, ""), data, vars, fns);
|
|
39
76
|
if (!!value && typeof value === "object") {
|
|
40
77
|
Array.isArray(value) ? argv.push(...value) : Object.assign(opt, value);
|
|
41
78
|
}
|
|
42
79
|
}
|
|
43
80
|
else {
|
|
44
|
-
argv.push(parse(argStr, data, vars));
|
|
81
|
+
argv.push(parse(argStr, data, vars, fns));
|
|
45
82
|
}
|
|
46
83
|
});
|
|
47
84
|
return [t, argv, opt];
|
|
48
85
|
};
|
|
49
86
|
|
|
87
|
+
// @description evaluate an expression
|
|
88
|
+
const evaluateExpression = (str = "", data = {}, vars = {}, fns = {}) => {
|
|
89
|
+
const [ fnName, args, opt ] = parseArgs(str, data, vars, fns);
|
|
90
|
+
if (typeof fns[fnName] === "function") {
|
|
91
|
+
return fns[fnName]({args, opt, options: opt, data, variables: vars});
|
|
92
|
+
}
|
|
93
|
+
// if no function has been found with this name
|
|
94
|
+
// throw new Error(`Unknown function '${fnName}'`);
|
|
95
|
+
return "";
|
|
96
|
+
};
|
|
97
|
+
|
|
50
98
|
// @description parse a string value to a native type
|
|
51
|
-
const parse = (v, data = {}, vars = {}) => {
|
|
99
|
+
const parse = (v, data = {}, vars = {}, fns = {}) => {
|
|
100
|
+
if (v.startsWith("(") && v.endsWith(")")) {
|
|
101
|
+
return evaluateExpression(v.slice(1, -1).trim(), data, vars, fns);
|
|
102
|
+
}
|
|
52
103
|
if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d+\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
|
|
53
104
|
return JSON.parse(v);
|
|
54
105
|
}
|
|
@@ -75,9 +126,17 @@ const findClosingToken = (tokens, i, token) => {
|
|
|
75
126
|
const defaultHelpers = {
|
|
76
127
|
"each": p => {
|
|
77
128
|
const items = typeof p.args[0] === "object" ? Object.entries(p.args[0] || {}) : [];
|
|
78
|
-
const limit = Math.min(items.length - (p.
|
|
79
|
-
return items.slice(p.
|
|
80
|
-
.map((item, index) =>
|
|
129
|
+
const limit = Math.min(items.length - (p.options?.skip || 0), p.options?.limit || items.length);
|
|
130
|
+
return items.slice(p.options?.skip || 0, (p.options?.skip || 0) + limit)
|
|
131
|
+
.map((item, index) => {
|
|
132
|
+
return p.fn(item[1], {
|
|
133
|
+
index: index,
|
|
134
|
+
key: item[0],
|
|
135
|
+
value: item[1],
|
|
136
|
+
first: index === 0,
|
|
137
|
+
last: index === items.length - 1,
|
|
138
|
+
});
|
|
139
|
+
})
|
|
81
140
|
.join("");
|
|
82
141
|
},
|
|
83
142
|
"if": p => !!p.args[0] ? p.fn(p.data) : "",
|
|
@@ -181,10 +240,7 @@ const create = (options = {}) => {
|
|
|
181
240
|
}
|
|
182
241
|
}
|
|
183
242
|
else if (tokens[i].startsWith("=")) {
|
|
184
|
-
|
|
185
|
-
if (typeof ctx.functions[t] === "function") {
|
|
186
|
-
output.push(ctx.functions[t]({args, opt, data, variables: vars}) || "");
|
|
187
|
-
}
|
|
243
|
+
output.push(evaluateExpression(tokens[i].slice(1), data, vars, ctx.functions) ?? "");
|
|
188
244
|
}
|
|
189
245
|
else if (tokens[i].startsWith("/")) {
|
|
190
246
|
if (tokens[i].slice(1).trim() !== section) {
|