pulldown_cmark_toc/
render.rs

1//! Render a limited set of Markdown events back to Markdown.
2
3use std::borrow::Borrow;
4use std::fmt;
5use std::fmt::Write;
6use std::ops::RangeInclusive;
7
8use pulldown_cmark::{Event, HeadingLevel, Tag, TagEnd};
9
10use crate::slug::{GitHubSlugifier, Slugify};
11
12/// Which symbol to use when rendering Markdown list items.
13pub enum ItemSymbol {
14    /// `-`
15    Hyphen,
16    /// `*`
17    Asterisk,
18}
19
20/// Configuration options to use when rendering the Table of Contents.
21///
22/// # Examples
23///
24/// ```
25/// # use pulldown_cmark_toc::{HeadingLevel, ItemSymbol, Options};
26///
27/// let options = Options::default()
28///     .item_symbol(ItemSymbol::Asterisk)
29///     .levels(HeadingLevel::H2..=HeadingLevel::H6)
30///     .indent(4);
31///
32/// ```
33pub struct Options {
34    pub(crate) item_symbol: ItemSymbol,
35    pub(crate) levels: RangeInclusive<HeadingLevel>,
36    pub(crate) indent: usize,
37    pub(crate) slugifier: Box<dyn Slugify>,
38}
39
40pub(crate) fn to_cmark<'a, I, E>(events: I) -> String
41where
42    I: Iterator<Item = E>,
43    E: Borrow<Event<'a>>,
44{
45    let mut buf = String::new();
46    for event in events {
47        let event = event.borrow();
48        match event {
49            Event::Start(Tag::Emphasis) | Event::End(TagEnd::Emphasis) => buf.push('*'),
50            Event::Start(Tag::Strong) | Event::End(TagEnd::Strong) => buf.push_str("**"),
51            Event::Text(s) => buf.push_str(s),
52            Event::Code(s) => {
53                buf.push('`');
54                buf.push_str(s);
55                buf.push('`');
56            }
57            Event::Html(s) => buf.push_str(s),
58            _ => {} // not yet implemented!
59        }
60    }
61    buf
62}
63
64impl fmt::Display for ItemSymbol {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::Hyphen => f.write_char('-'),
68            Self::Asterisk => f.write_char('*'),
69        }
70    }
71}
72
73impl Default for Options {
74    fn default() -> Self {
75        Self {
76            item_symbol: ItemSymbol::Hyphen,
77            levels: (HeadingLevel::H1..=HeadingLevel::H6),
78            indent: 2,
79            slugifier: Box::<GitHubSlugifier>::default(),
80        }
81    }
82}
83
84impl Options {
85    /// The symbol to use for Table of Contents list items.
86    #[must_use]
87    pub fn item_symbol(mut self, item_symbol: ItemSymbol) -> Self {
88        self.item_symbol = item_symbol;
89        self
90    }
91
92    /// Only levels in the given range will be rendered.
93    #[must_use]
94    pub fn levels(mut self, levels: RangeInclusive<HeadingLevel>) -> Self {
95        self.levels = levels;
96        self
97    }
98
99    /// The number of spaces to use for indentation between heading levels.
100    #[must_use]
101    pub fn indent(mut self, indent: usize) -> Self {
102        self.indent = indent;
103        self
104    }
105
106    /// The slugifier to use for the heading anchors.
107    #[must_use]
108    pub fn slugifier(mut self, slugifier: Box<dyn Slugify>) -> Self {
109        self.slugifier = slugifier;
110        self
111    }
112}