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