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, sync::Arc, vec::Vec};
8
9use crate::diagnostic::Diagnostic;
10use crate::parse::Definitions;
11use crate::span::Span;
12
13/// Metadata attached to every AST node; currently just the source span.
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct NodeMeta {
16    /// The node's source location, or `None` for hand-built nodes.
17    pub span: Option<Span>,
18}
19
20impl NodeMeta {
21    /// Wrap an optional [`Span`] into a [`NodeMeta`].
22    pub const fn new(span: Option<Span>) -> Self {
23        Self { span }
24    }
25}
26
27/// The root of a document: a sequence of top-level [`Block`]s, and for a
28/// parsed document the source it was parsed from.
29///
30/// The source is the only truth: writing a parsed document back with
31/// [`Document::to_markdown`] reuses the source of every top-level block that
32/// did not change. Only [`parse()`](crate::parse()) gives a document a
33/// source; [`Document::without_source`] drops it, and a document without one
34/// is written as canonical Markdown.
35#[derive(Clone, Debug, Default, Eq, PartialEq)]
36pub struct Document {
37    /// Node metadata (source span).
38    pub meta: NodeMeta,
39    /// The document's top-level blocks, in source order.
40    pub children: Vec<Block>,
41    /// The input `parse` read, shared by clones.
42    source: Option<Arc<str>>,
43}
44
45impl Document {
46    /// A document holding `children` and no source.
47    pub fn new(children: Vec<Block>) -> Self {
48        Self {
49            meta: NodeMeta::default(),
50            children,
51            source: None,
52        }
53    }
54
55    /// The document `parse` returns: `children` read from `source`.
56    pub(crate) fn parsed(meta: NodeMeta, children: Vec<Block>, source: &str) -> Self {
57        Self {
58            meta,
59            children,
60            source: Some(Arc::from(source)),
61        }
62    }
63
64    /// The input this document was parsed from, or `None` for a document
65    /// built by hand or returned by [`Document::without_source`].
66    pub fn source(&self) -> Option<&str> {
67        self.source.as_deref()
68    }
69
70    /// This document without its source; writing it gives canonical
71    /// Markdown.
72    #[must_use]
73    pub fn without_source(mut self) -> Self {
74        self.source = None;
75        self
76    }
77}
78
79impl From<Vec<Block>> for Document {
80    fn from(children: Vec<Block>) -> Self {
81        Self::new(children)
82    }
83}
84
85/// Child indices from a document down to one node. A list's children are its
86/// items, a table's are its rows, and a row's are its cells; every other
87/// container's are its blocks.
88#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct NodePath(pub Vec<usize>);
90
91impl From<Vec<usize>> for NodePath {
92    fn from(indices: Vec<usize>) -> Self {
93        Self(indices)
94    }
95}
96
97impl From<&[usize]> for NodePath {
98    fn from(indices: &[usize]) -> Self {
99        Self(indices.into())
100    }
101}
102
103impl<const N: usize> From<[usize; N]> for NodePath {
104    fn from(indices: [usize; N]) -> Self {
105        Self(indices.into())
106    }
107}
108
109/// A block-level node: the building blocks of a document's vertical structure.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub enum Block {
112    /// A paragraph of inline content.
113    Paragraph(Paragraph),
114    /// An ATX heading: `# h`.
115    Heading(Heading),
116    /// A thematic break / horizontal rule: `---`, `***`, or `___`.
117    ThematicBreak(ThematicBreak),
118    /// A block quote: lines prefixed with `> `.
119    BlockQuote(BlockQuote),
120    /// A GFM alert / admonition: `> [!NOTE]` etc.
121    Alert(Alert),
122    /// A bullet or ordered list.
123    List(List),
124    /// A fenced (```` ``` ````) or indented code block.
125    CodeBlock(CodeBlock),
126    /// A raw HTML block.
127    HtmlBlock(HtmlBlock),
128    /// A structured raw HTML container whose interior is parsed as Markdown.
129    HtmlContainer(HtmlContainer),
130    /// A link reference definition: `[label]: url "title"`.
131    Definition(Definition),
132    /// A footnote definition: `[^id]: text`.
133    FootnoteDefinition(FootnoteDefinition),
134    /// A GFM pipe table.
135    Table(Table),
136    /// A display math block: `$$ … $$`.
137    MathBlock(MathBlock),
138    /// A leading frontmatter block (`---` YAML or `+++` TOML).
139    Frontmatter(Frontmatter),
140    /// A leaf directive: `::name[label]{attrs}`.
141    LeafDirective(LeafDirective),
142    /// A container directive: `:::name … :::`.
143    ContainerDirective(ContainerDirective),
144    /// A blank line beyond the one that separates two blocks.
145    BlankLine(BlankLine),
146}
147
148/// A paragraph: a run of inline content. Source: any plain text line(s).
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub struct Paragraph {
151    /// Node metadata (source span).
152    pub meta: NodeMeta,
153    /// The paragraph's inline content.
154    pub children: Vec<Inline>,
155    /// The text the content was built from, by [`Paragraph::with_source`].
156    pub(crate) source: Option<String>,
157}
158
159/// A heading. Source: `# Title` … `###### Title` (ATX).
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub struct Heading {
162    /// Node metadata (source span).
163    pub meta: NodeMeta,
164    /// Heading level, 1..=6.
165    pub depth: u8,
166    /// The heading's inline content.
167    pub children: Vec<Inline>,
168    /// The text the content was built from, by [`Heading::with_source`].
169    pub(crate) source: Option<String>,
170}
171
172/// A blank line the source holds beyond the one that separates two blocks:
173/// K blank lines between two blocks are one separator and K − 1 of these.
174/// Before a document's first block, at the start or end of a block quote,
175/// alert, HTML container, or container directive, and after a document's
176/// last line, each blank line is one. Source: a line holding only
177/// whitespace and its containers' `>` markers.
178#[derive(Clone, Debug, Default, Eq, PartialEq)]
179pub struct BlankLine {
180    /// Node metadata (source span): the line with its line ending.
181    pub meta: NodeMeta,
182}
183
184/// A thematic break / horizontal rule. Source: `---`, `***`, or `___`.
185#[derive(Clone, Debug, Eq, PartialEq)]
186pub struct ThematicBreak {
187    /// Node metadata (source span).
188    pub meta: NodeMeta,
189    /// Which character formed the break.
190    pub marker: ThematicBreakMarker,
191}
192
193/// The character used to draw a [`ThematicBreak`].
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195pub enum ThematicBreakMarker {
196    /// Dashes: `---`.
197    Dash,
198    /// Asterisks: `***`.
199    Asterisk,
200    /// Underscores: `___`.
201    Underscore,
202}
203
204/// A block quote: content prefixed with `> `.
205#[derive(Clone, Debug, Eq, PartialEq)]
206pub struct BlockQuote {
207    /// Node metadata (source span).
208    pub meta: NodeMeta,
209    /// The quoted block content.
210    pub children: Vec<Block>,
211}
212
213/// A GFM alert / admonition. Source: `> [!NOTE]` followed by quoted content.
214#[derive(Clone, Debug, Eq, PartialEq)]
215pub struct Alert {
216    /// Node metadata (source span).
217    pub meta: NodeMeta,
218    /// The alert severity / type.
219    pub kind: AlertKind,
220    /// An optional custom title following the `[!KIND]` marker.
221    pub title: Option<String>,
222    /// The alert's block content.
223    pub children: Vec<Block>,
224}
225
226/// The kind of a GFM [`Alert`] (the `[!KIND]` marker).
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
228pub enum AlertKind {
229    /// `> [!NOTE]`.
230    Note,
231    /// `> [!TIP]`.
232    Tip,
233    /// `> [!IMPORTANT]`.
234    Important,
235    /// `> [!WARNING]`.
236    Warning,
237    /// `> [!CAUTION]`.
238    Caution,
239}
240
241/// A bullet or ordered list. Source: `- a` / `1. a` lines.
242#[derive(Clone, Debug, Eq, PartialEq)]
243pub struct List {
244    /// Node metadata (source span).
245    pub meta: NodeMeta,
246    /// `true` for an ordered list, `false` for a bullet list.
247    pub ordered: bool,
248    /// The marker delimiter used by the list items.
249    pub delimiter: ListDelimiter,
250    /// `true` if the list is tight (no blank lines between items / no `<p>`).
251    pub tight: bool,
252    /// The list's items.
253    pub children: Vec<ListItem>,
254}
255
256/// The marker character that delimits a list's items.
257#[derive(Clone, Copy, Debug, Eq, PartialEq)]
258pub enum ListDelimiter {
259    /// Bullet `-`.
260    Dash,
261    /// Bullet `*`.
262    Asterisk,
263    /// Bullet `+`.
264    Plus,
265    /// Ordered `1.`.
266    Period,
267    /// Ordered `1)`.
268    Paren,
269}
270
271/// A single list item, optionally a GFM task-list checkbox.
272#[derive(Clone, Debug, Eq, PartialEq)]
273pub struct ListItem {
274    /// Node metadata (source span).
275    pub meta: NodeMeta,
276    /// Task-list state: `Some(true)` for `[x]`, `Some(false)` for `[ ]`, `None` otherwise.
277    pub checked: Option<bool>,
278    /// The number an ordered item is written with (`3.` => `Some(3)`), which
279    /// for the first item is the list's start. `None` is written as the
280    /// previous item's number plus one, or 1 for the first item; a bullet
281    /// item holds `None`.
282    pub number: Option<u64>,
283    /// The item's block content.
284    pub children: Vec<Block>,
285}
286
287/// A code block. Source: ```` ```lang … ``` ```` (fenced) or 4-space-indented lines.
288#[derive(Clone, Debug, Eq, PartialEq)]
289pub struct CodeBlock {
290    /// Node metadata (source span).
291    pub meta: NodeMeta,
292    /// Whether the block is fenced (and with what fence) or indented.
293    pub kind: CodeBlockKind,
294    /// The info string after a fence (e.g. the `rust` in ```` ```rust ````).
295    pub info: Option<String>,
296    /// The literal code contents.
297    pub value: String,
298}
299
300/// Whether a [`CodeBlock`] is fenced or indented.
301#[derive(Clone, Copy, Debug, Eq, PartialEq)]
302pub enum CodeBlockKind {
303    /// A fenced code block; records the fence char and its run length.
304    Fenced {
305        /// Which character formed the fence (backtick or tilde).
306        marker: FenceMarker,
307        /// The number of fence characters in the opening fence (>=3).
308        length: usize,
309    },
310    /// A 4-space-indented code block.
311    Indented,
312}
313
314/// The character used to fence a [`CodeBlock`].
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
316pub enum FenceMarker {
317    /// Backtick fence: ```` ``` ````.
318    Backtick,
319    /// Tilde fence: `~~~`.
320    Tilde,
321}
322
323/// A raw HTML block: HTML emitted verbatim.
324#[derive(Clone, Debug, Eq, PartialEq)]
325pub struct HtmlBlock {
326    /// Node metadata (source span).
327    pub meta: NodeMeta,
328    /// The literal HTML source.
329    pub value: String,
330}
331
332/// A structured raw HTML container, such as `<details> ... </details>`.
333#[derive(Clone, Debug, Eq, PartialEq)]
334pub struct HtmlContainer {
335    /// Node metadata (source span).
336    pub meta: NodeMeta,
337    /// The opening HTML tag.
338    pub opening: HtmlTag,
339    /// The parsed container content.
340    pub content: HtmlContainerContent,
341    /// The closing HTML tag.
342    pub closing: HtmlTag,
343}
344
345/// A raw HTML tag that bounds an [`HtmlContainer`].
346#[derive(Clone, Debug, Eq, PartialEq)]
347pub struct HtmlTag {
348    /// Node metadata (source span).
349    pub meta: NodeMeta,
350    /// The normalized lowercase tag name.
351    pub name: String,
352    /// The literal tag source, including angle brackets and attributes.
353    pub raw: String,
354}
355
356/// The parsed content inside an [`HtmlContainer`].
357#[derive(Clone, Debug, Eq, PartialEq)]
358pub enum HtmlContainerContent {
359    /// Block-level Markdown content.
360    Blocks(Vec<Block>),
361    /// Inline-level Markdown content.
362    Inlines(Vec<Inline>),
363}
364
365/// A link reference definition. Source: `[label]: destination "title"`.
366#[derive(Clone, Debug, Eq, PartialEq)]
367pub struct Definition {
368    /// Node metadata (source span).
369    pub meta: NodeMeta,
370    /// The label as written in the source (e.g. `Foo Bar`).
371    pub label: String,
372    /// The normalized lookup key (case-folded, whitespace-collapsed) for matching references.
373    pub identifier: String,
374    /// The link target URL.
375    pub destination: String,
376    /// How the destination was delimited (bare or `<…>`).
377    pub destination_kind: LinkDestinationKind,
378    /// The optional link title, with the quotes it is written in.
379    pub title: Option<Title>,
380}
381
382/// A footnote definition. Source: `[^id]: footnote text`.
383#[derive(Clone, Debug, Eq, PartialEq)]
384pub struct FootnoteDefinition {
385    /// Node metadata (source span).
386    pub meta: NodeMeta,
387    /// The label as written in the source (the text after `^`).
388    pub label: String,
389    /// The normalized lookup key matching [`FootnoteReference`]s.
390    pub identifier: String,
391    /// The footnote's block content.
392    pub children: Vec<Block>,
393}
394
395/// A GFM pipe table: a header row, an alignment row, then body rows. Source:
396/// `| a | b |` / `|---|---|` / `| 1 | 2 |`.
397#[derive(Clone, Debug, Eq, PartialEq)]
398pub struct Table {
399    /// Node metadata (source span).
400    pub meta: NodeMeta,
401    /// Per-column alignment from the delimiter row.
402    pub alignments: Vec<TableAlignment>,
403    /// All rows; the first is the header row.
404    pub rows: Vec<TableRow>,
405}
406
407/// The alignment of a [`Table`] column, from the `:---:` delimiter row.
408#[derive(Clone, Copy, Debug, Eq, PartialEq)]
409pub enum TableAlignment {
410    /// No explicit alignment: `---`.
411    None,
412    /// Left-aligned: `:---`.
413    Left,
414    /// Center-aligned: `:---:`.
415    Center,
416    /// Right-aligned: `---:`.
417    Right,
418}
419
420/// A single row of a [`Table`].
421#[derive(Clone, Debug, Eq, PartialEq)]
422pub struct TableRow {
423    /// Node metadata (source span).
424    pub meta: NodeMeta,
425    /// The row's cells.
426    pub cells: Vec<TableCell>,
427}
428
429/// A single cell of a [`TableRow`].
430#[derive(Clone, Debug, Eq, PartialEq)]
431pub struct TableCell {
432    /// Node metadata (source span).
433    pub meta: NodeMeta,
434    /// The cell's inline content.
435    pub children: Vec<Inline>,
436    /// The text the content was built from, by [`TableCell::with_source`].
437    pub(crate) source: Option<String>,
438}
439
440/// A display math block. Source: `$$ … $$`.
441#[derive(Clone, Debug, Eq, PartialEq)]
442pub struct MathBlock {
443    /// Node metadata (source span).
444    pub meta: NodeMeta,
445    /// The literal math contents (between the `$$` fences).
446    pub value: String,
447}
448
449/// A leading frontmatter block. Source: `---` YAML or `+++` TOML at the top of
450/// the document.
451#[derive(Clone, Debug, Eq, PartialEq)]
452pub struct Frontmatter {
453    /// Node metadata (source span).
454    pub meta: NodeMeta,
455    /// Whether the frontmatter is YAML or TOML.
456    pub kind: FrontmatterKind,
457    /// The literal frontmatter contents (between the fences).
458    pub value: String,
459}
460
461/// The format of a [`Frontmatter`] block.
462#[derive(Clone, Copy, Debug, Eq, PartialEq)]
463pub enum FrontmatterKind {
464    /// YAML frontmatter, fenced by `---`.
465    Yaml,
466    /// TOML frontmatter, fenced by `+++`.
467    Toml,
468}
469
470/// A leaf directive. Source: `::name[label]{attrs}`.
471#[derive(Clone, Debug, Eq, PartialEq)]
472pub struct LeafDirective {
473    /// Node metadata (source span).
474    pub meta: NodeMeta,
475    /// The directive name following the `::`.
476    pub name: String,
477    /// The optional `[label]` inline content.
478    pub label: Vec<Inline>,
479    /// The optional `{attrs}` attributes.
480    pub attributes: Vec<DirectiveAttribute>,
481    /// The text the label was built from, by [`LeafDirective::with_source`].
482    pub(crate) label_source: Option<String>,
483}
484
485/// A container directive. Source: `:::name[label]{attrs}` … `:::`.
486#[derive(Clone, Debug, Eq, PartialEq)]
487pub struct ContainerDirective {
488    /// Node metadata (source span).
489    pub meta: NodeMeta,
490    /// The directive name following the `:::`.
491    pub name: String,
492    /// The optional `[label]` inline content.
493    pub label: Vec<Inline>,
494    /// The optional `{attrs}` attributes.
495    pub attributes: Vec<DirectiveAttribute>,
496    /// The directive's enclosed block content.
497    pub children: Vec<Block>,
498    /// The text the label was built from, by
499    /// [`ContainerDirective::with_source`].
500    pub(crate) label_source: Option<String>,
501}
502
503/// An inline-level node: the leaf and span content inside blocks.
504#[derive(Clone, Debug, Eq, PartialEq)]
505pub enum Inline {
506    /// Literal text.
507    Text(Text),
508    /// A backslash escape such as `\*`.
509    Escape(Escape),
510    /// A character reference such as `&amp;` or `&#247;`.
511    CharacterReference(CharacterReference),
512    /// Emphasis: `*text*` or `_text_`.
513    Emphasis(Emphasis),
514    /// Strong emphasis: `**text**` or `__text__`.
515    Strong(Strong),
516    /// Strikethrough: `~~text~~`.
517    Delete(Delete),
518    /// A highlight / "mark" span: `==text==`.
519    Mark(Mark),
520    /// An emoji-style shortcode: `:name:`.
521    Shortcode(Shortcode),
522    /// An inline code span: `` `code` ``.
523    Code(CodeInline),
524    /// An inline link: `[text](url)`.
525    Link(Link),
526    /// An autolink: `<https://example.com>` or a bare literal URL or email.
527    Autolink(Autolink),
528    /// An inline image: `![alt](url)`.
529    Image(Image),
530    /// A reference link: `[text][label]`.
531    LinkReference(LinkReference),
532    /// A reference image: `![alt][label]`.
533    ImageReference(ImageReference),
534    /// Raw inline HTML such as `<span>`.
535    Html(HtmlInline),
536    /// A soft line break (a plain newline within a paragraph).
537    SoftBreak(SoftBreak),
538    /// A hard line break (`\` or two trailing spaces).
539    LineBreak(LineBreak),
540    /// Inline math: `$x$`.
541    Math(MathInline),
542    /// A footnote reference: `[^id]`.
543    FootnoteReference(FootnoteReference),
544    /// An inline footnote: `^[inline note]`.
545    InlineFootnote(InlineFootnote),
546    /// A wiki link: `[[target|label]]`.
547    WikiLink(WikiLink),
548    /// A text directive: `:name[label]{attrs}`.
549    TextDirective(TextDirective),
550    /// A tag: `#name`.
551    Tag(Tag),
552}
553
554/// Literal text content.
555#[derive(Clone, Debug, Eq, PartialEq)]
556pub struct Text {
557    /// Node metadata (source span).
558    pub meta: NodeMeta,
559    /// The text value.
560    pub value: String,
561}
562
563/// A backslash escape such as `\*` or `\\`.
564#[derive(Clone, Debug, Eq, PartialEq)]
565pub struct Escape {
566    /// Node metadata (source span).
567    pub meta: NodeMeta,
568    /// The escaped (literal) character.
569    pub value: char,
570}
571
572/// A character reference such as `&amp;` or `&#247;`. The reference as
573/// written is the node's one fact; [`CharacterReference::value`] decodes it.
574#[derive(Clone, Debug, Eq, PartialEq)]
575pub struct CharacterReference {
576    /// Node metadata (source span).
577    pub meta: NodeMeta,
578    /// The reference as written, including `&` and `;` (e.g. `&amp;`).
579    pub reference: String,
580}
581
582/// Emphasis (typically italic): `*text*` or `_text_`.
583#[derive(Clone, Debug, Eq, PartialEq)]
584pub struct Emphasis {
585    /// Node metadata (source span).
586    pub meta: NodeMeta,
587    /// The delimiter the span was written with.
588    pub delimiter: EmphasisDelimiter,
589    /// The emphasized inline content.
590    pub children: Vec<Inline>,
591}
592
593/// Strong emphasis (typically bold): `**text**` or `__text__`.
594#[derive(Clone, Debug, Eq, PartialEq)]
595pub struct Strong {
596    /// Node metadata (source span).
597    pub meta: NodeMeta,
598    /// The delimiter the span was written with.
599    pub delimiter: EmphasisDelimiter,
600    /// The strongly-emphasized inline content.
601    pub children: Vec<Inline>,
602}
603
604/// The delimiter character of an [`Emphasis`] or [`Strong`] span.
605#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
606pub enum EmphasisDelimiter {
607    /// `*text*` / `**text**`.
608    #[default]
609    Asterisk,
610    /// `_text_` / `__text__`.
611    Underscore,
612}
613
614/// Strikethrough: `~~text~~`.
615#[derive(Clone, Debug, Eq, PartialEq)]
616pub struct Delete {
617    /// Node metadata (source span).
618    pub meta: NodeMeta,
619    /// The struck-through inline content.
620    pub children: Vec<Inline>,
621}
622
623/// A highlight / "mark" span: `==text==`.
624#[derive(Clone, Debug, Eq, PartialEq)]
625pub struct Mark {
626    /// Node metadata (source span).
627    pub meta: NodeMeta,
628    /// The highlighted inline content.
629    pub children: Vec<Inline>,
630}
631
632/// An emoji shortcode: `:name:`, where `name` is a gemoji name or alias.
633#[derive(Clone, Debug, Eq, PartialEq)]
634pub struct Shortcode {
635    /// Node metadata (source span).
636    pub meta: NodeMeta,
637    /// The shortcode name between the colons (e.g. `smile` for `:smile:`).
638    pub name: String,
639}
640
641/// A tag: `#` and a name, such as `#rust` or `#ai/agents`.
642#[derive(Clone, Debug, Eq, PartialEq)]
643pub struct Tag {
644    /// Node metadata (source span).
645    pub meta: NodeMeta,
646    /// The name as written, without the `#`. A `/` separates levels, which
647    /// the caller splits.
648    pub name: String,
649}
650
651/// An inline code span: `` `code` ``. The fence and the padding spaces are
652/// not recorded: the serializer chooses them from the value.
653#[derive(Clone, Debug, Eq, PartialEq)]
654pub struct CodeInline {
655    /// Node metadata (source span).
656    pub meta: NodeMeta,
657    /// The code text, normalized as CommonMark reads it: line endings as
658    /// spaces, and one space stripped from each end when both ends hold one
659    /// and the text is not all spaces.
660    pub value: String,
661}
662
663/// An inline link: `[text](destination "title")`.
664#[derive(Clone, Debug, Eq, PartialEq)]
665pub struct Link {
666    /// Node metadata (source span).
667    pub meta: NodeMeta,
668    /// The link target URL.
669    pub destination: String,
670    /// How the destination was delimited (bare or `<…>`).
671    pub destination_kind: LinkDestinationKind,
672    /// The optional link title, with the quotes it is written in.
673    pub title: Option<Title>,
674    /// The link's inline content (the visible text).
675    pub children: Vec<Inline>,
676}
677
678/// An autolink: `<https://example.com>`, `<a@b.c>`, or a GFM literal
679/// autolink, a bare `https://…`, `www.…`, or email address. The text as
680/// written is the node's one fact; [`Autolink::destination`] derives the link
681/// target from it.
682#[derive(Clone, Debug, Eq, PartialEq)]
683pub struct Autolink {
684    /// Node metadata (source span). An angle-bracket autolink's span covers
685    /// its brackets.
686    pub meta: NodeMeta,
687    /// Which autolink syntax the text is written in.
688    pub form: AutolinkForm,
689    /// The URL or address as written, without an angle-bracket autolink's
690    /// brackets.
691    pub text: String,
692}
693
694/// How an [`Autolink`] is written.
695#[derive(Clone, Copy, Debug, Eq, PartialEq)]
696pub enum AutolinkForm {
697    /// An angle-bracket autolink: `<https://example.com>` or `<a@b.c>`.
698    Angle,
699    /// A GFM literal autolink: a bare `https://…`, `www.…`, or email address.
700    Literal,
701}
702
703/// An inline image: `![alt](destination "title")`.
704#[derive(Clone, Debug, Eq, PartialEq)]
705pub struct Image {
706    /// Node metadata (source span).
707    pub meta: NodeMeta,
708    /// The image source URL.
709    pub destination: String,
710    /// How the destination was delimited (bare or `<…>`).
711    pub destination_kind: LinkDestinationKind,
712    /// The optional image title, with the quotes it is written in.
713    pub title: Option<Title>,
714    /// The image's alt-text inline content.
715    pub alt: Vec<Inline>,
716}
717
718/// How a link/image destination was delimited in the source.
719#[derive(Clone, Copy, Debug, Eq, PartialEq)]
720pub enum LinkDestinationKind {
721    /// A bare destination: `(url)`.
722    Bare,
723    /// An angle-bracketed destination: `(<url>)`.
724    Angle,
725    /// No destination present: `()`.
726    Omitted,
727}
728
729/// How a link/image title was quoted in the source.
730#[derive(Clone, Copy, Debug, Eq, PartialEq)]
731pub enum LinkTitleKind {
732    /// Double-quoted: `"title"`.
733    DoubleQuote,
734    /// Single-quoted: `'title'`.
735    SingleQuote,
736    /// Parenthesized: `(title)`.
737    Paren,
738}
739
740/// The title of a [`Link`], [`Image`], or [`Definition`]: its text and the
741/// quotes it is written in.
742#[derive(Clone, Debug, Eq, PartialEq)]
743pub struct Title {
744    /// The title text, with its escapes and character references decoded.
745    pub value: String,
746    /// How the title is quoted.
747    pub kind: LinkTitleKind,
748}
749
750/// A reference link: `[text][label]`, `[text][]`, or `[text]`.
751#[derive(Clone, Debug, Eq, PartialEq)]
752pub struct LinkReference {
753    /// Node metadata (source span).
754    pub meta: NodeMeta,
755    /// The normalized lookup key matching a [`Definition`].
756    pub identifier: String,
757    /// The label as written in the source.
758    pub label: String,
759    /// Whether the reference is full, collapsed, or shortcut form.
760    pub kind: ReferenceKind,
761    /// The link's inline content (the visible text).
762    pub children: Vec<Inline>,
763}
764
765/// A reference image: `![alt][label]`, `![alt][]`, or `![alt]`.
766#[derive(Clone, Debug, Eq, PartialEq)]
767pub struct ImageReference {
768    /// Node metadata (source span).
769    pub meta: NodeMeta,
770    /// The normalized lookup key matching a [`Definition`].
771    pub identifier: String,
772    /// The label as written in the source.
773    pub label: String,
774    /// Whether the reference is full, collapsed, or shortcut form.
775    pub kind: ReferenceKind,
776    /// The image's alt-text inline content.
777    pub alt: Vec<Inline>,
778}
779
780/// The form of a reference link/image.
781#[derive(Clone, Copy, Debug, Eq, PartialEq)]
782pub enum ReferenceKind {
783    /// Full reference: `[text][label]`.
784    Full,
785    /// Collapsed reference: `[label][]`.
786    Collapsed,
787    /// Shortcut reference: `[label]`.
788    Shortcut,
789}
790
791/// Raw inline HTML such as `<span>` or `</em>`.
792#[derive(Clone, Debug, Eq, PartialEq)]
793pub struct HtmlInline {
794    /// Node metadata (source span).
795    pub meta: NodeMeta,
796    /// The literal HTML source.
797    pub value: String,
798}
799
800/// A soft line break: a plain newline within a paragraph.
801#[derive(Clone, Debug, Eq, PartialEq)]
802pub struct SoftBreak {
803    /// Node metadata (source span).
804    pub meta: NodeMeta,
805}
806
807/// A hard line break: a trailing `\` or two trailing spaces.
808#[derive(Clone, Debug, Eq, PartialEq)]
809pub struct LineBreak {
810    /// Node metadata (source span).
811    pub meta: NodeMeta,
812    /// Which syntax produced the break.
813    pub kind: LineBreakKind,
814}
815
816/// Which syntax produced a hard [`LineBreak`].
817#[derive(Clone, Copy, Debug, Eq, PartialEq)]
818pub enum LineBreakKind {
819    /// A trailing backslash: `\`.
820    Backslash,
821    /// Two or more trailing spaces.
822    Spaces,
823}
824
825/// Which syntax delimited an inline [`MathInline`] span.
826#[derive(Clone, Copy, Debug, Eq, PartialEq)]
827pub enum MathInlineKind {
828    /// Dollar-fenced inline math (`$…$`, `$$…$$`, …); `dollars` is the fence
829    /// length (>=1). Dollar math always renders inline, while a 2-dollar fence
830    /// is conventionally treated as display elsewhere in the ecosystem.
831    Dollar {
832        /// The number of `$` characters in the fence (>=1).
833        dollars: u8,
834    },
835    /// Math-code span: `$`…`$`.
836    Code,
837}
838
839/// Inline math: `$x$`.
840#[derive(Clone, Debug, Eq, PartialEq)]
841pub struct MathInline {
842    /// Node metadata (source span).
843    pub meta: NodeMeta,
844    /// The literal math contents (between the fences).
845    pub value: String,
846    /// Which delimiter syntax was used.
847    pub kind: MathInlineKind,
848}
849
850/// A footnote reference: `[^id]`.
851#[derive(Clone, Debug, Eq, PartialEq)]
852pub struct FootnoteReference {
853    /// Node metadata (source span).
854    pub meta: NodeMeta,
855    /// The label as written in the source (the text after `^`).
856    pub label: String,
857    /// The normalized lookup key matching a [`FootnoteDefinition`].
858    pub identifier: String,
859}
860
861/// An inline footnote: `^[inline note]`.
862#[derive(Clone, Debug, Eq, PartialEq)]
863pub struct InlineFootnote {
864    /// Node metadata (source span).
865    pub meta: NodeMeta,
866    /// The footnote's inline content.
867    pub children: Vec<Inline>,
868}
869
870/// A wiki link: `[[target|label]]`, or an embed: `![[target|label]]`.
871#[derive(Clone, Debug, Eq, PartialEq)]
872pub struct WikiLink {
873    /// Node metadata (source span). An embed's span starts at its `!`.
874    pub meta: NodeMeta,
875    /// The link target (page name) as written in the source, its backslash
876    /// escapes and character references kept; [`WikiLink::decoded_target`]
877    /// gives the text they stand for.
878    pub target: String,
879    /// The visible label as written in the source, its backslash escapes and
880    /// character references kept; [`WikiLink::decoded_label`] gives the text
881    /// they stand for. A label equal to `target` means the link is written
882    /// without a `|label` part.
883    pub label: String,
884    /// Whether a `!` directly before the `[[` marks the link as an embed.
885    pub embed: bool,
886}
887
888/// A text directive. Source: `:name[label]{attrs}`.
889#[derive(Clone, Debug, Eq, PartialEq)]
890pub struct TextDirective {
891    /// Node metadata (source span).
892    pub meta: NodeMeta,
893    /// The directive name following the `:`.
894    pub name: String,
895    /// The optional `[label]` inline content.
896    pub label: Vec<Inline>,
897    /// The optional `{attrs}` attributes.
898    pub attributes: Vec<DirectiveAttribute>,
899}
900
901/// One attribute of a directive's `{name=value}` block.
902#[derive(Clone, Debug, Eq, PartialEq)]
903pub struct DirectiveAttribute {
904    /// The attribute name.
905    pub name: String,
906    /// The attribute value, or `None` for a valueless attribute.
907    pub value: Option<String>,
908}
909
910// ---------------------------------------------------------------------------
911// Ergonomic accessors and a minimal construction layer.
912//
913// Every node carries a `meta: NodeMeta`, so `meta()`/`span()` are uniform across
914// the enums and free callers from writing an exhaustive match just to read a
915// span. `From`/`new` collapse the `Variant(Struct { meta, .. })` boilerplate for
916// hand-built ASTs; the raw struct literals remain available for full control.
917// ---------------------------------------------------------------------------
918
919macro_rules! impl_meta_accessors {
920    ($enum:ident { $($variant:ident),+ $(,)? }) => {
921        impl $enum {
922            /// Borrow this node's [`NodeMeta`].
923            pub fn meta(&self) -> &NodeMeta {
924                match self { $( $enum::$variant(node) => &node.meta, )+ }
925            }
926
927            /// This node's source span, if it carries one.
928            pub fn span(&self) -> Option<Span> {
929                self.meta().span
930            }
931        }
932    };
933}
934
935macro_rules! impl_from_variants {
936    ($enum:ident { $($variant:ident($ty:ty)),+ $(,)? }) => {
937        $(
938            impl From<$ty> for $enum {
939                fn from(node: $ty) -> Self {
940                    $enum::$variant(node)
941                }
942            }
943        )+
944    };
945}
946
947impl_meta_accessors!(Block {
948    Paragraph,
949    Heading,
950    ThematicBreak,
951    BlockQuote,
952    Alert,
953    List,
954    CodeBlock,
955    HtmlBlock,
956    HtmlContainer,
957    Definition,
958    FootnoteDefinition,
959    Table,
960    MathBlock,
961    Frontmatter,
962    LeafDirective,
963    ContainerDirective,
964    BlankLine,
965});
966
967impl_from_variants!(Block {
968    Paragraph(Paragraph), Heading(Heading), ThematicBreak(ThematicBreak),
969    BlockQuote(BlockQuote), Alert(Alert), List(List),
970    CodeBlock(CodeBlock), HtmlBlock(HtmlBlock), Definition(Definition),
971    HtmlContainer(HtmlContainer),
972    FootnoteDefinition(FootnoteDefinition), Table(Table), MathBlock(MathBlock),
973    Frontmatter(Frontmatter), LeafDirective(LeafDirective), ContainerDirective(ContainerDirective),
974    BlankLine(BlankLine),
975});
976
977impl_meta_accessors!(Inline {
978    Text,
979    Escape,
980    CharacterReference,
981    Emphasis,
982    Strong,
983    Delete,
984    Mark,
985    Shortcode,
986    Code,
987    Link,
988    Autolink,
989    Image,
990    LinkReference,
991    ImageReference,
992    Html,
993    SoftBreak,
994    LineBreak,
995    Math,
996    FootnoteReference,
997    InlineFootnote,
998    WikiLink,
999    TextDirective,
1000    Tag,
1001});
1002
1003impl_from_variants!(Inline {
1004    Text(Text), Escape(Escape), CharacterReference(CharacterReference),
1005    Emphasis(Emphasis), Strong(Strong), Delete(Delete), Mark(Mark),
1006    Shortcode(Shortcode), Code(CodeInline), Link(Link), Autolink(Autolink), Image(Image),
1007    LinkReference(LinkReference), ImageReference(ImageReference), Html(HtmlInline),
1008    SoftBreak(SoftBreak), LineBreak(LineBreak), Math(MathInline),
1009    FootnoteReference(FootnoteReference), InlineFootnote(InlineFootnote), WikiLink(WikiLink),
1010    TextDirective(TextDirective), Tag(Tag),
1011});
1012
1013impl Block {
1014    /// The blocks directly inside this block: a block quote's, an alert's, a
1015    /// footnote definition's, a container directive's, or an HTML container's
1016    /// that holds blocks; an empty slice for every other block. A list's
1017    /// blocks are in its items ([`ListItem::children`]), as [`NodePath`]
1018    /// counts them, so a walker over blocks steps into a list's items itself.
1019    pub fn children(&self) -> &[Block] {
1020        match self {
1021            Block::BlockQuote(n) => &n.children,
1022            Block::Alert(n) => &n.children,
1023            Block::FootnoteDefinition(n) => &n.children,
1024            Block::ContainerDirective(n) => &n.children,
1025            Block::HtmlContainer(n) => match &n.content {
1026                HtmlContainerContent::Blocks(blocks) => blocks,
1027                HtmlContainerContent::Inlines(_) => &[],
1028            },
1029            Block::Paragraph(_)
1030            | Block::Heading(_)
1031            | Block::ThematicBreak(_)
1032            | Block::List(_)
1033            | Block::CodeBlock(_)
1034            | Block::HtmlBlock(_)
1035            | Block::Definition(_)
1036            | Block::Table(_)
1037            | Block::MathBlock(_)
1038            | Block::Frontmatter(_)
1039            | Block::LeafDirective(_)
1040            | Block::BlankLine(_) => &[],
1041        }
1042    }
1043}
1044
1045impl Inline {
1046    /// The inline subtree of this node, or an empty slice for a leaf. Covers the
1047    /// `alt`/`label` fields uniformly, so a generic walker never silently skips
1048    /// an image's alt text or a directive's label.
1049    pub fn children(&self) -> &[Inline] {
1050        match self {
1051            Inline::Emphasis(n) => &n.children,
1052            Inline::Strong(n) => &n.children,
1053            Inline::Delete(n) => &n.children,
1054            Inline::Mark(n) => &n.children,
1055            Inline::Link(n) => &n.children,
1056            Inline::Image(n) => &n.alt,
1057            Inline::LinkReference(n) => &n.children,
1058            Inline::ImageReference(n) => &n.alt,
1059            Inline::InlineFootnote(n) => &n.children,
1060            Inline::TextDirective(n) => &n.label,
1061            Inline::Text(_)
1062            | Inline::Escape(_)
1063            | Inline::CharacterReference(_)
1064            | Inline::Shortcode(_)
1065            | Inline::Code(_)
1066            | Inline::Autolink(_)
1067            | Inline::Html(_)
1068            | Inline::SoftBreak(_)
1069            | Inline::LineBreak(_)
1070            | Inline::Math(_)
1071            | Inline::FootnoteReference(_)
1072            | Inline::WikiLink(_)
1073            | Inline::Tag(_) => &[],
1074        }
1075    }
1076}
1077
1078impl CharacterReference {
1079    /// A character reference node for `reference`, written with its `&` and
1080    /// `;` (e.g. `&amp;`).
1081    pub fn new(reference: impl Into<String>) -> Self {
1082        Self {
1083            meta: NodeMeta::default(),
1084            reference: reference.into(),
1085        }
1086    }
1087
1088    /// The character the reference decodes to, or `None` when
1089    /// [`reference`](Self::reference) is not exactly one character reference.
1090    /// A valid document holds only references that decode.
1091    pub fn value(&self) -> Option<String> {
1092        crate::decode::decode_character_reference(&self.reference)
1093    }
1094}
1095
1096impl Autolink {
1097    /// An autolink of `form` whose text is `text`.
1098    pub fn new(form: AutolinkForm, text: impl Into<String>) -> Self {
1099        Self {
1100            meta: NodeMeta::default(),
1101            form,
1102            text: text.into(),
1103        }
1104    }
1105
1106    /// The link target the text writes, or `None` when the text is not
1107    /// exactly one autolink of its form. An angle-bracket URI links to itself
1108    /// and an email address to it after `mailto:`; a literal `www.` domain
1109    /// links to it after `http://`, a literal email address to it after
1110    /// `mailto:`, and any other literal autolink to itself. A valid document
1111    /// holds only autolinks with a destination.
1112    pub fn destination(&self) -> Option<String> {
1113        match self.form {
1114            AutolinkForm::Angle => crate::parse::angle_autolink_destination(&self.text),
1115            AutolinkForm::Literal => crate::parse::literal_autolink_destination(&self.text),
1116        }
1117    }
1118}
1119
1120impl Title {
1121    /// A title holding `value`, quoted as `kind`.
1122    pub fn new(value: impl Into<String>, kind: LinkTitleKind) -> Self {
1123        Self {
1124            value: value.into(),
1125            kind,
1126        }
1127    }
1128}
1129
1130impl Shortcode {
1131    /// The emoji the crate's pinned gemoji table gives this shortcode's
1132    /// name, or `None` for a name the table does not hold.
1133    pub fn glyph(&self) -> Option<&'static str> {
1134        crate::gemoji::glyph(&self.name)
1135    }
1136}
1137
1138impl Tag {
1139    /// A tag named `name`, written without its `#`.
1140    pub fn new(name: impl Into<String>) -> Self {
1141        Self {
1142            meta: NodeMeta::default(),
1143            name: name.into(),
1144        }
1145    }
1146}
1147
1148impl WikiLink {
1149    /// The target with its backslash escapes and character references
1150    /// decoded, as CommonMark decodes a link destination: `a \| b &amp; c`
1151    /// decodes to `a | b & c`.
1152    pub fn decoded_target(&self) -> String {
1153        crate::decode::decode_escapes_and_references(&self.target)
1154    }
1155
1156    /// The label with its backslash escapes and character references
1157    /// decoded, as [`WikiLink::decoded_target`] decodes the target.
1158    pub fn decoded_label(&self) -> String {
1159        crate::decode::decode_escapes_and_references(&self.label)
1160    }
1161}
1162
1163impl Text {
1164    /// A text node with the given string value.
1165    pub fn new(value: impl Into<String>) -> Self {
1166        Self {
1167            meta: NodeMeta::default(),
1168            value: value.into(),
1169        }
1170    }
1171}
1172
1173impl From<&str> for Text {
1174    fn from(value: &str) -> Self {
1175        Text::new(value)
1176    }
1177}
1178
1179impl From<String> for Text {
1180    fn from(value: String) -> Self {
1181        Text::new(value)
1182    }
1183}
1184
1185impl Paragraph {
1186    /// A paragraph from any iterator of inline-convertible children.
1187    pub fn new<I, T>(children: I) -> Self
1188    where
1189        I: IntoIterator<Item = T>,
1190        T: Into<Inline>,
1191    {
1192        Self {
1193            meta: NodeMeta::default(),
1194            children: children.into_iter().map(Into::into).collect(),
1195            source: None,
1196        }
1197    }
1198
1199    /// A paragraph whose content is `text`, read as
1200    /// [`parse_inline`](crate::parse_inline) reads it with `definitions`, and
1201    /// the diagnostics of that read. The paragraph keeps `text`, which the
1202    /// writer writes in place of its inlines while they are the ones `text`
1203    /// reads as; the spans of its inlines are offsets in `text`. Pass the
1204    /// definitions of the document the paragraph will be written in
1205    /// ([`Definitions::of`]); after they change, or for a leaf built with
1206    /// others, [`Document::reread_kept_sources`] reads its text again.
1207    pub fn with_source(text: &str, definitions: &Definitions) -> (Self, Vec<Diagnostic>) {
1208        let (children, diagnostics) = read_source(text, definitions);
1209        let paragraph = Self {
1210            source: Some(text.into()),
1211            ..Self::new(children)
1212        };
1213        (paragraph, diagnostics)
1214    }
1215
1216    /// The text [`Paragraph::with_source`] built the paragraph from.
1217    pub fn source(&self) -> Option<&str> {
1218        self.source.as_deref()
1219    }
1220}
1221
1222impl Heading {
1223    /// A heading of the given depth.
1224    pub fn new<I, T>(depth: u8, children: I) -> Self
1225    where
1226        I: IntoIterator<Item = T>,
1227        T: Into<Inline>,
1228    {
1229        Self {
1230            meta: NodeMeta::default(),
1231            depth,
1232            children: children.into_iter().map(Into::into).collect(),
1233            source: None,
1234        }
1235    }
1236
1237    /// A heading of the given depth whose content is `text`, built as
1238    /// [`Paragraph::with_source`] builds a paragraph.
1239    pub fn with_source(
1240        depth: u8,
1241        text: &str,
1242        definitions: &Definitions,
1243    ) -> (Self, Vec<Diagnostic>) {
1244        let (children, diagnostics) = read_source(text, definitions);
1245        let heading = Self {
1246            source: Some(text.into()),
1247            ..Self::new(depth, children)
1248        };
1249        (heading, diagnostics)
1250    }
1251
1252    /// The text [`Heading::with_source`] built the heading from.
1253    pub fn source(&self) -> Option<&str> {
1254        self.source.as_deref()
1255    }
1256}
1257
1258impl TableCell {
1259    /// A cell from any iterator of inline-convertible children.
1260    pub fn new<I, T>(children: I) -> Self
1261    where
1262        I: IntoIterator<Item = T>,
1263        T: Into<Inline>,
1264    {
1265        Self {
1266            meta: NodeMeta::default(),
1267            children: children.into_iter().map(Into::into).collect(),
1268            source: None,
1269        }
1270    }
1271
1272    /// A cell whose content is `text`, built as [`Paragraph::with_source`]
1273    /// builds a paragraph. A `|` in `text` is content: the writer escapes it.
1274    pub fn with_source(text: &str, definitions: &Definitions) -> (Self, Vec<Diagnostic>) {
1275        let (children, diagnostics) = read_source(text, definitions);
1276        let cell = Self {
1277            source: Some(text.into()),
1278            ..Self::new(children)
1279        };
1280        (cell, diagnostics)
1281    }
1282
1283    /// The text [`TableCell::with_source`] built the cell from.
1284    pub fn source(&self) -> Option<&str> {
1285        self.source.as_deref()
1286    }
1287}
1288
1289impl LeafDirective {
1290    /// A leaf directive named `name`, with no label and no attributes.
1291    pub fn new(name: impl Into<String>) -> Self {
1292        Self {
1293            meta: NodeMeta::default(),
1294            name: name.into(),
1295            label: Vec::new(),
1296            attributes: Vec::new(),
1297            label_source: None,
1298        }
1299    }
1300
1301    /// A leaf directive named `name` whose label is `text`, built as
1302    /// [`Paragraph::with_source`] builds a paragraph's content.
1303    pub fn with_source(
1304        name: impl Into<String>,
1305        text: &str,
1306        definitions: &Definitions,
1307    ) -> (Self, Vec<Diagnostic>) {
1308        let (label, diagnostics) = read_source(text, definitions);
1309        let directive = Self {
1310            label,
1311            label_source: Some(text.into()),
1312            ..Self::new(name)
1313        };
1314        (directive, diagnostics)
1315    }
1316
1317    /// The text [`LeafDirective::with_source`] built the label from.
1318    pub fn label_source(&self) -> Option<&str> {
1319        self.label_source.as_deref()
1320    }
1321}
1322
1323impl ContainerDirective {
1324    /// A container directive named `name` holding `children`, with no label
1325    /// and no attributes.
1326    pub fn new(name: impl Into<String>, children: Vec<Block>) -> Self {
1327        Self {
1328            meta: NodeMeta::default(),
1329            name: name.into(),
1330            label: Vec::new(),
1331            attributes: Vec::new(),
1332            children,
1333            label_source: None,
1334        }
1335    }
1336
1337    /// A container directive named `name` holding `children`, whose label
1338    /// is `text`, built as [`Paragraph::with_source`] builds a paragraph's
1339    /// content.
1340    pub fn with_source(
1341        name: impl Into<String>,
1342        text: &str,
1343        children: Vec<Block>,
1344        definitions: &Definitions,
1345    ) -> (Self, Vec<Diagnostic>) {
1346        let (label, diagnostics) = read_source(text, definitions);
1347        let directive = Self {
1348            label,
1349            label_source: Some(text.into()),
1350            ..Self::new(name, children)
1351        };
1352        (directive, diagnostics)
1353    }
1354
1355    /// The text [`ContainerDirective::with_source`] built the label from.
1356    pub fn label_source(&self) -> Option<&str> {
1357        self.label_source.as_deref()
1358    }
1359}
1360
1361/// The inlines `text` reads as, with the diagnostics of the read.
1362fn read_source(text: &str, definitions: &Definitions) -> (Vec<Inline>, Vec<Diagnostic>) {
1363    let output = crate::parse::parse_inline_tree(text, definitions);
1364    (output.inlines, output.diagnostics)
1365}
1366
1367impl Link {
1368    /// An inline, bare-destination link with no title.
1369    pub fn new<I, T>(destination: impl Into<String>, children: I) -> Self
1370    where
1371        I: IntoIterator<Item = T>,
1372        T: Into<Inline>,
1373    {
1374        Self {
1375            meta: NodeMeta::default(),
1376            destination: destination.into(),
1377            destination_kind: LinkDestinationKind::Bare,
1378            title: None,
1379            children: children.into_iter().map(Into::into).collect(),
1380        }
1381    }
1382}
1383
1384impl CodeInline {
1385    /// An inline code span holding `value`.
1386    pub fn new(value: impl Into<String>) -> Self {
1387        Self {
1388            meta: NodeMeta::default(),
1389            value: value.into(),
1390        }
1391    }
1392}
1393
1394impl List {
1395    /// A tight, dash-delimited bullet list.
1396    pub fn new<I>(children: I) -> Self
1397    where
1398        I: IntoIterator<Item = ListItem>,
1399    {
1400        Self {
1401            meta: NodeMeta::default(),
1402            ordered: false,
1403            delimiter: ListDelimiter::Dash,
1404            tight: true,
1405            children: children.into_iter().collect(),
1406        }
1407    }
1408}
1409
1410impl ListItem {
1411    /// An item holding `children`, with no task marker and no number.
1412    pub fn new<I, T>(children: I) -> Self
1413    where
1414        I: IntoIterator<Item = T>,
1415        T: Into<Block>,
1416    {
1417        Self {
1418            meta: NodeMeta::default(),
1419            checked: None,
1420            number: None,
1421            children: children.into_iter().map(Into::into).collect(),
1422        }
1423    }
1424}
1425
1426impl BlankLine {
1427    /// A blank line.
1428    pub fn new() -> Self {
1429        Self::default()
1430    }
1431}