mikel 0.18.1 → 0.19.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 CHANGED
@@ -380,7 +380,8 @@ Custom helper functions receive a single object as argument, containing the foll
380
380
 
381
381
  - `args`: an array containing the variables with the helper is called in the template.
382
382
  - `opt`: an object containing the keyword arguments provided to the helper.
383
- - `context`: the current context (data) where the helper has been executed.
383
+ - `data`: the current data where the helper has been executed.
384
+ - `context` (**DEPRECATED**): the current context (data) where the helper has been executed.
384
385
  - `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
385
386
 
386
387
  The helper function must return a string, which will be injected into the result string. Example:
@@ -537,6 +538,20 @@ console.log(template({name: "Susan"})); // --> "Hello, Susan!"
537
538
 
538
539
  It also exposes the following additional methods:
539
540
 
541
+ #### `template.use(options)`
542
+
543
+ > Added in `v0.19.0`.
544
+
545
+ Allows to extend the templating with custom **helpers**, **functions**, and **partials**.
546
+
547
+ ```javascript
548
+ template.use({
549
+ partials: {
550
+ foo: "bar",
551
+ },
552
+ });
553
+ ```
554
+
540
555
  #### `template.addHelper(helperName, helperFn)`
541
556
 
542
557
  Allows to register a new helper instead of using the `options` object.
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@ declare type HelperFunction = (params: {
2
2
  args: any[];
3
3
  opt: Record<string, any>;
4
4
  context: Record<string, any>;
5
+ data: Record<string, any>;
5
6
  fn: (context?: Record<string, any>, vars?: Record<string, any>, output?: string[]) => string;
6
7
  }) => string;
7
8
 
@@ -21,14 +22,20 @@ declare interface Functions {
21
22
  }) => string | void;
22
23
  }
23
24
 
25
+ declare interface Variables {
26
+ [key: string]: any;
27
+ }
28
+
24
29
  declare interface MikelOptions {
25
30
  helpers?: Helpers;
26
31
  partials?: Partials;
27
32
  functions?: Functions;
33
+ variables?: Variables;
28
34
  }
29
35
 
30
36
  declare interface MikelTemplate {
31
37
  (data?: any): string;
38
+ use(options: Partial<MikelOptions>): MikelTemplate;
32
39
  addHelper(name: string, fn: HelperFunction): void;
33
40
  removeHelper(name: string): void;
34
41
  addFunction(name: string, fn: (params: any) => string | void): void;
package/index.js CHANGED
@@ -17,22 +17,22 @@ const untokenize = (ts = [], s = "{{", e = "}}") => {
17
17
  };
18
18
 
19
19
  // @description parse string arguments
20
- const parseArgs = (argString = "", context = {}, vars = {}) => {
20
+ const parseArgs = (argString = "", data = {}, vars = {}) => {
21
21
  const [t, ...args] = argString.trim().match(/(?:[^\s"]+|"[^"]*")+/g);
22
- const argv = args.filter(a => !a.includes("=")).map(a => parse(a, context, vars));
22
+ const argv = args.filter(a => !a.includes("=")).map(a => parse(a, data, vars));
23
23
  const opt = Object.fromEntries(args.filter(a => a.includes("=")).map(a => {
24
24
  const [k, v] = a.split("=");
25
- return [k, parse(v, context, vars)];
25
+ return [k, parse(v, data, vars)];
26
26
  }));
27
27
  return [t, argv, opt];
28
28
  };
29
29
 
30
30
  // @description parse a string value to a native type
31
- const parse = (v, context = {}, vars = {}) => {
31
+ const parse = (v, data = {}, vars = {}) => {
32
32
  if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d+\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
33
33
  return JSON.parse(v);
34
34
  }
35
- return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || ".");
35
+ return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(data, v || ".");
36
36
  };
37
37
 
38
38
  // @description default helpers
@@ -44,35 +44,40 @@ const defaultHelpers = {
44
44
  .map((item, index) => p.fn(item[1], {index: index, key: item[0], value: item[1], first: index === 0, last: index === items.length - 1}))
45
45
  .join("");
46
46
  },
47
- "if": p => !!p.args[0] ? p.fn(p.context) : "",
48
- "unless": p => !!!p.args[0] ? p.fn(p.context) : "",
49
- "eq": p => p.args[0] === p.args[1] ? p.fn(p.context) : "",
50
- "ne": p => p.args[0] !== p.args[1] ? p.fn(p.context) : "",
47
+ "if": p => !!p.args[0] ? p.fn(p.data) : "",
48
+ "unless": p => !!!p.args[0] ? p.fn(p.data) : "",
49
+ "eq": p => p.args[0] === p.args[1] ? p.fn(p.data) : "",
50
+ "ne": p => p.args[0] !== p.args[1] ? p.fn(p.data) : "",
51
51
  "with": p => p.fn(p.args[0]),
52
- "escape": p => escape(p.fn(p.context)),
52
+ "escape": p => escape(p.fn(p.data)),
53
53
  };
54
54
 
55
55
  // @description create a new instance of mikel
56
56
  const create = (template = "", options = {}) => {
57
- const helpers = Object.assign({}, defaultHelpers, options?.helpers || {});
58
- const partials = Object.assign({}, options?.partials || {});
59
- const functions = options?.functions || {};
57
+ const ctx = {
58
+ tokens: tokenize(template),
59
+ helpers: Object.assign({}, defaultHelpers, options?.helpers || {}),
60
+ partials: Object.assign({}, options?.partials || {}),
61
+ functions: options?.functions || {},
62
+ variables: {},
63
+ };
60
64
  // internal method to compile the template
61
- const compile = (tokens, output, context, vars, index = 0, section = "") => {
65
+ const compile = (tokens, output, data, vars, index = 0, section = "") => {
62
66
  let i = index;
63
67
  while (i < tokens.length) {
64
68
  if (i % 2 === 0) {
65
69
  output.push(tokens[i]);
66
70
  }
67
- else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
68
- const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
71
+ else if (tokens[i].startsWith("#") && typeof ctx.helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
72
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
69
73
  const j = i + 1;
70
- output.push(helpers[t]({
74
+ output.push(ctx.helpers[t]({
71
75
  args: args,
72
76
  opt: opt,
73
- context: context,
74
- fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
75
- i = compile(tokens, blockOutput, blockContext, {...vars, ...blockVars, root: vars.root}, j, t);
77
+ data: data,
78
+ context: data,
79
+ fn: (blockData = {}, blockVars = {}, blockOutput = []) => {
80
+ i = compile(tokens, blockOutput, blockData, {...vars, ...blockVars, root: vars.root}, j, t);
76
81
  return blockOutput.join("");
77
82
  },
78
83
  }));
@@ -83,7 +88,7 @@ const create = (template = "", options = {}) => {
83
88
  }
84
89
  else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
85
90
  const t = tokens[i].slice(1).trim();
86
- const value = get(context, t);
91
+ const value = get(data, t);
87
92
  const negate = tokens[i].startsWith("^");
88
93
  if (!negate && value && Array.isArray(value)) {
89
94
  const j = i + 1;
@@ -93,7 +98,7 @@ const create = (template = "", options = {}) => {
93
98
  }
94
99
  else {
95
100
  const includeOutput = (!negate && !!value) || (negate && !!!value);
96
- i = compile(tokens, includeOutput ? output : [], context, vars, i + 1, t);
101
+ i = compile(tokens, includeOutput ? output : [], data, vars, i + 1, t);
97
102
  }
98
103
  }
99
104
  else if (tokens[i].startsWith("<")) {
@@ -101,31 +106,31 @@ const create = (template = "", options = {}) => {
101
106
  const lastIndex = partialTokens.findIndex((token, j) => {
102
107
  return j % 2 !== 0 && token.trim().startsWith("/") && token.trim().endsWith(t);
103
108
  });
104
- if (typeof partials[t] === "undefined") {
105
- partials[t] = untokenize(partialTokens.slice(0, lastIndex));
109
+ if (typeof ctx.partials[t] === "undefined") {
110
+ ctx.partials[t] = untokenize(partialTokens.slice(0, lastIndex));
106
111
  }
107
112
  i = i + lastIndex + 1;
108
113
  }
109
114
  else if (tokens[i].startsWith(">")) {
110
- const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""), context, vars);
115
+ const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""), data, vars);
111
116
  const blockContent = []; // to store partial block content
112
117
  if (tokens[i].startsWith(">>")) {
113
- i = compile(tokens, blockContent, context, vars, i + 1, t);
118
+ i = compile(tokens, blockContent, data, vars, i + 1, t);
114
119
  }
115
- if (typeof partials[t] === "string" || typeof partials[t]?.body === "string") {
116
- const newCtx = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : context);
120
+ if (typeof ctx.partials[t] === "string" || typeof ctx.partials[t]?.body === "string") {
121
+ const newData = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : data);
117
122
  const newVars = {
118
123
  ...vars,
119
124
  content: blockContent.join(""),
120
- partial: partials[t]?.attributes || partials[t]?.data || {},
125
+ partial: ctx.partials[t]?.attributes || ctx.partials[t]?.data || {},
121
126
  };
122
- compile(tokenize(partials[t]?.body || partials[t]), output, newCtx, newVars, 0, "");
127
+ compile(tokenize(ctx.partials[t]?.body || ctx.partials[t]), output, newData, newVars, 0, "");
123
128
  }
124
129
  }
125
130
  else if (tokens[i].startsWith("=")) {
126
- const [t, args, opt] = parseArgs(tokens[i].slice(1), context, vars);
127
- if (typeof functions[t] === "function") {
128
- output.push(functions[t]({args, opt, context}) || "");
131
+ const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
132
+ if (typeof ctx.functions[t] === "function") {
133
+ output.push(ctx.functions[t]({args, opt, data}) || "");
129
134
  }
130
135
  }
131
136
  else if (tokens[i].startsWith("/")) {
@@ -138,10 +143,10 @@ const create = (template = "", options = {}) => {
138
143
  const t = tokens[i].split("||").map(v => {
139
144
  // check if the returned value should not be escaped
140
145
  if (v.trim().startsWith("!")) {
141
- return parse(v.trim().slice(1).trim(), context, vars);
146
+ return parse(v.trim().slice(1).trim(), data, vars);
142
147
  }
143
148
  // escape the returned value
144
- return escape(parse(v.trim(), context, vars));
149
+ return escape(parse(v.trim(), data, vars));
145
150
  });
146
151
  output.push(t.find(v => !!v) ?? "");
147
152
  }
@@ -151,17 +156,28 @@ const create = (template = "", options = {}) => {
151
156
  };
152
157
  // entry method to compile the template with the provided data object
153
158
  const compileTemplate = (data = {}, output = []) => {
154
- compile(tokenize(template), output, data, {root: data}, 0, "");
159
+ compile(ctx.tokens, output, data, {root: data, ...ctx.variables}, 0, "");
155
160
  return output.join("");
156
161
  };
157
162
  // assign api methods and return method to compile the template
158
163
  return Object.assign(compileTemplate, {
159
- addHelper: (name, fn) => helpers[name] = fn,
160
- removeHelper: name => delete helpers[name],
161
- addFunction: (name, fn) => functions[name] = fn,
162
- removeFunction: name => delete functions[name],
163
- addPartial: (name, partial) => partials[name] = partial,
164
- removePartial: name => delete partials[name],
164
+ use: newOptions => {
165
+ if (typeof newOptions === "function") {
166
+ newOptions(ctx);
167
+ }
168
+ else if (!!newOptions && typeof newOptions === "object") {
169
+ ["helpers", "functions", "partials", "variables"].forEach(field => {
170
+ Object.assign(ctx[field], newOptions?.[field] || {});
171
+ });
172
+ }
173
+ return compileTemplate;
174
+ },
175
+ addHelper: (name, fn) => ctx.helpers[name] = fn,
176
+ removeHelper: name => delete ctx.helpers[name],
177
+ addFunction: (name, fn) => ctx.functions[name] = fn,
178
+ removeFunction: name => delete ctx.functions[name],
179
+ addPartial: (name, partial) => ctx.partials[name] = partial,
180
+ removePartial: name => delete ctx.partials[name],
165
181
  });
166
182
  };
167
183
 
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.18.1",
4
+ "version": "0.19.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",
@@ -19,7 +19,8 @@
19
19
  },
20
20
  "scripts": {
21
21
  "release": "node ./scripts/release.js",
22
- "test": "node test.js"
22
+ "test": "node test.js && yarn test:markdown",
23
+ "test:markdown": "node ./packages/mikel-markdown/test.js"
23
24
  },
24
25
  "keywords": [
25
26
  "template",