pub mod action;
use self::action::{Action, Extract, Find, Replace};
use super::transform::field::TransformField;
use crate::{
action::{
filter::Filter,
transform::{field::Field, result::TransformResult},
},
entry::Entry,
error::transform::RegexError,
};
use ExtractionResult::{Extracted, Matched, NotMatched};
use std::{borrow::Cow, convert::Infallible};
#[allow(missing_docs)]
#[derive(Debug)]
pub struct Regex<A> {
pub re: regex::Regex,
pub action: A,
}
impl<A: Action> Regex<A> {
pub fn new(re: &str, action: A) -> Result<Self, RegexError> {
Ok(Self {
re: regex::Regex::new(re)?,
action,
})
}
}
impl Regex<Extract> {
#[must_use]
pub fn extract<'a>(&self, text: &'a str) -> Option<&'a str> {
match find(&self.re, text) {
Extracted(s) => Some(s),
Matched | NotMatched => None,
}
}
}
impl TransformField for Regex<Extract> {
type Error = RegexError;
fn transform_field(&self, field: Option<&str>) -> Result<TransformResult<String>, RegexError> {
let field = match field {
Some(v) => v,
None => return Ok(TransformResult::Old(None)),
};
let transformed = match self.extract(field) {
Some(s) => s,
None if self.action.passthrough_if_not_found => field,
None => return Err(RegexError::CaptureGroupMissing),
};
Ok(TransformResult::New(Some(transformed.to_owned())))
}
}
impl Filter for Regex<Find> {
fn filter(&self, entries: &mut Vec<Entry>) {
entries.retain(|ent| {
let s = match self.action.in_field {
Field::Title => ent.msg.title.as_deref().map(Cow::Borrowed),
Field::Body => ent.msg.body.as_deref().map(Cow::Borrowed),
Field::Link => ent.msg.link.as_ref().map(|s| Cow::Owned(s.to_string())),
};
match s {
None => false,
Some(s) => match find(&self.re, &s) {
Matched | Extracted(_) => true,
NotMatched => false,
},
}
});
}
}
impl Regex<Replace> {
#[must_use]
pub fn replace<'a>(&self, text: &'a str) -> Cow<'a, str> {
self.re.replace(text, &self.action.with)
}
}
impl TransformField for Regex<Replace> {
type Error = Infallible;
fn transform_field(&self, field: Option<&str>) -> Result<TransformResult<String>, Self::Error> {
Ok(TransformResult::New(
field.map(|field| self.replace(field).into_owned()),
))
}
}
#[derive(Debug)]
pub(crate) enum ExtractionResult<'a> {
NotMatched,
Matched,
Extracted(&'a str),
}
pub(crate) fn find<'a>(re: ®ex::Regex, text: &'a str) -> ExtractionResult<'a> {
match re.captures(text) {
Some(capture_groups) => match capture_groups.name("s") {
Some(s) => ExtractionResult::Extracted(s.as_str()),
None => ExtractionResult::Matched,
},
None => ExtractionResult::NotMatched,
}
}
#[allow(clippy::unwrap_used)]
#[allow(unused)]
#[cfg(test)]
mod tests {
use super::action::*;
use super::*;
use assert_matches::assert_matches;
}