Skip to main content

rumdl_lib/utils/
mkdocs_critic.rs

1/// MkDocs Critic Markup detection.
2///
3/// Critic Markup is a PyMdown Extensions feature for tracking changes in
4/// documents using dedicated syntax for insertions, deletions, substitutions,
5/// highlights, and comments:
6///
7/// - `{++addition++}`
8/// - `{--deletion--}`
9/// - `{~~old~>new~~}`
10/// - `{==highlight==}`
11/// - `{>>comment<<}`
12///
13/// These patterns should be skipped from processing by most rules to avoid
14/// false positives.
15use regex::Regex;
16use std::sync::LazyLock;
17
18/// Pattern to match Critic Markup syntax. Simplified without lookahead/lookbehind
19/// for regex-crate compatibility.
20static CRITIC_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
21    Regex::new(
22        r"(?x)
23        \{                          # Opening brace
24        (?:
25            \+\+                    # Addition marker
26            [^}]*?                  # Content (non-greedy)
27            \+\+                    # Closing addition marker
28        |
29            --                      # Deletion marker
30            [^}]*?                  # Content (non-greedy)
31            --                      # Closing deletion marker
32        |
33            ~~                      # Substitution start
34            [^}]*?                  # Content including ~> (non-greedy)
35            ~~                      # Substitution end
36        |
37            ==                      # Highlight marker
38            [^}]*?                  # Content (non-greedy)
39            ==                      # Closing highlight marker
40        |
41            >>                      # Comment start
42            [^}]*?                  # Content (non-greedy)
43            <<                      # Comment end
44        )
45        \}                          # Closing brace
46        ",
47    )
48    .unwrap()
49});
50
51/// Fast pre-filter that avoids the full regex when the line can't contain
52/// Critic Markup.
53static CRITIC_QUICK_CHECK: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{(?:\+\+|--|~~|==|>>)").unwrap());
54
55/// Check if a line contains Critic Markup.
56pub fn contains_critic_markup(line: &str) -> bool {
57    if !CRITIC_QUICK_CHECK.is_match(line) {
58        return false;
59    }
60    CRITIC_PATTERN.is_match(line)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_critic_addition() {
69        assert!(contains_critic_markup("{++add this++}"));
70        assert!(contains_critic_markup("Text {++inserted here++} more text"));
71    }
72
73    #[test]
74    fn test_critic_deletion() {
75        assert!(contains_critic_markup("{--remove this--}"));
76        assert!(contains_critic_markup("Text {--deleted--} more"));
77    }
78
79    #[test]
80    fn test_critic_substitution() {
81        assert!(contains_critic_markup("{~~old~>new~~}"));
82        assert!(contains_critic_markup("Replace {~~this~>with that~~} text"));
83    }
84
85    #[test]
86    fn test_critic_highlight() {
87        assert!(contains_critic_markup("{==highlight me==}"));
88        assert!(contains_critic_markup("Important {==text==} here"));
89    }
90
91    #[test]
92    fn test_critic_comment() {
93        assert!(contains_critic_markup("{>>This is a comment<<}"));
94        assert!(contains_critic_markup("{==text==}{>>comment about it<<}"));
95    }
96
97    #[test]
98    fn test_multiline_critic() {
99        let content = "Here is {++some\ntext that\nspans lines++} ok";
100        assert!(contains_critic_markup(content));
101    }
102
103    #[test]
104    fn test_not_critic() {
105        assert!(!contains_critic_markup("Normal {text} here"));
106        assert!(!contains_critic_markup("Just ++ symbols"));
107        assert!(!contains_critic_markup("{+ incomplete +}"));
108        assert!(!contains_critic_markup("{{ template }}"));
109    }
110}