rumdl_lib/rules/
code_fence_utils.rs1use regex::Regex;
2use std::fmt;
3use std::sync::LazyLock;
4
5#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash)]
7pub enum CodeFenceStyle {
8 #[default]
10 Consistent,
11 Backtick,
13 Tilde,
15}
16
17impl fmt::Display for CodeFenceStyle {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 CodeFenceStyle::Backtick => write!(f, "backtick"),
21 CodeFenceStyle::Tilde => write!(f, "tilde"),
22 CodeFenceStyle::Consistent => write!(f, "consistent"),
23 }
24 }
25}
26
27pub fn get_code_fence_pattern() -> &'static Regex {
29 static CODE_FENCE_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(```|~~~)").unwrap());
30 &CODE_FENCE_PATTERN
31}
32
33pub fn get_fence_style(marker: &str) -> Option<CodeFenceStyle> {
35 match marker {
36 "```" => Some(CodeFenceStyle::Backtick),
37 "~~~" => Some(CodeFenceStyle::Tilde),
38 _ => None,
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn test_code_fence_style_default() {
48 let style = CodeFenceStyle::default();
49 assert_eq!(style, CodeFenceStyle::Consistent);
50 }
51
52 #[test]
53 fn test_code_fence_style_equality() {
54 assert_eq!(CodeFenceStyle::Backtick, CodeFenceStyle::Backtick);
55 assert_eq!(CodeFenceStyle::Tilde, CodeFenceStyle::Tilde);
56 assert_eq!(CodeFenceStyle::Consistent, CodeFenceStyle::Consistent);
57
58 assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Tilde);
59 assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Consistent);
60 assert_ne!(CodeFenceStyle::Tilde, CodeFenceStyle::Consistent);
61 }
62
63 #[test]
64 fn test_code_fence_style_display() {
65 assert_eq!(format!("{}", CodeFenceStyle::Backtick), "backtick");
66 assert_eq!(format!("{}", CodeFenceStyle::Tilde), "tilde");
67 assert_eq!(format!("{}", CodeFenceStyle::Consistent), "consistent");
68 }
69
70 #[test]
71 fn test_code_fence_style_debug() {
72 assert_eq!(format!("{:?}", CodeFenceStyle::Backtick), "Backtick");
73 assert_eq!(format!("{:?}", CodeFenceStyle::Tilde), "Tilde");
74 assert_eq!(format!("{:?}", CodeFenceStyle::Consistent), "Consistent");
75 }
76
77 #[test]
78 fn test_code_fence_style_clone() {
79 let style1 = CodeFenceStyle::Backtick;
80 let style2 = style1;
81 assert_eq!(style1, style2);
82 }
83
84 #[test]
85 fn test_get_code_fence_pattern() {
86 let pattern = get_code_fence_pattern();
87
88 assert!(pattern.is_match("```"));
90 assert!(pattern.is_match("```rust"));
91 assert!(pattern.is_match("```\n"));
92
93 assert!(pattern.is_match("~~~"));
95 assert!(pattern.is_match("~~~python"));
96 assert!(pattern.is_match("~~~\n"));
97
98 assert!(!pattern.is_match(" ```")); assert!(!pattern.is_match("text```")); assert!(!pattern.is_match("``")); assert!(pattern.is_match("````")); let captures = pattern.captures("```rust");
106 assert!(captures.is_some());
107 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
108
109 let captures = pattern.captures("~~~yaml");
110 assert!(captures.is_some());
111 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "~~~");
112 }
113
114 #[test]
115 fn test_get_fence_style() {
116 assert_eq!(get_fence_style("```"), Some(CodeFenceStyle::Backtick));
118 assert_eq!(get_fence_style("~~~"), Some(CodeFenceStyle::Tilde));
119
120 assert_eq!(get_fence_style("``"), None);
122 assert_eq!(get_fence_style("````"), None);
123 assert_eq!(get_fence_style("~~"), None);
124 assert_eq!(get_fence_style("~~~~"), None);
125 assert_eq!(get_fence_style(""), None);
126 assert_eq!(get_fence_style("random"), None);
127 assert_eq!(get_fence_style("```rust"), None); assert_eq!(get_fence_style("~~~yaml"), None); }
130
131 #[test]
132 fn test_pattern_singleton() {
133 let pattern1 = get_code_fence_pattern();
135 let pattern2 = get_code_fence_pattern();
136
137 assert_eq!(pattern1 as *const _, pattern2 as *const _);
139 }
140
141 #[test]
142 fn test_edge_cases() {
143 let pattern = get_code_fence_pattern();
144
145 assert!(!pattern.is_match(""));
147
148 assert!(pattern.is_match("```中文"));
150 assert!(pattern.is_match("~~~émoji🦀"));
151
152 assert!(pattern.is_match("```\t"));
154 assert!(pattern.is_match("~~~ "));
155
156 let captures = pattern.captures("```~~~");
158 assert!(captures.is_some());
159 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
160 }
161
162 #[test]
163 fn test_code_fence_style_hash() {
164 use std::collections::HashSet;
165
166 let mut set = HashSet::new();
167 set.insert(CodeFenceStyle::Backtick);
168 set.insert(CodeFenceStyle::Tilde);
169 set.insert(CodeFenceStyle::Consistent);
170
171 assert_eq!(set.len(), 3);
172 assert!(set.contains(&CodeFenceStyle::Backtick));
173 assert!(set.contains(&CodeFenceStyle::Tilde));
174 assert!(set.contains(&CodeFenceStyle::Consistent));
175 }
176
177 #[test]
178 fn test_pattern_usage_examples() {
179 let pattern = get_code_fence_pattern();
180
181 let test_cases = vec![
183 ("```rust", true, "```"),
184 ("```", true, "```"),
185 ("~~~python", true, "~~~"),
186 ("~~~", true, "~~~"),
187 ("```json\n", true, "```"),
188 ("~~~yaml\n", true, "~~~"),
189 (" ```", false, ""), ("Some text ```", false, ""), ];
192
193 for (input, should_match, expected_capture) in test_cases {
194 let is_match = pattern.is_match(input);
195 assert_eq!(is_match, should_match, "Failed for input: {input}");
196
197 if should_match {
198 let captures = pattern.captures(input).unwrap();
199 assert_eq!(
200 captures.get(1).unwrap().as_str(),
201 expected_capture,
202 "Failed capture for input: {input}"
203 );
204 }
205 }
206 }
207}