use super::engine::composition::{GraphId, MatchGraph, MatchSentence, PosMatcher};
use crate::types::*;
use crate::utils::{self, regex::Regex};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Conversion {
Nop,
AllLower,
StartLower,
AllUpper,
StartUpper,
}
impl Conversion {
fn convert(&self, input: &str) -> String {
match &self {
Conversion::Nop => input.to_string(),
Conversion::AllLower => input.to_lowercase(),
Conversion::StartLower => utils::apply_to_first(input, |c| c.to_lowercase().collect()),
Conversion::AllUpper => input.to_uppercase(),
Conversion::StartUpper => utils::apply_to_first(input, |c| c.to_uppercase().collect()),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Example {
pub(crate) text: String,
pub(crate) suggestion: Option<Suggestion>,
}
impl Example {
pub fn text(&self) -> &str {
&self.text
}
pub fn suggestion(&self) -> Option<&Suggestion> {
self.suggestion.as_ref()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PosReplacer {
pub(crate) matcher: PosMatcher,
}
impl PosReplacer {
fn apply(&self, _text: &str, _sentence: &MatchSentence) -> Option<String> {
unimplemented!()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Match {
pub(crate) id: GraphId,
pub(crate) conversion: Conversion,
pub(crate) pos_replacer: Option<PosReplacer>,
pub(crate) regex_replacer: Option<(Regex, String)>,
}
impl Match {
fn apply(&self, sentence: &MatchSentence, graph: &MatchGraph) -> Option<String> {
let text = graph.by_id(self.id).text(sentence);
let mut text = if let Some(replacer) = &self.pos_replacer {
replacer.apply(text, sentence)?
} else {
text.to_string()
};
text = if let Some((regex, replacement)) = &self.regex_replacer {
regex.replace_all(&text, replacement)
} else {
text
};
Some(self.conversion.convert(&text))
}
fn has_conversion(&self) -> bool {
!matches!(self.conversion, Conversion::Nop)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum SynthesizerPart {
Text(String),
Match(Box<Match>),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Synthesizer {
pub(crate) use_titlecase_adjust: bool,
pub(crate) parts: Vec<SynthesizerPart>,
}
impl Synthesizer {
pub fn apply(
&self,
sentence: &MatchSentence,
graph: &MatchGraph,
start: GraphId,
_end: GraphId,
) -> Option<String> {
let mut output = Vec::new();
let starts_with_conversion = match &self.parts[..] {
[SynthesizerPart::Match(m), ..] => m.has_conversion(),
_ => false,
};
for part in &self.parts {
match part {
SynthesizerPart::Text(t) => output.push(t.clone()),
SynthesizerPart::Match(m) => {
output.push(m.apply(sentence, graph)?);
}
}
}
let suggestion = utils::normalize_whitespace(&output.join(""));
let make_uppercase = !starts_with_conversion
&& graph.groups()[graph.get_index(start)..]
.iter()
.find_map(|x| x.tokens(sentence).next())
.map(|first_token| {
(self.use_titlecase_adjust
&& first_token
.word()
.as_str()
.chars()
.next() .map_or(false, char::is_uppercase))
|| first_token.span().start() == sentence.span().start()
})
.unwrap_or(false);
if make_uppercase {
Some(utils::apply_to_first(&suggestion, |x| {
x.to_uppercase().collect()
}))
} else {
Some(suggestion)
}
}
}