@thi.ng/hiccup-markdown 2.1.48 → 3.0.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/parse.js CHANGED
@@ -1,223 +1,533 @@
1
+ // thing:no-export
1
2
  import { peek } from "@thi.ng/arrays/peek";
2
- import { alts } from "@thi.ng/fsm/alts";
3
- import { fsm } from "@thi.ng/fsm/fsm";
4
- import { not } from "@thi.ng/fsm/not";
5
- import { whitespace } from "@thi.ng/fsm/range";
6
- import { repeat } from "@thi.ng/fsm/repeat";
7
- import { seq } from "@thi.ng/fsm/seq";
8
- import { str } from "@thi.ng/fsm/str";
9
- import { untilStr } from "@thi.ng/fsm/until";
10
- import { comp } from "@thi.ng/transducers/comp";
11
- import { filter } from "@thi.ng/transducers/filter";
3
+ import { isArray } from "@thi.ng/checks/is-array";
4
+ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
5
+ import { isPrimitive } from "@thi.ng/checks/is-primitive";
6
+ import { isString } from "@thi.ng/checks/is-string";
7
+ import { DEFAULT, defmulti } from "@thi.ng/defmulti";
8
+ import { EMOJI } from "@thi.ng/emoji/emoji";
9
+ import { defContext } from "@thi.ng/parse/context";
10
+ import { defGrammar } from "@thi.ng/parse/grammar";
11
+ import { escapeEntities } from "@thi.ng/strings/entities";
12
+ import { slugifyGH } from "@thi.ng/strings/slugify";
13
+ export const GRAMMAR = defGrammar(`
14
+ DNL1: <DNL>+ => discard ;
15
+ DNL2: <NL>{2,} ;
16
+ lbr: '\\\\'! <NL>! ;
17
+ esc: '\\\\'! ( '<' | '[' | '_' | '*' | '\`' | '~' | ':' ) ;
18
+ inlinedelim: ( "![" | '[' | "**" | '_' | '\`' | '<' | '\\\\' | "~~" | " :" | <lbr> ) ;
19
+ delim: ( <inlinedelim> | <DNL2> ) ;
20
+ delim1: ( <inlinedelim> | <NL> ) ;
21
+ body: .(?-<delim>!) => join ;
22
+ body1: .(?-<delim1>!) => join ;
23
+
24
+ wikiref: "[["! .(?+"]]"!) => join ;
25
+ fnref: "[^"! <label> ;
26
+ fnote: <LSTART> "[^"! <fnlabel> <WS1> <para> ;
27
+ fnlabel: .(?+"]:"!) => join ;
28
+ label: .(?+']'!) => join ;
29
+ target: .(?+')'!) => join ;
30
+ link: '['! <linklabel> '('! <target> ;
31
+ linkref: '['! <linklabel> '['! <label> ;
32
+ linkdef: <LSTART> '['! <label> ':'! <WS1> <ldtarget> ;
33
+ ldtarget: .(?+<DNL1>) => join ;
34
+ linklabel: (<img> | <bold> | <italic> | <strike> | <code> | <emoji> | <linkbody>)* ']'! ;
35
+ linkdelim: ( "![" | '[' | "**" | '_' | "~~" | '\`' | ']') ;
36
+ linkbody: .(?-<linkdelim>!) => join ;
37
+ img: "!["! <label> '('! <target> ;
38
+ bold: "**"! (<wikiref> | <img> | <fnref> | <linkref> | <link> | <italic> | <strike> | <code> | <emoji> | <body1>)* "**"! ;
39
+ italic: '_'! (<wikiref> | <img> | <fnref> | <linkref> | <link> | <bold> | <strike> | <code> | <emoji> | <body1>)* '_'! ;
40
+ strike: "~~"! (<wikiref> | <img> | <fnref> | <linkref> | <link> | <bold> | <italic> | <code> | <emoji> | <body1>)* "~~"! ;
41
+ code: '\`'! .(?+'\`'!) => join ;
42
+ kbd: "<kbd>"! .(?+"</kbd>"!) => join ;
43
+ emoji: ' '? ':'! (<ALPHA_NUM> | '+' | '-')(?+':'!) => join ;
44
+ para: (<wikiref> | <img> | <fnref> | <linkref> | <link> | <bold> | <italic> | <strike> | <code> | <kbd> | <emoji> | <lbr> | <esc> | <body>)* <DNL2>! ;
45
+
46
+ hdlevel: '#'+ => count ;
47
+ hd: <LSTART> <hdlevel> <WS0>
48
+ (<wikiref> | <img> | <fnref> | <link> | <bold> | <italic> | <strike> | <code> | <emoji> | <body1> )* <DNL1> ;
49
+
50
+ lilevel: ' '* => count ;
51
+ uint: <DIGIT>+ => int ;
52
+ ulid: <DNL> <WS0> '-'! ;
53
+ olid: <DNL> <WS0> <DIGIT>+! '.'! ;
54
+ lidelim: ( <delim> | <ulid> | <olid> ) ;
55
+ libody: .(?-<lidelim>!) => join ;
56
+ todo: '['! [ xX] ']'! <WS1> => hoistR ;
57
+ ulitem: <LSTART> <lilevel> "- "! <todo>?
58
+ (<wikiref> | <img> | <fnref> | <link> | <bold> | <italic> | <strike> | <code> | <emoji> | <libody> )* <DNL> ;
59
+ olitem: <LSTART> <lilevel> <uint> ". "! <todo>?
60
+ (<wikiref> | <img> | <fnref> | <link> | <bold> | <italic> | <strike> | <code> | <emoji> | <libody> )* <DNL> ;
61
+ list: (<ulitem> | <olitem>)+ <DNL1> ;
62
+
63
+ cbdelim: <LSTART> "\`\`\`"! ;
64
+ codeblock: <cbdelim>! <codemeta> <codebody> <DNL1> ;
65
+ codemeta: .(?+<NL>!) => join ;
66
+ codebody: .(?+<cbdelim>) => join ;
67
+
68
+ customdelim: <LSTART> ":::"! ;
69
+ customblock: <customdelim>! <custommeta> <custombody> <DNL1> ;
70
+ custommeta: .(?+<NL>!) => join ;
71
+ custombody: .(?+<customdelim>) => join ;
72
+
73
+ metablock: <LSTART> "{{{"! <metabody> <DNL1> ;
74
+ metabody: .(?+<metaend>!) => join ;
75
+ metaend: "}}}" <LEND> ;
76
+
77
+ bqlevel: '>'+ => count ;
78
+ bqline: <LSTART> <bqlevel> ' '?!
79
+ (<wikiref> | <img> | <fnref> | <link> | <bold> | <italic> | <strike> | <code> | <emoji> | <bqlbr> | <body1>)* <DNL> ;
80
+ bqlbr: '\\\\'!(?-<DNL>) ;
81
+ bquote: <bqline>+ <DNL1> ;
82
+
83
+ tdelim: (<inlinedelim> | '|' ) ;
84
+ tbody: .(?-<tdelim>!) => join ;
85
+ tcell: <WS0> (<wikiref> | <img> | <fnref> | <link> | <bold> | <italic> | <strike> | <code> | <emoji> | <tbody> )* '|'! ;
86
+ trow: <LSTART> '|'! <tcell>(?+<DNL>) ;
87
+ table: <trow>+ <DNL1> ;
88
+
89
+ hr: "--" '-'+ <DNL1> => join ;
90
+
91
+ main: <WS0> (<hd> | <list> | <bquote> | <codeblock> | <customblock> | <metablock> | <table> | <hr> | <fnote> | <linkdef> | <para>)* ;
92
+ `);
93
+ export const DEFAULT_TAG_TRANSFORMS = {
94
+ bold: (_, body) => ["strong", {}, ...body],
95
+ blockquote: (_, body, meta) => ["blockquote", withMeta({}, meta), ...body],
96
+ br: () => ["br", {}],
97
+ code: (_, body) => ["code", {}, body],
98
+ codeblock: (_, lang, __head, body, meta) => [
99
+ "pre",
100
+ withMeta({ data: { lang }, __head }, meta),
101
+ ["code", {}, body],
102
+ ],
103
+ custom: (_, type, __head, body, meta) => [
104
+ "custom",
105
+ withMeta({ type, __head }, meta),
106
+ body,
107
+ ],
108
+ emoji: (_, id) => EMOJI[id] || id,
109
+ footnote: (_, id, body, meta) => [
110
+ "li",
111
+ withMeta({ id: `fn-${id}` }, meta),
112
+ ["sup", {}, `[${id}] `],
113
+ ...body,
114
+ " ",
115
+ ["a", { href: `#fnref-${id}` }, "↩︎"],
116
+ ],
117
+ footnoteRef: (_, id) => [
118
+ "sup",
119
+ {},
120
+ ["a", { id: `fnref-${id}`, href: `#fn-${id}` }, `[${id}]`],
121
+ ],
122
+ footnoteWrapper: (_, notes) => [
123
+ "ul",
124
+ { id: "footnotes" },
125
+ ...Object.keys(notes)
126
+ .sort()
127
+ .map((id) => notes[id]),
128
+ ],
129
+ heading: (_, level, id, body, meta) => [
130
+ level > 6 ? "p" : `h${level}`,
131
+ withMeta({ id }, meta),
132
+ ...body,
133
+ ],
134
+ hr: (_, __length, meta) => ["hr", withMeta({ __length }, meta)],
135
+ img: (_, alt, src, title) => ["img", { src, alt, title }],
136
+ italic: (_, body) => ["em", {}, ...body],
137
+ kbd: (_, key) => ["kbd", {}, key],
138
+ link: (_, href, title, body) => ["a", { href, title }, ...body],
139
+ linkRef: (ctx, refID, body) => [
140
+ "a",
141
+ {
142
+ href: () => ctx.linkRefs[refID]?.[0],
143
+ title: () => ctx.linkRefs[refID]?.[1],
144
+ },
145
+ ...body,
146
+ ],
147
+ meta: (_, body) => body,
148
+ olitem: (_, attribs, index, body) => [
149
+ "li",
150
+ { ...attribs, __index: index },
151
+ ...body,
152
+ ],
153
+ ol: (_, items, meta) => ["ol", withMeta({}, meta), ...items],
154
+ para: (_, body, meta) => ["p", withMeta({}, meta), ...body],
155
+ strike: (_, body) => ["s", {}, ...body],
156
+ table: (_, __align, head, rows, meta) => [
157
+ "table",
158
+ withMeta({ __align }, meta),
159
+ ["thead", {}, head],
160
+ ["tbody", {}, ...rows],
161
+ ],
162
+ tableCell: (_, body) => ["td", {}, ...body],
163
+ tableHead: (_, body) => ["th", {}, ...body],
164
+ tableRow: (_, __, cells) => ["tr", {}, ...cells],
165
+ ul: (_, items, meta) => ["ul", withMeta({}, meta), ...items],
166
+ ulitem: (_, attribs, body) => ["li", attribs, ...body],
167
+ wikiref: (_, id, label) => [
168
+ "a",
169
+ { class: "wikiref", href: encodeURI(id.replace(/\s+/g, "_")) },
170
+ label || id,
171
+ ],
172
+ };
173
+ export class ParseError extends Error {
174
+ constructor(state) {
175
+ super(state
176
+ ? `stopped line: ${state.l}, col: ${state.c} (pos: ${state.p})`
177
+ : undefined);
178
+ this.state = state;
179
+ }
180
+ }
12
181
  /**
13
- * Parser state IDs
182
+ * Parses given Markdown source string into a tree structure defined by given
183
+ * {@link TagTransforms}.
184
+ *
185
+ * @remarks
186
+ * The tag transforms are optional and the default implementations can be
187
+ * overwritten on tag-by-tag basis. The default transforms yield a simple hiccup
188
+ * tree (aka each tag will be an array in the form: `["tagname", {...},
189
+ * ...body]`).
190
+ *
191
+ * See [thi.ng/hiccup](https://thi.ng/hiccup) and related packages for further
192
+ * details.
193
+ *
194
+ * @param src
195
+ * @param opts
14
196
  */
