mikel 0.11.1 → 0.13.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.
Files changed (3) hide show
  1. package/README.md +49 -9
  2. package/index.js +31 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -110,6 +110,27 @@ const result = m("User: {{> user currentUser}}", data, {partials});
110
110
  // Output: 'User: John Doe <john@example.com>'
111
111
  ```
112
112
 
113
+ #### Keyword arguments in partials
114
+
115
+ > This feature was added in `v0.13.0`.
116
+
117
+ You can provide keyword arguments in partials to generate a new context object using the provided keywords.
118
+
119
+ ```javascript
120
+ const data = {
121
+ name: "John Doe",
122
+ email: "john@example.com",
123
+ };
124
+ const partials = {
125
+ user: "{{userName}} <{{userEmail}}>",
126
+ };
127
+
128
+ const result = m("User: {{>user userName=name userEmail=email }}", data, {partials});
129
+ // Output: 'User: John Doe <john@example.com>'
130
+ ```
131
+
132
+ Please note that providing keyword arguments and a custom context to a partial is not supported. On this situation, the partial will be evaluated only with the custom context.
133
+
113
134
  ### Built-in helpers
114
135
 
115
136
  > Added in `v0.4.0`.
@@ -167,6 +188,17 @@ const data = {
167
188
  console.log(m("{{#each values}}{{@key}}: {{@value}}, {{/each}}", data)); // --> 'foo: 0, bar: 1, '
168
189
  ```
169
190
 
191
+ The `each` helper also supports the following options, provided as keyword arguments:
192
+ - `skip`: number of first items to skip (default is `0`).
193
+ - `limit`: allows to limit the number of items to display (default equals to the length of the items list).
194
+
195
+ Example:
196
+
197
+ ```javascript
198
+ console.log(m("{{each values limit=2}}{{.}}{{/each}}", {values: [0, 1, 2, 3]})); // --> '01'
199
+ ```
200
+
201
+
170
202
  #### if
171
203
 
172
204
  The `if` helper renders the block only if the condition is truthy.
@@ -239,6 +271,7 @@ console.log(m("{{#with autor}}{{name}} <{{email}}>{{/with}}", data)); // --> 'Bo
239
271
  ### Custom Helpers
240
272
 
241
273
  > Added in `v0.5.0`.
274
+ > Breaking change introduced in `v0.12.0`.
242
275
 
243
276
  Custom helpers should be provided as an object in the `options.helpers` field, where each key represents the name of the helper and the corresponding value is a function defining the helper's behavior.
244
277
 
@@ -251,8 +284,8 @@ const data = {
251
284
  };
