markdown_syntax/ast.rs
1//! The owned Markdown AST that [`parse()`](crate::parse()) produces and
2//! `Document::to_markdown`/`to_html` consume. Every node carries a [`NodeMeta`]
3//! with an optional source [`Span`]. [`Block`] and [`Inline`] are the two node
4//! enums; everything else is a concrete node struct or a small enum describing
5//! a node's variant.
6
7use alloc::{string::String, vec::Vec};
8
9use crate::span::Span;
10
11/// Metadata attached to every AST node; currently just the source span.
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13pub struct NodeMeta {
14 /// The node's source location, or `None` for hand-built nodes.
15 pub span: Option<Span>,
16}
17
18impl NodeMeta {
19 /// Wrap an optional [`Span`] into a [`NodeMeta`].
20 pub const fn new(span: Option<Span>) -> Self {
21 Self { span }
22 }
23}
24
25/// The root of a parsed document: a sequence of top-level [`Block`]s.
26#[derive(Clone, Debug, Default, Eq, PartialEq)]
27pub struct Document {
28 /// Node metadata (source span).
29 pub meta: NodeMeta,
30 /// The document's top-level blocks, in source order.
31 pub children: Vec<Block>,
32}
33
34/// A block-level node: the building blocks of a document's vertical structure.
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub enum Block {
37 /// A paragraph of inline content.
38 Paragraph(Paragraph),
39 /// An ATX (`# h`) or setext (underlined) heading.
40 Heading(Heading),
41 /// A thematic break / horizontal rule: `---`, `***`, or `___`.
42 ThematicBreak(ThematicBreak),
43 /// A block quote: lines prefixed with `> `.
44 BlockQuote(BlockQuote),
45 /// A GFM alert / admonition: `> [!NOTE]` etc.
46 Alert(Alert),
47 /// A bullet or ordered list.
48 List(List),
49 /// A fenced (```` ``` ````) or indented code block.
50 CodeBlock(CodeBlock),
51 /// A raw HTML block.
52 HtmlBlock(HtmlBlock),
53 /// A structured raw HTML container whose interior is parsed as Markdown.
54 HtmlContainer(HtmlContainer),
55 /// A link reference definition: `[label]: url "title"`.
56 Definition(Definition),
57 /// A footnote definition: `[^id]: text`.
58 FootnoteDefinition(FootnoteDefinition),
59 /// A GFM pipe table.
60 Table(Table),
61 /// A display math block: `$$ … $$`.
62 MathBlock(MathBlock),
63 /// A leading frontmatter block (`---` YAML or `+++` TOML).
64 Frontmatter(Frontmatter),
65 /// A leaf directive: `::name[label]{attrs}`.
66 LeafDirective(LeafDirective),
67 /// A container directive: `:::name … :::`.
68 ContainerDirective(ContainerDirective),
69}
70
71/// A paragraph: a run of inline content. Source: any plain text line(s).
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct Paragraph {
74 /// Node metadata (source span).
75 pub meta: NodeMeta,
76 /// The paragraph's inline content.
77 pub children: Vec<Inline>,
78}
79
80/// A heading. Source: `# Title` (ATX) or `Title\n===` (setext).
81#[derive(Clone, Debug, Eq, PartialEq)]
82pub struct Heading {
83 /// Node metadata (source span).
84 pub meta: NodeMeta,
85 /// Heading level, 1..=6.
86 pub depth: u8,
87 /// Whether the heading used ATX or setext syntax.
88 pub kind: HeadingKind,
89 /// The heading's inline content.
90 pub children: Vec<Inline>,
91}
92
93/// Which heading syntax produced a [`Heading`].
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
95pub enum HeadingKind {
96 /// ATX heading: `# Title` … `###### Title`.
97 Atx,
98 /// Setext heading: `Title` underlined with `===` (level 1) or `---` (level 2).
99 Setext,
100}
101
102/// A thematic break / horizontal rule. Source: `---`, `***`, or `___`.
103#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct ThematicBreak {
105 /// Node metadata (source span).
106 pub meta: NodeMeta,
107 /// Which character formed the break.
108 pub marker: ThematicBreakMarker,
109}
110
111/// The character used to draw a [`ThematicBreak`].
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum ThematicBreakMarker {
114 /// Dashes: `---`.
115 Dash,
116 /// Asterisks: `***`.
117 Asterisk,
118 /// Underscores: `___`.
119 Underscore,
120}
121
122/// A block quote: content prefixed with `> `.
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct BlockQuote {
125 /// Node metadata (source span).
126 pub meta: NodeMeta,
127 /// The quoted block content.
128 pub children: Vec<Block>,
129}
130
131/// A GFM alert / admonition. Source: `> [!NOTE]` followed by quoted content.
132#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct Alert {
134 /// Node metadata (source span).
135 pub meta: NodeMeta,
136 /// The alert severity / type.
137 pub kind: AlertKind,
138 /// An optional custom title following the `[!KIND]` marker.
139 pub title: Option<String>,
140 /// The alert's block content.
141 pub children: Vec<Block>,
142}
143
144/// The kind of a GFM [`Alert`] (the `[!KIND]` marker).
145#[derive(Clone, Copy, Debug, Eq, PartialEq)]
146pub enum AlertKind {
147 /// `> [!NOTE]`.
148 Note,
149 /// `> [!TIP]`.
150 Tip,
151 /// `> [!IMPORTANT]`.
152 Important,
153 /// `> [!WARNING]`.
154 Warning,
155 /// `> [!CAUTION]`.
156 Caution,
157}
158
159/// A bullet or ordered list. Source: `- a` / `1. a` lines.
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub struct List {
162 /// Node metadata (source span).
163 pub meta: NodeMeta,
164 /// `true` for an ordered list, `false` for a bullet list.
165 pub ordered: bool,
166 /// The starting number of an ordered list (e.g. `3.` => `Some(3)`).
167 pub start: Option<u64>,
168 /// The marker delimiter used by the list items.
169 pub delimiter: ListDelimiter,
170 /// `true` if the list is tight (no blank lines between items / no `<p>`).
171 pub tight: bool,
172 /// The list's items.
173 pub children: Vec<ListItem>,
174}
175
176/// The marker character that delimits a list's items.
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub enum ListDelimiter {
179 /// Bullet `-`.
180 Dash,
181 /// Bullet `*`.
182 Asterisk,
183 /// Bullet `+`.
184 Plus,
185 /// Ordered `1.`.
186 Period,
187 /// Ordered `1)`.
188 Paren,
189}
190
191/// A single list item, optionally a GFM task-list checkbox.
192#[derive(Clone, Debug, Eq, PartialEq)]
193pub struct ListItem {
194 /// Node metadata (source span).
195 pub meta: NodeMeta,
196 /// Task-list state: `Some(true)` for `[x]`, `Some(false)` for `[ ]`, `None` otherwise.
197 pub checked: Option<bool>,
198 /// The item's block content.
199 pub children: Vec<Block>,
200}
201
202/// A code block. Source: ```` ```lang … ``` ```` (fenced) or 4-space-indented lines.
203#[derive(Clone, Debug, Eq, PartialEq)]
204pub struct CodeBlock {
205 /// Node metadata (source span).
206 pub meta: NodeMeta,
207 /// Whether the block is fenced (and with what fence) or indented.
208 pub kind: CodeBlockKind,
209 /// The info string after a fence (e.g. the `rust` in ```` ```rust ````).
210 pub info: Option<String>,
211 /// The literal code contents.
212 pub value: String,
213}
214
215/// Whether a [`CodeBlock`] is fenced or indented.
216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
217pub enum CodeBlockKind {
218 /// A fenced code block; records the fence char and its run length.
219 Fenced {
220 /// Which character formed the fence (backtick or tilde).
221 marker: FenceMarker,
222 /// The number of fence characters in the opening fence (>=3).
223 length: usize,
224 },
225 /// A 4-space-indented code block.
226 Indented,
227}
228
229/// The character used to fence a [`CodeBlock`].
230#[derive(Clone, Copy, Debug, Eq, PartialEq)]
231pub enum FenceMarker {
232 /// Backtick fence: ```` ``` ````.
233 Backtick,
234 /// Tilde fence: `~~~`.
235 Tilde,
236}
237
238/// A raw HTML block: HTML emitted verbatim.
239#[derive(Clone, Debug, Eq, PartialEq)]
240pub struct HtmlBlock {
241 /// Node metadata (source span).
242 pub meta: NodeMeta,
243 /// The literal HTML source.
244 pub value: String,
245}
246
247/// A structured raw HTML container, such as `<details> ... </details>`.
248#[derive(Clone, Debug, Eq, PartialEq)]
249pub struct HtmlContainer {
250 /// Node metadata (source span).
251 pub meta: NodeMeta,
252 /// The opening HTML tag.
253 pub opening: HtmlTag,
254 /// The parsed container content.
255 pub content: HtmlContainerContent,
256 /// The closing HTML tag.
257 pub closing: HtmlTag,
258}
259
260/// A raw HTML tag that bounds an [`HtmlContainer`].
261#[derive(Clone, Debug, Eq, PartialEq)]
262pub struct HtmlTag {
263 /// Node metadata (source span).
264 pub meta: NodeMeta,
265 /// The normalized lowercase tag name.
266 pub name: String,
267 /// The literal tag source, including angle brackets and attributes.
268 pub raw: String,
269}
270
271/// The parsed content inside an [`HtmlContainer`].
272#[derive(Clone, Debug, Eq, PartialEq)]
273pub enum HtmlContainerContent {
274 /// Block-level Markdown content.
275 Blocks(Vec<Block>),
276 /// Inline-level Markdown content.
277 Inlines(Vec<Inline>),
278}
279
280/// A link reference definition. Source: `[label]: destination "title"`.
281#[derive(Clone, Debug, Eq, PartialEq)]
282pub struct Definition {
283 /// Node metadata (source span).
284 pub meta: NodeMeta,
285 /// The label as written in the source (e.g. `Foo Bar`).
286 pub label: String,
287 /// The normalized lookup key (case-folded, whitespace-collapsed) for matching references.
288 pub identifier: String,
289 /// The link target URL.
290 pub destination: String,
291 /// How the destination was delimited (bare or `<…>`).
292 pub destination_kind: LinkDestinationKind,
293 /// The optional link title, with the quotes it is written in.
294 pub title: Option<Title>,
295}
296
297/// A footnote definition. Source: `[^id]: footnote text`.
298#[derive(Clone, Debug, Eq, PartialEq)]
299pub struct FootnoteDefinition {
300 /// Node metadata (source span).
301 pub meta: NodeMeta,
302 /// The label as written in the source (the text after `^`).
303 pub label: String,
304 /// The normalized lookup key matching [`FootnoteReference`]s.
305 pub identifier: String,
306 /// The footnote's block content.
307 pub children: Vec<Block>,
308}
309
310/// A GFM pipe table: a header row, an alignment row, then body rows. Source:
311/// `| a | b |` / `|---|---|` / `| 1 | 2 |`.
312#[derive(Clone, Debug, Eq, PartialEq)]
313pub struct Table {
314 /// Node metadata (source span).
315 pub meta: NodeMeta,
316 /// Per-column alignment from the delimiter row.
317 pub alignments: Vec<TableAlignment>,
318 /// All rows; the first is the header row.
319 pub rows: Vec<TableRow>,
320}
321
322/// The alignment of a [`Table`] column, from the `:---:` delimiter row.
323#[derive(Clone, Copy, Debug, Eq, PartialEq)]
324pub enum TableAlignment {
325 /// No explicit alignment: `---`.
326 None,
327 /// Left-aligned: `:---`.
328 Left,
329 /// Center-aligned: `:---:`.
330 Center,
331 /// Right-aligned: `---:`.
332 Right,
333}
334
335/// A single row of a [`Table`].
336#[derive(Clone, Debug, Eq, PartialEq)]
337pub struct TableRow {
338 /// Node metadata (source span).
339 pub meta: NodeMeta,
340 /// The row's cells.
341 pub cells: Vec<TableCell>,
342}
343
344/// A single cell of a [`TableRow`].
345#[derive(Clone, Debug, Eq, PartialEq)]
346pub struct TableCell {
347 /// Node metadata (source span).
348 pub meta: NodeMeta,
349 /// The cell's inline content.
350 pub children: Vec<Inline>,
351}
352
353/// A display math block. Source: `$$ … $$`.
354#[derive(Clone, Debug, Eq, PartialEq)]
355pub struct MathBlock {
356 /// Node metadata (source span).
357 pub meta: NodeMeta,
358 /// The literal math contents (between the `$$` fences).
359 pub value: String,
360}
361
362/// A leading frontmatter block. Source: `---` YAML or `+++` TOML at the top of
363/// the document.
364#[derive(Clone, Debug, Eq, PartialEq)]
365pub struct Frontmatter {
366 /// Node metadata (source span).
367 pub meta: NodeMeta,
368 /// Whether the frontmatter is YAML or TOML.
369 pub kind: FrontmatterKind,
370 /// The literal frontmatter contents (between the fences).
371 pub value: String,
372}
373
374/// The format of a [`Frontmatter`] block.
375#[derive(Clone, Copy, Debug, Eq, PartialEq)]
376pub enum FrontmatterKind {
377 /// YAML frontmatter, fenced by `---`.
378 Yaml,
379 /// TOML frontmatter, fenced by `+++`.
380 Toml,
381}
382
383/// A leaf directive. Source: `::name[label]{attrs}`.
384#[derive(Clone, Debug, Eq, PartialEq)]
385pub struct LeafDirective {
386 /// Node metadata (source span).
387 pub meta: NodeMeta,
388 /// The directive name following the `::`.
389 pub name: String,
390 /// The optional `[label]` inline content.
391 pub label: Vec<Inline>,
392 /// The optional `{attrs}` attributes.
393 pub attributes: Vec<DirectiveAttribute>,
394}
395
396/// A container directive. Source: `:::name[label]{attrs}` … `:::`.
397#[derive(Clone, Debug, Eq, PartialEq)]
398pub struct ContainerDirective {
399 /// Node metadata (source span).
400 pub meta: NodeMeta,
401 /// The directive name following the `:::`.
402 pub name: String,
403 /// The optional `[label]` inline content.
404 pub label: Vec<Inline>,
405 /// The optional `{attrs}` attributes.
406 pub attributes: Vec<DirectiveAttribute>,
407 /// The directive's enclosed block content.
408 pub children: Vec<Block>,
409}
410
411/// An inline-level node: the leaf and span content inside blocks.
412#[derive(Clone, Debug, Eq, PartialEq)]
413pub enum Inline {
414 /// Literal text.
415 Text(Text),
416 /// A backslash escape such as `\*`.
417 Escape(Escape),
418 /// A character reference such as `&` or `÷`.
419 CharacterReference(CharacterReference),
420 /// Emphasis: `*text*` or `_text_`.
421 Emphasis(Emphasis),
422 /// Strong emphasis: `**text**` or `__text__`.
423 Strong(Strong),
424 /// Strikethrough: `~~text~~`.
425 Delete(Delete),
426 /// A highlight / "mark" span: `==text==`.
427 Mark(Mark),
428 /// An emoji-style shortcode: `:name:`.
429 Shortcode(Shortcode),
430 /// An inline code span: `` `code` ``.
431 Code(CodeInline),
432 /// An inline link: `[text](url)`.
433 Link(Link),
434 /// An autolink: `<https://example.com>` or a bare literal URL or email.
435 Autolink(Autolink),
436 /// An inline image: ``.
437 Image(Image),
438 /// A reference link: `[text][label]`.
439 LinkReference(LinkReference),
440 /// A reference image: `![alt][label]`.
441 ImageReference(ImageReference),
442 /// Raw inline HTML such as `<span>`.
443 Html(HtmlInline),
444 /// A soft line break (a plain newline within a paragraph).
445 SoftBreak(SoftBreak),
446 /// A hard line break (`\` or two trailing spaces).
447 LineBreak(LineBreak),
448 /// Inline math: `$x$`.
449 Math(MathInline),
450 /// A footnote reference: `[^id]`.
451 FootnoteReference(FootnoteReference),
452 /// An inline footnote: `^[inline note]`.
453 InlineFootnote(InlineFootnote),
454 /// A wiki link: `[[target|label]]`.
455 WikiLink(WikiLink),
456 /// A text directive: `:name[label]{attrs}`.
457 TextDirective(TextDirective),
458}
459
460/// Literal text content.
461#[derive(Clone, Debug, Eq, PartialEq)]
462pub struct Text {
463 /// Node metadata (source span).
464 pub meta: NodeMeta,
465 /// The text value.
466 pub value: String,
467}
468
469/// A backslash escape such as `\*` or `\\`.
470#[derive(Clone, Debug, Eq, PartialEq)]
471pub struct Escape {
472 /// Node metadata (source span).
473 pub meta: NodeMeta,
474 /// The escaped (literal) character.
475 pub value: char,
476}
477
478/// A character reference such as `&` or `÷`. The reference as
479/// written is the node's one fact; [`CharacterReference::value`] decodes it.
480#[derive(Clone, Debug, Eq, PartialEq)]
481pub struct CharacterReference {
482 /// Node metadata (source span).
483 pub meta: NodeMeta,
484 /// The reference as written, including `&` and `;` (e.g. `&`).
485 pub reference: String,
486}
487
488/// Emphasis (typically italic): `*text*` or `_text_`.
489#[derive(Clone, Debug, Eq, PartialEq)]
490pub struct Emphasis {
491 /// Node metadata (source span).
492 pub meta: NodeMeta,
493 /// The delimiter the span was written with.
494 pub delimiter: EmphasisDelimiter,
495 /// The emphasized inline content.
496 pub children: Vec<Inline>,
497}
498
499/// Strong emphasis (typically bold): `**text**` or `__text__`.
500#[derive(Clone, Debug, Eq, PartialEq)]
501pub struct Strong {
502 /// Node metadata (source span).
503 pub meta: NodeMeta,
504 /// The delimiter the span was written with.
505 pub delimiter: EmphasisDelimiter,
506 /// The strongly-emphasized inline content.
507 pub children: Vec<Inline>,
508}
509
510/// The delimiter character of an [`Emphasis`] or [`Strong`] span.
511#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
512pub enum EmphasisDelimiter {
513 /// `*text*` / `**text**`.
514 #[default]
515 Asterisk,
516 /// `_text_` / `__text__`.
517 Underscore,
518}
519
520/// Strikethrough: `~~text~~`.
521#[derive(Clone, Debug, Eq, PartialEq)]
522pub struct Delete {
523 /// Node metadata (source span).
524 pub meta: NodeMeta,
525 /// The struck-through inline content.
526 pub children: Vec<Inline>,
527}
528
529/// A highlight / "mark" span: `==text==`.
530#[derive(Clone, Debug, Eq, PartialEq)]
531pub struct Mark {
532 /// Node metadata (source span).
533 pub meta: NodeMeta,
534 /// The highlighted inline content.
535 pub children: Vec<Inline>,
536}
537
538/// An emoji shortcode: `:name:`, where `name` is a gemoji name or alias.
539#[derive(Clone, Debug, Eq, PartialEq)]
540pub struct Shortcode {
541 /// Node metadata (source span).
542 pub meta: NodeMeta,
543 /// The shortcode name between the colons (e.g. `smile` for `:smile:`).
544 pub name: String,
545}
546
547/// An inline code span: `` `code` ``. The fence and the padding spaces are
548/// not recorded: the serializer chooses them from the value.
549#[derive(Clone, Debug, Eq, PartialEq)]
550pub struct CodeInline {
551 /// Node metadata (source span).
552 pub meta: NodeMeta,
553 /// The code text, normalized as CommonMark reads it: line endings as
554 /// spaces, and one space stripped from each end when both ends hold one
555 /// and the text is not all spaces.
556 pub value: String,
557}
558
559/// An inline link: `[text](destination "title")`.
560#[derive(Clone, Debug, Eq, PartialEq)]
561pub struct Link {
562 /// Node metadata (source span).
563 pub meta: NodeMeta,
564 /// The link target URL.
565 pub destination: String,
566 /// How the destination was delimited (bare or `<…>`).
567 pub destination_kind: LinkDestinationKind,
568 /// The optional link title, with the quotes it is written in.
569 pub title: Option<Title>,
570 /// The link's inline content (the visible text).
571 pub children: Vec<Inline>,
572}
573
574/// An autolink: `<https://example.com>`, `<a@b.c>`, or a GFM literal
575/// autolink, a bare `https://…`, `www.…`, or email address. The text as
576/// written is the node's one fact; [`Autolink::destination`] derives the link
577/// target from it.
578#[derive(Clone, Debug, Eq, PartialEq)]
579pub struct Autolink {
580 /// Node metadata (source span). An angle-bracket autolink's span covers
581 /// its brackets.
582 pub meta: NodeMeta,
583 /// Which autolink syntax the text is written in.
584 pub form: AutolinkForm,
585 /// The URL or address as written, without an angle-bracket autolink's
586 /// brackets.
587 pub text: String,
588}
589
590/// How an [`Autolink`] is written.
591#[derive(Clone, Copy, Debug, Eq, PartialEq)]
592pub enum AutolinkForm {
593 /// An angle-bracket autolink: `<https://example.com>` or `<a@b.c>`.
594 Angle,
595 /// A GFM literal autolink: a bare `https://…`, `www.…`, or email address.
596 Literal,
597}
598
599/// An inline image: ``.
600#[derive(Clone, Debug, Eq, PartialEq)]
601pub struct Image {
602 /// Node metadata (source span).
603 pub meta: NodeMeta,
604 /// The image source URL.
605 pub destination: String,
606 /// How the destination was delimited (bare or `<…>`).
607 pub destination_kind: LinkDestinationKind,
608 /// The optional image title, with the quotes it is written in.
609 pub title: Option<Title>,
610 /// The image's alt-text inline content.
611 pub alt: Vec<Inline>,
612}
613
614/// How a link/image destination was delimited in the source.
615#[derive(Clone, Copy, Debug, Eq, PartialEq)]
616pub enum LinkDestinationKind {
617 /// A bare destination: `(url)`.
618 Bare,
619 /// An angle-bracketed destination: `(<url>)`.
620 Angle,
621 /// No destination present: `()`.
622 Omitted,
623}
624
625/// How a link/image title was quoted in the source.
626#[derive(Clone, Copy, Debug, Eq, PartialEq)]
627pub enum LinkTitleKind {
628 /// Double-quoted: `"title"`.
629 DoubleQuote,
630 /// Single-quoted: `'title'`.
631 SingleQuote,
632 /// Parenthesized: `(title)`.
633 Paren,
634}
635
636/// The title of a [`Link`], [`Image`], or [`Definition`]: its text and the
637/// quotes it is written in.
638#[derive(Clone, Debug, Eq, PartialEq)]
639pub struct Title {
640 /// The title text, with its escapes and character references decoded.
641 pub value: String,
642 /// How the title is quoted.
643 pub kind: LinkTitleKind,
644}
645
646/// A reference link: `[text][label]`, `[text][]`, or `[text]`.
647#[derive(Clone, Debug, Eq, PartialEq)]
648pub struct LinkReference {
649 /// Node metadata (source span).
650 pub meta: NodeMeta,
651 /// The normalized lookup key matching a [`Definition`].
652 pub identifier: String,
653 /// The label as written in the source.
654 pub label: String,
655 /// Whether the reference is full, collapsed, or shortcut form.
656 pub kind: ReferenceKind,
657 /// The link's inline content (the visible text).
658 pub children: Vec<Inline>,
659}
660
661/// A reference image: `![alt][label]`, `![alt][]`, or `![alt]`.
662#[derive(Clone, Debug, Eq, PartialEq)]
663pub struct ImageReference {
664 /// Node metadata (source span).
665 pub meta: NodeMeta,
666 /// The normalized lookup key matching a [`Definition`].
667 pub identifier: String,
668 /// The label as written in the source.
669 pub label: String,
670 /// Whether the reference is full, collapsed, or shortcut form.
671 pub kind: ReferenceKind,
672 /// The image's alt-text inline content.
673 pub alt: Vec<Inline>,
674}
675
676/// The form of a reference link/image.
677#[derive(Clone, Copy, Debug, Eq, PartialEq)]
678pub enum ReferenceKind {
679 /// Full reference: `[text][label]`.
680 Full,
681 /// Collapsed reference: `[label][]`.
682 Collapsed,
683 /// Shortcut reference: `[label]`.
684 Shortcut,
685}
686
687/// Raw inline HTML such as `<span>` or `</em>`.
688#[derive(Clone, Debug, Eq, PartialEq)]
689pub struct HtmlInline {
690 /// Node metadata (source span).
691 pub meta: NodeMeta,
692 /// The literal HTML source.
693 pub value: String,
694}
695
696/// A soft line break: a plain newline within a paragraph.
697#[derive(Clone, Debug, Eq, PartialEq)]
698pub struct SoftBreak {
699 /// Node metadata (source span).
700 pub meta: NodeMeta,
701}
702
703/// A hard line break: a trailing `\` or two trailing spaces.
704#[derive(Clone, Debug, Eq, PartialEq)]
705pub struct LineBreak {
706 /// Node metadata (source span).
707 pub meta: NodeMeta,
708 /// Which syntax produced the break.
709 pub kind: LineBreakKind,
710}
711
712/// Which syntax produced a hard [`LineBreak`].
713#[derive(Clone, Copy, Debug, Eq, PartialEq)]
714pub enum LineBreakKind {
715 /// A trailing backslash: `\`.
716 Backslash,
717 /// Two or more trailing spaces.
718 Spaces,
719}
720
721/// Which syntax delimited an inline [`MathInline`] span.
722#[derive(Clone, Copy, Debug, Eq, PartialEq)]
723pub enum MathInlineKind {
724 /// Dollar-fenced inline math (`$…$`, `$$…$$`, …); `dollars` is the fence
725 /// length (>=1). Dollar math always renders inline, while a 2-dollar fence
726 /// is conventionally treated as display elsewhere in the ecosystem.
727 Dollar {
728 /// The number of `$` characters in the fence (>=1).
729 dollars: u8,
730 },
731 /// Math-code span: `$`…`$`.
732 Code,
733}
734
735/// Inline math: `$x$`.
736#[derive(Clone, Debug, Eq, PartialEq)]
737pub struct MathInline {
738 /// Node metadata (source span).
739 pub meta: NodeMeta,
740 /// The literal math contents (between the fences).
741 pub value: String,
742 /// Which delimiter syntax was used.
743 pub kind: MathInlineKind,
744}
745
746/// A footnote reference: `[^id]`.
747#[derive(Clone, Debug, Eq, PartialEq)]
748pub struct FootnoteReference {
749 /// Node metadata (source span).
750 pub meta: NodeMeta,
751 /// The label as written in the source (the text after `^`).
752 pub label: String,
753 /// The normalized lookup key matching a [`FootnoteDefinition`].
754 pub identifier: String,
755}
756
757/// An inline footnote: `^[inline note]`.
758#[derive(Clone, Debug, Eq, PartialEq)]
759pub struct InlineFootnote {
760 /// Node metadata (source span).
761 pub meta: NodeMeta,
762 /// The footnote's inline content.
763 pub children: Vec<Inline>,
764}
765
766/// A wiki link: `[[target|label]]`, or an embed: `![[target|label]]`.
767#[derive(Clone, Debug, Eq, PartialEq)]
768pub struct WikiLink {
769 /// Node metadata (source span). An embed's span starts at its `!`.
770 pub meta: NodeMeta,
771 /// The link target (page name) as written in the source, its backslash
772 /// escapes and character references kept; [`WikiLink::decoded_target`]
773 /// gives the text they stand for.
774 pub target: String,
775 /// The visible label as written in the source, its backslash escapes and
776 /// character references kept; [`WikiLink::decoded_label`] gives the text
777 /// they stand for. A label equal to `target` means the link is written
778 /// without a `|label` part.
779 pub label: String,
780 /// Whether a `!` directly before the `[[` marks the link as an embed.
781 pub embed: bool,
782}
783
784/// A text directive. Source: `:name[label]{attrs}`.
785#[derive(Clone, Debug, Eq, PartialEq)]
786pub struct TextDirective {
787 /// Node metadata (source span).
788 pub meta: NodeMeta,
789 /// The directive name following the `:`.
790 pub name: String,
791 /// The optional `[label]` inline content.
792 pub label: Vec<Inline>,
793 /// The optional `{attrs}` attributes.
794 pub attributes: Vec<DirectiveAttribute>,
795}
796
797/// One attribute of a directive's `{name=value}` block.
798#[derive(Clone, Debug, Eq, PartialEq)]
799pub struct DirectiveAttribute {
800 /// The attribute name.
801 pub name: String,
802 /// The attribute value, or `None` for a valueless attribute.
803 pub value: Option<String>,
804}
805
806// ---------------------------------------------------------------------------
807// Ergonomic accessors and a minimal construction layer.
808//
809// Every node carries a `meta: NodeMeta`, so `meta()`/`span()` are uniform across
810// the enums and free callers from writing an exhaustive match just to read a
811// span. `From`/`new` collapse the `Variant(Struct { meta, .. })` boilerplate for
812// hand-built ASTs; the raw struct literals remain available for full control.
813// ---------------------------------------------------------------------------
814
815macro_rules! impl_meta_accessors {
816 ($enum:ident { $($variant:ident),+ $(,)? }) => {
817 impl $enum {
818 /// Borrow this node's [`NodeMeta`].
819 pub fn meta(&self) -> &NodeMeta {
820 match self { $( $enum::$variant(node) => &node.meta, )+ }
821 }
822
823 /// This node's source span, if it carries one.
824 pub fn span(&self) -> Option<Span> {
825 self.meta().span
826 }
827 }
828 };
829}
830
831macro_rules! impl_from_variants {
832 ($enum:ident { $($variant:ident($ty:ty)),+ $(,)? }) => {
833 $(
834 impl From<$ty> for $enum {
835 fn from(node: $ty) -> Self {
836 $enum::$variant(node)
837 }
838 }
839 )+
840 };
841}
842
843impl_meta_accessors!(Block {
844 Paragraph,
845 Heading,
846 ThematicBreak,
847 BlockQuote,
848 Alert,
849 List,
850 CodeBlock,
851 HtmlBlock,
852 HtmlContainer,
853 Definition,
854 FootnoteDefinition,
855 Table,
856 MathBlock,
857 Frontmatter,
858 LeafDirective,
859 ContainerDirective,
860});
861
862impl_from_variants!(Block {
863 Paragraph(Paragraph), Heading(Heading), ThematicBreak(ThematicBreak),
864 BlockQuote(BlockQuote), Alert(Alert), List(List),
865 CodeBlock(CodeBlock), HtmlBlock(HtmlBlock), Definition(Definition),
866 HtmlContainer(HtmlContainer),
867 FootnoteDefinition(FootnoteDefinition), Table(Table), MathBlock(MathBlock),
868 Frontmatter(Frontmatter), LeafDirective(LeafDirective), ContainerDirective(ContainerDirective),
869});
870
871impl_meta_accessors!(Inline {
872 Text,
873 Escape,
874 CharacterReference,
875 Emphasis,
876 Strong,
877 Delete,
878 Mark,
879 Shortcode,
880 Code,
881 Link,
882 Autolink,
883 Image,
884 LinkReference,
885 ImageReference,
886 Html,
887 SoftBreak,
888 LineBreak,
889 Math,
890 FootnoteReference,
891 InlineFootnote,
892 WikiLink,
893 TextDirective,
894});
895
896impl_from_variants!(Inline {
897 Text(Text), Escape(Escape), CharacterReference(CharacterReference),
898 Emphasis(Emphasis), Strong(Strong), Delete(Delete), Mark(Mark),
899 Shortcode(Shortcode), Code(CodeInline), Link(Link), Autolink(Autolink), Image(Image),
900 LinkReference(LinkReference), ImageReference(ImageReference), Html(HtmlInline),
901 SoftBreak(SoftBreak), LineBreak(LineBreak), Math(MathInline),
902 FootnoteReference(FootnoteReference), InlineFootnote(InlineFootnote), WikiLink(WikiLink),
903 TextDirective(TextDirective),
904});
905
906impl Inline {
907 /// The inline subtree of this node, or an empty slice for a leaf. Covers the
908 /// `alt`/`label` fields uniformly, so a generic walker never silently skips
909 /// an image's alt text or a directive's label.
910 pub fn children(&self) -> &[Inline] {
911 match self {
912 Inline::Emphasis(n) => &n.children,
913 Inline::Strong(n) => &n.children,
914 Inline::Delete(n) => &n.children,
915 Inline::Mark(n) => &n.children,
916 Inline::Link(n) => &n.children,
917 Inline::Image(n) => &n.alt,
918 Inline::LinkReference(n) => &n.children,
919 Inline::ImageReference(n) => &n.alt,
920 Inline::InlineFootnote(n) => &n.children,
921 Inline::TextDirective(n) => &n.label,
922 _ => &[],
923 }
924 }
925}
926
927impl CharacterReference {
928 /// A character reference node for `reference`, written with its `&` and
929 /// `;` (e.g. `&`).
930 pub fn new(reference: impl Into<String>) -> Self {
931 Self {
932 meta: NodeMeta::default(),
933 reference: reference.into(),
934 }
935 }
936
937 /// The character the reference decodes to, or `None` when
938 /// [`reference`](Self::reference) is not exactly one character reference.
939 /// A valid document holds only references that decode.
940 pub fn value(&self) -> Option<String> {
941 crate::decode::decode_character_reference(&self.reference)
942 }
943}
944
945impl Autolink {
946 /// An autolink of `form` whose text is `text`.
947 pub fn new(form: AutolinkForm, text: impl Into<String>) -> Self {
948 Self {
949 meta: NodeMeta::default(),
950 form,
951 text: text.into(),
952 }
953 }
954
955 /// The link target the text writes, or `None` when the text is not
956 /// exactly one autolink of its form. An angle-bracket URI links to itself
957 /// and an email address to it after `mailto:`; a literal `www.` domain
958 /// links to it after `http://`, a literal email address to it after
959 /// `mailto:`, and any other literal autolink to itself. A valid document
960 /// holds only autolinks with a destination.
961 pub fn destination(&self) -> Option<String> {
962 match self.form {
963 AutolinkForm::Angle => crate::parse::angle_autolink_destination(&self.text),
964 AutolinkForm::Literal => crate::parse::literal_autolink_destination(&self.text),
965 }
966 }
967}
968
969impl Title {
970 /// A title holding `value`, quoted as `kind`.
971 pub fn new(value: impl Into<String>, kind: LinkTitleKind) -> Self {
972 Self {
973 value: value.into(),
974 kind,
975 }
976 }
977}
978
979impl Shortcode {
980 /// The emoji the crate's pinned gemoji table gives this shortcode's
981 /// name, or `None` for a name the table does not hold.
982 pub fn glyph(&self) -> Option<&'static str> {
983 crate::gemoji::glyph(&self.name)
984 }
985}
986
987impl WikiLink {
988 /// The target with its backslash escapes and character references
989 /// decoded, as CommonMark decodes a link destination: `a \| b & c`
990 /// decodes to `a | b & c`.
991 pub fn decoded_target(&self) -> String {
992 crate::decode::decode_escapes_and_references(&self.target)
993 }
994
995 /// The label with its backslash escapes and character references
996 /// decoded, as [`WikiLink::decoded_target`] decodes the target.
997 pub fn decoded_label(&self) -> String {
998 crate::decode::decode_escapes_and_references(&self.label)
999 }
1000}
1001
1002impl Text {
1003 /// A text node with the given string value.
1004 pub fn new(value: impl Into<String>) -> Self {
1005 Self {
1006 meta: NodeMeta::default(),
1007 value: value.into(),
1008 }
1009 }
1010}
1011
1012impl From<&str> for Text {
1013 fn from(value: &str) -> Self {
1014 Text::new(value)
1015 }
1016}
1017
1018impl From<String> for Text {
1019 fn from(value: String) -> Self {
1020 Text::new(value)
1021 }
1022}
1023
1024impl Paragraph {
1025 /// A paragraph from any iterator of inline-convertible children.
1026 pub fn new<I, T>(children: I) -> Self
1027 where
1028 I: IntoIterator<Item = T>,
1029 T: Into<Inline>,
1030 {
1031 Self {
1032 meta: NodeMeta::default(),
1033 children: children.into_iter().map(Into::into).collect(),
1034 }
1035 }
1036}
1037
1038impl Heading {
1039 /// An ATX heading of the given depth.
1040 pub fn new<I, T>(depth: u8, children: I) -> Self
1041 where
1042 I: IntoIterator<Item = T>,
1043 T: Into<Inline>,
1044 {
1045 Self {
1046 meta: NodeMeta::default(),
1047 depth,
1048 kind: HeadingKind::Atx,
1049 children: children.into_iter().map(Into::into).collect(),
1050 }
1051 }
1052}
1053
1054impl Link {
1055 /// An inline, bare-destination link with no title.
1056 pub fn new<I, T>(destination: impl Into<String>, children: I) -> Self
1057 where
1058 I: IntoIterator<Item = T>,
1059 T: Into<Inline>,
1060 {
1061 Self {
1062 meta: NodeMeta::default(),
1063 destination: destination.into(),
1064 destination_kind: LinkDestinationKind::Bare,
1065 title: None,
1066 children: children.into_iter().map(Into::into).collect(),
1067 }
1068 }
1069}
1070
1071impl CodeInline {
1072 /// An inline code span holding `value`.
1073 pub fn new(value: impl Into<String>) -> Self {
1074 Self {
1075 meta: NodeMeta::default(),
1076 value: value.into(),
1077 }
1078 }
1079}
1080
1081impl List {
1082 /// A tight, dash-delimited bullet list.
1083 pub fn new<I>(children: I) -> Self
1084 where
1085 I: IntoIterator<Item = ListItem>,
1086 {
1087 Self {
1088 meta: NodeMeta::default(),
1089 ordered: false,
1090 start: None,
1091 delimiter: ListDelimiter::Dash,
1092 tight: true,
1093 children: children.into_iter().collect(),
1094 }
1095 }
1096}