rumdl_lib/rules/
code_fence_utils.rs1use lazy_static::lazy_static;
2use regex::Regex;
3use std::fmt;
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 lazy_static! {
30 static ref CODE_FENCE_PATTERN: Regex = Regex::new(r"^(```|~~~)").unwrap();
31 }
32 &CODE_FENCE_PATTERN
33}
34
35pub fn get_fence_style(marker: &str) -> Option<CodeFenceStyle> {
37 match marker {
38 "```" => Some(CodeFenceStyle::Backtick),
39 "~~~" => Some(CodeFenceStyle::Tilde),
40 _ => None,
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn test_code_fence_style_default() {
50 let style = CodeFenceStyle::default();
51 assert_eq!(style, CodeFenceStyle::Consistent);
52 }
53
54 #[test]
55 fn test_code_fence_style_equality() {
56 assert_eq!(CodeFenceStyle::Backtick, CodeFenceStyle::Backtick);
57 assert_eq!(CodeFenceStyle::Tilde, CodeFenceStyle::Tilde);
58 assert_eq!(CodeFenceStyle::Consistent, CodeFenceStyle::Consistent);
59
60 assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Tilde);
61 assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Consistent);
62 assert_ne!(CodeFenceStyle::Tilde, CodeFenceStyle::Consistent);
63 }
64
65 #[test]
66 fn test_code_fence_style_display() {
67 assert_eq!(format!("{}", CodeFenceStyle::Backtick), "backtick");
68 assert_eq!(format!("{}", CodeFenceStyle::Tilde), "tilde");
69 assert_eq!(format!("{}", CodeFenceStyle::Consistent), "consistent");
70 }
71
72 #[test]
73 fn test_code_fence_style_debug() {
74 assert_eq!(format!("{:?}", CodeFenceStyle::Backtick), "Backtick");
75 assert_eq!(format!("{:?}", CodeFenceStyle::Tilde), "Tilde");
76 assert_eq!(format!("{:?}", CodeFenceStyle::Consistent), "Consistent");
77 }
78
79 #[test]
80 fn test_code_fence_style_clone() {
81 let style1 = CodeFenceStyle::Backtick;
82 let style2 = style1;
83 assert_eq!(style1, style2);
84 }
85
86 #[test]
87 fn test_get_code_fence_pattern() {
88 let pattern = get_code_fence_pattern();
89
90 assert!(pattern.is_match("```"));
92 assert!(pattern.is_match("```rust"));
93 assert!(pattern.is_match("```\n"));
94
95 assert!(pattern.is_match("~~~"));
97 assert!(pattern.is_match("~~~python"));
98 assert!(pattern.is_match("~~~\n"));
99
100 assert!(!pattern.is_match(" ```")); assert!(!pattern.is_match("text```")); assert!(!pattern.is_match("``")); assert!(pattern.is_match("````")); let captures = pattern.captures("```rust");
108 assert!(captures.is_some());
109 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
110
111 let captures = pattern.captures("~~~yaml");
112 assert!(captures.is_some());
113 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "~~~");
114 }
115
116 #[test]
117 fn test_get_fence_style() {
118 assert_eq!(get_fence_style("```"), Some(CodeFenceStyle::Backtick));
120 assert_eq!(get_fence_style("~~~"), Some(CodeFenceStyle::Tilde));
121
122 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("~~~~"), None);
127 assert_eq!(get_fence_style(""), None);
128 assert_eq!(get_fence_style("random"), None);
129 assert_eq!(get_fence_style("```rust"), None); assert_eq!(get_fence_style("~~~yaml"), None); }
132
133 #[test]
134 fn test_pattern_singleton() {
135 let pattern1 = get_code_fence_pattern();
137 let pattern2 = get_code_fence_pattern();
138
139 assert_eq!(pattern1 as *const _, pattern2 as *const _);
141 }
142
143 #[test]
144 fn test_edge_cases() {
145 let pattern = get_code_fence_pattern();
146
147 assert!(!pattern.is_match(""));
149
150 assert!(pattern.is_match("```中文"));
152 assert!(pattern.is_match("~~~émoji🦀"));
153
154 assert!(pattern.is_match("```\t"));
156 assert!(pattern.is_match("~~~ "));
157
158 let captures = pattern.captures("```~~~");
160 assert!(captures.is_some());
161 assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
162 }
163
164 #[test]
165 fn test_code_fence_style_hash() {
166 use std::collections::HashSet;
167
168 let mut set = HashSet::new();
169 set.insert(CodeFenceStyle::Backtick);
170 set.insert(CodeFenceStyle::Tilde);
171 set.insert(CodeFenceStyle::Consistent);
172
173 assert_eq!(set.len(), 3);
174 assert!(set.contains(&CodeFenceStyle::Backtick));
175 assert!(set.contains(&CodeFenceStyle::Tilde));
176 assert!(set.contains(&CodeFenceStyle::Consistent));
177 }
178
179 #[test]
180 fn test_pattern_usage_examples() {
181 let pattern = get_code_fence_pattern();
182
183 let test_cases = vec![
185 ("```rust", true, "```"),
186 ("```", true, "```"),
187 ("~~~python", true, "~~~"),
188 ("~~~", true, "~~~"),
189 ("```json\n", true, "```"),
190 ("~~~yaml\n", true, "~~~"),
191 (" ```", false, ""), ("Some text ```", false, ""), ];
194
195 for (input, should_match, expected_capture) in test_cases {
196 let is_match = pattern.is_match(input);
197 assert_eq!(is_match, should_match, "Failed for input: {input}");
198
199 if should_match {
200 let captures = pattern.captures(input).unwrap();
201 assert_eq!(
202 captures.get(1).unwrap().as_str(),
203 expected_capture,
204 "Failed capture for input: {input}"
205 );
206 }
207 }
208 }
209}