252
285
  const options = {
253
286
  helpers: {
254
- customHelper: value => {
255
- return `Hello, ${value}!`;
287
+ customHelper: params => {
288
+ return `Hello, ${params.args[0]}!`;
256
289
  },
257
290
  },
258
291
  };
@@ -261,8 +294,10 @@ const result = m(template, data, options);
261
294
  console.log(result); // Output: "Hello, World!"
262
295
  ```
263
296
 
264
- Custom helper functions receive multiple arguments, where the first N arguments are the variables with the helper is called in the template, and the last argument is an options object containing the following keys:
297
+ Custom helper functions receive a single object as argument, containing the following keys:
265
298
 
299
+ - `args`: an array containing the variables with the helper is called in the template.
300
+ - `opt`: an object containing the keyword arguments provided to the helper.
266
301
  - `context`: the current context (data) where the helper has been executed.
267
302
  - `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
268
303
 
@@ -278,8 +313,8 @@ const data = {
278
313
  };
279
314
  const options = {
280
315
  helpers: {
281
- customEach: (items, opt) => {
282
- return items.map((item, index) => opt.fn({ ...item, index: index})).join("");
316
+ customEach: ({args, fn}) => {
317
+ return args[0].map((item, index) => fn({ ...item, index: index})).join("");
283
318
  },
284
319
  },
285
320
  };
@@ -343,11 +378,18 @@ The `@last` variable allows to check if the current iteration using the `#each`
343
378
  ### Functions
344
379
 
345
380
  > Added in `v0.8.0`.
381
+ > Breaking change introduced in `v0.12.0`.
346
382
 
347
383
  Mikel allows users to define custom functions that can be used within templates to perform dynamic operations. Functions can be invoked in the template using the `=` character, followed by the function name and the variables to be provided to the function. Variables should be separated by spaces.
348
384
 
349
385
  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.
350
386
 
387
+ Functions will receive a single object as argument, containing the following keys:
388
+
389
+ - `args`: an array containing the variables with the function is called in the template.
390
+ - `opt`: an object containing the keyword arguments provided to the function.
391
+ - `context`: the current context (data) where the function has been executed.
392
+
351
393
  Example:
352
394
 
353
395
  ```javascript
@@ -359,8 +401,8 @@ const data = {
359
401
  };
360
402
  const options = {
361
403
  functions: {
362
- fullName: (firstName, lastName) => {
363
- return `${firstName} ${lastName}`;
404
+ fullName: ({args}) => {
405
+ return `${args[0]} ${args[1]}`;
364
406
  }
365
407
  },
366
408
  };
@@ -369,8 +411,6 @@ const result = m("My name is: {{=fullName user.firstName user.lastName}}", data,
369
411
  console.log(result); // --> "My name is: John Doe"
370
412
  ```
371
413
 
372
- In this example, the custom function `fullName` is defined to take two arguments, `firstName` and `lastName`, and return the full name. The template then uses this function to concatenate and render the full name.
373
-
374
414
 
375
415
  ## API
376
416
 
package/index.js CHANGED
@@ -11,6 +11,18 @@ const escape = s => s.toString().replace(/[&<>\"']/g, m => escapedChars[m]);
11
11
 
12
12
  const get = (c, p) => (p === "." ? c : p.split(".").reduce((x, k) => x?.[k], c)) ?? "";
13
13
 
14
+ // @description parse string arguments
15
+ const parseArgs = (argString = "", context = {}, vars = {}) => {
16
+ const [t, ...args] = argString.trim().match(/(?:[^\s"]+|"[^"]*")+/g);
17
+ const argv = args.filter(a => !a.includes("=")).map(a => parse(a, context, vars));
18
+ const opt = Object.fromEntries(args.filter(a => a.includes("=")).map(a => {
19
+ const [k, v] = a.split("=");
20
+ return [k, parse(v, context, vars)];
21
+ }));
22
+ return [t, argv, opt];
23
+ };
24
+
25
+ // @description parse a string value to a native type
14
26
  const parse = (v, context = {}, vars = {}) => {
15
27
  if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d+\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
16
28
  return JSON.parse(v);
@@ -32,16 +44,18 @@ const frontmatter = (str = "", parser = null) => {
32
44
 
33
45
  // @description default helpers
34
46
  const defaultHelpers = {
35
- "each": (value, opt) => {
36
- return (typeof value === "object" ? Object.entries(value || {}) : [])
37
- .map((item, index, items) => opt.fn(item[1], {index: index, key: item[0], value: item[1], first: index === 0, last: index === items.length - 1}))
47
+ "each": p => {
48
+ const items = typeof p.args[0] === "object" ? Object.entries(p.args[0] || {}) : [];
49
+ const limit = Math.min(items.length - (p.opt.skip || 0), p.opt.limit || items.length);
50
+ return items.slice(p.opt.skip || 0, (p.opt.skip || 0) + limit)
51
+ .map((item, index) => p.fn(item[1], {index: index, key: item[0], value: item[1], first: index === 0, last: index === items.length - 1}))
38
52
  .join("");
39
53
  },
40
- "if": (value, opt) => !!value ? opt.fn(opt.context) : "",
41
- "unless": (value, opt) => !!!value ? opt.fn(opt.context) : "",
42
- "eq": (a, b, opt) => a === b ? opt.fn(opt.context) : "",
43
- "ne": (a, b, opt) => a !== b ? opt.fn(opt.context) : "",
44
- "with": (value, opt) => opt.fn(value),
54
+ "if": p => !!p.args[0] ? p.fn(p.context) : "",
55
+ "unless": p => !!!p.args[0] ? p.fn(p.context) : "",
56
+ "eq": p => p.args[0] === p.args[1] ? p.fn(p.context) : "",
57
+ "ne": p => p.args[0] !== p.args[1] ? p.fn(p.context) : "",
58
+ "with": p => p.fn(p.args[0]),
45
59
  };
46
60
 
47
61
  // @description create a new instance of mikel
@@ -64,9 +78,11 @@ const create = (template = "", options = {}) => {
64
78
  output.push(get(context, tokens[i].slice(1).trim()));
65
79
  }
66
80
  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);
81
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
68
82
  const j = i + 1;
69
- output.push(helpers[t](...args.map(v => parse(v, context, vars)), {
83
+ output.push(helpers[t]({
84
+ args: args,
85
+ opt: opt,
70
86
  context: context,
71
87
  fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
72
88
  i = compile(tokens, blockOutput, blockContext, {...vars, ...blockVars, root: vars.root}, j, t);
@@ -94,15 +110,16 @@ const create = (template = "", options = {}) => {
94
110
  }
95
111
  }
96
112
  else if (tokens[i].startsWith(">")) {
97
- const [t, v] = tokens[i].slice(1).trim().split(" ");
113
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
98
114
  if (typeof partials[t] === "string") {
99
- compile(partials[t].split(tags), output, v ? get(context, v) : context, vars, 0, "");
115
+ const newCtx = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : context);
116
+ compile(partials[t].split(tags), output, newCtx, vars, 0, "");
100
117
  }
101
118
  }
102
119
  else if (tokens[i].startsWith("=")) {
103
- const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
120
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
104
121
  if (typeof functions[t] === "function") {
105
- output.push(functions[t](...args.map(v => parse(v, context, vars))) || "");
122
+ output.push(functions[t]({args, opt, context}) || "");
106
123
  }
107
124
  }
108
125
  else if (tokens[i].startsWith("/")) {
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.11.1",
4
+ "version": "0.13.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",