mikel 0.11.1 → 0.12.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 +28 -9
  2. package/index.js +28 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -167,6 +167,17 @@ const data = {
167
167
  console.log(m("{{#each values}}{{@key}}: {{@value}}, {{/each}}", data)); // --> 'foo: 0, bar: 1, '
168
168
  ```
169
169
 
170
+ The `each` helper also supports the following options, provided as keyword arguments:
171
+ - `skip`: number of first items to skip (default is `0`).
172
+ - `limit`: allows to limit the number of items to display (default equals to the length of the items list).
173
+
174
+ Example:
175
+
176
+ ```javascript
177
+ console.log(m("{{each values limit=2}}{{.}}{{/each}}", {values: [0, 1, 2, 3]})); // --> '01'
178
+ ```
179
+
180
+
170
181
  #### if
171
182
 
172
183
  The `if` helper renders the block only if the condition is truthy.
@@ -239,6 +250,7 @@ console.log(m("{{#with autor}}{{name}} <{{email}}>{{/with}}", data)); // --> 'Bo
239
250
  ### Custom Helpers
240
251
 
241
252
  > Added in `v0.5.0`.
253
+ > Breaking change introduced in `v0.12.0`.
242
254
 
243
255
  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
256
 
@@ -251,8 +263,8 @@ const data = {
251
263
  };
252
264
  const options = {
253
265
  helpers: {
254
- customHelper: value => {
255
- return `Hello, ${value}!`;
266
+ customHelper: params => {
267
+ return `Hello, ${params.args[0]}!`;
256
268
  },
257
269
  },
258
270
  };
@@ -261,8 +273,10 @@ const result = m(template, data, options);
261
273
  console.log(result); // Output: "Hello, World!"
262
274
  ```
263
275
 
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:
276
+ Custom helper functions receive a single object as argument, containing the following keys:
265
277
 
278
+ - `args`: an array containing the variables with the helper is called in the template.
279
+ - `opt`: an object containing the keyword arguments provided to the helper.
266
280
  - `context`: the current context (data) where the helper has been executed.
267
281
  - `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
282
 
@@ -278,8 +292,8 @@ const data = {
278
292
  };
279
293
  const options = {
280
294
  helpers: {
281
- customEach: (items, opt) => {
282
- return items.map((item, index) => opt.fn({ ...item, index: index})).join("");
295
+ customEach: ({args, fn}) => {
296
+ return args[0].map((item, index) => fn({ ...item, index: index})).join("");
283
297
  },
284
298
  },
285
299
  };
@@ -343,11 +357,18 @@ The `@last` variable allows to check if the current iteration using the `#each`
343
357
  ### Functions
344
358
 
345
359
  > Added in `v0.8.0`.
360
+ > Breaking change introduced in `v0.12.0`.
346
361
 
347
362
  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
363
 
349
364
  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
365
 
366
+ Functions will receive a single object as argument, containing the following keys:
367
+
368
+ - `args`: an array containing the variables with the function is called in the template.
369
+ - `opt`: an object containing the keyword arguments provided to the function.
370
+ - `context`: the current context (data) where the function has been executed.
371
+
351
372
  Example:
352
373
 
353
374
  ```javascript
@@ -359,8 +380,8 @@ const data = {
359
380
  };
360
381
  const options = {
361
382
  functions: {
362
- fullName: (firstName, lastName) => {
363
- return `${firstName} ${lastName}`;
383
+ fullName: ({args}) => {
384
+ return `${args[0]} ${args[1]}`;
364
385
  }
365
386
  },
366
387
  };
@@ -369,8 +390,6 @@ const result = m("My name is: {{=fullName user.firstName user.lastName}}", data,
369
390
  console.log(result); // --> "My name is: John Doe"
370
391
  ```
371
392
 
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
393
 
375
394
  ## API
376
395
 
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);
@@ -100,9 +116,9 @@ const create = (template = "", options = {}) => {
100
116
  }
101
117
  }
102
118
  else if (tokens[i].startsWith("=")) {
103
- const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
119
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
104
120
  if (typeof functions[t] === "function") {
105
- output.push(functions[t](...args.map(v => parse(v, context, vars))) || "");
121
+ output.push(functions[t]({args, opt, context}) || "");
106
122
  }
107
123
  }
108
124
  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.12.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",