use fancy_regex::Regex as FancyRegex;
use regex::Regex;
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct RegexCache {
cache: HashMap<String, Arc<Regex>>,
usage_stats: HashMap<String, u64>,
}
impl Default for RegexCache {
fn default() -> Self {
Self::new()
}
}
impl RegexCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
usage_stats: HashMap::new(),
}
}
pub fn get_regex(&mut self, pattern: &str) -> Result<Arc<Regex>, regex::Error> {
if let Some(regex) = self.cache.get(pattern) {
*self.usage_stats.entry(pattern.to_string()).or_insert(0) += 1;
return Ok(regex.clone());
}
let regex = Arc::new(Regex::new(pattern)?);
self.cache.insert(pattern.to_string(), regex.clone());
*self.usage_stats.entry(pattern.to_string()).or_insert(0) += 1;
Ok(regex)
}
pub fn get_stats(&self) -> HashMap<String, u64> {
self.usage_stats.clone()
}
pub fn clear(&mut self) {
self.cache.clear();
self.usage_stats.clear();
}
}
static GLOBAL_REGEX_CACHE: LazyLock<Arc<Mutex<RegexCache>>> = LazyLock::new(|| Arc::new(Mutex::new(RegexCache::new())));
pub fn get_cached_regex(pattern: &str) -> Result<Arc<Regex>, regex::Error> {
let mut cache = GLOBAL_REGEX_CACHE.lock().unwrap_or_else(|poisoned| {
let mut guard = poisoned.into_inner();
guard.clear();
guard
});
cache.get_regex(pattern)
}
pub fn get_cache_stats() -> HashMap<String, u64> {
match GLOBAL_REGEX_CACHE.lock() {
Ok(cache) => cache.get_stats(),
Err(_) => HashMap::new(),
}
}
#[macro_export]
macro_rules! regex_lazy {
($pattern:expr) => {{
static REGEX: LazyLock<regex::Regex> = LazyLock::new(|| regex::Regex::new($pattern).unwrap());
&*REGEX
}};
}
#[macro_export]
macro_rules! regex_cached {
($pattern:expr) => {{ $crate::utils::regex_cache::get_cached_regex($pattern).expect("Failed to compile regex") }};
}
pub use crate::regex_lazy;
pub const URL_STANDARD_STR: &str = concat!(
r#"(?:https?|ftps?|ftp)://"#, r#"(?:"#,
r#"\[[0-9a-fA-F:%.\-a-zA-Z]+\]"#, r#"|"#,
r#"[^\s<>\[\]()\\'\"`/]+"#, r#")"#,
r#"(?::\d+)?"#, r#"(?:/[^\s<>\[\]\\'\"`]*)?"#, r#"(?:\?[^\s<>\[\]\\'\"`]*)?"#, r#"(?:#[^\s<>\[\]\\'\"`]*)?"#, );
pub const URL_WWW_STR: &str = concat!(
r#"www\.(?:[a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}"#, r#"(?::\d+)?"#, r#"(?:/[^\s<>\[\]\\'\"`]*)?"#, r#"(?:\?[^\s<>\[\]\\'\"`]*)?"#, r#"(?:#[^\s<>\[\]\\'\"`]*)?"#, );
pub const URL_IPV6_STR: &str = concat!(
r#"(?:https?|ftps?|ftp)://"#,
r#"\[[0-9a-fA-F:%.\-a-zA-Z]+\]"#, r#"(?::\d+)?"#, r#"(?:/[^\s<>\[\]\\'\"`]*)?"#, r#"(?:\?[^\s<>\[\]\\'\"`]*)?"#, r#"(?:#[^\s<>\[\]\\'\"`]*)?"#, );
pub const XMPP_URI_STR: &str = r#"xmpp:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[^\s<>\[\]\\'\"`]*)?"#;
pub const URL_QUICK_CHECK_STR: &str = r#"(?:https?|ftps?|ftp|xmpp)://|xmpp:|@|www\."#;
pub const URL_SIMPLE_STR: &str = r#"(?:https?|ftps?|ftp)://[^\s<>]+[^\s<>.,]"#;
pub static URL_STANDARD_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(URL_STANDARD_STR).unwrap());
pub static URL_WWW_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(URL_WWW_STR).unwrap());
pub static URL_IPV6_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(URL_IPV6_STR).unwrap());
pub static URL_QUICK_CHECK_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(URL_QUICK_CHECK_STR).unwrap());
pub static URL_SIMPLE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(URL_SIMPLE_STR).unwrap());
pub static URL_PATTERN: LazyLock<Regex> = LazyLock::new(|| URL_SIMPLE_REGEX.clone());
pub static XMPP_URI_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(XMPP_URI_STR).unwrap());
pub static ATX_HEADING_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\s*)(#{1,6})(\s+|$)").unwrap());
pub static UNORDERED_LIST_MARKER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\s*)([*+-])(\s+)").unwrap());
pub static ORDERED_LIST_MARKER_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(\s*)(\d+)([.)])(\s+)").unwrap());
pub static ASTERISK_EMPHASIS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?:^|[^*])\*(\s+[^*]+\s*|\s*[^*]+\s+)\*(?:[^*]|$)").unwrap());
pub static UNDERSCORE_EMPHASIS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?:^|[^_])_(\s+[^_]+\s*|\s*[^_]+\s+)_(?:[^_]|$)").unwrap());
pub static DOUBLE_UNDERSCORE_EMPHASIS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?:^|[^_])__(\s+[^_]+\s*|\s*[^_]+\s+)__(?:[^_]|$)").unwrap());
pub static FENCED_CODE_BLOCK_START: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\s*)```(?:[^`\r\n]*)$").unwrap());
pub static FENCED_CODE_BLOCK_END: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\s*)```\s*$").unwrap());
pub static HTML_TAG_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"<([a-zA-Z][^>]*)>").unwrap());
pub static HTML_TAG_QUICK_CHECK: LazyLock<Regex> = LazyLock::new(|| Regex::new("(?i)</?[a-zA-Z]").unwrap());
pub static IMAGE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"!\[([^\]]*)\]\(([^)]+)\)").unwrap());
pub static BLOCKQUOTE_PREFIX_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\s*>+\s*)").unwrap());
pub fn is_blank_in_blockquote_context(line: &str) -> bool {
if line.trim().is_empty() {
return true;
}
if let Some(m) = BLOCKQUOTE_PREFIX_RE.find(line) {
let remainder = &line[m.end()..];
is_blank_in_blockquote_context(remainder)
} else {
false
}
}
pub static IMAGE_REF_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^!\[.*?\]\[.*?\]$").unwrap());
pub static LINK_REF_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"^\[.*?\]:\s*\S+(\s+["'(].*)?\s*$"#).unwrap());
pub static ABBREVIATION: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\b(?:Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|i\.e|e\.g|Inc|Corp|Ltd|Co|St|Ave|Blvd|Rd|Ph\.D|M\.D|B\.A|M\.A|Ph\.D|U\.S|U\.K|U\.N|N\.Y|L\.A|D\.C)\.\s+[A-Z]").unwrap()
});
pub static LIST_ITEM: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\s*\d+\.\s+").unwrap());
pub static EMAIL_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}").unwrap());
pub static REF_LINK_REGEX: LazyLock<FancyRegex> =
LazyLock::new(|| FancyRegex::new(r"(?<!\\)\[((?:[^\[\]\\]|\\.|\[[^\]]*\])*)\]\[([^\]]*)\]").unwrap());
pub static SHORTCUT_REF_REGEX: LazyLock<FancyRegex> =
LazyLock::new(|| FancyRegex::new(r"(?<![\\)\]])\[((?:[^\[\]\\]|\\.|\[[^\]]*\])*)\](?!\s*[\[\(])").unwrap());
pub static INLINE_LINK_FANCY_REGEX: LazyLock<FancyRegex> =
LazyLock::new(|| FancyRegex::new(r"(?<!\\)\[([^\]]+)\]\(([^)]+)\)").unwrap());
pub static INLINE_IMAGE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"!\[([^\]]*)\]\(([^)]+)\)").unwrap());
pub static LINKED_IMAGE_INLINE_INLINE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)").unwrap());
pub static LINKED_IMAGE_REF_INLINE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[!\[([^\]]*)\]\[([^\]]*)\]\]\(([^)]+)\)").unwrap());
pub static LINKED_IMAGE_INLINE_REF: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[!\[([^\]]*)\]\(([^)]+)\)\]\[([^\]]*)\]").unwrap());
pub static LINKED_IMAGE_REF_REF: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[!\[([^\]]*)\]\[([^\]]*)\]\]\[([^\]]*)\]").unwrap());
pub static REF_IMAGE_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"!\[((?:[^\[\]\\]|\\.|\[[^\]]*\])*)\]\[([^\]]*)\]").unwrap());
pub static FOOTNOTE_REF_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[\^([^\]]+)\]").unwrap());
pub static WIKI_LINK_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[\[([^\]]+)\]\]").unwrap());
pub static INLINE_MATH_REGEX: LazyLock<FancyRegex> =
LazyLock::new(|| FancyRegex::new(r"(?<!\$)\$(?!\$)([^\$]+)\$(?!\$)").unwrap());
pub static DISPLAY_MATH_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$\$([^\$]+)\$\$").unwrap());
pub static EMOJI_SHORTCODE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r":([a-zA-Z0-9_+-]+):").unwrap());
pub static HTML_TAG_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"</?[a-zA-Z][^>]*>|<[a-zA-Z][^>]*/\s*>").unwrap());
pub static HTML_ENTITY_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"&[a-zA-Z][a-zA-Z0-9]*;|&#\d+;|&#x[0-9a-fA-F]+;").unwrap());
pub static HUGO_SHORTCODE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{[<%][\s\S]*?[%>]\}\}").unwrap());
pub static HTML_COMMENT_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"<!--[\s\S]*?-->").unwrap());
pub static HTML_HEADING_PATTERN: LazyLock<FancyRegex> =
LazyLock::new(|| FancyRegex::new(r"^\s*<h([1-6])(?:\s[^>]*)?>.*</h\1>\s*$").unwrap());
pub fn escape_regex(s: &str) -> String {
let mut result = String::with_capacity(s.len() * 2);
for c in s.chars() {
if matches!(
c,
'.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\'
) {
result.push('\\');
}
result.push(c);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_regex_cache_new() {
let cache = RegexCache::new();
assert!(cache.cache.is_empty());
assert!(cache.usage_stats.is_empty());
}
#[test]
fn test_regex_cache_default() {
let cache = RegexCache::default();
assert!(cache.cache.is_empty());
assert!(cache.usage_stats.is_empty());
}
#[test]
fn test_get_regex_compilation() {
let mut cache = RegexCache::new();
let regex1 = cache.get_regex(r"\d+").unwrap();
assert_eq!(cache.cache.len(), 1);
assert_eq!(cache.usage_stats.get(r"\d+"), Some(&1));
let regex2 = cache.get_regex(r"\d+").unwrap();
assert_eq!(cache.cache.len(), 1);
assert_eq!(cache.usage_stats.get(r"\d+"), Some(&2));
assert!(Arc::ptr_eq(®ex1, ®ex2));
}
#[test]
fn test_get_regex_invalid_pattern() {
let mut cache = RegexCache::new();
let result = cache.get_regex(r"[unterminated");
assert!(result.is_err());
assert!(cache.cache.is_empty());
}
#[test]
fn test_get_stats() {
let mut cache = RegexCache::new();
let _ = cache.get_regex(r"\d+").unwrap();
let _ = cache.get_regex(r"\d+").unwrap();
let _ = cache.get_regex(r"\w+").unwrap();
let stats = cache.get_stats();
assert_eq!(stats.get(r"\d+"), Some(&2));
assert_eq!(stats.get(r"\w+"), Some(&1));
}
#[test]
fn test_clear_cache() {
let mut cache = RegexCache::new();
let _ = cache.get_regex(r"\d+").unwrap();
assert!(!cache.cache.is_empty());
assert!(!cache.usage_stats.is_empty());
cache.clear();
assert!(cache.cache.is_empty());
assert!(cache.usage_stats.is_empty());
}
#[test]
fn test_global_cache_functions() {
let regex1 = get_cached_regex(r"\d{3}").unwrap();
let regex2 = get_cached_regex(r"\d{3}").unwrap();
assert!(Arc::ptr_eq(®ex1, ®ex2));
let stats = get_cache_stats();
assert!(stats.contains_key(r"\d{3}"));
}
#[test]
fn test_regex_lazy_macro() {
let re = regex_lazy!(r"^test.*end$");
assert!(re.is_match("test something end"));
assert!(!re.is_match("test something"));
let re2 = regex_lazy!(r"^start.*finish$");
assert!(re2.is_match("start and finish"));
assert!(!re2.is_match("start without end"));
}
#[test]
fn test_escape_regex() {
assert_eq!(escape_regex("a.b"), "a\\.b");
assert_eq!(escape_regex("a+b*c"), "a\\+b\\*c");
assert_eq!(escape_regex("(test)"), "\\(test\\)");
assert_eq!(escape_regex("[a-z]"), "\\[a-z\\]");
assert_eq!(escape_regex("normal text"), "normal text");
assert_eq!(escape_regex(".$^{[(|)*+?\\"), "\\.\\$\\^\\{\\[\\(\\|\\)\\*\\+\\?\\\\");
assert_eq!(escape_regex(""), "");
assert_eq!(escape_regex("test.com/path?query=1"), "test\\.com/path\\?query=1");
}
#[test]
fn test_static_regex_patterns() {
assert!(URL_SIMPLE_REGEX.is_match("https://example.com"));
assert!(URL_SIMPLE_REGEX.is_match("http://test.org/path"));
assert!(URL_SIMPLE_REGEX.is_match("ftp://files.com"));
assert!(!URL_SIMPLE_REGEX.is_match("not a url"));
assert!(ATX_HEADING_REGEX.is_match("# Heading"));
assert!(ATX_HEADING_REGEX.is_match(" ## Indented"));
assert!(ATX_HEADING_REGEX.is_match("### "));
assert!(!ATX_HEADING_REGEX.is_match("Not a heading"));
assert!(UNORDERED_LIST_MARKER_REGEX.is_match("* Item"));
assert!(UNORDERED_LIST_MARKER_REGEX.is_match("- Item"));
assert!(UNORDERED_LIST_MARKER_REGEX.is_match("+ Item"));
assert!(ORDERED_LIST_MARKER_REGEX.is_match("1. Item"));
assert!(ORDERED_LIST_MARKER_REGEX.is_match("99. Item"));
assert!(HTML_TAG_REGEX.is_match("<div>"));
assert!(HTML_TAG_REGEX.is_match("<span class='test'>"));
assert!(BLOCKQUOTE_PREFIX_RE.is_match("> Quote"));
assert!(BLOCKQUOTE_PREFIX_RE.is_match(" > Indented quote"));
assert!(BLOCKQUOTE_PREFIX_RE.is_match(">> Nested"));
}
#[test]
fn test_thread_safety() {
use std::thread;
let handles: Vec<_> = (0..10)
.map(|i| {
thread::spawn(move || {
let pattern = format!(r"\d{{{i}}}");
let regex = get_cached_regex(&pattern).unwrap();
assert!(regex.is_match(&"1".repeat(i)));
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_url_standard_basic() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com"));
assert!(URL_STANDARD_REGEX.is_match("http://example.com"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path"));
assert!(URL_STANDARD_REGEX.is_match("ftp://files.example.com"));
assert!(URL_STANDARD_REGEX.is_match("ftps://secure.example.com"));
assert!(!URL_STANDARD_REGEX.is_match("not a url"));
assert!(!URL_STANDARD_REGEX.is_match("example.com"));
assert!(!URL_STANDARD_REGEX.is_match("www.example.com"));
}
#[test]
fn test_url_standard_with_path() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path/to/page"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path/to/page.html"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path/to/page/"));
}
#[test]
fn test_url_standard_with_query() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com?query=value"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path?query=value"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path?a=1&b=2"));
}
#[test]
fn test_url_standard_with_fragment() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com#section"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path#section"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com/path?query=value#section"));
}
#[test]
fn test_url_standard_with_port() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com:8080"));
assert!(URL_STANDARD_REGEX.is_match("https://example.com:443/path"));
assert!(URL_STANDARD_REGEX.is_match("http://localhost:3000"));
assert!(URL_STANDARD_REGEX.is_match("https://192.168.1.1:8080/path"));
}
#[test]
fn test_url_standard_wikipedia_style_parentheses() {
let url = "https://en.wikipedia.org/wiki/Rust_(programming_language)";
assert!(URL_STANDARD_REGEX.is_match(url));
let cap = URL_STANDARD_REGEX.find(url).unwrap();
assert_eq!(cap.as_str(), url);
let url2 = "https://example.com/path_(foo)_(bar)";
let cap2 = URL_STANDARD_REGEX.find(url2).unwrap();
assert_eq!(cap2.as_str(), url2);
}
#[test]
fn test_url_standard_ipv6() {
assert!(URL_STANDARD_REGEX.is_match("https://[::1]/path"));
assert!(URL_STANDARD_REGEX.is_match("https://[2001:db8::1]:8080/path"));
assert!(URL_STANDARD_REGEX.is_match("http://[fe80::1%eth0]/"));
}
#[test]
fn test_url_www_basic() {
assert!(URL_WWW_REGEX.is_match("www.example.com"));
assert!(URL_WWW_REGEX.is_match("www.example.co.uk"));
assert!(URL_WWW_REGEX.is_match("www.sub.example.com"));
assert!(!URL_WWW_REGEX.is_match("example.com"));
assert!(URL_WWW_REGEX.is_match("https://www.example.com"));
}
#[test]
fn test_url_www_with_path() {
assert!(URL_WWW_REGEX.is_match("www.example.com/path"));
assert!(URL_WWW_REGEX.is_match("www.example.com/path/to/page"));
assert!(URL_WWW_REGEX.is_match("www.example.com/path_(with_parens)"));
}
#[test]
fn test_url_ipv6_basic() {
assert!(URL_IPV6_REGEX.is_match("https://[::1]/"));
assert!(URL_IPV6_REGEX.is_match("http://[2001:db8::1]/path"));
assert!(URL_IPV6_REGEX.is_match("https://[fe80::1]:8080/path"));
assert!(URL_IPV6_REGEX.is_match("ftp://[::ffff:192.168.1.1]/file"));
}
#[test]
fn test_url_ipv6_with_zone_id() {
assert!(URL_IPV6_REGEX.is_match("https://[fe80::1%eth0]/path"));
assert!(URL_IPV6_REGEX.is_match("http://[fe80::1%25eth0]:8080/"));
}
#[test]
fn test_url_simple_detection() {
assert!(URL_SIMPLE_REGEX.is_match("https://example.com"));
assert!(URL_SIMPLE_REGEX.is_match("http://test.org/path"));
assert!(URL_SIMPLE_REGEX.is_match("ftp://files.com/file.zip"));
assert!(!URL_SIMPLE_REGEX.is_match("not a url"));
}
#[test]
fn test_url_quick_check() {
assert!(URL_QUICK_CHECK_REGEX.is_match("https://example.com"));
assert!(URL_QUICK_CHECK_REGEX.is_match("http://example.com"));
assert!(URL_QUICK_CHECK_REGEX.is_match("ftp://files.com"));
assert!(URL_QUICK_CHECK_REGEX.is_match("www.example.com"));
assert!(URL_QUICK_CHECK_REGEX.is_match("user@example.com"));
assert!(!URL_QUICK_CHECK_REGEX.is_match("just plain text"));
}
#[test]
fn test_url_edge_cases() {
let url = "https://example.com/path";
assert!(URL_STANDARD_REGEX.is_match(url));
let text = "Check https://example.com, it's great!";
let cap = URL_STANDARD_REGEX.find(text).unwrap();
assert!(cap.as_str().ends_with(','));
let text2 = "See <https://example.com> for more";
assert!(URL_STANDARD_REGEX.is_match(text2));
let cap2 = URL_STANDARD_REGEX.find(text2).unwrap();
assert!(!cap2.as_str().contains('>'));
}
#[test]
fn test_url_with_complex_paths() {
let urls = [
"https://github.com/owner/repo/blob/main/src/file.rs#L123",
"https://docs.example.com/api/v2/endpoint?format=json&page=1",
"https://cdn.example.com/assets/images/logo.png?v=2023",
"https://search.example.com/results?q=test+query&filter=all",
];
for url in urls {
assert!(URL_STANDARD_REGEX.is_match(url), "Should match: {url}");
}
}
#[test]
fn test_url_pattern_strings_are_valid() {
assert!(URL_STANDARD_REGEX.is_match("https://example.com"));
assert!(URL_WWW_REGEX.is_match("www.example.com"));
assert!(URL_IPV6_REGEX.is_match("https://[::1]/"));
assert!(URL_QUICK_CHECK_REGEX.is_match("https://example.com"));
assert!(URL_SIMPLE_REGEX.is_match("https://example.com"));
}
#[test]
fn test_is_blank_in_blockquote_context_regular_blanks() {
assert!(is_blank_in_blockquote_context(""));
assert!(is_blank_in_blockquote_context(" "));
assert!(is_blank_in_blockquote_context("\t"));
assert!(is_blank_in_blockquote_context(" \t "));
}
#[test]
fn test_is_blank_in_blockquote_context_blockquote_blanks() {
assert!(is_blank_in_blockquote_context(">"));
assert!(is_blank_in_blockquote_context("> "));
assert!(is_blank_in_blockquote_context("> "));
assert!(is_blank_in_blockquote_context(">>"));
assert!(is_blank_in_blockquote_context(">> "));
assert!(is_blank_in_blockquote_context(">>>"));
assert!(is_blank_in_blockquote_context(">>> "));
}
#[test]
fn test_is_blank_in_blockquote_context_spaced_nested() {
assert!(is_blank_in_blockquote_context("> > "));
assert!(is_blank_in_blockquote_context("> > > "));
assert!(is_blank_in_blockquote_context("> > "));
}
#[test]
fn test_is_blank_in_blockquote_context_with_leading_space() {
assert!(is_blank_in_blockquote_context(" >"));
assert!(is_blank_in_blockquote_context(" > "));
assert!(is_blank_in_blockquote_context(" >>"));
}
#[test]
fn test_is_blank_in_blockquote_context_not_blank() {
assert!(!is_blank_in_blockquote_context("text"));
assert!(!is_blank_in_blockquote_context("> text"));
assert!(!is_blank_in_blockquote_context(">> text"));
assert!(!is_blank_in_blockquote_context("> | table |"));
assert!(!is_blank_in_blockquote_context("| table |"));
assert!(!is_blank_in_blockquote_context("> # Heading"));
assert!(!is_blank_in_blockquote_context(">text")); }
#[test]
fn test_is_blank_in_blockquote_context_edge_cases() {
assert!(!is_blank_in_blockquote_context(">a")); assert!(!is_blank_in_blockquote_context("> a")); assert!(is_blank_in_blockquote_context("> ")); assert!(!is_blank_in_blockquote_context("> text")); }
}