mikel-frontmatter 0.31.0 → 0.33.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 +79 -295
- package/index.d.ts +11 -4
- package/index.js +134 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
A [mikel](https://github.com/jmjuanes/mikel) plugin to define new data inside templates using a frontmatter-like syntax. Supports
|
|
6
|
+
A [mikel](https://github.com/jmjuanes/mikel) plugin to define new data inside templates using a frontmatter-like syntax. Supports **YAML**, **JSON**, and **TOML** formats.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
|
-
You can install
|
|
10
|
+
You can install it via npm or yarn:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
## Install using npm
|
|
@@ -19,362 +19,146 @@ $ yarn add mikel mikel-frontmatter
|
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
The plugin automatically detects the format:
|
|
25
|
-
- **YAML format**: When the content doesn't start with `{`
|
|
26
|
-
- **JSON format**: When the content starts with `{` and ends with `}`
|
|
27
|
-
|
|
28
|
-
### YAML Example
|
|
22
|
+
Import the `mikel-frontmatter` package:
|
|
29
23
|
|
|
30
24
|
```javascript
|
|
31
25
|
import mikel from "mikel";
|
|
32
26
|
import mikelFrontmatter from "mikel-frontmatter";
|
|
27
|
+
```
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
{{#frontmatter}}
|
|
36
|
-
title: My Page Title
|
|
37
|
-
author: John Doe
|
|
38
|
-
date: 2026-02-03
|
|
39
|
-
tags:
|
|
40
|
-
- javascript
|
|
41
|
-
- templating
|
|
42
|
-
settings:
|
|
43
|
-
published: true
|
|
44
|
-
featured: false
|
|
45
|
-
{{/frontmatter}}
|
|
46
|
-
|
|
47
|
-
<h1>{{@frontmatter.title}}</h1>
|
|
48
|
-
<p>By {{@frontmatter.author}} on {{@frontmatter.date}}</p>
|
|
49
|
-
|
|
50
|
-
{{#if @frontmatter.settings.published}}
|
|
51
|
-
<span class="badge">Published</span>
|
|
52
|
-
{{/if}}
|
|
53
|
-
|
|
54
|
-
<ul>
|
|
55
|
-
{{#each @frontmatter.tags}}
|
|
56
|
-
<li>{{this}}</li>
|
|
57
|
-
{{/each}}
|
|
58
|
-
</ul>
|
|
59
|
-
`;
|
|
29
|
+
Include the plugin using the `use` method of mikel:
|
|
60
30
|
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
```javascript
|
|
32
|
+
const m = mikel.create();
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
console.log(result);
|
|
34
|
+
m.use(mikelFrontmatter({ ... }));
|
|
66
35
|
```
|
|
67
36
|
|
|
68
|
-
|
|
37
|
+
In your template, the `{{#frontmatter}}` helper allows you to define metadata at the beginning of your templates, similar to frontmatter in Markdown files. The parsed data is stored as a state variable accessible via `@frontmatter` (or a custom variable name, see below).
|
|
69
38
|
|
|
70
|
-
|
|
71
|
-
import mikel from "mikel";
|
|
72
|
-
import mikelFrontmatter from "mikel-frontmatter";
|
|
39
|
+
Example:
|
|
73
40
|
|
|
74
|
-
|
|
41
|
+
```html
|
|
75
42
|
{{#frontmatter}}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"published": true,
|
|
83
|
-
"featured": false
|
|
84
|
-
}
|
|
85
|
-
}
|
|
43
|
+
title: My Page Title
|
|
44
|
+
author: John Doe
|
|
45
|
+
date: 2026-02-03
|
|
46
|
+
tags:
|
|
47
|
+
- javascript
|
|
48
|
+
- templating
|
|
86
49
|
{{/frontmatter}}
|
|
87
|
-
|
|
88
50
|
<h1>{{@frontmatter.title}}</h1>
|
|
89
51
|
<p>By {{@frontmatter.author}} on {{@frontmatter.date}}</p>
|
|
90
|
-
|
|
91
|
-
{{#if @frontmatter.settings.published}}
|
|
92
|
-
<span class="badge">Published</span>
|
|
93
|
-
{{/if}}
|
|
94
|
-
|
|
95
52
|
<ul>
|
|
96
|
-
{{#each @frontmatter.tags}}
|
|
97
|
-
|
|
98
|
-
{{/each}}
|
|
53
|
+
{{#each @frontmatter.tags}}
|
|
54
|
+
<li>{{this}}</li>
|
|
55
|
+
{{/each}}
|
|
99
56
|
</ul>
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
const render = mikel.create();
|
|
103
|
-
render.use(mikelFrontmatter());
|
|
104
|
-
|
|
105
|
-
const result = render(template, {});
|
|
106
|
-
console.log(result);
|
|
107
57
|
```
|
|
108
58
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
### Format Auto-Detection
|
|
112
|
-
|
|
113
|
-
The plugin automatically detects whether your frontmatter is in YAML or JSON format:
|
|
114
|
-
|
|
115
|
-
```javascript
|
|
116
|
-
// YAML format (default)
|
|
117
|
-
{{#frontmatter}}
|
|
118
|
-
title: My Title
|
|
119
|
-
author: John Doe
|
|
120
|
-
{{/frontmatter}}
|
|
121
|
-
|
|
122
|
-
// JSON format (automatically detected)
|
|
123
|
-
{{#frontmatter}}
|
|
124
|
-
{
|
|
125
|
-
"title": "My Title",
|
|
126
|
-
"author": "John Doe"
|
|
127
|
-
}
|
|
128
|
-
{{/frontmatter}}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Basic Key-Value Pairs
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
const template = `
|
|
135
|
-
{{#frontmatter}}
|
|
136
|
-
title: Hello World
|
|
137
|
-
author: Jane Smith
|
|
138
|
-
version: 1.0.0
|
|
139
|
-
{{/frontmatter}}
|
|
140
|
-
|
|
141
|
-
Title: {{@frontmatter.title}}
|
|
142
|
-
`;
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Data Types
|
|
146
|
-
|
|
147
|
-
The plugin supports common data types in both YAML and JSON:
|
|
59
|
+
Note that this helper doesn't produce any output in the rendered template.
|
|
148
60
|
|
|
149
|
-
|
|
150
|
-
- **Strings**: `name: John Doe` or `name: "John Doe"` or `name: 'John Doe'`
|
|
151
|
-
- **Numbers**: `age: 25` or `price: 19.99`
|
|
152
|
-
- **Booleans**: `published: true` or `active: false` (also `yes/no`, `on/off`)
|
|
153
|
-
- **Null**: `value: null` or `value: ~` or `value:`
|
|
61
|
+
### Customization
|
|
154
62
|
|
|
155
|
-
|
|
156
|
-
- **Strings**: `"name": "John Doe"`
|
|
157
|
-
- **Numbers**: `"age": 25` or `"price": 19.99`
|
|
158
|
-
- **Booleans**: `"published": true` or `"active": false`
|
|
159
|
-
- **Null**: `"value": null`
|
|
63
|
+
The `{{#frontmatter}}` helper accepts the following arguments:
|
|
160
64
|
|
|
161
|
-
|
|
65
|
+
#### as
|
|
162
66
|
|
|
163
|
-
|
|
67
|
+
Custom state variable name to save parsed metadata. If not provided, parsed metadata will be saved in `@frontmatter`. Example:
|
|
164
68
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{{#frontmatter}}
|
|
169
|
-
author:
|
|
170
|
-
name: John Doe
|
|
171
|
-
email: john@example.com
|
|
172
|
-
social:
|
|
173
|
-
twitter: @johndoe
|
|
174
|
-
github: johndoe
|
|
175
|
-
{{/frontmatter}}
|
|
176
|
-
|
|
177
|
-
Author: {{@frontmatter.author.name}} ({{@frontmatter.author.email}})
|
|
178
|
-
Twitter: {{@frontmatter.author.social.twitter}}
|
|
179
|
-
`;
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**JSON:**
|
|
183
|
-
```javascript
|
|
184
|
-
const template = `
|
|
185
|
-
{{#frontmatter}}
|
|
186
|
-
{
|
|
187
|
-
"author": {
|
|
188
|
-
"name": "John Doe",
|
|
189
|
-
"email": "john@example.com",
|
|
190
|
-
"social": {
|
|
191
|
-
"twitter": "@johndoe",
|
|
192
|
-
"github": "johndoe"
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
{{/frontmatter}}
|
|
197
|
-
|
|
198
|
-
Author: {{@frontmatter.author.name}} ({{@frontmatter.author.email}})
|
|
199
|
-
Twitter: {{@frontmatter.author.social.twitter}}
|
|
200
|
-
`;
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Arrays
|
|
204
|
-
|
|
205
|
-
Both formats support arrays:
|
|
206
|
-
|
|
207
|
-
**YAML:**
|
|
208
|
-
```javascript
|
|
209
|
-
const template = `
|
|
210
|
-
{{#frontmatter}}
|
|
211
|
-
tags:
|
|
212
|
-
- javascript
|
|
213
|
-
- nodejs
|
|
214
|
-
- template
|
|
215
|
-
colors:
|
|
216
|
-
- red
|
|
217
|
-
- green
|
|
218
|
-
- blue
|
|
219
|
-
{{/frontmatter}}
|
|
220
|
-
|
|
221
|
-
Tags: {{#each @frontmatter.tags}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
|
|
222
|
-
`;
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**JSON:**
|
|
226
|
-
```javascript
|
|
227
|
-
const template = `
|
|
228
|
-
{{#frontmatter}}
|
|
229
|
-
{
|
|
230
|
-
"tags": ["javascript", "nodejs", "template"],
|
|
231
|
-
"colors": ["red", "green", "blue"]
|
|
232
|
-
}
|
|
69
|
+
```html
|
|
70
|
+
{{#frontmatter as="meta"}}
|
|
71
|
+
name: Bob
|
|
233
72
|
{{/frontmatter}}
|
|
234
|
-
|
|
235
|
-
Tags: {{#each @frontmatter.tags}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
|
|
236
|
-
`;
|
|
73
|
+
<div>Hello {{@meta.name}}</div>
|
|
237
74
|
```
|
|
238
75
|
|
|
239
|
-
|
|
76
|
+
#### format
|
|
240
77
|
|
|
241
|
-
|
|
242
|
-
```javascript
|
|
243
|
-
const template = `
|
|
244
|
-
{{#frontmatter}}
|
|
245
|
-
contributors:
|
|
246
|
-
- name: Alice
|
|
247
|
-
role: Developer
|
|
248
|
-
- name: Bob
|
|
249
|
-
role: Designer
|
|
250
|
-
- name: Carol
|
|
251
|
-
role: Manager
|
|
252
|
-
{{/frontmatter}}
|
|
78
|
+
The format of the frontmatter content. Supported values are `yaml` (default), `json`, and `toml`.
|
|
253
79
|
|
|
254
|
-
|
|
255
|
-
<div>{{name}} - {{role}}</div>
|
|
256
|
-
{{/each}}
|
|
257
|
-
`;
|
|
258
|
-
```
|
|
80
|
+
Example using JSON:
|
|
259
81
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const template = `
|
|
263
|
-
{{#frontmatter}}
|
|
82
|
+
```html
|
|
83
|
+
{{#frontmatter format="json"}}
|
|
264
84
|
{
|
|
265
|
-
"
|
|
266
|
-
|
|
267
|
-
{ "name": "Bob", "role": "Designer" },
|
|
268
|
-
{ "name": "Carol", "role": "Manager" }
|
|
269
|
-
]
|
|
85
|
+
"title": "My JSON Page",
|
|
86
|
+
"tags": ["json", "mikel"]
|
|
270
87
|
}
|
|
271
88
|
{{/frontmatter}}
|
|
272
|
-
|
|
273
|
-
{{#each @frontmatter.contributors}}
|
|
274
|
-
<div>{{name}} - {{role}}</div>
|
|
275
|
-
{{/each}}
|
|
276
|
-
`;
|
|
277
89
|
```
|
|
278
90
|
|
|
279
|
-
|
|
91
|
+
Example using TOML:
|
|
280
92
|
|
|
281
|
-
|
|
93
|
+
```html
|
|
94
|
+
{{#frontmatter format="toml"}}
|
|
95
|
+
title = "My TOML Page"
|
|
96
|
+
tags = ["toml", "mikel"]
|
|
282
97
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
{{#frontmatter as="meta"}}
|
|
286
|
-
title: My Page
|
|
287
|
-
description: A great page
|
|
98
|
+
[author]
|
|
99
|
+
name = "John Doe"
|
|
288
100
|
{{/frontmatter}}
|
|
289
|
-
|
|
290
|
-
<title>{{@meta.title}}</title>
|
|
291
|
-
<meta name="description" content="{{@meta.description}}">
|
|
292
|
-
`;
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Multiple Frontmatter Blocks
|
|
296
|
-
|
|
297
|
-
You can have multiple `{{#frontmatter}}` blocks in a template. Later blocks will overwrite earlier ones:
|
|
298
|
-
|
|
299
|
-
```javascript
|
|
300
|
-
const template = `
|
|
301
|
-
{{#frontmatter}}
|
|
302
|
-
title: Original Title
|
|
303
|
-
author: John
|
|
304
|
-
{{/frontmatter}}
|
|
305
|
-
|
|
306
|
-
{{#frontmatter}}
|
|
307
|
-
title: Updated Title
|
|
308
|
-
status: draft
|
|
309
|
-
{{/frontmatter}}
|
|
310
|
-
|
|
311
|
-
Title: {{@frontmatter.title}}
|
|
312
|
-
Author: {{@frontmatter.author}}
|
|
313
|
-
Status: {{@frontmatter.status}}
|
|
314
|
-
`;
|
|
315
|
-
|
|
316
|
-
// Output:
|
|
317
|
-
// Title: Updated Title
|
|
318
|
-
// Author: (empty, overwritten)
|
|
319
|
-
// Status: draft
|
|
320
101
|
```
|
|
321
102
|
|
|
322
103
|
## API
|
|
323
104
|
|
|
324
105
|
### mikelFrontmatter(options?)
|
|
325
106
|
|
|
326
|
-
Creates a new instance of the frontmatter plugin.
|
|
327
|
-
|
|
328
|
-
**Parameters:**
|
|
329
|
-
- `options` (optional): Configuration options for the plugin.
|
|
330
|
-
- `parser` (Function): Custom parser function to override the default YAML/JSON parser.
|
|
331
|
-
|
|
332
|
-
**Returns:** A plugin object with a `frontmatter` helper.
|
|
333
|
-
|
|
334
|
-
### Helper: `{{#frontmatter}}...{{/frontmatter}}`
|
|
107
|
+
Creates a new instance of the frontmatter plugin. It accepts an `options` parameters containing the following fields:
|
|
108
|
+
- `parser` (Function): Custom parser function to override the default YAML/JSON/TOML parser.
|
|
335
109
|
|
|
336
|
-
Parses the content inside the block as YAML or JSON (auto-detected) and stores it as a variable.
|
|
337
110
|
|
|
338
|
-
|
|
339
|
-
- `as`: Custom variable name (default: `"frontmatter"`). Example: `{{#frontmatter as="meta"}}`
|
|
340
|
-
|
|
341
|
-
**Output:** This helper doesn't produce any output in the rendered template.
|
|
342
|
-
|
|
343
|
-
### Custom Parser
|
|
111
|
+
## Custom Parser
|
|
344
112
|
|
|
345
113
|
You can provide a custom parser function if you need special parsing logic:
|
|
346
114
|
|
|
347
115
|
```javascript
|
|
348
|
-
const customParser = (content) => {
|
|
349
|
-
// Your custom parsing logic
|
|
116
|
+
const customParser = (content, format) => {
|
|
117
|
+
// Your custom parsing logic based on content and format
|
|
350
118
|
return parsedData;
|
|
351
119
|
};
|
|
352
120
|
|
|
353
121
|
const render = mikel.create();
|
|
354
|
-
render.use(mikelFrontmatter({
|
|
122
|
+
render.use(mikelFrontmatter({
|
|
123
|
+
parser: customParser,
|
|
124
|
+
}));
|
|
355
125
|
```
|
|
356
126
|
|
|
357
127
|
## YAML Syntax Support
|
|
358
128
|
|
|
359
129
|
This plugin includes a basic YAML parser that supports:
|
|
360
130
|
|
|
361
|
-
- ✅ Key-value pairs
|
|
362
|
-
- ✅ Nested objects (with indentation)
|
|
363
|
-
- ✅ Arrays (using `- item` syntax)
|
|
364
|
-
- ✅ Arrays of objects
|
|
365
|
-
- ✅
|
|
366
|
-
- ✅
|
|
367
|
-
- ✅
|
|
368
|
-
-
|
|
369
|
-
- ❌ Multi-
|
|
370
|
-
- ❌ Anchors and aliases (`&anchor`, `*alias`)
|
|
371
|
-
- ❌ Complex YAML features
|
|
372
|
-
|
|
373
|
-
|
|
131
|
+
- ✅ Key-value pairs.
|
|
132
|
+
- ✅ Nested objects (with indentation).
|
|
133
|
+
- ✅ Arrays (using `- item` syntax).
|
|
134
|
+
- ✅ Arrays of objects.
|
|
135
|
+
- ✅ Inline arrays `[item1, item2, ...]`.
|
|
136
|
+
- ✅ Inline objects `{key1: value1, key2: value2, ...}`.
|
|
137
|
+
- ✅ Boolean variants (`yes/no`, `on/off`, `true/false`).
|
|
138
|
+
- ❌ Multi-line strings (with `|` or `>`).
|
|
139
|
+
- ❌ Multi-document syntax (`---`).
|
|
140
|
+
- ❌ Anchors and aliases (`&anchor`, `*alias`).
|
|
141
|
+
- ❌ Complex YAML features (e.g., tags, merge keys).
|
|
142
|
+
|
|
143
|
+
## TOML Support
|
|
144
|
+
|
|
145
|
+
The plugin includes a basic TOML parser that supports:
|
|
146
|
+
|
|
147
|
+
- ✅ Key-value pairs (`key = "value"`).
|
|
148
|
+
- ✅ Tables (`[section]`).
|
|
149
|
+
- ✅ Nested tables (`[section.subsection]`).
|
|
150
|
+
- ✅ Array of Tables (`[[table]]`).
|
|
151
|
+
- ✅ Inline arrays and objects.
|
|
152
|
+
- ✅ Strings, integers, floats, booleans, and null.
|
|
153
|
+
- ✅ Comments (starting with `#`).
|
|
154
|
+
- ❌ Multi-line strings (`"""` or `'''`).
|
|
155
|
+
- ❌ Date and time values.
|
|
156
|
+
- ❌ Dotted keys (e.g. `a.b = "val"`) in a single line.
|
|
157
|
+
- ❌ Hex, octal, binary, and scientific notation.
|
|
374
158
|
|
|
375
159
|
## JSON Support
|
|
376
160
|
|
|
377
|
-
The plugin fully supports standard JSON syntax through the native `JSON.parse()` method.
|
|
161
|
+
The plugin fully supports standard JSON syntax through the native `JSON.parse()` method.
|
|
378
162
|
|
|
379
163
|
## License
|
|
380
164
|
|
package/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface MikelFrontmatterOptions {
|
|
|
7
7
|
* @param content - The raw frontmatter content string
|
|
8
8
|
* @returns Parsed data object
|
|
9
9
|
*/
|
|
10
|
-
parser?: (content: string) => Record<string, any>;
|
|
10
|
+
parser?: (content: string, format: string) => Record<string, any>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -17,6 +17,13 @@ export interface MikelFrontmatterOptions {
|
|
|
17
17
|
*/
|
|
18
18
|
export type YamlParser = (yaml: string) => Record<string, any>;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* TOML parser function
|
|
22
|
+
* @param toml - TOML string to parse
|
|
23
|
+
* @returns Parsed object
|
|
24
|
+
*/
|
|
25
|
+
export type TomlParser = (toml: string) => Record<string, any>;
|
|
26
|
+
|
|
20
27
|
/**
|
|
21
28
|
* Mikel frontmatter plugin
|
|
22
29
|
* @param options - Plugin configuration options
|
|
@@ -26,12 +33,11 @@ declare function mikelFrontmatter(options?: MikelFrontmatterOptions): {
|
|
|
26
33
|
helpers: {
|
|
27
34
|
frontmatter: (params: {
|
|
28
35
|
args: any[];
|
|
29
|
-
opt?: Record<string, any>;
|
|
30
36
|
options: Record<string, any>;
|
|
31
37
|
tokens: string[];
|
|
32
38
|
data: Record<string, any>;
|
|
33
|
-
|
|
34
|
-
fn: (blockData?: Record<string, any>,
|
|
39
|
+
state: Record<string, any>;
|
|
40
|
+
fn: (blockData?: Record<string, any>, blockState?: Record<string, any>, blockOutput?: string[]) => string;
|
|
35
41
|
}) => string;
|
|
36
42
|
};
|
|
37
43
|
};
|
|
@@ -41,6 +47,7 @@ declare function mikelFrontmatter(options?: MikelFrontmatterOptions): {
|
|
|
41
47
|
*/
|
|
42
48
|
declare namespace mikelFrontmatter {
|
|
43
49
|
export const yamlParser: YamlParser;
|
|
50
|
+
export const tomlParser: TomlParser;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export default mikelFrontmatter;
|
package/index.js
CHANGED
|
@@ -1,17 +1,42 @@
|
|
|
1
|
+
// @description split a string by separator, ignoring separators inside quotes
|
|
2
|
+
const splitSafe = (str = "", separator = ",") => {
|
|
3
|
+
const result = [];
|
|
4
|
+
let current = "", inQuotes = false, quoteChar = null;
|
|
5
|
+
for (let i = 0; i < str.length; i++) {
|
|
6
|
+
const char = str[i];
|
|
7
|
+
if ((char === '"' || char === "'")) {
|
|
8
|
+
if (!inQuotes) {
|
|
9
|
+
inQuotes = true;
|
|
10
|
+
quoteChar = char;
|
|
11
|
+
} else if (char === quoteChar) {
|
|
12
|
+
inQuotes = false;
|
|
13
|
+
quoteChar = null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (char === separator && !inQuotes) {
|
|
17
|
+
result.push(current);
|
|
18
|
+
current = "";
|
|
19
|
+
} else {
|
|
20
|
+
current = current + char;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
result.push(current);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
|
|
1
27
|
// @description parse a single value from YAML
|
|
2
28
|
const parseValue = (str = "") => {
|
|
3
|
-
// str = str.trim();
|
|
4
29
|
// 1. quoted strings
|
|
5
30
|
if ((str.startsWith(`"`) && str.endsWith(`"`)) || (str.startsWith(`'`) && str.endsWith(`'`))) {
|
|
6
31
|
return str.slice(1, -1);
|
|
7
32
|
}
|
|
8
33
|
// 2. inline array
|
|
9
34
|
if (str.startsWith("[") && str.endsWith("]")) {
|
|
10
|
-
return str.slice(1, -1)
|
|
35
|
+
return splitSafe(str.slice(1, -1), ",").map(item => parseValue(item.trim()));
|
|
11
36
|
}
|
|
12
37
|
// 3. inline object
|
|
13
38
|
if (str.startsWith("{") && str.endsWith("}")) {
|
|
14
|
-
return str.slice(1, -1)
|
|
39
|
+
return splitSafe(str.slice(1, -1), ",").reduce((result, pair) => {
|
|
15
40
|
const [key, value] = pair.split(":");
|
|
16
41
|
if (key && value) {
|
|
17
42
|
result[key.trim()] = parseValue(value.trim());
|
|
@@ -43,77 +68,72 @@ const getIndent = (line = "") => {
|
|
|
43
68
|
return line.length - line.trimStart().length;
|
|
44
69
|
};
|
|
45
70
|
|
|
71
|
+
// @description extract a key-value pair from a line
|
|
72
|
+
const extractKeyValue = (line = "", separator = ":") => {
|
|
73
|
+
const separatorIndex = line.indexOf(separator);
|
|
74
|
+
return [
|
|
75
|
+
line.slice(0, separatorIndex).trim(),
|
|
76
|
+
line.slice(separatorIndex + 1).trim(),
|
|
77
|
+
];
|
|
78
|
+
};
|
|
79
|
+
|
|
46
80
|
// @description parse a simple YAML string into an object
|
|
47
81
|
const parseYaml = (yaml = "") => {
|
|
48
|
-
const lines = yaml.split("\n")
|
|
82
|
+
const lines = yaml.split("\n").filter(line => {
|
|
83
|
+
return line.trim() !== "" && !line.trim().startsWith("#");
|
|
84
|
+
});
|
|
49
85
|
let i = 0;
|
|
50
86
|
const parse = (minIndent = 0, result = {}) => {
|
|
51
87
|
while (i < lines.length) {
|
|
52
|
-
const line = lines[i];
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// skip empty and comments
|
|
57
|
-
if (!trimmed || trimmed[0] === "#") {
|
|
58
|
-
i++;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
// Return if dedented
|
|
88
|
+
const line = lines[i].trim();
|
|
89
|
+
const indent = getIndent(lines[i]);
|
|
90
|
+
// 1. return if dedented
|
|
62
91
|
if (indent < minIndent) {
|
|
63
92
|
return result;
|
|
64
93
|
}
|
|
65
|
-
//
|
|
66
|
-
if (
|
|
67
|
-
//
|
|
94
|
+
// 2. array item
|
|
95
|
+
else if (line.startsWith("- ")) {
|
|
96
|
+
// first array item - initialize array
|
|
68
97
|
if (!Array.isArray(result)) {
|
|
69
98
|
result = [];
|
|
70
99
|
}
|
|
71
|
-
const content =
|
|
100
|
+
const content = line.slice(2).trim();
|
|
72
101
|
i++;
|
|
102
|
+
// check for nested content on next lines
|
|
73
103
|
if (!content) {
|
|
74
|
-
// Nested content on next lines
|
|
75
104
|
result.push(parse(indent + 2, {}));
|
|
76
105
|
}
|
|
77
106
|
// inline content with key:value (object)
|
|
78
107
|
else if (content.includes(":")) {
|
|
79
108
|
const obj = {};
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
// Check for nested content on following lines
|
|
109
|
+
const [key, value] = extractKeyValue(content, ":");
|
|
110
|
+
obj[key] = !!value ? parseValue(value) : null;
|
|
111
|
+
// check for nested content on following lines
|
|
85
112
|
if (i < lines.length && getIndent(lines[i]) > indent) {
|
|
86
113
|
Object.assign(obj, parse(indent + 2, {}));
|
|
87
114
|
}
|
|
88
115
|
result.push(obj);
|
|
89
116
|
}
|
|
117
|
+
// simple value
|
|
90
118
|
else {
|
|
91
|
-
// Simple value
|
|
92
119
|
result.push(parseValue(content));
|
|
93
120
|
}
|
|
94
121
|
}
|
|
95
|
-
//
|
|
96
|
-
else if (
|
|
97
|
-
const
|
|
98
|
-
const key = trimmed.slice(0, colonIdx).trim();
|
|
99
|
-
const value = trimmed.slice(colonIdx + 1).trim();
|
|
122
|
+
// 3. key-value pair
|
|
123
|
+
else if (line.includes(":")) {
|
|
124
|
+
const [key, value] = extractKeyValue(line, ":");
|
|
100
125
|
i++;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const nextLine = lines[i].trim();
|
|
105
|
-
if (nextLine.startsWith("- ")) {
|
|
106
|
-
result[key] = parse(indent + 2, []);
|
|
107
|
-
} else {
|
|
108
|
-
result[key] = parse(indent + 2, {});
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
result[key] = null;
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
126
|
+
result[key] = null;
|
|
127
|
+
// check for inline content with key: value (object)
|
|
128
|
+
if (value) {
|
|
114
129
|
result[key] = parseValue(value);
|
|
115
130
|
}
|
|
131
|
+
// check if the next line is an array or object
|
|
132
|
+
else if (!value && i < lines.length && getIndent(lines[i]) > indent) {
|
|
133
|
+
result[key] = parse(indent + 2, lines[i].trim().startsWith("- ") ? [] : {});
|
|
134
|
+
}
|
|
116
135
|
}
|
|
136
|
+
// 4. other case??
|
|
117
137
|
else {
|
|
118
138
|
i++;
|
|
119
139
|
}
|
|
@@ -123,43 +143,92 @@ const parseYaml = (yaml = "") => {
|
|
|
123
143
|
return parse(0, {});
|
|
124
144
|
};
|
|
125
145
|
|
|
146
|
+
// @description parse a simple TOML string into an object
|
|
147
|
+
const parseToml = (toml = "") => {
|
|
148
|
+
const lines = toml.split("\n");
|
|
149
|
+
const result = {};
|
|
150
|
+
let currentTable = result;
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
const line = lines[i].trim();
|
|
153
|
+
if (!!line && !line.startsWith("#")) {
|
|
154
|
+
// 1. table header [section] or [[array]]
|
|
155
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
156
|
+
const isArray = line.startsWith("[[") && line.endsWith("]]");
|
|
157
|
+
const path = isArray ? line.slice(2, -2).trim() : line.slice(1, -1).trim();
|
|
158
|
+
currentTable = result;
|
|
159
|
+
path.split(".").forEach((part, index, parts) => {
|
|
160
|
+
const isLast = index === parts.length - 1;
|
|
161
|
+
// 1.1. if is an array of tables and is the last part
|
|
162
|
+
if (isLast && isArray) {
|
|
163
|
+
if (!Array.isArray(currentTable[part])) {
|
|
164
|
+
currentTable[part] = [];
|
|
165
|
+
}
|
|
166
|
+
currentTable[part].push({});
|
|
167
|
+
currentTable = currentTable[part][currentTable[part].length - 1];
|
|
168
|
+
}
|
|
169
|
+
// 1.2. other cases
|
|
170
|
+
else {
|
|
171
|
+
if (!currentTable[part]) {
|
|
172
|
+
currentTable[part] = {};
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(currentTable[part])) {
|
|
175
|
+
currentTable = currentTable[part][currentTable[part].length - 1];
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
currentTable = currentTable[part];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// 2. key-value pair: key = value
|
|
184
|
+
else if (line.includes("=")) {
|
|
185
|
+
const [key, value] = extractKeyValue(line, "=");
|
|
186
|
+
currentTable[key] = parseValue(value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
|
|
126
194
|
// @description internal method to parse the content of the frontmatter block
|
|
127
|
-
const parseFrontmatterBlock = (content = "", parser = null) => {
|
|
195
|
+
const parseFrontmatterBlock = (content = "", format = "yaml", parser = null) => {
|
|
128
196
|
// 1. use custom parser if provided
|
|
129
197
|
if (typeof parser === "function") {
|
|
130
|
-
return parser(content);
|
|
198
|
+
return parser(content, format);
|
|
131
199
|
}
|
|
132
|
-
// 2.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return JSON.parse(content);
|
|
200
|
+
// 2. use the appropriate parser based on the format
|
|
201
|
+
if (format === "json") {
|
|
202
|
+
return JSON.parse(content.trim());
|
|
136
203
|
}
|
|
204
|
+
if (format === "toml") {
|
|
205
|
+
return parseToml(content);
|
|
206
|
+
}
|
|
207
|
+
// 3. fallback to YAML
|
|
137
208
|
return parseYaml(content);
|
|
138
209
|
};
|
|
139
210
|
|
|
140
211
|
// @description plugin to register a #frontmatter helper
|
|
141
212
|
// @param {Object} options - plugin options
|
|
142
213
|
// @param {Function} options.parser - custom YAML parser function
|
|
143
|
-
const mikelFrontmatter = (options = {}) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// don't render anything
|
|
155
|
-
return "";
|
|
156
|
-
},
|
|
214
|
+
const mikelFrontmatter = (options = {}) => ({
|
|
215
|
+
helpers: {
|
|
216
|
+
frontmatter: params => {
|
|
217
|
+
const variableName = params.options.as || "frontmatter";
|
|
218
|
+
const format = params.options.format || "yaml";
|
|
219
|
+
// register the variable (overwrite if it already exists)
|
|
220
|
+
Object.assign(params.state, {
|
|
221
|
+
[variableName]: parseFrontmatterBlock(params.fn(params.data) || "", format, options.parser),
|
|
222
|
+
});
|
|
223
|
+
// don't render anything
|
|
224
|
+
return "";
|
|
157
225
|
},
|
|
158
|
-
}
|
|
159
|
-
};
|
|
226
|
+
},
|
|
227
|
+
});
|
|
160
228
|
|
|
161
229
|
// assign additional metadata to the plugin function
|
|
162
230
|
mikelFrontmatter.yamlParser = parseYaml;
|
|
231
|
+
mikelFrontmatter.tomlParser = parseToml;
|
|
163
232
|
|
|
164
233
|
// export the plugin as default
|
|
165
234
|
export default mikelFrontmatter;
|
package/package.json
CHANGED