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: String = self.value.to_string();
138 if let Some(minfd: usize) = self.options.minimum_fraction_digits {
139 if let Some(pos: usize) = val.find('.') {
140 let frac_num: usize = val.len() - pos - 1;
141 let missing: usize = 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: f64| {
160 let mfd: Option = input.find('.').map(|pos: usize| input.len() - pos - 1);
161 let opts: FluentNumberOptions = FluentNumberOptions {
162 minimum_fraction_digits: mfd,
163 ..Default::default()
164 };
165 Self::new(value:n, options: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(msg:"Failed to generate operands out of FluentNumber");
226 if let Some(mfd: usize) = input.options.minimum_fraction_digits {
227 if mfd > operands.v {
228 operands.f *= 10_u64.pow(exp: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}
253