use std::collections::HashMap;
use std::convert::From;
use std::default::Default;
use std::fmt::{self, Display};
use std::str::FromStr;
use strum::{Display, EnumString, IntoStaticStr};
use crate::utils::get_with_fallback;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Display, EnumString, IntoStaticStr)]
#[strum(serialize_all = "kebab_case", ascii_case_insensitive)]
pub enum Variant {
Zh,
ZhHant,
ZhHans,
ZhTW,
ZhHK,
ZhMO,
ZhMY,
ZhSG,
ZhCN,
}
impl Variant {
#[inline(always)]
pub fn get_name(self) -> &'static str {
use Variant::*;
match self {
Zh => "原文", ZhHant => "繁体",
ZhHans => "简体",
ZhTW => "臺灣",
ZhHK => "香港",
ZhMO => "澳門",
ZhMY => "大马",
ZhSG => "新加坡",
ZhCN => "大陆", }
}
}
impl Default for Variant {
fn default() -> Self {
Variant::Zh
}
}
#[derive(Debug, Clone)]
pub struct VariantMap<T>(pub HashMap<Variant, T>);
impl VariantMap<String> {
#[inline(always)]
pub fn get_text(&self, target: Variant) -> Option<&str> {
self.0.get(&target).map(String::as_str)
}
pub fn get_text_with_fallback(&self, target: Variant) -> Option<&str> {
use Variant::*;
dbg!(target, &self);
match_fallback!(
self.0,
target,
Zh -> [ZhHans, ZhHant, ZhCN, ZhTW, ZhHK, ZhSG, ZhMO, ZhMY],
ZhHans -> [ ZhCN, ZhSG, ZhMY ],
ZhHant -> [ ZhTW, ZhHK, ZhMO ],
ZhCN -> [ ZhHans, ZhSG, ZhMY ],
ZhSG -> [ ZhHans, ZhCN, ZhMY ],
ZhMY -> [ ZhHans, ZhSG, ZhCN ],
ZhTW -> [ ZhHant, ZhHK, ZhMO ],
ZhHK -> [ ZhHant, ZhMO, ZhTW ],
ZhMO -> [ ZhHant, ZhHK, ZhTW ],
)
}
pub fn get_convs_by_target(&self, target: Variant) -> Vec<(&str, &str)> {
use Variant::*;
match target {
Zh | ZhHant | ZhHans => vec![],
_ => {
let to = match_fallback!(
self.0,
target,
ZhCN -> [ ZhHans, ZhSG, ZhMY ],
ZhSG -> [ ZhHans, ZhCN, ZhMY ],
ZhMY -> [ ZhHans, ZhSG, ZhCN ],
ZhTW -> [ ZhHant, ZhHK, ZhMO ],
ZhHK -> [ ZhHant, ZhMO, ZhTW ],
ZhMO -> [ ZhHant, ZhHK, ZhTW ],
);
if let Some(to) = to {
let mut pairs = vec![];
for (_variant, from) in self.0.iter() {
pairs.push((from.as_ref(), to));
}
pairs
} else {
vec![]
}
}
}
}
}
impl VariantMap<Vec<(String, String)>> {
pub fn get_convs_by_target(&self, target: Variant) -> &[(String, String)] {
self.0.get(&target).map(|p| p.as_slice()).unwrap_or(&[])
}
}
impl<T> VariantMap<T> {
pub fn into_inner(self) -> HashMap<Variant, T> {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty() }
}
impl FromStr for VariantMap<String> {
type Err = ();
fn from_str(s: &str) -> Result<VariantMap<String>, Self::Err> {
let s = s.trim();
let mut map = HashMap::new();
let mut parse_single = |s: &str| -> Result<(), Self::Err> {
let (v, t) = s.split_at(s.find(':').ok_or(())?);
let t = &t[1..]; map.insert(
Variant::from_str(v.trim()).map_err(|_| ())?,
t.trim().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..])?;
}
Ok(VariantMap(map))
}
}
impl Display for VariantMap<String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (v, t) in self.0.iter() {
write!(f, "{}:{};", v.get_name(), t)?;
}
Ok(())
}
}
impl Display for VariantMap<Vec<(String, String)>> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (variant, pairs) in self.0.iter() {
for (from, to) in pairs.iter() {
write!(f, "{}⇒{}: {}", from, variant, to)?;
}
}
Ok(())
}
}
impl<T> From<HashMap<Variant, T>> for VariantMap<T> {
fn from(hm: HashMap<Variant, T>) -> Self {
Self(hm)
}
}
macro_rules! match_fallback {
( $map:expr, $target:expr, $($t:tt)* ) => {
match_fallback!(@build $map, $target, (), $($t)*)
};
(@build $map:expr, $target:expr, ($($arms:tt)*), $variant:ident -> [ $($fallbacks:tt)* ], $($others:tt)* ) => {
match_fallback!(@build $map, $target, ($($arms)* $variant => get_with_fallback!($map, $variant, $($fallbacks)*),), $($others)*)
};
(@build $map:expr, $target:expr, ($($arms:tt)*) $(,)? ) => {
match $target {
$($arms)*
#[allow(unreachable_patterns)]
_ => None
}.map(String::as_str)
};
}
use match_fallback;