fluent_bundle/types/
number.rs

1use std::borrow::Cow;
2use std::convert::TryInto;
3use std::default::Default;
4use std::str::FromStr;
5
6use intl_pluralrules::operands::PluralOperands;
7
8use crate::args::FluentArgs;
9use crate::types::FluentValue;
10
11#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
12pub enum FluentNumberStyle {
13    Decimal,
14    Currency,
15    Percent,
16}
17
18impl std::default::Default for FluentNumberStyle {
19    fn default() -> Self {
20        Self::Decimal
21    }
22}
23
24impl From<&str> for FluentNumberStyle {
25    fn from(input: &str) -> Self {
26        match input {
27            "decimal" => Self::Decimal,
28            "currency" => Self::Currency,
29            "percent" => Self::Percent,
30            _ => Self::default(),
31        }
32    }
33}
34
35#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
36pub enum FluentNumberCurrencyDisplayStyle {
37    Symbol,
38    Code,
39    Name,
40}
41
42impl std::default::Default for FluentNumberCurrencyDisplayStyle {
43    fn default() -> Self {
44        Self::Symbol
45    }
46}
47
48impl From<&str> for FluentNumberCurrencyDisplayStyle {
49    fn from(input: &str) -> Self {
50        match input {
51            "symbol" => Self::Symbol,
52            "code" => Self::Code,
53            "name" => Self::Name,
54            _ => Self::default(),
55        }
56    }
57}
58
59#[derive(Debug, Clone, Hash, PartialEq, Eq)]
60pub struct FluentNumberOptions {
61    pub style: FluentNumberStyle,
62    pub currency: Option<String>,
63    pub currency_display: FluentNumberCurrencyDisplayStyle,
64    pub use_grouping: bool,
65    pub minimum_integer_digits: Option<usize>,
66    pub minimum_fraction_digits: Option<usize>,
67    pub maximum_fraction_digits: Option<usize>,
68    pub minimum_significant_digits: Option<usize>,
69    pub maximum_significant_digits: Option<usize>,
70}
71
72impl Default for FluentNumberOptions {
73    fn default() -> Self {
74        Self {
75            style: Default::default(),
76            currency: None,
77            currency_display: Default::default(),
78            use_grouping: true,
79            minimum_integer_digits: None,
80            minimum_fraction_digits: None,
81            maximum_fraction_digits: None,
82            minimum_significant_digits: None,
83            maximum_significant_digits: None,
84        }
85    }
86}
87
88impl FluentNumberOptions {
89    pub fn merge(&mut self, opts: &FluentArgs) {
90        for (key, value) in opts.iter() {
91            match (key, value) {
92                ("style", FluentValue::String(n)) => {
93                    self.style = n.as_ref().into();
94                }
95                ("currency", FluentValue::String(n)) => {
96                    self.currency = Some(n.to_string());
97                }
98                ("currencyDisplay", FluentValue::String(n)) => {
99                    self.currency_display = n.as_ref().into();
100                }
101                ("useGrouping", FluentValue::String(n)) => {
102                    self.use_grouping = n != "false";
103                }
104                ("minimumIntegerDigits", FluentValue::Number(n)) => {
105                    self.minimum_integer_digits = Some(n.into());
106                }
107                ("minimumFractionDigits", FluentValue::Number(n)) => {
108                    self.minimum_fraction_digits = Some(n.into());
109                }
110                ("maximumFractionDigits", FluentValue::Number(n)) => {
111                    self.maximum_fraction_digits = Some(n.into());
112                }
113                ("minimumSignificantDigits", FluentValue::Number(n)) => {
114                    self.minimum_significant_digits = Some(n.into());
115                }
116                ("maximumSignificantDigits", FluentValue::Number(n)) => {
117                    self.maximum_significant_digits = Some(n.into());
118                }
119                _ => {}
120            }
121        }
122    }
123}
124
125#[derive(Debug, PartialEq, Clone)]
126pub struct FluentNumber {
127    pub value: f64,
128    pub options: FluentNumberOptions,
129}
130
131impl FluentNumber {
132    pub const fn new(value: f64, options: FluentNumberOptions) -> Self {
133        Self { value, options }
134    }
135
136    pub fn as_string(&self) -> Cow<'static, str> {
137        let mut val = self.value.to_string();
138        if let Some(minfd) = self.options.minimum_fraction_digits {
139            if let Some(pos) = val.find('.') {
140                let frac_num = val.len() - pos - 1;
141                let missing = if frac_num > minfd {
142                    0
143                } else {
144                    minfd - frac_num
145                };
146                val = format!("{}{}", val, "0".repeat(missing));
147            } else {
148                val = format!("{}.{}", val, "0".repeat(minfd));
149            }
150        }
151        val.into()
152    }
153}
154
155impl FromStr for FluentNumber {
156    type Err = std::num::ParseFloatError;
157
158    fn from_str(input: &str) -> Result<Self, Self::Err> {
159        f64::from_str(input).map(|n| {
160            let mfd = input.find('.').map(|pos| input.len() - pos - 1);
161            let opts = FluentNumberOptions {
162                minimum_fraction_digits: mfd,
163                ..Default::default()
164            };
165            Self::new(n, opts)
166        })
167    }
168}
169
170impl<'l> From<FluentNumber> for FluentValue<'l> {
171    fn from(input: FluentNumber) -> Self {
172        FluentValue::Number(input)
173    }
174}
175
176macro_rules! from_num {
177    ($num:ty) => {
178        impl From<$num> for FluentNumber {
179            fn from(n: $num) -> Self {
180                Self {
181                    value: n as f64,
182                    options: FluentNumberOptions::default(),
183                }
184            }
185        }
186        impl From<&$num> for FluentNumber {
187            fn from(n: &$num) -> Self {
188                Self {
189                    value: *n as f64,
190                    options: FluentNumberOptions::default(),
191                }
192            }
193        }
194        impl From<FluentNumber> for $num {
195            fn from(input: FluentNumber) -> Self {
196                input.value as $num
197            }
198        }
199        impl From<&FluentNumber> for $num {
200            fn from(input: &FluentNumber) -> Self {
201                input.value as $num
202            }
203        }
204        impl From<$num> for FluentValue<'_> {
205            fn from(n: $num) -> Self {
206                FluentValue::Number(n.into())
207            }
208        }
209        impl From<&$num> for FluentValue<'_> {
210            fn from(n: &$num) -> Self {
211                FluentValue::Number(n.into())
212            }
213        }
214    };
215    ($($num:ty)+) => {
216        $(from_num!($num);)+
217    };
218}
219
220impl From<&FluentNumber> for PluralOperands {
221    fn from(input: &FluentNumber) -> Self {
222        let mut operands: Self = input
223            .value
224            .try_into()
225            .expect("Failed to generate operands out of FluentNumber");
226        if let Some(mfd) = input.options.minimum_fraction_digits {
227            if mfd > operands.v {
228                operands.f *= 10_u64.pow(mfd as u32 - operands.v as u32);
229                operands.v = mfd;
230            }
231        }
232        // XXX: Add support for other options.
233        operands
234    }
235}
236
237from_num!(i8 i16 i32 i64 i128 isize);
238from_num!(u8 u16 u32 u64 u128 usize);
239from_num!(f32 f64);
240
241#[cfg(test)]
242mod tests {
243    use crate::types::FluentValue;
244
245    #[test]
246    fn value_from_copy_ref() {
247        let x = 1i16;
248        let y = &x;
249        let z: FluentValue = y.into();
250        assert_eq!(z, FluentValue::try_number("1"));
251    }
252}