mikel 0.29.0 → 0.30.1

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.
Files changed (3) hide show
  1. package/README.md +61 -1
  2. package/index.js +58 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -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 addedin `v0.20.0`.
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
 
@@ -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.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().match(/(?:[^\s"]+|"[^"]*")+/g);
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
  }
@@ -189,10 +240,7 @@ const create = (options = {}) => {
189
240
  }
190
241
  }
191
242
  else if (tokens[i].startsWith("=")) {
192
- const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
193
- if (typeof ctx.functions[t] === "function") {
194
- output.push(ctx.functions[t]({args, opt, options: opt, data, variables: vars}) || "");
195
- }
243
+ output.push(evaluateExpression(tokens[i].slice(1), data, vars, ctx.functions) ?? "");
196
244
  }
197
245
  else if (tokens[i].startsWith("/")) {
198
246
  if (tokens[i].slice(1).trim() !== section) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikel",
3
3
  "description": "Micro templating library with zero dependencies",
4
- "version": "0.29.0",
4
+ "version": "0.30.1",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",