use std::collections::HashMap;
use std::convert::AsRef;
use std::fmt::{self, Display};
use std::iter::{self, Map};
use std::str::FromStr;
use regex::{Match, Matches};
use crate::utils::regex;
use crate::variant::{Variant, VariantMap};
#[derive(Debug, Clone)]
pub struct ConvRule {
pub(crate) action: Option<Action>,
pub(crate) output: Option<Output>,
pub(crate) conv: Option<Conv>,
pub(crate) set_title: bool,
}
#[derive(Debug, Clone)]
pub struct ConvRuleWithVariant<'r> {
rule: &'r ConvRule,
variant: Variant,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Output {
Normal,
VariantName(Variant),
Description,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Action {
Add,
Remove,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RuleError {
InvalidFlag(char),
InvalidConv,
InvalidConvForTitle,
InvalidVariant,
}
impl ConvRule {
pub fn targeted(&self, target: Variant) -> ConvRuleWithVariant<'_> {
ConvRuleWithVariant {
rule: self,
variant: target,
}
}
pub fn into_conv_action(self) -> Option<ConvAction> {
if let (Some(action), Some(conv)) = (self.action, self.conv) {
Some(ConvAction(action, conv))
} else {
None
}
}
pub fn from_str_infallible(s: &str) -> ConvRule {
s.parse().unwrap_or_else(|_| ConvRule {
action: None,
output: Some(Output::Normal),
conv: Some(Conv::Asis(s.to_owned())),
set_title: true,
})
}
}
impl FromStr for ConvRule {
type Err = RuleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (flags, body) = s.find('|').map_or_else(
|| ("", s),
|i| {
let (first, last) = s.split_at(i);
(first, &last[1..])
},
);
let mut set_title = false;
let mut action = None;
let mut output = Some(Output::Normal);
for flag in flags.chars() {
match flag {
'+' => action = Some(Action::Add),
'-' => {
action = {
output = None;
Some(Action::Remove)
}
}
'R' => {
return Ok(ConvRule {
action: None,
output: Some(Output::Normal),
conv: Some(Conv::Asis(body.to_owned())),
set_title: false,
});
}
'N' => {
output = Some(Output::VariantName(
Variant::from_str(body).map_err(|_| RuleError::InvalidVariant)?,
));
}
'D' => {
output = Some(Output::Description);
}
'H' => {
action = Some(Action::Add);
output = None;
}
'S' => {}
'A' => action = Some(Action::Add),
'T' => {
set_title = true;
output = None
}
unknown => return Err(RuleError::InvalidFlag(unknown)),
}
}
let conv = if let Some(Output::VariantName(_)) = output {
None
} else {
Some(Conv::from_str(body).map_err(|_| RuleError::InvalidConv)?)
};
if set_title
&& conv
.as_ref()
.and_then(|c| c.get_bid())
.map(|b| !b.is_empty())
!= Some(true)
{
return Err(RuleError::InvalidConvForTitle);
}
Ok(Self {
action,
output,
conv,
set_title,
})
}
}
impl Display for ConvRuleWithVariant<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let rule = self.rule;
match &rule.output {
None => Ok(()),
Some(Output::Normal) => write!(
f,
"{}",
rule.conv
.as_ref()
.and_then(|c| c.get_text_by_target(self.variant))
.unwrap_or("") ),
Some(Output::VariantName(variant)) => write!(f, "{}", variant.get_name()),
Some(Output::Description) => {
if let Some(conv) = rule.conv.as_ref() {
write!(f, "{}", conv)
} else {
Ok(())
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum Conv {
Asis(String),
Map(ConvMap),
}
#[derive(Debug, Clone)]
pub struct ConvMap {
pub bid: VariantMap<String>,
pub unid: VariantMap<Vec<(String, String)>>,
}
impl Conv {
#[inline]
pub fn get_text_by_target(&self, target: Variant) -> Option<&str> {
use Conv::*;
match self {
Asis(s) => Some(s),
Map(m) => m.bid.get_text_with_fallback(target),
}
}
pub fn get_conv_pairs(&self, target: Variant) -> impl Iterator<Item = (&str, &str)> {
let mut mit = self
.as_map()
.map(|m| {
m.bid.get_conv_pairs(target).chain(
m.unid
.get_conv_pairs(target)
.iter()
.map(|(f, t)| (f.as_ref(), t.as_ref())),
)
})
.into_iter()
.flatten()
.filter(|(f, _t)| !f.is_empty()); let mut maybe_asis = self.as_asis().map(|s| Some((s, s)));
iter::from_fn(move || {
if let Some(asis_yielding) = maybe_asis.as_mut() {
asis_yielding.take()
} else {
mit.next()
}
})
}
pub fn as_asis(&self) -> Option<&str> {
use Conv::*;
match self {
Asis(s) => Some(s.as_ref()),
_ => None,
}
}
pub fn as_map(&self) -> Option<&ConvMap> {
use Conv::*;
match self {
Map(m) => Some(m),
_ => None,
}
}
pub fn get_bid(&self) -> Option<&VariantMap<String>> {
self.as_map().map(|m| &m.bid)
}
pub fn from_str_infallible(s: &str) -> Conv {
s.parse().unwrap_or_else(|_| Conv::Asis(s.to_owned()))
}
}
impl Display for Conv {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use Conv::*;
match self {
Asis(s) => write!(fmt, "{}", s),
Map(m) => {
write!(fmt, "{}{}", m.bid, m.unid)
}
}
}
}
impl FromStr for Conv {
type Err = ();
#[allow(clippy::needless_as_bytes)]
fn from_str(s: &str) -> Result<Conv, Self::Err> {
let s = s.trim();
if s.is_empty() {
}
let mut bid = HashMap::new();
let mut unid = HashMap::new();
let mut parse_single = |s: &str| -> Result<(), Self::Err> {
if s.trim().is_empty() {
return Ok(());
};
let (left, right) = s.find("=>").map_or_else(
|| (None, s),
|i| {
assert!(0 < i && i < s.len());
let (first, last) = s.split_at(i);
(Some(first), &last[2..])
},
);
let (variant, to) = right.split_at(right.find(':').ok_or(())?);
let to = &to[1..]; let variant = variant.trim().parse::<Variant>().map_err(|_| ())?;
if let Some(from) = left {
if from.is_empty() {
return Err(()); }
unid.entry(variant)
.or_insert_with(Vec::new)
.push((from.to_owned(), to.to_owned()));
} else {
bid.insert(variant, to.to_owned());
}
Ok(())
};
let mut i = 0;
let mut ampersand = None; for (j, &c) in s.as_bytes().iter().enumerate() {
match c {
b'&' => {
ampersand = Some(j);
}
b';' => {
if !(ampersand.is_some() && j - ampersand.unwrap() > 1) {
parse_single(&s[i..j])?;
i = j + 1;
}
}
_ => {
if ampersand.is_some() & !(b'#' == c || char::from(c).is_ascii_alphanumeric()) {
ampersand = None;
}
}
}
}
if i != s.as_bytes().len() && parse_single(&s[i..]).is_err() {
if bid.is_empty() && unid.is_empty() {
return Ok(Conv::Asis(s.to_owned()));
} else {
return Err(());
}
}
Ok(Conv::Map(ConvMap {
bid: bid.into(),
unid: unid.into(),
}))
}
}
#[derive(Debug, Clone)]
pub struct ConvAction(Action, Conv);
impl ConvAction {
pub fn is_add(&self) -> bool {
self.0 == Action::Add
}
pub fn is_remove(&self) -> bool {
self.0 == Action::Remove
}
pub fn as_conv(&self) -> &Conv {
&self.1
}
}
impl AsRef<Conv> for ConvAction {
fn as_ref(&self) -> &Conv {
&self.1
}
}
pub fn extract_rules<'s>(
text: &'s str,
) -> Map<Matches<'static, 's>, impl FnMut(Match<'s>) -> Result<ConvRule, RuleError>> {
let pat_rule = regex!(r"-\{.+?\}-");
pat_rule.find_iter(text).map(|m| {
let rule = m.as_str();
ConvRule::from_str(&rule[2..rule.len() - 2])
})
}