mikel 0.8.1 → 0.9.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 +20 -0
  2. package/index.js +78 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -199,6 +199,26 @@ const data = {
199
199
  console.log(m("{{#unless isAdmin}}Hello guest{{/unless}}", data)); // --> 'Hello guest'
200
200
  ```
201
201
 
202
+ #### eq
203
+
204
+ > Added in `v0.9.0`.
205
+
206
+ The `eq` helper renders the blocks only if the two values provided as argument are equal. Example:
207
+
208
+ ```javascript
209
+ console.log(m(`{{#eq name "bob"}}Hello bob{{/eq}}`, {name: "bob"})); // --> 'Hello bob'
210
+ ```
211
+
212
+ #### ne
213
+
214
+ > Added in `v0.9.0`.
215
+
216
+ The `ne` helper renders the block only if the two values provided as argument are not equal. Example:
217
+
218
+ ```javascript
219
+ console.log(m(`{{#ne name "bob"}}Not bob{{/ne}}`, {name: "John"})); // --> 'Not bob'
220
+ ```
221
+
202
222
  ### Custom Helpers
203
223
 
204
224
  > Added in `v0.5.0`.
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const tags = /\{\{|\}\}/;
2
+ const endl = "\n";
2
3
  const escapedChars = {
3
4
  "&": "&",
4
5
  "<": "&lt;",
@@ -11,6 +12,74 @@ const escape = s => s.toString().replace(/[&<>\"']/g, m => escapedChars[m]);
11
12
 
12
13
  const get = (c, p) => (p === "." ? c : p.split(".").reduce((x, k) => x?.[k], c)) ?? "";
13
14
 
15
+ const parse = (v, context = {}, vars = {}) => {
16
+ if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d*\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
17
+ return JSON.parse(v);
18
+ }
19
+ return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || ".");
20
+ };
21
+
22
+ // @description tiny yaml parser
23
+ const yamlParser = (str = "") => {
24
+ const lines = str.split(endl), result = {}, levels = [{ctx: result, indent: 0}];
25
+ let level = 0, i = 0;
26
+ while (i < lines.length) {
27
+ const line = lines[i] || "";
28
+ if (!!line.trim() && !line.trim().startsWith("#")) {
29
+ const indent = (line.match(/^( *)/m)?.[0] || "").length;
30
+ while(level > 0 && indent < levels[level].indent) {
31
+ levels.pop();
32
+ level = level - 1;
33
+ }
34
+ const isArrayItem = line.trim().startsWith("-");
35
+ let [key, value] = line.trim().split(":").map(v => v.trim());
36
+ // Check if is a new item of the array
37
+ if (isArrayItem) {
38
+ key = key.replace(/^(- *)/m, "");
39
+ if (typeof value === "undefined") {
40
+ value = key;
41
+ }
42
+ else {
43
+ const newIndent = (line.slice(0, line.indexOf(key))).length;
44
+ levels[level].ctx.push({});
45
+ levels.push({ctx: levels[level].ctx[levels[level].ctx.length - 1], indent: newIndent});
46
+ level = level + 1;
47
+ }
48
+ }
49
+ // Check for empty value --> entering into a nested object or array
50
+ if (!value) {
51
+ const nextLine = lines[i + 1] || "";
52
+ const nextIndent = (nextLine.match(/^( *)/m)?.[0] || "").length;
53
+ levels[level].ctx[key] = nextLine.trim().startsWith("-") ? [] : {};
54
+ if (nextIndent > levels[level].indent) {
55
+ levels.push({ctx: levels[level].ctx[key], indent: nextIndent});
56
+ level = level + 1;
57
+ }
58
+ }
59
+ else if(value && Array.isArray(levels[level].ctx)) {
60
+ levels[level].ctx.push(JSON.parse(value));
61
+ }
62
+ else if (value) {
63
+ levels[level].ctx[key] = JSON.parse(value);
64
+ }
65
+ }
66
+ i = i + 1;
67
+ }
68
+ return result;
69
+ };
70
+
71
+ // @description tiny front-matter parser
72
+ const frontmatter = (str = "", parser = null) => {
73
+ let body = (str || "").trim(), data = {};
74
+ const matches = Array.from(body.matchAll(/^(--- *)/gm))
75
+ if (matches?.length === 2 && matches[0].index === 0) {
76
+ const front = body.substring(0 + matches[0][1].length, matches[1].index).trim();
77
+ body = body.substring(matches[1].index + matches[1][1].length).trim();
78
+ data = typeof parser === "function" ? parser(front) : yamlParser(front);
79
+ }
80
+ return {body, data};
81
+ };
82
+
14
83
  const defaultHelpers = {
15
84
  "each": (value, opt) => {
16
85
  return (typeof value === "object" ? Object.entries(value || {}) : [])
@@ -19,6 +88,8 @@ const defaultHelpers = {
19
88
  },
20
89
  "if": (value, opt) => !!value ? opt.fn(opt.context) : "",
21
90
  "unless": (value, opt) => !!!value ? opt.fn(opt.context) : "",
91
+ "eq": (a, b, opt) => a === b ? opt.fn(opt.context) : "",
92
+ "ne": (a, b, opt) => a !== b ? opt.fn(opt.context) : "",
22
93
  };
23
94
 
24
95
  const compile = (tokens, output, context, partials, helpers, vars, fn = {}, index = 0, section = "") => {
@@ -34,9 +105,9 @@ const compile = (tokens, output, context, partials, helpers, vars, fn = {}, inde
34
105
  output.push(get(context, tokens[i].slice(1).trim()));
35
106
  }
36
107
  else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
37
- const [t, ...args] = tokens[i].slice(1).trim().split(" ");
108
+ const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
38
109
  const j = i + 1;
39
- output.push(helpers[t](...args.map(v => (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || ".")), {
110
+ output.push(helpers[t](...args.map(v => parse(v, context, vars)), {
40
111
  context: context,
41
112
  fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
42
113
  i = compile(tokens, blockOutput, blockContext, partials, helpers, {...vars, ...blockVars, root: vars.root}, fn, j, t);
@@ -70,9 +141,9 @@ const compile = (tokens, output, context, partials, helpers, vars, fn = {}, inde
70
141
  }
71
142
  }
72
143
  else if (tokens[i].startsWith("=")) {
73
- const [t, ...args] = tokens[i].slice(1).trim().split(" ");
144
+ const [t, ...args] = tokens[i].slice(1).trim().match(/(?:[^\s"]+|"[^"]*")+/g);
74
145
  if (typeof fn[t] === "function") {
75
- output.push(fn[t](...args.map(v => (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(context, v || "."))) || "");
146
+ output.push(fn[t](...args.map(v => parse(v, context, vars))) || "");
76
147
  }
77
148
  }
78
149
  else if (tokens[i].startsWith("/")) {
@@ -101,5 +172,8 @@ const mikel = (str, context = {}, opt = {}, output = []) => {
101
172
  // @description assign utilities
102
173
  mikel.escape = escape;
103
174
  mikel.get = get;
175
+ mikel.parse = parse;
176
+ mikel.yaml = yamlParser;
177
+ mikel.frontmatter = frontmatter;
104
178
 
105
179
  export default mikel;
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.8.1",
4
+ "version": "0.9.1",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",