use lazy_static::*;
use pest::iterators::{Pair, Pairs};
use pest::Parser as PestParser;
use pest_derive::Parser;
use regex::Regex;
use crate::error::Error::*;
use crate::error::*;
use std::convert::AsRef;
use crate::address_list::*;
lazy_static! {
static ref CSV: Regex = Regex::new(
r#"[^",]*"[^"\\]*\\.[^"\\]+"[^,"]+@[^,"]+|[^",]*".*?"[^,"]*@[^,"]*|[^,"]+@[^,"]+"#,
)
.unwrap();
static ref SSV: Regex = Regex::new(r#"[^;"]?".*?"[^;"]*|[^;"]*"#).unwrap();
}
#[derive(Parser)]
#[grammar = "../grammars/permissive.pest"]
struct Parser;
fn parse_contact_pair(pair: Pair<'_, Rule>) -> Option<Result<Contact>> {
let mut c: EmailContact = Default::default();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::malformed | Rule::malformed_comment_name => c = c.set_name(inner.as_str()),
Rule::name => match inner.into_inner().next() {
Some(s) => c = c.set_name(s.as_str()),
None => return Some(Err(invalid_empty("name"))),
},
Rule::email | Rule::mailbox => c = c.set_email(inner.as_str()),
Rule::email_angle | Rule::mailbox_angle => match inner.into_inner().next() {
Some(s) => c = c.set_email(s.as_str()),
None => {
return Some(Err(invalid_empty("email_angle or mailbox_angle")));
}
},
Rule::comment => c = c.set_comment(inner.as_str()),
Rule::garbage => {
let garbage = inner.as_str();
if garbage.is_empty() {
return None;
}
return Some(Ok(GarbageContact::new(garbage).into()));
}
Rule::garbage_nongreedy => {
let garbage = inner.as_str().trim();
let new_email = format!("{}{}", c.email().unwrap(), garbage);
c = c.set_email(new_email);
}
_ => return Some(Err(invalid_nesting("contact"))),
}
}
Some(Ok(c.into()))
}
fn parse_pairs(pairs: Pairs<'_, Rule>) -> Result<AddressList> {
let mut contacts = Contacts::new();
for pair in pairs {
match pair.as_rule() {
Rule::group => {
let mut group: Group = Default::default();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::name => {
group.name = inner.into_inner().as_str().to_string();
}
Rule::contact_list => {
group.contacts = inner
.into_inner()
.filter_map(parse_contact_pair)
.collect::<Result<Contacts>>()?
}
_ => return Err(invalid_nesting("group")),
}
}
return Ok(AddressList::from(group));
}
Rule::address_list => return parse_pairs(pair.into_inner()),
Rule::contact_list => {
contacts = pair
.into_inner()
.filter_map(parse_contact_pair)
.collect::<Result<Contacts>>()?
}
_ => {
return Err(UnexpectedError(format!(
"{:?} can't be parsed with this function",
pair.as_rule(),
)));
}
}
}
Ok(AddressList::from(contacts))
}
fn check_empty<T>(address_list: &T) -> Result<&str>
where
T: AsRef<str>,
T: ?Sized,
{
let input = address_list.as_ref().trim();
match input {
"" => Err(Error::Empty),
_ => Ok(input),
}
}
pub fn parse_address_list<T>(address_list: &T) -> Result<AddressList>
where
T: AsRef<str>,
T: ?Sized,
{
let input = check_empty(address_list)?;
let mut output = parse_pairs(Parser::parse(Rule::address_list, input)?)?;
fn normalise(input: &str) -> String {
[",", "\"", "'", "<", ">"]
.iter()
.fold(input.to_string(), |o, p| o.replace(p, ""))
.replace(char::is_whitespace, "")
}
fn csv(input: &str) -> Vec<String> {
CSV.captures_iter(input)
.filter_map(|c| {
if let Some(c) = c.get(0) {
return Some(c.as_str().into());
}
None
})
.collect()
}
fn expand_undelimited(mut input: Vec<String>) -> Vec<String> {
let mut output = <Vec<String>>::new();
for (r, i) in (0..input.len()).enumerate() {
let j = &input[i - r];
if j.contains('>') {
for s in j.split('>') {
if s.contains('<') {
output.push(format!("{}>", s));
} else if s.is_empty() {
} else {
output.push(s.into());
}
}
input.remove(i - r);
} else {
output.push(input.remove(i - r));
}
}
output
}
fn add_absent_contacts(input: &[String], output: &mut AddressList) -> Result<()> {
for contact in input.iter().map(parse_contact) {
let contact = contact?;
if let Contact::Email(_) = contact {
if !output.contains(&contact) {
output.add(contact);
}
}
}
Ok(())
}
let input_n = normalise(input);
let output_n = normalise(&format!("{}", output));
if input_n.len() > output_n.len() {
let input_c = csv(input);
if let AddressList::Contacts(_) = output {
if input_n.contains(';') {
let sc_input = SSV.captures_iter(input).fold(String::from(""), |mut f, c| {
if let Some(cpt) = c.get(0) {
f.push_str(cpt.as_str());
f.push(',');
}
f
});
let mut sc_output = parse_pairs(Parser::parse(
Rule::address_list,
sc_input.trim_end_matches(','),
)?)?;
if sc_output.len() > output.len() && sc_output.len() > input_c.len() {
let sc_output_n = normalise(&format!("{}", sc_output));
if input_n.len() > sc_output_n.len() {
let sc_input_c_a = expand_undelimited(csv(&sc_input));
add_absent_contacts(&sc_input_c_a, &mut sc_output)?;
}
return Ok(sc_output);
}
}
}
let input_c_a = expand_undelimited(input_c);
if input_c_a.len() > output.len() {
add_absent_contacts(&input_c_a, &mut output)?;
}
}
Ok(output)
}
pub fn parse_contact<T>(contact: &T) -> Result<Contact>
where
T: AsRef<str>,
T: ?Sized,
{
let contact = check_empty(contact)?;
let mut pairs = Parser::parse(Rule::contact, contact)?;
if let Some(contact) = pairs.next() {
if let Some(c) = parse_contact_pair(contact) {
return c;
}
}
Err(Error::Empty)
}