use crate::error::DshApiResult;
use crate::parse::{parse_function1, parse_function2};
use crate::query_processor::Part::{Matching, NonMatching};
use crate::DshApiError;
use itertools::Itertools;
use regex::Regex;
use std::fmt::{Display, Formatter};
#[derive(Debug, PartialEq)]
pub enum Part {
Matching { part: String },
NonMatching { part: String },
}
#[derive(Debug, PartialEq)]
pub enum Match {
Expression { kind: String, first_parameter: String, second_parameter: Option<String> },
Parts { parts: Vec<Part> },
Simple,
}
pub trait QueryProcessor: Send + Sync {
fn describe(&self) -> String;
fn matching(&self, haystack: &str) -> Option<Match>;
fn matching_expression(&self, haystack: &str) -> Option<(String, String, Option<String>)>;
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>>;
fn matching_simple(&self, haystack: &str) -> bool;
}
#[derive(Clone, Debug, PartialEq)]
pub struct ExactMatchQueryProcessor {
exact_match: String,
}
impl ExactMatchQueryProcessor {
pub fn create<T: Into<String>>(exact_match: T) -> DshApiResult<Self> {
Ok(Self { exact_match: exact_match.into() })
}
pub fn new<T: Into<String>>(exact_match: T) -> Self {
Self { exact_match: exact_match.into() }
}
}
impl QueryProcessor for ExactMatchQueryProcessor {
fn describe(&self) -> String {
format!("match the string \"{}\"", self.exact_match)
}
fn matching(&self, haystack: &str) -> Option<Match> {
if self.exact_match == haystack {
Some(Match::Simple)
} else {
None
}
}
fn matching_expression(&self, _haystack: &str) -> Option<(String, String, Option<String>)> {
None
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
if self.exact_match == haystack {
Some(vec![Part::matching(haystack.to_string())])
} else {
None
}
}
fn matching_simple(&self, haystack: &str) -> bool {
self.exact_match == haystack
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StringQueryProcessor {
string: String,
match_substring: bool,
ignore_case: bool,
}
impl StringQueryProcessor {
pub fn create<T: Into<String>>(string: T, match_substring: bool, ignore_case: bool) -> DshApiResult<Self> {
Ok(Self { string: string.into(), match_substring, ignore_case })
}
pub fn new<T: Into<String>>(string: T, match_substring: bool, ignore_case: bool) -> Self {
Self { string: string.into(), match_substring, ignore_case }
}
}
impl QueryProcessor for StringQueryProcessor {
fn describe(&self) -> String {
if self.match_substring {
if self.ignore_case {
format!("match substring \"{}\", case-insensitive", self.string,)
} else {
format!("match substring \"{}\", case-sensitive", self.string,)
}
} else if self.ignore_case {
format!("match full string \"{}\", case-insensitive", self.string,)
} else {
format!("match full string \"{}\", case-sensitive", self.string,)
}
}
fn matching(&self, haystack: &str) -> Option<Match> {
self.matching_parts(haystack).map(Match::parts)
}
fn matching_expression(&self, _haystack: &str) -> Option<(String, String, Option<String>)> {
None
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
fn find_ignore_case(ignore_case: bool, haystack: &str, pattern: &str) -> Option<usize> {
if ignore_case {
haystack.to_lowercase().find(pattern.to_lowercase().as_str())
} else {
haystack.find(pattern)
}
}
fn strip_prefix_ignore_case<'a>(ignore_case: bool, haystack: &'a str, prefix: &str) -> Option<(&'a str, &'a str)> {
if ignore_case {
if haystack.to_lowercase().starts_with(prefix.to_lowercase().as_str()) {
Some((&haystack[0..prefix.len()], &haystack[prefix.len()..]))
} else {
None
}
} else if let Some(stripped_prefix) = haystack.strip_prefix(prefix) {
Some((&haystack[0..prefix.len()], stripped_prefix))
} else {
None
}
}
if self.match_substring {
let mut parts: Vec<Part> = vec![];
let mut leftover = haystack;
let mut match_found = false;
while !leftover.is_empty() {
match strip_prefix_ignore_case(self.ignore_case, leftover, &self.string) {
Some((prefix, rest)) => {
match_found = true;
parts.push(Part::matching(prefix.to_string()));
leftover = rest;
}
None => match find_ignore_case(self.ignore_case, leftover, &self.string) {
Some(index) => {
parts.push(Part::non_matching(leftover[0..index].to_string()));
leftover = &leftover[index..];
}
None => {
parts.push(Part::non_matching(leftover.to_string()));
leftover = "";
}
},
}
}
if match_found {
Some(parts)
} else {
None
}
} else if self.ignore_case {
if self.string.eq_ignore_ascii_case(haystack) {
Some(vec![Matching { part: haystack.to_string() }])
} else {
None
}
} else if self.string == haystack {
Some(vec![Matching { part: haystack.to_string() }])
} else {
None
}
}
fn matching_simple(&self, haystack: &str) -> bool {
self.matching_parts(haystack).is_some()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SubstringQueryProcessor {
substring: String,
}
impl SubstringQueryProcessor {
pub fn create<T: Into<String>>(substring: T) -> DshApiResult<Self> {
Ok(Self { substring: substring.into() })
}
pub fn new<T: Into<String>>(substring: T) -> Self {
Self { substring: substring.into() }
}
}
impl QueryProcessor for SubstringQueryProcessor {
fn describe(&self) -> String {
format!("match substring \"{}\"", self.substring)
}
fn matching(&self, haystack: &str) -> Option<Match> {
self.matching_parts(haystack).map(Match::parts)
}
fn matching_expression(&self, _haystack: &str) -> Option<(String, String, Option<String>)> {
None
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
let mut parts: Vec<Part> = vec![];
let mut leftover = haystack;
let mut match_found = false;
while !leftover.is_empty() {
match leftover.strip_prefix(&self.substring) {
Some(stripped) => {
match_found = true;
parts.push(Part::matching(self.substring.to_string()));
leftover = stripped;
}
None => match leftover.find(&self.substring) {
Some(index) => {
parts.push(Part::non_matching(leftover[0..index].to_string()));
leftover = &leftover[index..];
}
None => {
parts.push(Part::non_matching(leftover.to_string()));
leftover = "";
}
},
}
}
if match_found {
Some(parts)
} else {
None
}
}
fn matching_simple(&self, haystack: &str) -> bool {
self.matching_parts(haystack).is_some()
}
}
#[derive(Clone, Debug)]
pub struct RegexQueryProcessor {
regex: Regex,
}
impl RegexQueryProcessor {
pub fn new<T: Into<Regex>>(regex: Regex) -> Self {
Self { regex }
}
pub fn create<T: TryInto<Regex>>(pattern: T) -> DshApiResult<Self> {
match pattern.try_into() {
Ok(regex) => Ok(Self { regex }),
Err(_) => Err(DshApiError::configuration("illegal regular expression")),
}
}
}
impl QueryProcessor for RegexQueryProcessor {
fn describe(&self) -> String {
format!("match against regular expression \"{}\"", self.regex.as_str())
}
fn matching(&self, haystack: &str) -> Option<Match> {
self.matching_parts(haystack).map(Match::parts)
}
fn matching_expression(&self, _haystack: &str) -> Option<(String, String, Option<String>)> {
None
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
let mut parts: Vec<Part> = vec![];
let mut ptr: usize = 0;
let mut match_found = false;
for matching in self.regex.find_iter(haystack) {
if matching.start() > ptr {
parts.push(Part::non_matching(&haystack[ptr..matching.start()]))
}
match_found = true;
parts.push(Part::matching(matching.as_str()));
ptr = matching.end();
}
if haystack.len() > ptr {
parts.push(Part::non_matching(&haystack[ptr..haystack.len()]));
}
if match_found {
Some(parts)
} else {
None
}
}
fn matching_simple(&self, haystack: &str) -> bool {
self.matching_parts(haystack).is_some()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ExpressionQueryProcessor {
kind: String,
}
impl ExpressionQueryProcessor {
pub fn new<T: Into<String>>(kind: T) -> Self {
Self { kind: kind.into() }
}
pub fn create<T: Into<String>>(kind: T) -> DshApiResult<Self> {
Ok(Self { kind: kind.into() })
}
}
impl QueryProcessor for ExpressionQueryProcessor {
fn describe(&self) -> String {
format!("match against dsh expression {}()", self.kind)
}
fn matching(&self, haystack: &str) -> Option<Match> {
self
.matching_expression(haystack)
.map(|(name, first, second)| Match::expression(name, first, second))
}
fn matching_expression(&self, haystack: &str) -> Option<(String, String, Option<String>)> {
match parse_function2(haystack, &self.kind) {
Ok((parameter1, parameter2)) => Some((self.kind.to_string(), parameter1.to_string(), Some(parameter2.to_string()))),
Err(_) => match parse_function1(haystack, &self.kind) {
Ok(parameter) => Some((self.kind.to_string(), parameter.to_string(), None)),
Err(_) => None,
},
}
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
self.matching_expression(haystack).map(|(name, first, second)| match second {
Some(second_parameter) => vec![Part::matching(name), Part::matching(first), Part::matching(second_parameter)],
None => vec![Part::matching(name), Part::matching(first)],
})
}
fn matching_simple(&self, haystack: &str) -> bool {
self.matching_expression(haystack).is_some()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DummyQueryProcessor {}
impl DummyQueryProcessor {
pub fn create() -> DshApiResult<Self> {
Ok(Self {})
}
}
impl QueryProcessor for DummyQueryProcessor {
fn describe(&self) -> String {
"accept all input".to_string()
}
fn matching(&self, haystack: &str) -> Option<Match> {
self.matching_parts(haystack).map(Match::parts)
}
fn matching_expression(&self, _haystack: &str) -> Option<(String, String, Option<String>)> {
None
}
fn matching_parts(&self, haystack: &str) -> Option<Vec<Part>> {
Some(vec![Part::non_matching(haystack)])
}
fn matching_simple(&self, _haystack: &str) -> bool {
true
}
}
impl Part {
pub fn matching(matching_part: impl Into<String>) -> Part {
Matching { part: matching_part.into() }
}
pub fn non_matching(non_matching_part: impl Into<String>) -> Part {
NonMatching { part: non_matching_part.into() }
}
}
impl Display for Part {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Matching { part } => write!(f, "{}", part),
NonMatching { part } => write!(f, "{}", part),
}
}
}
impl Match {
pub fn expression<T, U, V>(kind: T, first_parameter: U, second_parameter: Option<V>) -> Self
where
T: Into<String>,
U: Into<String>,
V: Into<String>,
{
Match::Expression { kind: kind.into(), first_parameter: first_parameter.into(), second_parameter: second_parameter.map(|sp| sp.into()) }
}
pub fn parts(parts: Vec<Part>) -> Self {
Match::Parts { parts }
}
pub fn simple() -> Self {
Match::Simple
}
}
impl Display for Match {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Match::Expression { kind, first_parameter, second_parameter } => match second_parameter {
Some(second) => write!(f, "{{ {}('{}', '{}') }}", kind, first_parameter, second),
None => write!(f, "{{ {}('{}') }}", kind, first_parameter),
},
Match::Parts { parts } => write!(f, "{}", parts.iter().join("")),
Match::Simple => write!(f, "match"),
}
}
}