nmd_core/codex/modifier/
standard_paragraph_modifier.rs

1use std::collections::HashMap;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use super::{base_modifier::BaseModifier, constants::{build_strict_reserved_line_pattern, IDENTIFIER_PATTERN, MULTI_LINES_CONTENT_PATTERN, MULTI_LINES_CONTENT_EXCLUDING_HEADINGS_PATTERN, NEW_LINE_PATTERN, STYLE_PATTERN}, ModifierIdentifier, ModifierPattern, ModifiersBucket};
7
8
9pub const PARAGRAPH_SEPARATOR_START: &str = r"(?m:^[ \t]*\r?\n)+";
10pub const PARAGRAPH_SEPARATOR_END: &str = r"(?m:[ \t]*\r?\n){2}";
11
12
13static MODIFIER_PATTERNS_REGEX: Lazy<HashMap<ModifierIdentifier, Regex>> = Lazy::new(|| {
14    let mut regex: HashMap<ModifierIdentifier, Regex> = HashMap::new();
15
16    StandardParagraphModifier::ordered().into_iter().for_each(|m| {
17        regex.insert(m.identifier(), Regex::new(&m.modifier_pattern()).unwrap());
18    });
19
20    regex
21});
22
23
24
25#[derive(Debug, PartialEq, Clone)]
26pub enum StandardParagraphModifier {
27    List,
28    ListItem,
29    Table,
30    Image,
31    AbridgedImage,
32    MultiImage,
33    CodeBlock,
34    CommentBlock,
35    ExtendedBlockQuote,
36    ExtendedBlockQuoteLine,
37    FocusBlock,
38    MathBlock,
39    LineBreakDash,
40    LineBreakStar,
41    LineBreakPlus,
42    CommonParagraph,
43    EmbeddedParagraphStyle,
44    ParagraphIdentifier,
45    PageBreak,
46    Todo,
47    AbridgedTodo,
48    MultilineTodo,
49}
50
51impl StandardParagraphModifier {
52    pub fn ordered() -> Vec<Self> {
53
54        //! they must have the compatibility order
55        vec![
56            Self::CodeBlock,
57            Self::MathBlock,
58            Self::EmbeddedParagraphStyle,
59            Self::ParagraphIdentifier,
60            Self::Table,
61            Self::ExtendedBlockQuote,
62            Self::FocusBlock,
63            Self::List,
64            Self::MultilineTodo,
65            Self::Todo,
66            Self::AbridgedTodo,
67            Self::MultiImage,
68            Self::AbridgedImage,
69            Self::Image,
70            Self::PageBreak,
71            Self::LineBreakDash,
72            Self::LineBreakStar,
73            Self::LineBreakPlus,
74            Self::CommentBlock,
75        ]
76    }
77
78    pub fn identifier(&self) -> ModifierIdentifier {
79        match self {
80            Self::Image => String::from("image"),
81            Self::CommonParagraph => String::from("common-paragraph"),
82            Self::CodeBlock => String::from("code-block"),
83            Self::MathBlock => String::from("math-block"),
84            Self::ListItem => String::from("list-item"),
85            Self::List => String::from("list"),
86            Self::ExtendedBlockQuoteLine => String::from("extended-block-quote-line"),
87            Self::ExtendedBlockQuote => String::from("extended-block-quote"),
88            Self::LineBreakDash => String::from("line-break-dash"),
89            Self::LineBreakStar => String::from("line-break-star"),
90            Self::LineBreakPlus => String::from("line-break-plus"),
91            Self::FocusBlock => String::from("focus-block"),
92            Self::ParagraphIdentifier => String::from("paragraph-identifier"),
93            Self::EmbeddedParagraphStyle => String::from("embedded-paragraph-style"),
94            Self::PageBreak => String::from("page-break"),
95            Self::Todo => String::from("todo"),
96            Self::AbridgedTodo => String::from("abridged-todo"),
97            Self::MultilineTodo => String::from("multiline-todo"),
98            Self::AbridgedImage => String::from(r"abridged-image"),
99            Self::MultiImage => String::from("multi-image"),
100            Self::Table => String::from("table"),
101            Self::CommentBlock => String::from("comment-block"),
102        }
103    }
104
105    // Return the modifier pattern
106    pub fn modifier_pattern(&self) -> ModifierPattern {
107        match *self {
108            Self::Image => build_strict_reserved_line_pattern(&format!(r"!\[([^\]]*)\](?:{})?\(([^)]+)\)(?:\{{\{{{}\}}\}})?", IDENTIFIER_PATTERN, STYLE_PATTERN)),
109            Self::AbridgedImage => build_strict_reserved_line_pattern(&format!(r"!\[\((.*)\)\](?:{})?(?:\{{\{{{}\}}\}})?", IDENTIFIER_PATTERN, STYLE_PATTERN)),
110            Self::MultiImage => String::from(r"!!(?::([\w-]+):)?\[\[(?s:(.*?))\]\]"),
111            Self::CommonParagraph => String::from(r"([\s\S]+)"),
112            Self::CommentBlock => format!(r"<!--(?s:(.*?))-->"),
113            Self::CodeBlock => format!(r"{}{}{}{}", build_strict_reserved_line_pattern(r"```[ \t]*(\w+)?"), NEW_LINE_PATTERN, MULTI_LINES_CONTENT_PATTERN, build_strict_reserved_line_pattern("```")),
114            Self::MathBlock => format!(r"{}{}{}", build_strict_reserved_line_pattern(r"\$\$"), MULTI_LINES_CONTENT_PATTERN, build_strict_reserved_line_pattern(r"\$\$")),
115            Self::FocusBlock => format!(r"{}{}{}{}", build_strict_reserved_line_pattern(r":::[ \t]*(\w+)?"), NEW_LINE_PATTERN, MULTI_LINES_CONTENT_EXCLUDING_HEADINGS_PATTERN, build_strict_reserved_line_pattern(":::")),
116            Self::ListItem => format!(r#"(?m:^([\t ]*)(-\[\]|-\[ \]|-\[x\]|-\[X\]|-|->|\||\*|\+|--|\d[\.)]?|[a-zA-Z]{{1,8}}[\.)]|&[^;]+;) (.*){}?)"#, NEW_LINE_PATTERN),
117            Self::List => format!(r#"((?:{}+)+)"#, Self::ListItem.modifier_pattern()),
118            Self::ExtendedBlockQuoteLine => String::from(r"(?m:^> (.*))"),
119            Self::ExtendedBlockQuote => format!(r"(?m)(^[ \t]*>.*(?:\r?\n>.*)*)"),
120            Self::LineBreakDash => build_strict_reserved_line_pattern(r"-{3,}"),
121            Self::LineBreakStar => build_strict_reserved_line_pattern(r"\*{3,}"),
122            Self::LineBreakPlus => build_strict_reserved_line_pattern(r"\+{3,}"),
123            Self::ParagraphIdentifier => format!(r"\[\[(?sx:(.*?))\]\]{}?{}", NEW_LINE_PATTERN, IDENTIFIER_PATTERN),
124            Self::EmbeddedParagraphStyle => format!(r"\[\[(?sx:(.*?))\]\]{}?(?:{})?{}?\{{\{{{}\}}\}}", NEW_LINE_PATTERN, IDENTIFIER_PATTERN, NEW_LINE_PATTERN, STYLE_PATTERN),
125            Self::PageBreak => build_strict_reserved_line_pattern(r"#{3,}"),
126            Self::Todo => build_strict_reserved_line_pattern(r"(?i:TODO):\s(?:(.*?))"),
127            Self::AbridgedTodo => build_strict_reserved_line_pattern(r"(?i:TODO)"),
128            Self::MultilineTodo => format!("{}{}", build_strict_reserved_line_pattern(r"(?i:TODO):"), r"(?s:(.*?)):(?i:TODO)"),
129            Self::Table => format!(r"(\|(.*)\|{}?)+(?:\|(.*)\|)(?:{}?(?:\[(.*)\])?(?:{})?(?:\{{\{{{}\}}\}})?)?", NEW_LINE_PATTERN, NEW_LINE_PATTERN, IDENTIFIER_PATTERN, STYLE_PATTERN),
130        }
131    }
132
133    pub fn incompatible_modifiers(&self) -> ModifiersBucket {
134        match self {
135
136            Self::Image => ModifiersBucket::All,
137            Self::AbridgedImage => ModifiersBucket::All,
138            Self::MultiImage => ModifiersBucket::All,
139            Self::CodeBlock => ModifiersBucket::All,
140            Self::MathBlock => ModifiersBucket::All,
141            Self::CommentBlock => ModifiersBucket::All,
142
143            _ => ModifiersBucket::None
144        }
145    }
146
147    pub fn modifier_pattern_regex(&self) -> &Regex {
148        MODIFIER_PATTERNS_REGEX.get(&self.identifier()).unwrap()
149    }
150}
151
152impl Into<BaseModifier> for StandardParagraphModifier {
153    fn into(self) -> BaseModifier {
154        BaseModifier::new(self.modifier_pattern(), self.modifier_pattern_regex().clone(), self.incompatible_modifiers())
155    }
156}
157
158
159#[cfg(test)]
160mod test {
161    use regex::Regex;
162
163    use super::StandardParagraphModifier;
164
165    #[test]
166    #[cfg(not(windows))]
167    fn match_list() {
168        let regex = Regex::new(StandardParagraphModifier::List.modifier_pattern().as_str()).unwrap();
169
170        let list = concat!(
171            "\n",
172            "\n",
173            "- [Element 1](#element-1)",
174            "- [Element 2](#element-2)",
175            "\n",
176            "\n",
177        );
178
179        assert!(regex.is_match(list));
180
181    }
182
183}