smartcalc/
config.rs

1/*
2 * smartcalc v1.0.8
3 * Copyright (c) Erhan BARIS (Ruslan Ognyanov Asenov)
4 * Licensed under the GNU General Public License v2.0.
5 */
6
7use core::borrow::Borrow;
8use alloc::format;
9use alloc::rc::Rc;
10use alloc::string::String;
11use alloc::string::ToString;
12use alloc::vec::Vec;
13use alloc::collections::btree_map::BTreeMap;
14use regex::Regex;
15use serde_json::from_str;
16use crate::session::Session;
17use crate::tokinizer::RuleItemList;
18use crate::tokinizer::RuleType;
19use crate::types::CurrencyInfo;
20use crate::types::TimeOffset;
21use crate::tokinizer::Tokinizer;
22use crate::tokinizer::TokenInfo;
23use crate::tokinizer::RULE_FUNCTIONS;
24use crate::constants::*;
25
26pub type LanguageData<T> = BTreeMap<String, T>;
27pub type CurrencyData<T> = BTreeMap<Rc<CurrencyInfo>, T>;
28
29#[derive(Default)]
30#[derive(Clone)]
31#[derive(Debug)]
32#[derive(PartialEq)]
33pub struct MoneyConfig {
34    pub remove_fract_if_zero: bool,
35    pub use_fract_rounding: bool
36}
37
38#[derive(Default)]
39#[derive(Clone)]
40#[derive(Debug)]
41#[derive(PartialEq)]
42pub struct NumberConfig {
43    pub decimal_digits: u8,
44    pub remove_fract_if_zero: bool,
45    pub use_fract_rounding: bool
46}
47
48#[derive(Default)]
49#[derive(Clone)]
50#[derive(Debug)]
51#[derive(PartialEq)]
52pub struct DynamicType {
53    pub group_name: String,
54    pub index: usize,
55    pub format: String,
56    pub parse: Vec<Vec<Rc<TokenInfo>>>,
57    pub upgrade_code: String,
58    pub downgrade_code: String,
59    pub names:Vec<String>,
60    pub decimal_digits: Option<u8>,
61    pub use_fract_rounding: Option<bool>,
62    pub remove_fract_if_zero: Option<bool>
63}
64
65impl DynamicType {
66    pub fn new(group_name: String, index: usize, format: String, parse: Vec<Vec<Rc<TokenInfo>>>, upgrade_code: String, downgrade_code: String, names:Vec<String>, decimal_digits: Option<u8>, use_fract_rounding: Option<bool>, remove_fract_if_zero: Option<bool>) -> Self {
67        DynamicType {
68            group_name,
69            index,
70            format,
71            parse,
72            upgrade_code,
73            downgrade_code,
74            names,
75            decimal_digits,
76            use_fract_rounding,
77            remove_fract_if_zero
78        }
79    }
80}
81
82pub struct SmartCalcConfig {
83    pub(crate) json_data: JsonConstant,
84    pub(crate) format: LanguageData<JsonFormat>,
85    pub(crate) currency: LanguageData<Rc<CurrencyInfo>>,
86    pub(crate) currency_alias: LanguageData<Rc<CurrencyInfo>>,
87    pub(crate) timezones: BTreeMap<String, i32>,
88    pub(crate) currency_rate: CurrencyData<f64>,
89    pub(crate) token_parse_regex: LanguageData<Vec<Regex>>,
90    pub(crate) word_group: LanguageData<BTreeMap<String, Vec<String>>>,
91    pub(crate) constant_pair: LanguageData<BTreeMap<String, ConstantType>>,
92    pub(crate) language_alias_regex: LanguageData<Vec<(Regex, String)>>,
93    pub(crate) alias_regex: Vec<(Regex, String)>,
94    pub(crate) rule: LanguageData<RuleItemList>,
95    pub(crate) types: BTreeMap<String, BTreeMap<usize, Rc<DynamicType>>>,
96    pub(crate) type_conversion: Vec<JsonTypeConversion>,
97    pub(crate) month_regex: LanguageData<MonthItemList>,
98    pub(crate) money_config: MoneyConfig,
99    pub(crate) number_config: NumberConfig,
100    pub(crate) percentage_config: NumberConfig,
101    pub(crate) decimal_seperator: String,
102    pub(crate) thousand_separator: String,
103    pub(crate) timezone: String,
104    pub(crate) timezone_offset: i32
105}
106
107impl Default for SmartCalcConfig {
108    fn default() -> Self {
109        SmartCalcConfig::load_from_json(JSON_DATA)
110    }
111}
112
113impl SmartCalcConfig {
114    pub fn get_time_offset(&self) -> TimeOffset {
115        TimeOffset {
116            name: self.timezone.to_string(),
117            offset: self.timezone_offset
118        }
119    }
120
121    pub fn get_currency<T: Borrow<String>>(&self, currency: T) -> Option<Rc<CurrencyInfo>> {
122        self.currency
123            .get(currency.borrow())
124            .cloned()
125    }
126
127    pub fn load_from_json(json_data: &str) -> Self {
128        let mut config = SmartCalcConfig {
129            json_data: match from_str(json_data) {
130                Ok(data) => data,
131                Err(error) => panic!("JSON parse error: {}", error)
132            },
133            format: LanguageData::new(),
134            currency: LanguageData::new(),
135            currency_alias: LanguageData::new(),
136            timezones: BTreeMap::new(),
137            currency_rate: CurrencyData::new(),
138            token_parse_regex: LanguageData::new(),
139            word_group: LanguageData::new(),
140            constant_pair: LanguageData::new(),
141            language_alias_regex: LanguageData::new(),
142            rule: LanguageData::new(),
143            types: BTreeMap::new(),
144            type_conversion: Vec::new(),
145            month_regex: LanguageData::new(),
146            alias_regex: Vec::new(),
147            decimal_seperator: ",".to_string(),
148            thousand_separator: ".".to_string(),
149            timezone: "UTC".to_string(),
150            timezone_offset: 0,
151            money_config: MoneyConfig {
152                remove_fract_if_zero: false,
153                use_fract_rounding: true
154            },
155            number_config: NumberConfig {
156                decimal_digits: 2,
157                remove_fract_if_zero: true,
158                use_fract_rounding: true
159            },
160            percentage_config: NumberConfig {
161                decimal_digits: 2,
162                remove_fract_if_zero: true,
163                use_fract_rounding: true
164            },
165        };
166        
167        for (name, currency) in config.json_data.currencies.iter() {
168            config.currency.insert(name.to_lowercase(), currency.clone());
169        }
170
171        for (timezone, offset) in config.json_data.timezones.iter() {
172            config.timezones.insert(timezone.clone(), *offset);
173        }
174
175        for (from, to) in config.json_data.alias.iter() {
176            match Regex::new(&format!(r"\b{}\b", from)) {
177                Ok(re) => config.alias_regex.push((re, to.to_string())),
178                Err(error) => log::error!("Alias parser error ({}) {}", from, error)
179            }
180        }
181
182        for (language, language_object) in config.json_data.languages.iter() {
183            let mut language_clone = language_object.format.clone();
184            language_clone.language = language.to_string();
185            config.format.insert(language.to_string(), language_clone);
186        }
187
188        for (key, value) in config.json_data.currency_alias.iter() {
189            match config.get_currency(value) {
190                Some(currency) => { config.currency_alias.insert(key.to_string(), currency.clone()); },
191                None => log::warn!("'{}' currency not found at alias", value)
192            };
193        }
194
195        for (key, value) in config.json_data.currency_rates.iter() {
196            match config.get_currency(key) {
197                Some(currency) => { config.currency_rate.insert(currency.clone(), *value); },
198                None => log::warn!("'{}' currency not found at rate", key)
199            };
200        }
201
202        for (language, language_constant) in config.json_data.languages.iter() {
203            let mut language_aliases = Vec::new();
204            for (alias, target_name) in language_constant.alias.iter() {
205                
206                match Regex::new(&format!(r"\b{}\b", alias)) {
207                    Ok(re) => language_aliases.push((re, target_name.to_string())),
208                    Err(error) => log::error!("Alias parser error ({}) {}", alias, error)
209                }
210
211            }
212
213            config.language_alias_regex.insert(language.to_string(), language_aliases);
214        }
215        
216        for (parse_type, items) in &config.json_data.parse {
217            let mut patterns = Vec::new();
218            for pattern in items {
219                match Regex::new(pattern) {
220                    Ok(re) => patterns.push(re),
221                    Err(error) => log::error!("Token parse regex error ({}) {}", pattern, error)
222                }
223            }
224
225            config.token_parse_regex.insert(parse_type.to_string(), patterns);
226        }
227
228        for (language, language_constant) in config.json_data.languages.iter() {
229            let mut language_group = Vec::new();
230            let mut month_list = Vec::with_capacity(12);
231            for i in 0..12 {
232                month_list.push(MonthInfo {
233                    short: String::new(),
234                    long: String::new(),
235                    month: i + 1
236                });
237            }
238
239            for (month_name, month_number) in &language_constant.long_months {
240                match month_list.get_mut((*month_number - 1) as usize) {
241                    Some(month_object) => month_object.long = month_name.to_string(),
242                    None => log::warn!("Month not fetched. {}", month_number)
243                };
244            }
245
246            for (month_name, month_number) in &language_constant.short_months {
247                match month_list.get_mut((*month_number - 1) as usize) {
248                    Some(month_object) => month_object.short = month_name.to_string(),
249                    None => log::warn!("Month not fetched. {}", month_number)
250                };
251            }
252
253            for month in month_list.iter() {
254                let pattern = &format!(r"\b{}\b|\b{}\b", month.long, month.short);
255                match Regex::new(pattern) {
256                    Ok(re) => language_group.push((re, month.clone())),
257                    Err(error) => log::error!("Month parser error ({}) {}", month.long, error)
258                }
259            }
260
261            config.month_regex.insert(language.to_string(), language_group);
262        }
263
264        for (language, language_constant) in config.json_data.languages.iter() {
265            let mut word_groups = BTreeMap::new();
266            for (word_group_name, word_group_items) in language_constant.word_group.iter() {
267                let mut patterns = Vec::new();
268
269                for pattern in word_group_items {
270                    patterns.push(pattern.to_string());
271                }
272
273                word_groups.insert(word_group_name.to_string(), patterns);
274            }
275
276            config.word_group.insert(language.to_string(), word_groups);
277        }
278
279        for (language, language_constant) in config.json_data.languages.iter() {
280            let mut constants = BTreeMap::new();
281            for (alias_name, constant_type) in language_constant.constant_pair.iter() {
282
283                match ConstantType::from_u8(*constant_type) {
284                    Some(const_type) => {
285                        constants.insert(alias_name.to_string(), const_type);
286                    },
287                    _ => log::error!("Constant type not parsed. {}", constant_type)
288                };
289            }
290
291            config.constant_pair.insert(language.to_string(), constants);
292        }
293        
294        for (language, language_constant) in config.json_data.languages.iter() {
295            let mut language_rules = Vec::new();
296            for (rule_name, rule) in language_constant.rules.iter() {
297                if let Some(function_ref) = RULE_FUNCTIONS.get(rule_name) {
298                    let mut function_items = Vec::new();
299
300                    for rule_item in &rule.rules {
301                        let mut session = Session::new();
302                        session.set_language(language.to_string());
303                        session.set_text(rule_item.to_string());
304                        function_items.push(Tokinizer::token_infos(&config, &session));
305                    }
306
307                    language_rules.push(RuleType::Internal {
308                        function_name: rule_name.to_string(),
309                        function: *function_ref,
310                        tokens_list: function_items
311                    });
312                }
313                else {
314                    log::warn!("Function not found : {}", rule_name);
315                }
316            }
317
318            config.rule.insert(language.to_string(), language_rules);
319        }
320        
321        for dynamic_type in config.json_data.types.iter() {
322            let mut dynamic_type_holder = BTreeMap::new();
323            
324            for type_item in dynamic_type.items.iter() {
325                
326                if type_item.upgrade_code.is_none() || type_item.downgrade_code.is_none() {
327                    log::warn!("Dynamic type {}:{} has missing calculation code. Please check upgrade_code and downgrade_code fields", dynamic_type.name, type_item.index);
328                    continue;
329                }
330                
331                let upgrade_code = type_item.upgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
332                let downgrade_code = type_item.downgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
333
334                let mut token_info = DynamicType {
335                    group_name: dynamic_type.name.to_string(),
336                    index: type_item.index,
337                    format: type_item.format.to_string(),
338                    parse: Vec::new(),
339                    upgrade_code,
340                    downgrade_code,
341                    names: type_item.names.clone(),
342                    decimal_digits: type_item.decimal_digits,
343                    use_fract_rounding: type_item.use_fract_rounding,
344                    remove_fract_if_zero: type_item.remove_fract_if_zero
345                };
346
347                for type_parse_item in type_item.parse.iter() {
348                    let mut session = Session::new();
349                    session.set_language("en".to_string());
350                    session.set_text(type_parse_item.to_string());
351                    
352                    let tokens = Tokinizer::token_infos(&config, &session);
353                    token_info.parse.push(tokens);
354                }
355                
356                dynamic_type_holder.insert(token_info.index, Rc::new(token_info));
357            }
358            
359            config.types.insert(dynamic_type.name.to_string(), dynamic_type_holder);
360        }
361        
362        for type_conversion in config.json_data.type_conversion.iter() {
363            let source = config.types.get(&type_conversion.source.name);
364            let target = config.types.get(&type_conversion.target.name);
365
366            let mut source_found = false;
367            let mut target_found = false;
368
369            if let Some(source) = source {
370                source_found = source.contains_key(&type_conversion.source.index)
371
372            }
373
374            if let Some(target) = target {
375                target_found = target.contains_key(&type_conversion.target.index)
376
377            }
378
379            if !source_found {
380                log::warn!("{} type not defined", type_conversion.source.name);
381            }
382
383            if !target_found {
384                log::warn!("{} type not defined", type_conversion.target.name);
385            }
386            
387            if source_found && target_found {
388                config.type_conversion.push(type_conversion.clone());
389            }
390        }
391
392        config
393    }
394}