No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
9850845
... +7 ...
74dd621
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
1 | + | /** |
|
2 | + | * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension |
|
3 | + | * @typedef {import('micromark-util-types').Handle} Handle |
|
4 | + | * @typedef {import('./matters.js').Options} Options |
|
5 | + | */ |
|
6 | + | ||
7 | + | import {matters} from './matters.js' |
|
8 | + | ||
9 | + | /** |
|
10 | + | * Create an extension to support frontmatter (YAML, TOML, and more). |
|
11 | + | * |
|
12 | + | * @param {Options} [options='yaml'] One preset or matter, or an array of them. |
|
13 | + | * @returns {HtmlExtension} |
|
14 | + | */ |
|
15 | + | export function frontmatterHtml(options) { |
|
16 | + | const settings = matters(options) |
|
17 | + | /** @type {HtmlExtension['enter']} */ |
|
18 | + | const enter = {} |
|
19 | + | /** @type {HtmlExtension['exit']} */ |
|
20 | + | const exit = {} |
|
21 | + | let index = -1 |
|
22 | + | ||
23 | + | while (++index < settings.length) { |
|
24 | + | const type = settings[index].type |
|
25 | + | enter[type] = start |
|
26 | + | exit[type] = end |
|
27 | + | } |
|
28 | + | ||
29 | + | return {enter, exit} |
|
30 | + | ||
31 | + | /** @type {Handle} */ |
|
32 | + | function start() { |
|
33 | + | this.buffer() |
|
34 | + | } |
|
35 | + | ||
36 | + | /** @type {Handle} */ |
|
37 | + | function end() { |
|
38 | + | this.resume() |
|
39 | + | this.setData('slurpOneLineEnding', true) |
|
40 | + | } |
|
41 | + | } |
1 | + | /** |
|
2 | + | * @typedef {import('micromark-util-types').Extension} Extension |
|
3 | + | * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord |
|
4 | + | * @typedef {import('micromark-util-types').Construct} Construct |
|
5 | + | * @typedef {import('micromark-util-types').Tokenizer} Tokenizer |
|
6 | + | * @typedef {import('micromark-util-types').State} State |
|
7 | + | * @typedef {import('./matters.js').Options} Options |
|
8 | + | * @typedef {import('./matters.js').Matter} Matter |
|
9 | + | * @typedef {import('./matters.js').Info} Info |
|
10 | + | */ |
|
11 | + | ||
12 | + | import {markdownLineEnding, markdownSpace} from 'micromark-util-character' |
|
13 | + | import {codes} from 'micromark-util-symbol/codes.js' |
|
14 | + | import {types} from 'micromark-util-symbol/types.js' |
|
15 | + | import {matters} from './matters.js' |
|
16 | + | ||
17 | + | /** |
|
18 | + | * Create an extension to support frontmatter (YAML, TOML, and more). |
|
19 | + | * |
|
20 | + | * @param {Options} [options='yaml'] One preset or matter, or an array of them. |
|
21 | + | * @returns {Extension} |
|
22 | + | */ |
|
23 | + | export function frontmatter(options) { |
|
24 | + | const settings = matters(options) |
|
25 | + | /** @type {ConstructRecord} */ |
|
26 | + | const flow = {} |
|
27 | + | let index = -1 |
|
28 | + | /** @type {Matter} */ |
|
29 | + | let matter |
|
30 | + | /** @type {number} */ |
|
31 | + | let code |
|
32 | + | ||
33 | + | while (++index < settings.length) { |
|
34 | + | matter = settings[index] |
|
35 | + | code = fence(matter, 'open').charCodeAt(0) |
|
36 | + | if (code in flow) { |
|
37 | + | // @ts-expect-error it clearly does exist. |
|
38 | + | flow[code].push(parse(matter)) |
|
39 | + | } else { |
|
40 | + | flow[code] = [parse(matter)] |
|
41 | + | } |
|
42 | + | } |
|
43 | + | ||
44 | + | return {flow} |
|
45 | + | } |
|
46 | + | ||
47 | + | /** |
|
48 | + | * @param {Matter} matter |
|
49 | + | * @returns {Construct} |
|
50 | + | */ |
|
51 | + | function parse(matter) { |
|
52 | + | const name = matter.type |
|
53 | + | const anywhere = matter.anywhere |
|
54 | + | const valueType = name + 'Value' |
|
55 | + | const fenceType = name + 'Fence' |
|
56 | + | const sequenceType = fenceType + 'Sequence' |
|
57 | + | const fenceConstruct = {tokenize: tokenizeFence, partial: true} |
|
58 | + | /** @type {string} */ |
|
59 | + | let buffer |
|
60 | + | ||
61 | + | return {tokenize: tokenizeFrontmatter, concrete: true} |
|
62 | + | ||
63 | + | /** @type {Tokenizer} */ |
|
64 | + | function tokenizeFrontmatter(effects, ok, nok) { |
|
65 | + | const self = this |
|
66 | + | ||
67 | + | return start |
|
68 | + | ||
69 | + | /** @type {State} */ |
|
70 | + | function start(code) { |
|
71 | + | const position = self.now() |
|
72 | + | ||
73 | + | if (position.column !== 1 || (!anywhere && position.line !== 1)) { |
|
74 | + | return nok(code) |
|
75 | + | } |
|
76 | + | ||
77 | + | effects.enter(name) |
|
78 | + | buffer = fence(matter, 'open') |
|
79 | + | return effects.attempt(fenceConstruct, afterOpeningFence, nok)(code) |
|
80 | + | } |
|
81 | + | ||
82 | + | /** @type {State} */ |
|
83 | + | function afterOpeningFence(code) { |
|
84 | + | buffer = fence(matter, 'close') |
|
85 | + | return lineEnd(code) |
|
86 | + | } |
|
87 | + | ||
88 | + | /** @type {State} */ |
|
89 | + | function lineStart(code) { |
|
90 | + | if (code === codes.eof || markdownLineEnding(code)) { |
|
91 | + | return lineEnd(code) |
|
92 | + | } |
|
93 | + | ||
94 | + | effects.enter(valueType) |
|
95 | + | return lineData(code) |
|
96 | + | } |
|
97 | + | ||
98 | + | /** @type {State} */ |
|
99 | + | function lineData(code) { |
|
100 | + | if (code === codes.eof || markdownLineEnding(code)) { |
|
101 | + | effects.exit(valueType) |
|
102 | + | return lineEnd(code) |
|
103 | + | } |
|
104 | + | ||
105 | + | effects.consume(code) |
|
106 | + | return lineData |
|
107 | + | } |
|
108 | + | ||
109 | + | /** @type {State} */ |
|
110 | + | function lineEnd(code) { |
|
111 | + | // Require a closing fence. |
|
112 | + | if (code === codes.eof) { |
|
113 | + | return nok(code) |
|
114 | + | } |
|
115 | + | ||
116 | + | // Can only be an eol. |
|
117 | + | effects.enter(types.lineEnding) |
|
118 | + | effects.consume(code) |
|
119 | + | effects.exit(types.lineEnding) |
|
120 | + | return effects.attempt(fenceConstruct, after, lineStart) |
|
121 | + | } |
|
122 | + | ||
123 | + | /** @type {State} */ |
|
124 | + | function after(code) { |
|
125 | + | effects.exit(name) |
|
126 | + | return ok(code) |
|
127 | + | } |
|
128 | + | } |
|
129 | + | ||
130 | + | /** @type {Tokenizer} */ |
|
131 | + | function tokenizeFence(effects, ok, nok) { |
|
132 | + | let bufferIndex = 0 |
|
133 | + | ||
134 | + | return start |
|
135 | + | ||
136 | + | /** @type {State} */ |
|
137 | + | function start(code) { |
|
138 | + | if (code === buffer.charCodeAt(bufferIndex)) { |
|
139 | + | effects.enter(fenceType) |
|
140 | + | effects.enter(sequenceType) |
|
141 | + | return insideSequence(code) |
|
142 | + | } |
|
143 | + | ||
144 | + | return nok(code) |
|
145 | + | } |
|
146 | + | ||
147 | + | /** @type {State} */ |
|
148 | + | function insideSequence(code) { |
|
149 | + | if (bufferIndex === buffer.length) { |
|
150 | + | effects.exit(sequenceType) |
|
151 | + | ||
152 | + | if (markdownSpace(code)) { |
|
153 | + | effects.enter(types.whitespace) |
|
154 | + | return insideWhitespace(code) |
|
155 | + | } |
|
156 | + | ||
157 | + | return fenceEnd(code) |
|
158 | + | } |
|
159 | + | ||
160 | + | if (code === buffer.charCodeAt(bufferIndex++)) { |
|
161 | + | effects.consume(code) |
|
162 | + | return insideSequence |
|
163 | + | } |
|
164 | + | ||
165 | + | return nok(code) |
|
166 | + | } |
|
167 | + | ||
168 | + | /** @type {State} */ |
|
169 | + | function insideWhitespace(code) { |
|
170 | + | if (markdownSpace(code)) { |
|
171 | + | effects.consume(code) |
|
172 | + | return insideWhitespace |
|
173 | + | } |
|
174 | + | ||
175 | + | effects.exit(types.whitespace) |
|
176 | + | return fenceEnd(code) |
|
177 | + | } |
|
178 | + | ||
179 | + | /** @type {State} */ |
|
180 | + | function fenceEnd(code) { |
|
181 | + | if (code === codes.eof || markdownLineEnding(code)) { |
|
182 | + | effects.exit(fenceType) |
|
183 | + | return ok(code) |
|
184 | + | } |
|
185 | + | ||
186 | + | return nok(code) |
|
187 | + | } |
|
188 | + | } |
|
189 | + | } |
|
190 | + | ||
191 | + | /** |
|
192 | + | * @param {Matter} matter |
|
193 | + | * @param {'open'|'close'} prop |
|
194 | + | * @returns {string} |
|
195 | + | */ |
|
196 | + | function fence(matter, prop) { |
|
197 | + | return matter.marker |
|
198 | + | ? pick(matter.marker, prop).repeat(3) |
|
199 | + | : // @ts-expect-error: They’re mutually exclusive. |
|
200 | + | pick(matter.fence, prop) |
|
201 | + | } |
|
202 | + | ||
203 | + | /** |
|
204 | + | * @param {Info|string} schema |
|
205 | + | * @param {'open'|'close'} prop |
|
206 | + | * @returns {string} |
|
207 | + | */ |
|
208 | + | function pick(schema, prop) { |
|
209 | + | return typeof schema === 'string' ? schema : schema[prop] |
|
210 | + | } |
1 | + | /** |
|
2 | + | * @typedef {'yaml'|'toml'} Preset |
|
3 | + | * Either `'yaml'` or `'toml'` |
|
4 | + | * |
|
5 | + | * @typedef Info |
|
6 | + | * @property {string} open |
|
7 | + | * @property {string} close |
|
8 | + | * |
|
9 | + | * @typedef MatterProps |
|
10 | + | * @property {string} type |
|
11 | + | * Type to tokenize as |
|
12 | + | * @property {boolean} [anywhere=false] |
|
13 | + | * If `true`, matter can be found anywhere in the document. |
|
14 | + | * If `false` (default), only matter at the start of the document is |
|
15 | + | * recognized. |
|
16 | + | * |
|
17 | + | * @typedef MarkerProps |
|
18 | + | * @property {string|Info} marker |
|
19 | + | * Character used to construct fences. |
|
20 | + | * By providing an object with `open` and `close` different characters can be |
|
21 | + | * used for opening and closing fences. |
|
22 | + | * For example the character `'-'` will result in `'---'` being used as the |
|
23 | + | * fence |
|
24 | + | * @property {never} [fence] |
|
25 | + | * |
|
26 | + | * @typedef FenceProps |
|
27 | + | * @property {string|Info} fence |
|
28 | + | * String used as the complete fence. |
|
29 | + | * By providing an object with `open` and `close` different values can be used |
|
30 | + | * for opening and closing fences. |
|
31 | + | * This can be used too if fences contain different characters or lengths |
|
32 | + | * other than 3. |
|
33 | + | * @property {never} [marker] |
|
34 | + | * |
|
35 | + | * @typedef {(MatterProps & FenceProps)|(MatterProps & MarkerProps)} Matter |
|
36 | + | * |
|
37 | + | * @typedef {Preset|Matter|Array.<Preset|Matter>} Options |
|
38 | + | */ |
|
39 | + | ||
40 | + | import {fault} from 'fault' |
|
41 | + | ||
42 | + | const own = {}.hasOwnProperty |
|
43 | + | const markers = {yaml: '-', toml: '+'} |
|
44 | + | ||
45 | + | /** |
|
46 | + | * @param {Options} [options='yaml'] |
|
47 | + | * @returns {Array<Matter>} |
|
48 | + | */ |
|
49 | + | export function matters(options = 'yaml') { |
|
50 | + | /** @type {Array<Matter>} */ |
|
51 | + | const results = [] |
|
52 | + | let index = -1 |
|
53 | + | ||
54 | + | // One preset or matter. |
|
55 | + | if (!Array.isArray(options)) { |
|
56 | + | options = [options] |
|
57 | + | } |
|
58 | + | ||
59 | + | while (++index < options.length) { |
|
60 | + | results[index] = matter(options[index]) |
|
61 | + | } |
|
62 | + | ||
63 | + | return results |
|
64 | + | } |
|
65 | + | ||
66 | + | /** |
|
67 | + | * @param {Preset|Matter} option |
|
68 | + | * @returns {Matter} |
|
69 | + | */ |
|
70 | + | function matter(option) { |
|
71 | + | let result = option |
|
72 | + | ||
73 | + | if (typeof result === 'string') { |
|
74 | + | if (!own.call(markers, result)) { |
|
75 | + | throw fault('Missing matter definition for `%s`', result) |
|
76 | + | } |
|
77 | + | ||
78 | + | result = {type: result, marker: markers[result]} |
|
79 | + | } else if (typeof result !== 'object') { |
|
80 | + | throw fault('Expected matter to be an object, not `%j`', result) |
|
81 | + | } |
|
82 | + | ||
83 | + | if (!own.call(result, 'type')) { |
|
84 | + | throw fault('Missing `type` in matter `%j`', result) |
|
85 | + | } |
|
86 | + | ||
87 | + | if (!own.call(result, 'fence') && !own.call(result, 'marker')) { |
|
88 | + | throw fault('Missing `marker` or `fence` in matter `%j`', result) |
|
89 | + | } |
|
90 | + | ||
91 | + | return result |
|
92 | + | } |
Learn more Showing 4 files with coverage changes found.
dev/lib/matters.js
dev/lib/syntax.js
dev/index.js
dev/lib/html.js
Files | Coverage |
---|---|
dev | +100.00% 100.00% |
Project Totals (4 files) | 100.00% |
74dd621
b1ac00f
4f86d92
1beee12
bc627a3
755406b
a7ae869
0f123fb
9850845