rumdl_lib/rules/
emphasis_style.rs

1use regex::Regex;
2use std::fmt;
3use std::sync::LazyLock;
4
5/// The style for emphasis (MD049)
6#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash)]
7pub enum EmphasisStyle {
8    /// Consistent with the most prevalent emphasis style found (or first found if tied)
9    #[default]
10    Consistent,
11    /// Asterisk style (*)
12    Asterisk,
13    /// Underscore style (_)
14    Underscore,
15}
16
17impl fmt::Display for EmphasisStyle {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            EmphasisStyle::Asterisk => write!(f, "asterisk"),
21            EmphasisStyle::Underscore => write!(f, "underscore"),
22            EmphasisStyle::Consistent => write!(f, "consistent"),
23        }
24    }
25}
26
27impl From<&str> for EmphasisStyle {
28    fn from(s: &str) -> Self {
29        match s {
30            "asterisk" => EmphasisStyle::Asterisk,
31            "underscore" => EmphasisStyle::Underscore,
32            _ => EmphasisStyle::Consistent,
33        }
34    }
35}
36
37/// Get regex pattern for finding emphasis markers based on style
38pub fn get_emphasis_pattern(style: EmphasisStyle) -> &'static Regex {
39    static ASTERISK_EMPHASIS: LazyLock<Regex> =
40        LazyLock::new(|| Regex::new(r"\*([^\s*][^*]*?[^\s*]|[^\s*])\*").unwrap());
41    static UNDERSCORE_EMPHASIS: LazyLock<Regex> =
42        LazyLock::new(|| Regex::new(r"_([^\s_][^_]*?[^\s_]|[^\s_])_").unwrap());
43
44    match style {
45        EmphasisStyle::Asterisk => &ASTERISK_EMPHASIS,
46        EmphasisStyle::Underscore => &UNDERSCORE_EMPHASIS,
47        EmphasisStyle::Consistent => &ASTERISK_EMPHASIS, // Default to asterisk for consistent style
48    }
49}
50
51/// Determine the emphasis style from a marker
52pub fn get_emphasis_style(marker: &str) -> Option<EmphasisStyle> {
53    match marker {
54        "*" => Some(EmphasisStyle::Asterisk),
55        "_" => Some(EmphasisStyle::Underscore),
56        _ => None,
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_emphasis_style_from_str() {
66        assert_eq!(EmphasisStyle::from("asterisk"), EmphasisStyle::Asterisk);
67        assert_eq!(EmphasisStyle::from("underscore"), EmphasisStyle::Underscore);
68        assert_eq!(EmphasisStyle::from(""), EmphasisStyle::Consistent);
69        assert_eq!(EmphasisStyle::from("unknown"), EmphasisStyle::Consistent);
70    }
71
72    #[test]
73    fn test_get_emphasis_pattern() {
74        // Test asterisk pattern
75        let asterisk_pattern = get_emphasis_pattern(EmphasisStyle::Asterisk);
76        assert!(asterisk_pattern.is_match("*text*"));
77        assert!(asterisk_pattern.is_match("*t*"));
78        assert!(!asterisk_pattern.is_match("* text*"));
79        assert!(!asterisk_pattern.is_match("*text *"));
80
81        // Test underscore pattern
82        let underscore_pattern = get_emphasis_pattern(EmphasisStyle::Underscore);
83        assert!(underscore_pattern.is_match("_text_"));
84        assert!(underscore_pattern.is_match("_t_"));
85        assert!(!underscore_pattern.is_match("_ text_"));
86        assert!(!underscore_pattern.is_match("_text _"));
87
88        // Test consistent pattern (default to asterisk)
89        let consistent_pattern = get_emphasis_pattern(EmphasisStyle::Consistent);
90        assert!(consistent_pattern.is_match("*text*"));
91        assert!(!consistent_pattern.is_match("_text_"));
92    }
93
94    #[test]
95    fn test_get_emphasis_style() {
96        assert_eq!(get_emphasis_style("*"), Some(EmphasisStyle::Asterisk));
97        assert_eq!(get_emphasis_style("_"), Some(EmphasisStyle::Underscore));
98        assert_eq!(get_emphasis_style(""), None);
99        assert_eq!(get_emphasis_style("**"), None);
100        assert_eq!(get_emphasis_style("__"), None);
101    }
102}