1use crate::feature_cell::FeatureCell;
2use crate::Map;
3use arrayvec::ArrayString;
4use lazy_static::lazy_static;
5use std::collections::hash_map::Entry;
6use std::ops::Deref;
7
8lazy_static! {
9 pub(crate) static ref REPLACEMENTS: FeatureCell<Replacements> = FeatureCell::new(Replacements(
10 include_str!("replacements.csv")
11 .lines()
12 .filter(|line| !line.is_empty())
13 .map(|line| {
14 let comma = line.find(',').unwrap();
15 (
16 line[..comma].chars().next().unwrap(),
17 ArrayString::from(&line[comma + 1..]).unwrap(),
18 )
19 })
20 .collect()
21 ));
22}
23
24#[derive(Clone, Debug)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct Replacements(Map<char, ArrayString<12>>);
30
31impl Default for Replacements {
32 fn default() -> Self {
33 REPLACEMENTS.deref().deref().clone()
34 }
35}
36
37impl Replacements {
38 pub fn new() -> Self {
40 Self(Default::default())
41 }
42
43 #[cfg(feature = "customize")]
51 #[cfg_attr(doc, doc(cfg(feature = "customize")))]
52 pub unsafe fn customize_default() -> &'static mut Self {
53 REPLACEMENTS.get_mut()
54 }
55
56 pub(crate) fn get(&self, src: char) -> Option<&ArrayString<12>> {
57 self.0.get(&src)
58 }
59
60 pub fn insert(&mut self, src: char, dst: char) {
66 let replacements = self.0.entry(src).or_default();
67 if !replacements.contains(dst) {
68 replacements.push(dst);
69 }
70 }
71
72 pub fn remove(&mut self, src: char, dst: char) {
74 if let Entry::Occupied(mut occupied) = self.0.entry(src) {
75 let mut filtered = ArrayString::default();
76 for c in occupied.get().chars() {
77 if c != dst {
78 filtered.push(c);
79 }
80 }
81 if filtered.is_empty() {
82 occupied.remove();
83 } else {
84 occupied.insert(filtered);
85 }
86 }
87 }
88}