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 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}