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