1use std::cell::RefCell;
2use std::collections::HashSet;
3use std::fmt;
4use std::fmt::{Display, Formatter};
5use std::hash::{Hash, Hasher};
6
7use super::super::parser::{GrammarParser, Rule};
8use crate::models::{FromDirective, HasAliases, HasName, Origin};
9use pest::Parser;
10use std::cmp::Ordering;
11#[derive(Debug, Clone)]
51pub struct Currency {
52 name: String,
53 origin: Origin,
54 note: Option<String>,
55 aliases: HashSet<String>,
56 pub(crate) format: Option<String>,
57 default: bool,
58 pub(crate) display_format: RefCell<CurrencyDisplayFormat>,
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct CurrencyDisplayFormat {
64 pub symbol_placement: CurrencySymbolPlacement,
65 pub negative_amount_display: NegativeAmountDisplay,
66 pub decimal_separator: Separator,
67 pub digit_grouping: DigitGrouping,
68 pub thousands_separator: Option<Separator>,
69 pub precision: usize,
70 pub max_decimals: Option<usize>,
71}
72
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub enum CurrencySymbolPlacement {
75 BeforeAmount,
76 AfterAmount,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum NegativeAmountDisplay {
81 BeforeSymbolAndNumber, BeforeNumberBehindCurrency, AfterNumber, Parentheses, }
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum DigitGrouping {
89 Thousands,
90 Indian,
91 None,
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum Separator {
96 Dot,
97 Comma,
98 Space,
99 Other(char),
100}
101
102impl Currency {
103 pub fn from_directive(name: String) -> Self {
104 Currency {
105 name,
106 origin: Origin::FromDirective,
107 note: None,
108 aliases: HashSet::new(),
109 format: None,
110 default: false,
111 display_format: RefCell::new(DEFAULT_DISPLAY_FORMAT),
112 }
113 }
114
115 pub fn set_note(&mut self, note: String) {
116 self.note = Some(note);
117 }
118 pub fn set_default(&mut self) {
119 self.default = true;
120 }
121 pub fn set_aliases(&mut self, aliases: HashSet<String>) {
122 self.aliases = aliases;
123 }
124 pub fn set_precision(&self, precision: usize) {
125 self.display_format.borrow_mut().set_precision(precision);
126 }
127 pub fn get_precision(&self) -> usize {
128 self.display_format.borrow().precision
129 }
130 pub fn update_precision(&self, precision: usize) {
131 self.display_format.borrow_mut().update_precision(precision);
132 }
133 pub fn set_format(&self, format: &CurrencyDisplayFormat) {
134 let mut current_format = self.display_format.borrow_mut();
135 current_format.symbol_placement = format.symbol_placement;
136 current_format.negative_amount_display = format.negative_amount_display;
137 current_format.decimal_separator = format.decimal_separator;
138 current_format.digit_grouping = format.digit_grouping;
139 current_format.thousands_separator = format.thousands_separator;
140 current_format.precision = format.precision;
141 current_format.max_decimals = format.max_decimals;
142 }
144}
145impl CurrencyDisplayFormat {
146 pub fn get_decimal_separator_str(&self) -> char {
147 match self.decimal_separator {
148 Separator::Dot => '.',
149 Separator::Comma => ',',
150 Separator::Space => '\u{202f}',
151 Separator::Other(x) => x,
152 }
153 }
154 pub fn set_decimal_separator(&mut self, separator: char) {
155 self.decimal_separator = match separator {
156 '.' => Separator::Dot,
157 ',' => Separator::Comma,
158 x => Separator::Other(x),
159 };
160 }
161 pub fn get_thousands_separator_str(&self) -> Option<char> {
162 match self.thousands_separator {
163 Some(Separator::Dot) => Some('.'),
164 Some(Separator::Comma) => Some(','),
165 Some(Separator::Space) => Some('\u{202f}'),
166 Some(Separator::Other(x)) => Some(x),
167 None => None,
168 }
169 }
170 pub fn set_thousands_separator(&mut self, separator: char) {
171 self.thousands_separator = match separator {
172 '.' => Some(Separator::Dot),
173 ',' => Some(Separator::Comma),
174 '\u{202f}' => Some(Separator::Space),
175 x => Some(Separator::Other(x)),
176 };
177 }
178 pub fn get_digit_grouping(&self) -> DigitGrouping {
179 self.digit_grouping
180 }
181 pub fn set_digit_grouping(&mut self, grouping: DigitGrouping) {
182 self.digit_grouping = grouping
183 }
184 pub fn update_precision(&mut self, precision: usize) {
185 if precision > self.precision {
186 self.precision = precision;
187 }
188 }
189 pub fn set_precision(&mut self, precision: usize) {
190 self.max_decimals = Some(precision);
191 }
192 pub fn get_precision(&self) -> usize {
193 match self.max_decimals {
194 Some(precision) => precision,
195 None => self.precision,
196 }
197 }
198}
199
200impl From<&str> for CurrencyDisplayFormat {
201 fn from(format: &str) -> Self {
203 let mut display_format = DEFAULT_DISPLAY_FORMAT;
205 let mut parsed = GrammarParser::parse(Rule::currency_format, format)
206 .unwrap()
207 .next()
208 .unwrap()
209 .into_inner();
210
211 let mut first = parsed.next().unwrap();
212 let integer_format;
213
214 if first.as_rule() == Rule::currency_format_positive {
215 display_format.negative_amount_display = NegativeAmountDisplay::BeforeSymbolAndNumber;
216 if first.as_str().starts_with('(') {
217 display_format.negative_amount_display = NegativeAmountDisplay::Parentheses;
218 }
219 parsed = first.into_inner();
220 first = parsed.next().unwrap();
221 }
222 match first.as_rule() {
223 Rule::integer_part => {
224 integer_format = Some(first);
225 let rule = parsed.next().unwrap();
226 if rule.as_rule() == Rule::space {
227 parsed.next();
228 }
229 parsed.next();
230 }
231 Rule::currency_string => {
232 let mut rule = parsed.next().unwrap();
233 if rule.as_rule() == Rule::space {
234 rule = parsed.next().unwrap();
235 }
236 integer_format = Some(rule);
237 display_format.symbol_placement = CurrencySymbolPlacement::BeforeAmount;
238 display_format.negative_amount_display =
239 NegativeAmountDisplay::BeforeSymbolAndNumber;
240 }
241 other => {
242 panic!("Other: {:?}", other);
243 }
244 }
245
246 match integer_format {
248 Some(x) => {
249 let mut separators = vec![];
250 let num_str = x.as_str();
251 for sep in x.into_inner() {
252 separators.push((sep.as_str().chars().next().unwrap(), sep.as_span().start()));
253 }
254 let len = separators.len();
255 display_format.thousands_separator = None;
256 if len == 0 {
257 display_format.digit_grouping = DigitGrouping::None;
258 } else {
259 display_format.set_decimal_separator(separators[len - 1].0);
260 display_format.max_decimals =
262 Some(num_str.split(separators[len - 1].0).last().unwrap().len());
263 }
264 if len > 1 {
265 display_format.set_thousands_separator(separators[len - 2].0);
266 }
267 if len > 2 {
268 let n = separators[len - 2].1 - separators[len - 3].1;
269 match n {
270 2 => display_format.digit_grouping = DigitGrouping::Indian,
271 3 => display_format.digit_grouping = DigitGrouping::Thousands,
272 _ => eprintln!("Wrong number format: {}", &format),
273 }
274 }
275 }
276 None => display_format.digit_grouping = DigitGrouping::None,
277 }
278 display_format
279 }
280}
281
282impl Display for Currency {
283 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
284 write!(f, "{}", self.name)
285 }
286}
287
288impl HasName for Currency {
289 fn get_name(&self) -> &str {
290 self.name.as_str()
291 }
292}
293impl HasAliases for Currency {
294 fn get_aliases(&self) -> &HashSet<String> {
295 &self.aliases
296 }
297}
298impl<'a> From<&'a str> for Currency {
299 fn from(name: &'a str) -> Self {
300 let mut cur = Currency::from_directive(name.to_string());
301 cur.origin = Origin::Other;
302 cur
303 }
304}
305
306impl FromDirective for Currency {
307 fn is_from_directive(&self) -> bool {
308 matches!(self.origin, Origin::FromDirective)
309 }
310}
311
312impl PartialEq for Currency {
313 fn eq(&self, other: &Self) -> bool {
314 self.name == other.name
315 }
316}
317impl Eq for Currency {}
318
319impl Hash for Currency {
320 fn hash<H: Hasher>(&self, state: &mut H) {
321 self.name.hash(state);
322 }
323}
324
325impl Ord for Currency {
326 fn cmp(&self, other: &Self) -> Ordering {
327 self.name.cmp(&other.name)
328 }
329}
330impl PartialOrd for Currency {
331 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
332 self.name.partial_cmp(&other.name)
333 }
334}
335
336const DEFAULT_DISPLAY_FORMAT: CurrencyDisplayFormat = CurrencyDisplayFormat {
337 symbol_placement: CurrencySymbolPlacement::AfterAmount,
338 negative_amount_display: NegativeAmountDisplay::BeforeSymbolAndNumber,
339 decimal_separator: Separator::Dot,
340 digit_grouping: DigitGrouping::Thousands,
341 thousands_separator: Some(Separator::Comma),
342 precision: 0,
343 max_decimals: None,
344};
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 #[test]
350 fn format_1() {
351 let format_str = "-1.234,00 €";
352 let format = CurrencyDisplayFormat::from(format_str);
353
354 assert_eq!(format.get_precision(), 2);
355 assert_eq!(format.get_thousands_separator_str(), Some('.'));
356 assert_eq!(format.get_decimal_separator_str(), ',');
357 assert_eq!(format.get_digit_grouping(), DigitGrouping::Thousands);
358 assert_eq!(
359 format.symbol_placement,
360 CurrencySymbolPlacement::AfterAmount
361 );
362 assert_eq!(
363 format.negative_amount_display,
364 NegativeAmountDisplay::BeforeSymbolAndNumber
365 );
366 }
367 #[test]
368 fn format_2() {
369 let format_str = "($1,234.00)";
370 let format = CurrencyDisplayFormat::from(format_str);
371
372 assert_eq!(format.get_precision(), 2);
373 assert_eq!(format.get_thousands_separator_str(), Some(','));
374 assert_eq!(format.get_decimal_separator_str(), '.');
375 assert_eq!(format.get_digit_grouping(), DigitGrouping::Thousands);
376 assert_eq!(
377 format.symbol_placement,
378 CurrencySymbolPlacement::BeforeAmount
379 );
380 assert_eq!(
381 format.negative_amount_display,
382 NegativeAmountDisplay::BeforeSymbolAndNumber
383 );
384 }
385 #[test]
386 fn format_3() {
387 let format_str = "-1234,00 €";
388 let format = CurrencyDisplayFormat::from(format_str);
389
390 assert_eq!(format.get_precision(), 2);
391 assert_eq!(format.get_thousands_separator_str(), None);
392 assert_eq!(format.get_decimal_separator_str(), ',');
393 assert_eq!(
395 format.symbol_placement,
396 CurrencySymbolPlacement::AfterAmount
397 );
398 assert_eq!(
399 format.negative_amount_display,
400 NegativeAmountDisplay::BeforeSymbolAndNumber
401 );
402 }
403}