Skip to main content

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 `&amp;` or `&#247;`.
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: `![alt](url)`.
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 `&amp;` or `&#247;`. 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. `&amp;`).
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: `![alt](destination "title")`.
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. `&amp;`).
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 &amp; 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}