fluent_static_value/number/
format.rs

1use std::fmt::Display;
2use std::str::FromStr;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5pub enum NumberStyle {
6    Decimal,
7    Currency {
8        code: CurrencyCode,
9        style: CurrencyDisplayStyle,
10        sign: CurrencySignMode,
11    },
12    Percent,
13    Unit {
14        identifier: UnitIdentifier,
15        style: UnitDisplayStyle,
16    },
17}
18
19impl Default for NumberStyle {
20    fn default() -> Self {
21        Self::Decimal
22    }
23}
24
25impl NumberStyle {
26    pub fn is_currency(&self) -> bool {
27        match self {
28            NumberStyle::Currency { .. } => true,
29            _ => false,
30        }
31    }
32
33    pub fn is_unit(&self) -> bool {
34        match self {
35            NumberStyle::Unit { .. } => true,
36            _ => false,
37        }
38    }
39
40    pub fn is_decimal(&self) -> bool {
41        match self {
42            NumberStyle::Decimal => true,
43            _ => false,
44        }
45    }
46
47    pub fn is_percent(&self) -> bool {
48        match self {
49            NumberStyle::Percent => true,
50            _ => false,
51        }
52    }
53
54    pub fn set_currency_display_style(&mut self, new_style: CurrencyDisplayStyle) -> bool {
55        if let NumberStyle::Currency { style, .. } = self {
56            *style = new_style;
57            true
58        } else {
59            false
60        }
61    }
62
63    pub fn set_currency_sign_mode(&mut self, new_sign: CurrencySignMode) -> bool {
64        if let NumberStyle::Currency { sign, .. } = self {
65            *sign = new_sign;
66            true
67        } else {
68            false
69        }
70    }
71
72    pub fn set_unit_display_style(&mut self, new_style: UnitDisplayStyle) -> bool {
73        if let NumberStyle::Unit { style, .. } = self {
74            *style = new_style;
75            true
76        } else {
77            false
78        }
79    }
80}
81
82#[derive(Debug, thiserror::Error)]
83#[error("Invalid currency code: '{0}")]
84pub struct InvalidCurrencyCode(String);
85
86macro_rules! create_currency_code_enum {
87    ($($code:ident),*) => {
88        #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
89        pub enum CurrencyCode {
90            $($code),*
91        }
92
93        impl FromStr for CurrencyCode {
94            type Err = InvalidCurrencyCode;
95
96            fn from_str(s: &str) -> Result<Self, Self::Err> {
97                match s {
98                    $(stringify!($code) => Ok(CurrencyCode::$code),)*
99                    _ => Err(InvalidCurrencyCode(s.to_string())),
100                }
101            }
102        }
103
104        impl Display for CurrencyCode {
105            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
106                let s = match self {
107                    $(CurrencyCode::$code => stringify!($code)),*
108                };
109                f.write_str(s)
110            }
111        }
112    };
113}
114
115create_currency_code_enum!(
116    AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB,
117    BOV, BRL, BSD, BTN, BWP, BYN, BZD, CAD, CDF, CHE, CHF, CHW, CLF, CLP, CNY, COP, COU, CRC, CUP,
118    CVE, CZK, DJF, DKK, DOP, DZD, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ,
119    GYD, HKD, HNL, HTG, HUF, IDR, ILS, INR, IQD, IRR, ISK, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW,
120    KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRU, MUR,
121    MVR, MWK, MXN, MXV, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN,
122    PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLE, SOS, SRD, SSP, STN, SVC,
123    SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, USN, UYI, UYU, UYW, UZS,
124    VED, VES, VND, VUV, WST, XAF, XAG, XAU, XBA, XBB, XBC, XBD, XCD, XDR, XOF, XPD, XPF, XPT, XSU,
125    XTS, XUA, XXX, YER, ZAR, ZMW, ZWG
126);
127
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub enum CurrencyDisplayStyle {
130    Code,
131    Symbol,
132    NarrowSymbol,
133    Name,
134}
135
136#[derive(Debug, thiserror::Error)]
137#[error("Invalid currency display style: '{0}'")]
138pub struct InvalidCurrencyDisplayStyleError(String);
139
140impl FromStr for CurrencyDisplayStyle {
141    type Err = InvalidCurrencyDisplayStyleError;
142
143    fn from_str(s: &str) -> Result<Self, Self::Err> {
144        match s.to_lowercase().as_str() {
145            "code" => Ok(CurrencyDisplayStyle::Code),
146            "symbol" => Ok(CurrencyDisplayStyle::Symbol),
147            "narrowsymbol" => Ok(CurrencyDisplayStyle::NarrowSymbol),
148            "name" => Ok(CurrencyDisplayStyle::Name),
149            _ => Err(InvalidCurrencyDisplayStyleError(s.to_string())),
150        }
151    }
152}
153
154impl Default for CurrencyDisplayStyle {
155    fn default() -> Self {
156        Self::Symbol
157    }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
161pub enum CurrencySignMode {
162    Standard,
163    Accounting,
164}
165
166#[derive(Debug, thiserror::Error)]
167#[error("Invalid currency sign mode: '{0}'")]
168pub struct InvalidCurrencySignModeError(String);
169
170impl FromStr for CurrencySignMode {
171    type Err = InvalidCurrencySignModeError;
172
173    fn from_str(s: &str) -> Result<Self, Self::Err> {
174        match s.to_lowercase().as_str() {
175            "standard" => Ok(CurrencySignMode::Standard),
176            "accounting" => Ok(CurrencySignMode::Accounting),
177            _ => Err(InvalidCurrencySignModeError(s.to_string())),
178        }
179    }
180}
181
182impl Default for CurrencySignMode {
183    fn default() -> Self {
184        Self::Standard
185    }
186}
187
188#[derive(Debug, Clone, thiserror::Error)]
189#[error("Invalid currency sign mode: '{0}'")]
190pub struct InvalidUnitIdentifierError(String);
191
192macro_rules! generate_unit_identifier {
193    ($($raw_id:ident),*) => {
194        ::paste::paste! {
195            #[derive(Debug, Clone, PartialEq, Eq, Hash)]
196            pub enum UnitIdentifier {
197                Derived(Box<UnitIdentifier>, Box<UnitIdentifier>),
198                $( [< $raw_id:camel >] ),*
199            }
200
201            impl UnitIdentifier {
202                pub fn per(&self, denominator: UnitIdentifier) -> UnitIdentifier {
203                    Self::Derived(Box::new(self.clone()), Box::new(denominator))
204                }
205            }
206
207            impl FromStr for UnitIdentifier {
208                type Err = InvalidUnitIdentifierError;
209
210                fn from_str(s: &str) -> Result<Self, Self::Err> {
211                    match s {
212                        $(stringify!($raw_id) => Ok(UnitIdentifier::[< $raw_id:camel >]),)*
213                        _ => if let Some(index) = s.find("-per-") {
214                                let (left_str, right_str) = s.split_at(index);
215                                let right_str = &right_str[5..]; // Skip the "-per-" part
216
217                                let left = UnitIdentifier::from_str(left_str)?;
218                                let right = UnitIdentifier::from_str(right_str)?;
219
220                                Ok(UnitIdentifier::Derived(Box::new(left), Box::new(right)))
221                            } else {
222                                Err(InvalidUnitIdentifierError(s.to_string()))
223                        }
224                    }
225                }
226            }
227
228            impl Display for UnitIdentifier {
229                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
230                    match self {
231                        UnitIdentifier::Derived(n, d) => write!(f, "{}-per-{}", n, d),
232                        $(UnitIdentifier::[< $raw_id:camel >] => f.write_str(stringify!($raw_id))),*
233                    }
234                }
235            }
236        }
237    };
238}
239
240// Generate the UnitIdentifier enum and its implementations
241generate_unit_identifier!(
242    acre,
243    bit,
244    byte,
245    celsius,
246    centimeter,
247    day,
248    degree,
249    fahrenheit,
250    foot,
251    gallon,
252    gigabit,
253    gigabyte,
254    gram,
255    hectare,
256    hour,
257    inch,
258    kilobit,
259    kilobyte,
260    kilogram,
261    kilometer,
262    liter,
263    megabit,
264    megabyte,
265    meter,
266    microsecond,
267    mile,
268    milliliter,
269    millimeter,
270    millisecond,
271    minute,
272    month,
273    nanosecond,
274    ounce,
275    percent,
276    petabyte,
277    pound,
278    second,
279    stone,
280    terabit,
281    terabyte,
282    week,
283    yard,
284    year
285);
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
288pub enum UnitDisplayStyle {
289    Short,
290    Narrow,
291    Long,
292}
293
294impl Default for UnitDisplayStyle {
295    fn default() -> Self {
296        Self::Short
297    }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301pub enum GroupingStyle {
302    Always,
303    Auto,
304    Min2,
305    Off,
306}
307
308impl Default for GroupingStyle {
309    fn default() -> Self {
310        Self::Auto
311    }
312}
313
314#[derive(Debug, thiserror::Error)]
315#[error("Invalid unit display style: '{0}")]
316pub struct InvalidUnitDisplayStyleError(String);
317
318impl FromStr for UnitDisplayStyle {
319    type Err = InvalidUnitDisplayStyleError;
320
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        match s.to_lowercase().as_str() {
323            "short" => Ok(UnitDisplayStyle::Short),
324            "narrow" => Ok(UnitDisplayStyle::Narrow),
325            "long" => Ok(UnitDisplayStyle::Long),
326            _ => Err(InvalidUnitDisplayStyleError(s.to_string())),
327        }
328    }
329}
330
331#[derive(Debug, thiserror::Error)]
332#[error("Invalid grouping style: '{0}")]
333pub struct InvalidGroupingStyleError(String);
334
335impl FromStr for GroupingStyle {
336    type Err = InvalidGroupingStyleError;
337
338    fn from_str(s: &str) -> Result<Self, Self::Err> {
339        match s {
340            "auto" | "true" => Ok(Self::Auto),
341            "off" | "false" => Ok(Self::Off),
342            "always" => Ok(Self::Always),
343            "min2" => Ok(Self::Min2),
344            _ => Err(InvalidGroupingStyleError(s.to_string())),
345        }
346    }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Hash)]
350pub struct NumberFormat {
351    pub style: NumberStyle,
352
353    pub use_grouping: GroupingStyle,
354
355    pub minimum_integer_digits: Option<usize>,
356    pub minimum_fraction_digits: Option<usize>,
357    pub maximum_fraction_digits: Option<usize>,
358    pub minimum_significant_digits: Option<usize>,
359    pub maximum_significant_digits: Option<usize>,
360}
361
362impl NumberFormat {
363    pub fn currency(code: CurrencyCode) -> Self {
364        Self {
365            style: NumberStyle::Currency {
366                code,
367                style: CurrencyDisplayStyle::default(),
368                sign: CurrencySignMode::default(),
369            },
370            ..Default::default()
371        }
372    }
373
374    pub fn unit(unit_id: UnitIdentifier) -> Self {
375        Self {
376            style: NumberStyle::Unit {
377                identifier: unit_id,
378                style: UnitDisplayStyle::default(),
379            },
380            ..Default::default()
381        }
382    }
383
384    pub fn percent() -> Self {
385        Self {
386            style: NumberStyle::Percent,
387            ..Default::default()
388        }
389    }
390}
391
392impl Default for NumberFormat {
393    fn default() -> Self {
394        Self {
395            style: NumberStyle::Decimal,
396            use_grouping: GroupingStyle::Auto,
397            minimum_integer_digits: None,
398            minimum_fraction_digits: None,
399            maximum_fraction_digits: None,
400            minimum_significant_digits: None,
401            maximum_significant_digits: None,
402        }
403    }
404}
405
406#[cfg(test)]
407mod test {
408    use crate::number::format::UnitIdentifier;
409
410    #[test]
411    fn test_unit_from_str() {
412        assert_eq!(UnitIdentifier::Meter, "meter".parse().unwrap());
413        assert_eq!(
414            UnitIdentifier::Meter.per(UnitIdentifier::Second),
415            "meter-per-second".parse().unwrap()
416        );
417    }
418
419    #[test]
420    fn test_unit_display() {
421        assert_eq!(
422            "kilometer-per-hour",
423            format!("{}", UnitIdentifier::Kilometer.per(UnitIdentifier::Hour))
424        );
425    }
426}