use crate::currency::FormattableCurrency;
use crate::{Money, Round};
use std::cmp::Ordering;
pub struct Formatter;
impl<'a> Formatter {
pub fn money<T: FormattableCurrency>(money: &Money<'a, T>, params: Params) -> String {
let mut decimal = *money.amount();
if let Some(x) = params.rounding {
decimal = *money.round(x, Round::HalfEven).amount();
}
let amount = Formatter::amount(&format!("{}", decimal), ¶ms);
let mut result = String::new();
for position in params.positions.iter() {
match position {
Position::Space => result.push(' '),
Position::Amount => result.push_str(&amount),
Position::Code => result.push_str(params.code.unwrap_or("")),
Position::Symbol => result.push_str(params.symbol.unwrap_or("")),
Position::Sign => result.push_str(if money.is_negative() { "-" } else { "" }),
}
}
result
}
fn amount(raw_amount: &str, params: &Params) -> String {
let amount_split: Vec<&str> = raw_amount.split('.').collect();
let mut amount_digits = amount_split[0].to_string();
amount_digits.retain(|c| c != '-');
amount_digits = Formatter::digits(
&amount_digits,
params.digit_separator,
¶ms.separator_pattern,
);
let mut result = amount_digits;
match amount_split.len().cmp(&2) {
Ordering::Equal => {
result.push(params.exponent_separator);
result += amount_split[1];
}
Ordering::Less => {
}
Ordering::Greater => panic!("More than 1 exponent separators when parsing Decimal"),
}
result
}
fn digits(raw_digits: &str, separator: char, pattern: &[usize]) -> String {
let mut digits = raw_digits.to_string();
let mut current_position: usize = 0;
for position in pattern.iter() {
current_position += position;
if digits.len() > current_position {
digits.insert(digits.len() - current_position, separator);
current_position += 1;
}
}
digits
}
}
#[derive(Debug, Clone)]
pub enum Position {
Space,
Amount,
Code,
Symbol,
Sign,
}
#[derive(Debug, Clone)]
pub struct Params {
pub digit_separator: char,
pub exponent_separator: char,
pub separator_pattern: Vec<usize>,
pub positions: Vec<Position>,
pub rounding: Option<u32>,
pub symbol: Option<&'static str>,
pub code: Option<&'static str>,
}
impl Default for Params {
fn default() -> Params {
Params {
digit_separator: ',',
exponent_separator: '.',
separator_pattern: vec![3, 3, 3],
positions: vec![Position::Sign, Position::Symbol, Position::Amount],
rounding: None,
symbol: None,
code: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::define_currency_set;
define_currency_set!(
test {
USD: {
code: "USD",
exponent: 2,
locale: EnUs,
minor_units: 100,
name: "USD",
symbol: "$",
symbol_first: true,
}
}
);
#[test]
fn format_position() {
let _usd = test::find("USD");
let money = Money::from_major(-1000, test::USD);
let params = Params {
symbol: Some("$"),
code: Some("USD"),
positions: vec![
Position::Sign,
Position::Space,
Position::Symbol,
Position::Amount,
Position::Space,
Position::Code,
],
..Default::default()
};
assert_eq!("- $1,000 USD", Formatter::money(&money, params));
let params = Params {
symbol: Some("$"),
code: Some("USD"),
positions: vec![
Position::Code,
Position::Space,
Position::Amount,
Position::Symbol,
Position::Space,
Position::Sign,
],
..Default::default()
};
assert_eq!("USD 1,000$ -", Formatter::money(&money, params));
let params = Params {
positions: vec![Position::Amount],
..Default::default()
};
assert_eq!("1,000", Formatter::money(&money, params));
let params = Params {
symbol: Some("$"),
positions: vec![Position::Symbol],
..Default::default()
};
assert_eq!("$", Formatter::money(&money, params));
let params = Params {
positions: vec![Position::Amount, Position::Symbol],
..Default::default()
};
assert_eq!("1,000", Formatter::money(&money, params));
}
#[test]
fn format_digit_separators_with_custom_separators() {
let params = Params {
digit_separator: '/',
..Default::default()
};
let money = Money::from_major(1_000_000, test::USD);
assert_eq!("1/000/000", Formatter::money(&money, params.clone()));
let money = Money::from_major(1_000, test::USD);
assert_eq!("1/000", Formatter::money(&money, params.clone()));
let money = Money::from_major(0, test::USD);
assert_eq!("0", Formatter::money(&money, params.clone()));
}
#[test]
fn format_digit_separators_with_custom_sequences() {
let params = Params {
separator_pattern: vec![3, 2, 2],
..Default::default()
};
let money = Money::from_major(1_00_00_000, test::USD);
assert_eq!("1,00,00,000", Formatter::money(&money, params.clone()));
let money = Money::from_major(1_00_000, test::USD);
assert_eq!("1,00,000", Formatter::money(&money, params.clone()));
let money = Money::from_major(1_000, test::USD);
assert_eq!("1,000", Formatter::money(&money, params.clone()));
let params = Params {
separator_pattern: vec![0, 2],
..Default::default()
};
let money = Money::from_major(100, test::USD);
assert_eq!("1,00,", Formatter::money(&money, params.clone()));
let money = Money::from_major(0, test::USD);
assert_eq!("0,", Formatter::money(&money, params.clone()));
}
#[test]
fn format_rounding() {
let money = Money::from_minor(1000, test::USD) / 3;
let params = Params {
rounding: Some(0),
..Default::default()
};
assert_eq!("3", Formatter::money(&money, params));
let params = Params {
rounding: Some(2),
..Default::default()
};
assert_eq!("3.33", Formatter::money(&money, params));
let params = Params {
..Default::default()
};
assert_eq!(
"3.3333333333333333333333333333",
Formatter::money(&money, params)
);
}
}