Skip to main content

markdown_it/plugins/extra/
front_matter.rs

1use crate::parser::block::{BlockRule, BlockState};
2use crate::parser::extset::{MarkdownItExt, RootExt};
3use crate::{MarkdownIt, Node};
4
5/// Default maximum number of document lines searched for the closing delimiter.
6pub const DEFAULT_MAX_LINES: usize = 256;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum FrontMatterKind {
10    Yaml,
11    Toml,
12}
13
14#[derive(Debug, Clone)]
15pub struct FrontMatter {
16    pub kind: FrontMatterKind,
17    pub raw: String,
18    pub start_line: usize,
19    pub end_line: usize,
20}
21
22impl FrontMatter {
23    pub fn parse_with<T, E>(
24        &self,
25        parser: impl FnOnce(FrontMatterKind, &str) -> Result<T, E>,
26    ) -> Result<T, E> {
27        parser(self.kind, &self.raw)
28    }
29}
30
31impl RootExt for FrontMatter {}
32
33#[derive(Debug, Clone, Copy)]
34struct FrontMatterSettings {
35    max_lines: usize,
36}
37
38impl MarkdownItExt for FrontMatterSettings {}
39
40pub struct FrontMatterScanner;
41
42impl BlockRule for FrontMatterScanner {
43    fn run(state: &mut BlockState) -> Option<(Node, usize)> {
44        if state.line != 0 {
45            return None;
46        }
47        if state.line_indent(state.line) != 0 {
48            return None;
49        }
50
51        let opener = state.get_line(0).trim_end();
52        let (kind, closer) = match opener {
53            "---" => (FrontMatterKind::Yaml, "---"),
54            "+++" => (FrontMatterKind::Toml, "+++"),
55            _ => return None,
56        };
57
58        let max_lines = state
59            .md
60            .ext
61            .get::<FrontMatterSettings>()
62            .map(|settings| settings.max_lines)
63            .unwrap_or(DEFAULT_MAX_LINES);
64
65        let line_limit = state.line_max.min(max_lines);
66        let mut end_line = 1;
67        while end_line < line_limit {
68            if state.line_indent(end_line) == 0 && state.get_line(end_line).trim_end() == closer {
69                let (raw, _) = state.get_lines(1, end_line, 0, false);
70                state.root_ext.insert(FrontMatter {
71                    kind,
72                    raw,
73                    start_line: 0,
74                    end_line,
75                });
76                return Some((Node::default(), end_line + 1));
77            }
78
79            end_line += 1;
80        }
81
82        None
83    }
84}
85
86pub fn add(md: &mut MarkdownIt) {
87    add_with_max_lines(md, DEFAULT_MAX_LINES);
88}
89
90pub fn add_with_max_lines(md: &mut MarkdownIt, max_lines: usize) {
91    md.ext.insert(FrontMatterSettings { max_lines });
92    md.block.add_rule::<FrontMatterScanner>().before_all();
93}
94
95pub fn set_max_lines(md: &mut MarkdownIt, max_lines: usize) {
96    md.ext.insert(FrontMatterSettings { max_lines });
97}