mikel 0.6.0 → 0.8.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 +89 -19
  2. package/index.js +20 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -214,7 +214,7 @@ const data = {
214
214
  };
215
215
  const options = {
216
216
  helpers: {
217
- customHelper: ({context, value, key, options, fn}) => {
217
+ customHelper: value => {
218
218
  return `Hello, ${value}!`;
219
219
  },
220
220
  },
@@ -224,21 +224,38 @@ const result = m(template, data, options);
224
224
  console.log(result); // Output: "Hello, World!"
225
225
  ```
226
226
 
227
- Custom helper functions receive a single object parameter containing the following fields:
227
+ 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:
228
228
 
229
- - `context`: The current context (data) where the helper has been executed.
230
- - `value`: The current value passed to the helper.
231
- - `key`: The field used to extract the value from the current context.
232
- - `options`: The global options object.
233
- - `fn`: A function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
229
+ - `context`: the current context (data) where the helper has been executed.
230
+ - `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
234
231
 
235
- The helper function must return a string, which will be injected into the result string.
232
+ The helper function must return a string, which will be injected into the result string. Example:
236
233
 
237
- ### Variables
234
+ ```javascript
235
+ const data = {
236
+ items: [
237
+ { name: "John" },
238
+ { name: "Alice" },
239
+ { name: "Bob" },
240
+ ],
241
+ };
242
+ const options = {
243
+ helpers: {
244
+ customEach: (items, opt) => {
245
+ return items.map((item, index) => opt.fn({ ...item, index: index})).join("");
246
+ },
247
+ },
248
+ };
249
+
250
+ const result = m("{{#customEach items}}{{index}}: {{name}}, {{/customEach}}", data, options);
251
+ console.log(result); // --> "0: John, 1: Alice, 2: Bob,"
252
+ ```
253
+
254
+ ### Runtime Variables
238
255
 
239
256
  > Added in `v0.4.0`.
240
257
 
241
- Data Variables in Mikel provide convenient access to special values within your templates. These variables, denoted by the `@` symbol, allow users to interact with specific data contexts or values.
258
+ Runtime Variables in Mikel provide convenient access to special values within your templates. These variables, denoted by the `@` symbol, allow users to interact with specific data contexts or values at runtime. Runtime variables are usually generated by helpers like `#each`.
242
259
 
243
260
  #### @root
244
261
 
@@ -266,9 +283,29 @@ The `@key` variable allows users to retrieve the current key of the object entry
266
283
 
267
284
  The `@value` variable allows users to retrieve the current value of the object entry when iterating over an object using the `#each` helper. It simplifies access to object values for dynamic rendering and data manipulation.
268
285
 
269
- ### Custom variables
286
+ #### @first
287
+
288
+ > Added in `v0.7.0`.
289
+
290
+ The `@first` variable allows to check if the current iteration using the `#each` helper is the first item in the array or object.
291
+
292
+ ```
293
+ {{#each items}} {{.}}: {{#if @first}}first item!{{/if}}{{#unless @first}}not first{{/if}} {{/each}}
294
+ ```
295
+
296
+ #### @last
297
+
298
+ > Added in `v0.7.0`.
299
+
300
+ The `@last` variable allows to check if the current iteration using the `#each` helper is the last item in the array or object.
270
301
 
271
- > Added in `v0.5.0`
302
+ ```
303
+ {{#each items}}{{@index}}:{{.}} {{#unless @last}},{{/unless}}{{/each}}
304
+ ```
305
+
306
+ ### Custom runtime variables
307
+
308
+ > Added in `v0.5.0`.
272
309
 
273
310
  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.
274
311
 
@@ -287,18 +324,51 @@ console.log(result); // --> 'Hello, World!'
287
324
 
288
325
  In this example, the custom data variable `customVariable` is defined with the value `"World"`, and it can be accessed in the template using `@customVariable`.
289
326
 
327
+ ### Functions
328
+
329
+ > Added in `v0.8.0`.
330
+
331
+ 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.
332
+
333
+ 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.
334
+
335
+ Example:
336
+
337
+ ```javascript
338
+ const data = {
339
+ user: {
340
+ firstName: "John",
341
+ lastName: "Doe",
342
+ },
343
+ };
344
+ const options = {
345
+ functions: {
346
+ fullName: (firstName, lastName) => {
347
+ return `${firstName} ${lastName}`;
348
+ }
349
+ },
350
+ };
351
+
352
+ const result = m("My name is: {{=fullName user.firstName user.lastName}}", data, options);
353
+ console.log(result); // --> "My name is: John Doe"
354
+ ```
355
+
356
+ 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.
357
+
358
+
290
359
  ## API
291
360
 
292
361
  ### `mikel(template, data[, options])`
293
362
 
294
- Render the given template string with the provided data object.
363
+ Render the given template string with the provided data object and options.
295
364
 
296
- - `template` (string): The Mustache template string.
297
- - `data` (object): The data object containing the values to render.
298
- - `options` (object): An object containing the following optional values:
299
- - `partials` (object): An object containing the available partials.
300
- - `variables` (object): An object containing custom data variables.
301
- - `helpers` (object): An object containing custom helpers.
365
+ - `template` (string): the template string.
366
+ - `data` (object): the data object containing the values to render.
367
+ - `options` (object): an object containing the following optional values:
368
+ - `partials` (object): an object containing the available partials.
369
+ - `variables` (object): an object containing custom data variables.
370
+ - `helpers` (object): an object containing custom helpers.
371
+ - `functions` (object): and object containing custom functions.
302
372
 
303
373
  Returns: A string with the rendered output.
304
374
 
package/index.js CHANGED
@@ -12,16 +12,16 @@ const escape = s => s.toString().replace(/[&<>\"']/g, m => escapedChars[m]);
12
12
  const get = (c, p) => (p === "." ? c : p.split(".").reduce((x, k) => x?.[k], c)) ?? "";
13
13
 
14
14
  const defaultHelpers = {
15
- "each": ({value, fn}) => {
15
+ "each": (value, opt) => {
16
16
  return (typeof value === "object" ? Object.entries(value || {}) : [])
17
- .map((item, index) => fn(item[1], {index: index, key: item[0], value: item[1]}))
17
+ .map((item, index, items) => opt.fn(item[1], {index: index, key: item[0], value: item[1], first: index === 0, last: index === items.length - 1}))
18
18
  .join("");
19
19
  },
20
- "if": ({value, fn, context}) => !!value ? fn(context) : "",
21
- "unless": ({value, fn, context}) => !!!value ? fn(context) : "",
20
+ "if": (value, opt) => !!value ? opt.fn(opt.context) : "",
21
+ "unless": (value, opt) => !!!value ? opt.fn(opt.context) : "",
22
22
  };
23
23
 
24
- const compile = (tokens, output, context, partials, helpers, vars, index = 0, section = "") => {
24
+ const compile = (tokens, output, context, partials, helpers, vars, fn = {}, index = 0, section = "") => {
25
25
  let i = index;
26
26
  while (i < tokens.length) {
27
27
  if (i % 2 === 0) {
@@ -34,20 +34,18 @@ const compile = (tokens, output, context, partials, helpers, vars, index = 0, se
34
34
  output.push(get(context, tokens[i].slice(1).trim()));
35
35
  }
36
36
  else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
37
- const [t, v] = tokens[i].slice(1).trim().split(" ");
37
+ const [t, ...args] = tokens[i].slice(1).trim().split(" ");
38
38
  const j = i + 1;
39
- output.push(helpers[t]({
39
+ output.push(helpers[t](...args.map(v => (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || ".")), {
40
40
  context: context,
41
- key: v || ".",
42
- value: get(context, v || "."),
43
41
  fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
44
- i = compile(tokens, blockOutput, blockContext, partials, helpers, {...vars, ...blockVars, root: vars.root}, j, t);
42
+ i = compile(tokens, blockOutput, blockContext, partials, helpers, {...vars, ...blockVars, root: vars.root}, fn, j, t);
45
43
  return blockOutput.join("");
46
44
  },
47
45
  }));
48
- // Make sure that this block has been executed
46
+ // Make sure that this block is executed at least once
49
47
  if (i + 1 === j) {
50
- i = compile(tokens, [], {}, partials, helpers, vars, j, t);
48
+ i = compile(tokens, [], {}, {}, {}, {}, {}, j, t);
51
49
  }
52
50
  }
53
51
  else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
@@ -57,18 +55,24 @@ const compile = (tokens, output, context, partials, helpers, vars, index = 0, se
57
55
  if (!negate && value && Array.isArray(value)) {
58
56
  const j = i + 1;
59
57
  (value.length > 0 ? value : [""]).forEach(item => {
60
- i = compile(tokens, value.length > 0 ? output : [], item, partials, helpers, vars, j, t);
58
+ i = compile(tokens, value.length > 0 ? output : [], item, partials, helpers, vars, fn, j, t);
61
59
  });
62
60
  }
63
61
  else {
64
62
  const includeOutput = (!negate && !!value) || (negate && !!!value);
65
- i = compile(tokens, includeOutput ? output : [], context, partials, helpers, vars, i + 1, t);
63
+ i = compile(tokens, includeOutput ? output : [], context, partials, helpers, vars, fn, i + 1, t);
66
64
  }
67
65
  }
68
66
  else if (tokens[i].startsWith(">")) {
69
67
  const [t, v] = tokens[i].slice(1).trim().split(" ");
70
68
  if (typeof partials[t] === "string") {
71
- compile(partials[t].split(tags), output, v ? get(context, v) : context, partials, helpers, vars, 0, "");
69
+ compile(partials[t].split(tags), output, v ? get(context, v) : context, partials, helpers, vars, fn, 0, "");
70
+ }
71
+ }
72
+ else if (tokens[i].startsWith("=")) {
73
+ const [t, ...args] = tokens[i].slice(1).trim().split(" ");
74
+ if (typeof fn[t] === "function") {
75
+ output.push(fn[t](...args.map(v => (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || "."))) || "");
72
76
  }
73
77
  }
74
78
  else if (tokens[i].startsWith("/")) {
@@ -90,7 +94,7 @@ const mikel = (str, context = {}, opt = {}, output = []) => {
90
94
  const partials = Object.assign({}, opt.partials || {});
91
95
  const helpers = Object.assign({}, defaultHelpers, opt.helpers || {});
92
96
  const variables = Object.assign({}, opt.variables || {}, {root: context});
93
- compile(str.split(tags), output, context, partials, helpers, variables, 0, "");
97
+ compile(str.split(tags), output, context, partials, helpers, variables, opt.functions || {}, 0, "");
94
98
  return output.join("");
95
99
  };
96
100
 
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.6.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",