use std::{fmt, ops, sync::LazyLock};
pub use regex::Regex;
#[doc(hidden)] #[non_exhaustive]
#[derive(Debug, Clone)]
pub enum PatternDisplay {
Exact(&'static str),
Regex(String),
Generic(String),
}
impl fmt::Display for PatternDisplay {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exact(s) => write!(formatter, "{s:?}"),
Self::Regex(regex) => write!(formatter, "Regex({})", RawStr(regex)),
Self::Generic(s) => formatter.write_str(s),
}
}
}
#[doc(hidden)] #[derive(Clone, Copy)]
pub struct RawStr<'a>(pub &'a str);
impl fmt::Debug for RawStr<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, formatter)
}
}
impl fmt::Display for RawStr<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let hash_count = self.hash_count();
write!(formatter, "r")?;
for _ in 0..hash_count {
write!(formatter, "#")?;
}
write!(formatter, "\"{}\"", self.0)?;
for _ in 0..hash_count {
write!(formatter, "#")?;
}
Ok(())
}
}
impl RawStr<'_> {
fn hash_count(self) -> usize {
let has_double_quotes = self.0.chars().any(|ch| ch == '"');
if has_double_quotes {
let mut max_hashes = 0;
let mut hash_start = None;
for (i, ch) in self.0.chars().enumerate() {
if ch == '#' {
if hash_start.is_none() {
hash_start = Some(i);
}
} else if let Some(hash_start) = hash_start.take() {
max_hashes = max_hashes.max(i - hash_start);
}
}
max_hashes + 1
} else {
0
}
}
}
pub trait Split: Send + Sync + 'static {
fn split_once<'s>(&self, haystack: &'s str) -> Option<(&'s str, &'s str)>;
fn split<'s>(&self, haystack: &'s str) -> impl Iterator<Item = &'s str>;
#[doc(hidden)]
fn display(&self) -> PatternDisplay;
}
impl<const N: usize> Split for [char; N] {
fn split_once<'s>(&self, haystack: &'s str) -> Option<(&'s str, &'s str)> {
haystack.split_once(self)
}
fn split<'s>(&self, haystack: &'s str) -> impl Iterator<Item = &'s str> {
haystack.split(self)
}
fn display(&self) -> PatternDisplay {
PatternDisplay::Generic(format!("{self:?}"))
}
}
impl Split for &'static str {
fn split_once<'s>(&self, haystack: &'s str) -> Option<(&'s str, &'s str)> {
haystack.split_once(self)
}
fn split<'s>(&self, haystack: &'s str) -> impl Iterator<Item = &'s str> {
haystack.split(self)
}
fn display(&self) -> PatternDisplay {
PatternDisplay::Exact(self)
}
}
pub struct LazyRegex<T = LazyLock<Regex>>(pub T);
#[macro_export]
macro_rules! lazy_regex {
($regex:tt) => {
$crate::pat::LazyRegex(::std::sync::LazyLock::new(|| {
$crate::pat::Regex::new($regex).unwrap()
}))
};
(ref $regex:tt) => {{
static __REGEX: $crate::pat::LazyRegex = $crate::pat::lazy_regex!($regex);
const { &__REGEX }
}};
}
pub use lazy_regex;
impl<T: fmt::Debug> fmt::Debug for LazyRegex<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, formatter)
}
}
impl<T> Split for &'static LazyRegex<T>
where
T: ops::Deref<Target = Regex> + Send + Sync,
{
fn split_once<'s>(&self, haystack: &'s str) -> Option<(&'s str, &'s str)> {
let mut it = self.0.splitn(haystack, 2);
let head = it.next()?;
let tail = it.next()?;
Some((head, tail))
}
fn split<'s>(&self, haystack: &'s str) -> impl Iterator<Item = &'s str> {
Regex::split(&self.0, haystack)
}
fn display(&self) -> PatternDisplay {
PatternDisplay::Regex(self.0.as_str().to_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_count_for_raw_strings_is_correct() {
let s = RawStr("Hello, world!");
assert_eq!(s.hash_count(), 0);
assert_eq!(s.to_string(), "r\"Hello, world!\"");
let s = RawStr("####");
assert_eq!(RawStr("####").hash_count(), 0);
assert_eq!(s.to_string(), "r\"####\"");
let s = RawStr(r#"x="1""#);
assert_eq!(s.hash_count(), 1);
assert_eq!(s.to_string(), "r#\"x=\"1\"\"#");
let s = RawStr(r##"x="#1""##);
assert_eq!(s.hash_count(), 2);
assert_eq!(s.to_string(), "r##\"x=\"#1\"\"##");
}
}