15
- var State;
16
- (function (State) {
17
- State[State["BLOCKQUOTE"] = 0] = "BLOCKQUOTE";
18
- State[State["CODE"] = 1] = "CODE";
19
- State[State["CODEBLOCK"] = 2] = "CODEBLOCK";
20
- State[State["EMPHASIS"] = 3] = "EMPHASIS";
21
- State[State["END_BLOCKQUOTE"] = 4] = "END_BLOCKQUOTE";
22
- State[State["END_LI"] = 5] = "END_LI";
23
- State[State["END_PARA"] = 6] = "END_PARA";
24
- State[State["END_HEADING"] = 7] = "END_HEADING";
25
- State[State["END_TABLE"] = 8] = "END_TABLE";
26
- State[State["HEADING"] = 9] = "HEADING";
27
- State[State["IMG"] = 10] = "IMG";
28
- State[State["LINK"] = 11] = "LINK";
29
- State[State["LI"] = 12] = "LI";
30
- State[State["PARA"] = 13] = "PARA";
31
- State[State["START"] = 14] = "START";
32
- State[State["START_CODEBLOCK"] = 15] = "START_CODEBLOCK";
33
- State[State["STRIKE"] = 16] = "STRIKE";
34
- State[State["STRONG"] = 17] = "STRONG";
35
- State[State["TABLE"] = 18] = "TABLE";
36
- })(State || (State = {}));
197
+ export const parse = (src, { tags, opts, linkRefs, logger, } = {}) => {
198
+ const parseCtx = parseRaw(src, opts?.retain);
199
+ const mdCtx = defTransformContext(tags, opts, linkRefs, logger);
200
+ const result = [];
201
+ transformScope(parseCtx.root, mdCtx, result);
202
+ return {
203
+ result,
204
+ ctx: mdCtx,
205
+ complete: !!parseCtx.done,
206
+ state: parseCtx.state,
207
+ };
208
+ };
37
209
  /**
38
- * Default hiccup element factories
210
+ * 1st stage of the parsing (with out result transformations). This calls the
211
+ * `main` rule of the provided parse {@link GRAMMAR} and returns a parse
212
+ * context, incl. the raw abstract syntax tree of the parsed document. If
213
+ * parsing failed entirely (due to invalid input), throws a {@link ParseError}.
214
+ *
215
+ * @remarks
216
+ * Note: Even if the function returns a result, parsing might only have
217
+ * partially successful (can be checked via the [`.done`
218
+ * flag](https://docs.thi.ng/umbrella/parse/classes/ParseContext.html#done)).
219
+ *
220
+ * This function is only for advanced use. Mostly you'll probably want to use
221
+ * the main {@link parse} function instead.
222
+ *
223
+ * @param src
224
+ * @param retain
39
225
  */
