#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(clippy::nursery)]
#![deny(clippy::cargo)]
#![warn(missing_docs)]
#[macro_use]
extern crate log;
use linked_hash_map::LinkedHashMap;
use regex::Regex;
mod regex_wrapper;
use regex_wrapper::RegexWrapper;
use serde::{
Deserialize,
Serialize,
};
pub const EXPAND_SLANG_JSON :&str = include_str!("../data/expand/slang.json");
pub const EXPAND_SINGLE_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/single_contractions.json");
pub const EXPAND_DOUBLE_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/double_contractions.json");
pub const EXPAND_TRIPPLE_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/tripple_contractions.json");
pub const EXPAND_SINGLE_NO_APOSTROPHE_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/single_no_apostroph_contractions.json");
pub const EXPAND_DOUBLE_NO_APOSTROPHE_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/double_no_apostroph_contractions.json");
pub const EXPAND_PARTIAL_CONTRACTIONS_JSON :&str =
include_str!("../data/expand/partial_contractions.json");
pub const CONTRACTIONS_JSON_ORDER :&[&str] = &[
EXPAND_SLANG_JSON,
EXPAND_DOUBLE_NO_APOSTROPHE_CONTRACTIONS_JSON,
EXPAND_SINGLE_NO_APOSTROPHE_CONTRACTIONS_JSON,
EXPAND_TRIPPLE_CONTRACTIONS_JSON,
EXPAND_DOUBLE_CONTRACTIONS_JSON,
EXPAND_SINGLE_CONTRACTIONS_JSON,
];
#[derive(Debug, Serialize, Deserialize)]
struct Contraction {
#[serde(with = "serde_regex")]
find :Regex,
replace :LinkedHashMap<RegexWrapper, String>,
}
impl Contraction {
fn is_match(&self, text :&str) -> bool {
if self.find.is_match(text) {
debug!(
"Found match - Pattern: \"{}\" - Text: \"{}\"",
&self.find, &text
);
true
} else {
false
}
}
fn replace_all(&self, text :&mut String) {
debug!("Replace all - Pattern: \"{}\"", &self.find);
for (search, replace) in self.replace.iter() {
*text = search.0.replace_all(text, replace).into_owned();
}
}
}
#[derive(Debug)]
pub struct Contractions {
contractions :Vec<Contraction>,
}
impl Default for Contractions {
fn default() -> Self {
Self::from_json(CONTRACTIONS_JSON_ORDER).unwrap()
}
}
impl Contractions {
#[must_use]
pub const fn new() -> Self {
Self {
contractions :vec![],
}
}
pub fn from_json(contractions_as_str :&[&str]) -> Result<Self, Box<dyn std::error::Error>> {
let mut contractions = Self::new();
for s in contractions_as_str {
contractions.add_from_json(s)?;
}
Ok(contractions)
}
pub fn add_from_json(
&mut self,
contractions_as_str :&str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut contr_part :Vec<Contraction> = serde_json::from_str(contractions_as_str)?;
debug!("Added contractions from json.\n{:#?}\n", contr_part);
self.contractions.append(&mut contr_part);
Ok(())
}
pub fn remove(&mut self, key :&str) {
self.contractions.retain(|c| c.find.as_str() != key);
}
pub fn add(
&mut self,
find :&str,
replace :LinkedHashMap<&str, &str>,
) -> Result<(), Box<dyn std::error::Error>> {
let find = Regex::new(find)?;
let in_replace = replace;
let mut replace :LinkedHashMap<RegexWrapper, String> = LinkedHashMap::new();
for (f, r) in in_replace {
replace.insert(RegexWrapper(Regex::new(f)?), r.to_string());
}
let contraction = Contraction { find, replace };
self.contractions.push(contraction);
Ok(())
}
#[must_use]
pub fn apply(&self, input :&str) -> String {
let mut output = input.to_string();
for contraction in &self.contractions {
if contraction.is_match(&output) {
contraction.replace_all(&mut output);
}
}
output
}
}