use regex::Regex;
use std::fmt;
use std::sync::LazyLock;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash)]
pub enum CodeFenceStyle {
#[default]
Consistent,
Backtick,
Tilde,
}
impl fmt::Display for CodeFenceStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CodeFenceStyle::Backtick => write!(f, "backtick"),
CodeFenceStyle::Tilde => write!(f, "tilde"),
CodeFenceStyle::Consistent => write!(f, "consistent"),
}
}
}
pub fn get_code_fence_pattern() -> &'static Regex {
static CODE_FENCE_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(```|~~~)").unwrap());
&CODE_FENCE_PATTERN
}
pub fn get_fence_style(marker: &str) -> Option<CodeFenceStyle> {
match marker {
"```" => Some(CodeFenceStyle::Backtick),
"~~~" => Some(CodeFenceStyle::Tilde),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_fence_style_default() {
let style = CodeFenceStyle::default();
assert_eq!(style, CodeFenceStyle::Consistent);
}
#[test]
fn test_code_fence_style_equality() {
assert_eq!(CodeFenceStyle::Backtick, CodeFenceStyle::Backtick);
assert_eq!(CodeFenceStyle::Tilde, CodeFenceStyle::Tilde);
assert_eq!(CodeFenceStyle::Consistent, CodeFenceStyle::Consistent);
assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Tilde);
assert_ne!(CodeFenceStyle::Backtick, CodeFenceStyle::Consistent);
assert_ne!(CodeFenceStyle::Tilde, CodeFenceStyle::Consistent);
}
#[test]
fn test_code_fence_style_display() {
assert_eq!(format!("{}", CodeFenceStyle::Backtick), "backtick");
assert_eq!(format!("{}", CodeFenceStyle::Tilde), "tilde");
assert_eq!(format!("{}", CodeFenceStyle::Consistent), "consistent");
}
#[test]
fn test_code_fence_style_debug() {
assert_eq!(format!("{:?}", CodeFenceStyle::Backtick), "Backtick");
assert_eq!(format!("{:?}", CodeFenceStyle::Tilde), "Tilde");
assert_eq!(format!("{:?}", CodeFenceStyle::Consistent), "Consistent");
}
#[test]
fn test_code_fence_style_clone() {
let style1 = CodeFenceStyle::Backtick;
let style2 = style1;
assert_eq!(style1, style2);
}
#[test]
fn test_get_code_fence_pattern() {
let pattern = get_code_fence_pattern();
assert!(pattern.is_match("```"));
assert!(pattern.is_match("```rust"));
assert!(pattern.is_match("```\n"));
assert!(pattern.is_match("~~~"));
assert!(pattern.is_match("~~~python"));
assert!(pattern.is_match("~~~\n"));
assert!(!pattern.is_match(" ```")); assert!(!pattern.is_match("text```")); assert!(!pattern.is_match("``")); assert!(pattern.is_match("````"));
let captures = pattern.captures("```rust");
assert!(captures.is_some());
assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
let captures = pattern.captures("~~~yaml");
assert!(captures.is_some());
assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "~~~");
}
#[test]
fn test_get_fence_style() {
assert_eq!(get_fence_style("```"), Some(CodeFenceStyle::Backtick));
assert_eq!(get_fence_style("~~~"), Some(CodeFenceStyle::Tilde));
assert_eq!(get_fence_style("``"), None);
assert_eq!(get_fence_style("````"), None);
assert_eq!(get_fence_style("~~"), None);
assert_eq!(get_fence_style("~~~~"), None);
assert_eq!(get_fence_style(""), None);
assert_eq!(get_fence_style("random"), None);
assert_eq!(get_fence_style("```rust"), None); assert_eq!(get_fence_style("~~~yaml"), None); }
#[test]
fn test_pattern_singleton() {
let pattern1 = get_code_fence_pattern();
let pattern2 = get_code_fence_pattern();
assert_eq!(pattern1 as *const _, pattern2 as *const _);
}
#[test]
fn test_edge_cases() {
let pattern = get_code_fence_pattern();
assert!(!pattern.is_match(""));
assert!(pattern.is_match("```中文"));
assert!(pattern.is_match("~~~émoji🦀"));
assert!(pattern.is_match("```\t"));
assert!(pattern.is_match("~~~ "));
let captures = pattern.captures("```~~~");
assert!(captures.is_some());
assert_eq!(captures.unwrap().get(1).unwrap().as_str(), "```");
}
#[test]
fn test_code_fence_style_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(CodeFenceStyle::Backtick);
set.insert(CodeFenceStyle::Tilde);
set.insert(CodeFenceStyle::Consistent);
assert_eq!(set.len(), 3);
assert!(set.contains(&CodeFenceStyle::Backtick));
assert!(set.contains(&CodeFenceStyle::Tilde));
assert!(set.contains(&CodeFenceStyle::Consistent));
}
#[test]
fn test_pattern_usage_examples() {
let pattern = get_code_fence_pattern();
let test_cases = vec![
("```rust", true, "```"),
("```", true, "```"),
("~~~python", true, "~~~"),
("~~~", true, "~~~"),
("```json\n", true, "```"),
("~~~yaml\n", true, "~~~"),
(" ```", false, ""), ("Some text ```", false, ""), ];
for (input, should_match, expected_capture) in test_cases {
let is_match = pattern.is_match(input);
assert_eq!(is_match, should_match, "Failed for input: {input}");
if should_match {
let captures = pattern.captures(input).unwrap();
assert_eq!(
captures.get(1).unwrap().as_str(),
expected_capture,
"Failed capture for input: {input}"
);
}
}
}
}