macro_rules! define
{ ($({
ident: $name:ident,
desc: $desc:literal,
captures: [ $( $capture:ident ),* ],
format: $format:literal,
pattern: $pattern:literal,
tests: { $($test_value:literal => $expr:expr),* }
}),* $(,)?)
=>
{ paste::paste! {
use lazy_static::lazy_static;
use regex::{Regex, Captures};
use regex::{RegexSet};
$(
#[doc = "Static parser for " $name]
pub struct [<$name Parser>] {
re: Regex
}
impl [<$name Parser>] {
#[doc = "Create enw instance of " $name " parser"]
pub fn new(re: Regex) -> Self {
Self { re }
}
pub fn captures<'a>(&'a self, text: &'a str) -> Captures<'a> {
self.re.captures(text).unwrap()
}
pub fn format<'a>(&'a self, _captures: &'a Captures<'a>) -> Option<String> {
if $format.is_empty() {
return None
}
$(
#[allow(unused_variables)]
let $capture = &_captures[stringify!($capture)];
)*
Some(format!($format))
}
pub fn data<'a>(&'a self, _captures: &'a Captures<'a>) -> [<$name Data>] {
[<$name Data>] {
$($capture: _captures[stringify!($capture)].to_string()),*
}
}
}
#[doc = "Data representation of " $name]
pub struct [<$name Data>] {
$(#[doc = $capture:upper]
pub $capture: String
),*
}
)*
pub enum InnerParser {
$(
#[doc = "..."]
$name([<$name Parser>])
),*
}
impl InnerParser {
pub fn captures<'a>(&'a self, text: &'a str) -> Captures<'a> {
match self {
$(Self::$name(v) => v.captures(text),)*
}
}
pub fn format<'a>(&'a self, text: &'a str) -> Option<String> {
match self {
$(Self::$name(v) => v.format(&v.captures(text)),)*
}
}
}
pub struct Matcher {
inner: RegexSet,
}
impl Matcher {
pub fn get_parser_for(&self, text: &str) -> Option<&'static InnerParser> {
let matches = self.inner.matches(text);
if !matches.matched_any() {
#[cfg(feature = "with_tracing")]
tracing::warn!("No match for `{text}`");
return None;
}
if matches.len() > 1 {
let patterns = matches
.iter()
.map(|idx| self.inner.patterns().get(idx).unwrap());
#[cfg(feature = "with_tracing")]
tracing::error!(
"Multiple matches for {text}\n\nmatching patterns {:#?}",
patterns
);
};
for match_idx in matches.iter() {
if let Some(matched) = PARSERS.get(match_idx) {
return Some(matched);
}
}
None
}
}
lazy_static! {
pub static ref PARSERS: Vec<InnerParser> = vec![
$(InnerParser::$name([<$name Parser>]::new(Regex::new($pattern).unwrap() ))),*
];
pub static ref MATCHER: Matcher = {
let mut patterns = vec![];
$(patterns.push($pattern));*;
let inner = RegexSet::new(&patterns).unwrap();
Matcher {inner}
};
}
#[cfg(test)]
mod tests {
use regex::{Captures, Regex};
use lazy_static::lazy_static;
fn run_tests(captures: Captures, testfn: impl FnOnce(Captures)) {
testfn(captures)
}
$(
lazy_static! { static ref [<$name:snake:upper>]: Regex = Regex::new($pattern).unwrap(); }
#[test]
fn [<test_ $name:snake:lower>]() {
$(
let captures = match [<$name:snake:upper>].captures($test_value) {
Some(cp)=> cp,
None => {
panic!("\nNo capture groups in\n\n```\n{}\n```\n\npattern:\n\n```\n{}\n```\n\n", $test_value, $pattern);
}
};
run_tests(captures, $expr);
)*
}
)*
}
}};}
pub(crate) use define;