40
- const DEFAULT_TAGS = {
41
- blockquote: (xs) => ["blockquote", {}, ...xs],
42
- code: (body) => ["code", {}, body],
43
- codeblock: (lang, body) => ["pre", { lang }, body],
44
- em: (body) => ["em", {}, body],
45
- heading: (level, xs) => [level < 7 ? `h${level}` : "p", {}, ...xs],
46
- hr: () => ["hr", {}],
47
- img: (src, alt) => ["img", { src, alt }],
48
- li: (xs) => ["li", {}, ...xs],
49
- link: (href, body) => ["a", { href }, body],
50
- list: (type, xs) => [type, {}, ...xs],
51
- paragraph: (xs) => ["p", {}, ...xs],
52
- strong: (body) => ["strong", {}, body],
53
- strike: (body) => ["del", {}, body],
54
- table: (rows) => ["table", {}, ["tbody", {}, ...rows]],
55
- td: (_, xs) => ["td", {}, ...xs],
56
- tr: (_, xs) => ["tr", {}, ...xs],
57
- };
58
- const BQUOTE = ">";
59
- const CODE = "`";
60
- const CODEBLOCK = "```";
61
- const CODEBLOCK_END = "\n```\n";
62
- const EM = "_";
63
- const HD = "#";
64
- const HR = "-";
65
- const IMG = "![";
66
- const LI = "- ";
67
- const LINK_LABEL = "[";
68
- const LINK_LABEL_END = "]";
69
- const LINK_HREF = "(";
70
- const LINK_HREF_END = ")";
71
- const NL = "\n";
72
- const STRIKE = "~~";
73
- const STRONG = "**";
74
- const TD = "|";
75
- // state / context handling helpers
76
- const transition = (ctx, id) => {
77
- ctx.children = [];
78
- ctx.body = "";
79
- return [id];
80
- };
81
- const push = (id, next) => (ctx) => {
82
- ctx.stack.push({ id, children: ctx.children.concat(ctx.body) });
83
- return transition(ctx, next);
226
+ export const parseRaw = (src, retain = false) => {
227
+ const ctx = defContext(src + "\n\n", { retain });
228
+ if (!GRAMMAR.rules.main(ctx))
229
+ throw new ParseError(ctx.state);
230
+ return ctx;
84
231
  };
85
- const pop = (result) => (ctx, body) => {
86
- const { id, children } = ctx.stack.pop();
87
- children.push(result(ctx, body));
88
- ctx.children = children;
89
- ctx.body = "";
90
- return [id];
91
- };
92
- const collectChildren = (ctx) => (ctx.children.push(ctx.body), ctx.children);
93
- const collect = (id) => (ctx, buf) => {
94
- ctx.body += buf.join("");
95
- return [id];
96
- };
97
- const collectHeading = (tag) => (ctx) => [14 /* State.START */, [tag(ctx.hd, collectChildren(ctx))]];
98
- const collectAndRestart = (tag) => (ctx) => [14 /* State.START */, [tag(collectChildren(ctx))]];
99
- const collectBlockQuote = (ctx) => (ctx.children.push(ctx.body, ["br", {}]),
100
- (ctx.body = ""),
101
- [0 /* State.BLOCKQUOTE */]);
102
- const collectCodeBlock = (tag) => (ctx, body) => [14 /* State.START */, [tag(ctx.lang, body)]];
103
- const collectLi = (ctx, tag) => ctx.container.push(tag(collectChildren(ctx)));
104
- const collectList = (type, list, item) => (ctx) => {
105
- collectLi(ctx, item);
106
- return [14 /* State.START */, [list(type, ctx.container)]];
232
+ export const defTransformContext = (tags, opts, linkRefs, logger) => ({
233
+ footnotes: {},
234
+ headings: [],
235
+ linkRefs: linkRefs || {},
236
+ hasFootnotes: false,
237
+ logger,
238
+ meta: null,
239
+ row: 0,
240
+ opts: {
241
+ escape: false,
242
+ retain: false,
243
+ ...opts,
244
+ },
245
+ tags: { ...DEFAULT_TAG_TRANSFORMS, ...tags },
246
+ });
247
+ /**
248
+ * Polymorphic & recursive parse scope/node transformation function. Takes a
249
+ * single scope, context and accumulator array, then calls itself recursively
250
+ * for any child scopes and passes relevant data to its user defined
251
+ * {@link TagTransforms} handler and adds result to the accumulator array.
252
+ *
253
+ */
254
+ export const transformScope = defmulti((x, ctx) => {
255
+ ctx.logger && ctx.logger.debug(x);
256
+ return x.id;
257
+ }, {
258
+ body1: "body",
259
+ bqlbr: "lbr",
260
+ bqline: "repeat0",
261
+ label: "body",
262
+ libody: "body",
263
+ linkbody: "body",
264
+ main: "root",
265
+ repeat1: "repeat0",
266
+ tbody: "body",
267
+ }, {
268
+ [DEFAULT]: (scope, ctx) => {
269
+ throw new Error(`unknown ID: ${scope.id}, ctx: ${JSON.stringify(ctx)}`);
270
+ },
271
+ root: ({ children }, ctx, acc) => {
272
+ if (!children)
273
+ return;
274
+ transformScope(children[0], ctx, acc);
275
+ if (ctx.hasFootnotes) {
276
+ __collect(acc, ctx.tags.footnoteWrapper(ctx, ctx.footnotes));
277
+ }
278
+ },
279
+ main: (scope, ctx, acc) => transformScope(scope.children[0], ctx, acc),
280
+ repeat0: (scope, ctx, acc) => scope.children && __children(ctx, scope.children, acc),
281
+ body: (scope, ctx, acc) => __collect(acc, __escape(ctx, scope.result)),
282
+ bold: (scope, ctx, acc) => __collect(acc, ctx.tags.bold(ctx, __children(ctx, scope.children))),
283
+ bquote: (scope, ctx, acc) => {
284
+ const stack = [[]];
285
+ const children = scope.children[0].children;
286
+ const $unwind = (level) => {
287
+ while (level < stack.length) {
288
+ const nested = stack.pop();
289
+ __collect(peek(stack), ctx.tags.blockquote(ctx, __trimBody(nested)));
290
+ }
291
+ return peek(stack);
292
+ };
293
+ for (let i = 0, n = children.length - 1; i <= n; i++) {
294
+ const [{ result: level }, bqline] = children[i].children;
295
+ let body = peek(stack);
296
+ if (level > stack.length) {
297
+ while (level > stack.length)
298
+ stack.push((body = []));
299
+ }
300
+ else if (level < stack.length) {
301
+ body = $unwind(level);
302
+ }
303
+ else if (body.length) {
304
+ const prev = children[i - 1].children[1];
305
+ if (!bqline.children) {
306
+ __collect(body, ctx.tags.br(ctx));
307
+ __collect(body, ctx.tags.br(ctx));
308
+ }
309
+ else if (prev.children &&
310
+ peek(prev.children).id !== "bqlbr") {
311
+ body.push(" ");
312
+ }
313
+ }
314
+ transformScope(bqline, ctx, body);
315
+ }
316
+ __collect(acc, ctx.tags.blockquote(ctx, __trimBody($unwind(1)), ctx.meta));
317
+ ctx.meta = null;
318
+ },
319
+ code: (scope, ctx, acc) => __collect(acc, ctx.tags.code(ctx, __escape(ctx, scope.result))),
320
+ codeblock: ({ children }, ctx, acc) => {
321
+ const [lang, ...head] = children[0].result.split(" ");
322
+ const body = children[1].result.trim();
323
+ __collect(acc, ctx.tags.codeblock(ctx, lang, head, __escape(ctx, body), ctx.meta));
324
+ ctx.meta = null;
325
+ },
326
+ customblock: ({ children }, ctx, acc) => {
327
+ const [type, ...head] = children[0].result.split(" ");
328
+ __collect(acc, ctx.tags.custom(ctx, type, head, children[1].result.trim(), ctx.meta));
329
+ ctx.meta = null;
330
+ },
331
+ emoji: ({ result }, ctx, acc) => {
332
+ if (result[0] === " ") {
333
+ __collect(acc, " ");
334
+ result = result.substring(1);
335
+ }
336
+ __collect(acc, ctx.tags.emoji(ctx, result));
337
+ },
338
+ esc: (scope, ctx, acc) => acc.push(__escape(ctx, scope.children[0].result)),
339
+ fnote: ({ children }, ctx) => {
340
+ const body = [];
341
+ const id = children[0].result;
342
+ transformScope(children[1].children[0], ctx, body);
343
+ const res = ctx.tags.footnote(ctx, id, body, ctx.meta);
344
+ if (res != null) {
345
+ ctx.hasFootnotes = true;
346
+ ctx.footnotes[id] = res;
347
+ }
348
+ ctx.meta = null;
349
+ },
350
+ fnref: (scope, ctx, acc) => __collect(acc, ctx.tags.footnoteRef(ctx, scope.children[0].result)),
351
+ hd: ({ children }, ctx, acc) => {
352
+ const body = [];
353
+ const level = children[0].result;
354
+ transformScope(children[1], ctx, body);
355
+ ctx.headings.push({ level, body });
356
+ __trimBody(body);
357
+ __collect(acc, ctx.tags.heading(ctx, level, slugifyGH(extractBody(body).join("")), body, ctx.meta));
358
+ ctx.meta = null;
359
+ },
360
+ hr: (scope, ctx, acc) => {
361
+ __collect(acc, ctx.tags.hr(ctx, scope.result.length, ctx.meta));
362
+ ctx.meta = null;
363
+ },
364
+ img: ({ children }, ctx, acc) => __collect(acc, ctx.tags.img(ctx, __escape(ctx, children[0].result.trim()), ...__linkTitle(children[1].result))),
365
+ italic: (scope, ctx, acc) => __collect(acc, ctx.tags.italic(ctx, __children(ctx, scope.children))),
366
+ kbd: (scope, ctx, acc) => __collect(acc, ctx.tags.kbd(ctx, scope.result)),
367
+ lbr: (_, ctx, acc) => __collect(acc, ctx.tags.br(ctx)),
368
+ link: ({ children }, ctx, acc) => __collect(acc, ctx.tags.link(ctx, ...__linkTitle(children[1].result), __children(ctx, children[0].children))),
369
+ linkdef: ({ children }, ctx) => {
370
+ ctx.linkRefs[children[0].result] = __linkTitle(children[1].result);
371
+ },
372
+ linkref: ({ children }, ctx, acc) => __collect(acc, ctx.tags.linkRef(ctx, children[1].result, __children(ctx, children[0].children))),
373
+ list: (scope, ctx, acc) => {
374
+ const children = scope.children[0].children;
375
+ const stack = [
376
+ [children[0].id === "ulitem" ? "ul" : "ol"],
377
+ ];
378
+ const levels = [0];
379
+ for (let item of children) {
380
+ const currLevel = item.children[0].result;
381
+ if (currLevel > peek(levels)) {
382
+ const sublist = [item.id === "ulitem" ? "ul" : "ol"];
383
+ const parent = peek(stack);
384
+ parent.length > 1
385
+ ? peek(parent).push(sublist)
386
+ : parent.push([
387
+ parent[0] === "ul" ? "ulitem" : "olitem",
388
+ {},
389
+ sublist,
390
+ ]);
391
+ stack.push(sublist);
392
+ levels.push(currLevel);
393
+ }
394
+ else if (currLevel < peek(levels)) {
395
+ while (currLevel < peek(levels)) {
396
+ stack.pop();
397
+ levels.pop();
398
+ }
399
+ }
400
+ transformScope(item, ctx, peek(stack));
401
+ }
402
+ const $list = (root, isRoot = false) => ctx.tags[root[0]](ctx, root.slice(1).map($item), isRoot ? ctx.meta : null);
403
+ const $item = (item) => {
404
+ let last = item[item.length - 1];
405
+ if (last[0] === "ul" || last[0] === "ol")
406
+ item[item.length - 1] = $list(last);
407
+ return item[0] === "ulitem"
408
+ ? ctx.tags.ulitem(ctx, item[1], item.slice(2))
409
+ : ctx.tags.olitem(ctx, item[1], item[2], item.slice(3));
410
+ };
411
+ __collect(acc, $list(stack[0], true));
412
+ ctx.meta = null;
413
+ },
414
+ metablock: ({ children }, ctx) => {
415
+ ctx.meta = ctx.tags.meta(ctx, children[0].result.trim());
416
+ },
417
+ olitem: ({ children }, ctx, acc) => {
418
+ const body = [];
419
+ transformScope(children[3], ctx, body);
420
+ __collect(acc, [
421
+ "olitem",
422
+ __listItemAttribs(children[2]),
423
+ children[1].result,
424
+ ...__trimBody(body),
425
+ ]);
426
+ },
427
+ para: (scope, ctx, acc) => {
428
+ __collect(acc, ctx.tags.para(ctx, __trimBody(__children(ctx, scope.children)), ctx.meta));
429
+ ctx.meta = null;
430
+ },
431
+ strike: (scope, ctx, acc) => __collect(acc, ctx.tags.strike(ctx, __children(ctx, scope.children))),
432
+ table: (scope, ctx, acc) => {
433
+ const children = scope.children[0].children;
434
+ const head = [];
435
+ const rows = [];
436
+ ctx.row = 0;
437
+ transformScope(children[0], ctx, head);
438
+ let align;
439
+ if (children.length > 1) {
440
+ align = __columnAlignments(children[1].children[0].children);
441
+ for (let i = 2, n = children.length; i < n; i++) {
442
+ ctx.row = i - 1;
443
+ transformScope(children[i], ctx, rows);
444
+ }
445
+ }
446
+ else {
447
+ align = new Array(children[0].children[0].children.length).fill("left");
448
+ }
449
+ __collect(acc, ctx.tags.table(ctx, align, head[0], rows, ctx.meta));
450
+ ctx.meta = null;
451
+ },
452
+ tcell: ({ children }, ctx, acc) => __collect(acc, (ctx.row > 0 ? ctx.tags.tableCell : ctx.tags.tableHead)(ctx, __trimBody(__children(ctx, children)))),
453
+ trow: ({ children }, ctx, acc) => __collect(acc, ctx.tags.tableRow(ctx, ctx.row, __children(ctx, children[0].children))),
454
+ ulitem: ({ children }, ctx, acc) => {
455
+ const body = [];
456
+ transformScope(children[2], ctx, body);
457
+ __collect(acc, [
458
+ "ulitem",
459
+ __listItemAttribs(children[1]),
460
+ ...__trimBody(body),
461
+ ]);
462
+ },
463
+ wikiref: (scope, ctx, acc) => {
464
+ const [id, label] = scope.result.split("|");
465
+ __collect(acc, ctx.tags.wikiref(ctx, id, label));
466
+ },
467
+ });
468
+ /**
469
+ * Takes an attributes object and optional metadata. If `meta` is not nullish,
470
+ * assigns it as `__meta` key in given object. Returns object.
471
+ *
472
+ * @param target
473
+ * @param meta
474
+ */
475
+ export const withMeta = (target, meta) => {
476
+ if (meta != null)
477
+ target.__meta = meta;
478
+ return target;
107
479
  };
108
- const collectTD = (tag) => (ctx) => {
109
- ctx.children.push(ctx.body);
110
- ctx.container.push(tag(peek(ctx.stack).container.length, ctx.children));
111
- return transition(ctx, 18 /* State.TABLE */);
480
+ /**
481
+ * Takes a hiccup tree and extracts only the primitive body values (strings,
482
+ * numbers, booleans) and returns them as array.
483
+ *
484
+ * @param body
485
+ * @param acc
486
+ */
487
+ export const extractBody = (body, acc = []) => {
488
+ for (let x of isPlainObject(body[1]) ? body.slice(2) : body) {
489
+ if (isPrimitive(x))
490
+ acc.push(x);
491
+ else if (isArray(x))
492
+ extractBody(x, acc);
493
+ }
494
+ return acc;
112
495
  };
113
- const collectTR = (tag) => (ctx) => {
114
- const rows = peek(ctx.stack).container;
115
- rows.push(tag(rows.length, ctx.container));
116
- ctx.container = [];
117
- return transition(ctx, 8 /* State.END_TABLE */);
496
+ /** @internal */
497
+ const __collect = (acc, x) => x != null && acc.push(x);
498
+ /** @internal */
499
+ const __children = (ctx, children, acc = []) => {
500
+ for (let c of children)
501
+ transformScope(c, ctx, acc);
502
+ return acc;
118
503
  };
119
- const collectTable = (tag) => (ctx) => {
120
- const rows = ctx.stack.pop().container;
121
- rows.splice(1, 1);
122
- return [14 /* State.START */, [tag(rows)]];
504
+ /** @internal */
505
+ const __escape = (ctx, x) => ctx.opts.escape ? escapeEntities(x) : x;
506
+ /** @internal */
507
+ const __listItemAttribs = (scope) => scope?.id === "todo"
508
+ ? {
509
+ __todo: true,
510
+ __done: scope.result === "x",
511
+ }
512
+ : {};
513
+ /** @internal */
514
+ const __trimBody = (body) => {
515
+ if (body.length === 1 && isString(body[0]))
516
+ body[0] = body[0].trim();
517
+ return body;
123
518
  };
124
- const collectInline = (fn) => pop((ctx, body) => fn(ctx.body + body.trim()));
125
- const heading = (ctx, body) => ((ctx.hd = body.length), transition(ctx, 9 /* State.HEADING */));
126
- const matchInline = (id) => [
127
- str("![", push(id, 10 /* State.IMG */)),
128
- str(LINK_LABEL, push(id, 11 /* State.LINK */)),
129
- str(STRIKE, push(id, 16 /* State.STRIKE */)),
130
- str(STRONG, push(id, 17 /* State.STRONG */)),
131
- str(EM, push(id, 3 /* State.EMPHASIS */)),
132
- str(CODE, push(id, 1 /* State.CODE */)),
133
- ];
134
- const matchLink = (result) => seq([
135
- untilStr(LINK_LABEL_END, (ctx, body) => ((ctx.title = body), undefined)),
136
- str(LINK_HREF),
137
- untilStr(LINK_HREF_END, (ctx, body) => ((ctx.href = body), undefined)),
138
- ], pop((ctx) => result(ctx.href, ctx.title)));
139
- const matchPara = (id, next) => alts([
140
- ...matchInline(id),
141
- str(NL, (ctx) => ((ctx.body += " "), [next])),
142
- ], collect(id));
143
- const newPara = (ctx, buf) => ((ctx.body = buf.join("")), (ctx.children = []), [13 /* State.PARA */]);
144
- const newParaInline = (next) => (ctx) => {
145
- ctx.stack.push({ id: 13 /* State.PARA */, children: [] });
146
- return transition(ctx, next);
519
+ /** @internal */
520
+ const __columnAlignments = (children) => {
521
+ const align = [];
522
+ for (let c of children) {
523
+ const raw = c.children[0].children[0].result.trim();
524
+ const isLeft = raw.startsWith(":-") ? 1 : 0;
525
+ const isRight = raw.endsWith("-:") ? 2 : 0;
526
+ align.push((["default", "left", "right", "center"][isLeft | isRight]));
527
+ }
528
+ return align;
147
529
  };
148
- const newParaCode = (ctx, x) => ((ctx.body = x[1]),
149
- ctx.stack.push({ id: 13 /* State.PARA */, children: [] }),
150
- [1 /* State.CODE */]);
151
- const newList = (ctx) => ((ctx.container = []), transition(ctx, 12 /* State.LI */));
152
- const newTable = (ctx) => (ctx.stack.push({ id: 18 /* State.TABLE */, container: [] }),
153
- (ctx.container = []),
154
- transition(ctx, 18 /* State.TABLE */));
155
- /**
156
- * Main parser / transducer. Defines state map with the various Markdown
157
- * syntax matchers and state transition handlers. The returned parser
158
- * itself is only used in `index.ts`.
159
- */
160
- export const parse = (_tags) => {
161
- const tags = { ...DEFAULT_TAGS, ..._tags };
162
- return comp(filter((x) => x !== "\r"), fsm({
163
- [14 /* State.START */]: alts([
164
- whitespace(() => [14 /* State.START */]),
165
- repeat(str(HD), 1, Infinity, heading),
166
- str(BQUOTE, (ctx) => transition(ctx, 0 /* State.BLOCKQUOTE */)),
167
- str(LI, newList),
168
- alts([
169
- seq([str(CODE), not(str(CODE))], newParaCode),
170
- str(CODEBLOCK, () => [15 /* State.START_CODEBLOCK */]),
171
- ], undefined, (_, next) => next),
172
- seq([repeat(str(HR), 3, Infinity), str(NL)], () => [
173
- 14 /* State.START */,
174
- [tags.hr()],
175
- ]),
176
- str(IMG, newParaInline(10 /* State.IMG */)),
177
- str(LINK_LABEL, newParaInline(11 /* State.LINK */)),
178
- str(STRONG, newParaInline(17 /* State.STRONG */)),
179
- str(STRIKE, newParaInline(16 /* State.STRIKE */)),
180
- str(EM, newParaInline(3 /* State.EMPHASIS */)),
181
- str(TD, newTable),
182
- ], newPara),
183
- [13 /* State.PARA */]: matchPara(13 /* State.PARA */, 6 /* State.END_PARA */),
184
- [6 /* State.END_PARA */]: alts([
185
- ...matchInline(13 /* State.PARA */),
186
- str(NL, collectAndRestart(tags.paragraph)),
187
- ], collect(13 /* State.PARA */)),
188
- [0 /* State.BLOCKQUOTE */]: matchPara(0 /* State.BLOCKQUOTE */, 4 /* State.END_BLOCKQUOTE */),
189
- [4 /* State.END_BLOCKQUOTE */]: alts([
190
- ...matchInline(0 /* State.BLOCKQUOTE */),
191
- str(BQUOTE, collectBlockQuote),
192
- str(NL, collectAndRestart(tags.blockquote)),
193
- ], collect(0 /* State.BLOCKQUOTE */)),
194
- [9 /* State.HEADING */]: matchPara(9 /* State.HEADING */, 7 /* State.END_HEADING */),
195
- [7 /* State.END_HEADING */]: alts([
196
- ...matchInline(9 /* State.HEADING */),
197
- str(NL, collectHeading(tags.heading)),
198
- ], collect(9 /* State.HEADING */)),
199
- [15 /* State.START_CODEBLOCK */]: untilStr(NL, (ctx, lang) => ((ctx.lang = lang), [2 /* State.CODEBLOCK */])),
200
- [2 /* State.CODEBLOCK */]: untilStr(CODEBLOCK_END, collectCodeBlock(tags.codeblock)),
201
- [12 /* State.LI */]: matchPara(12 /* State.LI */, 5 /* State.END_LI */),
202
- [5 /* State.END_LI */]: alts([
203
- str(NL, collectList("ul", tags.list, tags.li)),
204
- str(LI, (ctx) => (collectLi(ctx, tags.li),
205
- transition(ctx, 12 /* State.LI */))),
206
- ], collect(12 /* State.LI */)),
207
- [11 /* State.LINK */]: matchLink(tags.link),
208
- [10 /* State.IMG */]: matchLink(tags.img),
209
- [17 /* State.STRONG */]: untilStr(STRONG, collectInline(tags.strong)),
210
- [16 /* State.STRIKE */]: untilStr(STRIKE, collectInline(tags.strike)),
211
- [3 /* State.EMPHASIS */]: untilStr(EM, collectInline(tags.em)),
212
- [1 /* State.CODE */]: untilStr(CODE, collectInline(tags.code)),
213
- [18 /* State.TABLE */]: alts([
214
- ...matchInline(18 /* State.TABLE */),
215
- str(TD, collectTD(tags.td)),
216
- str(NL, collectTR(tags.tr)),
217
- ], collect(18 /* State.TABLE */)),
218
- [8 /* State.END_TABLE */]: alts([
219
- str(NL, collectTable(tags.table)),
220
- str(TD, () => [18 /* State.TABLE */]),
221
- ]),
222
- }, { stack: [] }, 14 /* State.START */));
530
+ const __linkTitle = (src) => {
531
+ const match = /\s"(.+)"$/.exec(src);
532
+ return match ? [src.substring(0, match.index), match[1]] : [src, undefined];
223
533
  };