fluent_static_function/
builtins.rs

1use std::str::FromStr;
2
3use fluent_static_value::{
4    number::format::{CurrencyDisplayStyle, GroupingStyle, UnitDisplayStyle},
5    Number, NumberFormat, Value,
6};
7
8pub fn number<'a, 'b>(
9    positional_args: &'a [Value<'a>],
10    named_args: &'a [(&'a str, Value<'a>)],
11) -> Value<'b> {
12    if let Some(value) = positional_args.get(0) {
13        match value {
14            Value::String(s) => Number::from_str(s)
15                .map(|n| Value::Number {
16                    value: n,
17                    format: Some(parse_number_format(None, named_args)),
18                })
19                .unwrap_or(Value::Error),
20            Value::Number { value, format } => Value::Number {
21                value: value.clone(),
22                format: Some(parse_number_format(format.clone(), named_args)),
23            },
24            Value::Empty => Value::Empty,
25            Value::Error => Value::Error,
26        }
27    } else {
28        Value::Error
29    }
30}
31
32fn parse_number_format<'a>(
33    value_format: Option<NumberFormat>,
34    named_args: &'a [(&'a str, Value<'a>)],
35) -> NumberFormat {
36    let mut result = value_format.unwrap_or_default();
37    for (key, value) in named_args {
38        match *key {
39            "currencyDisplay" if value.is_string() && result.style.is_currency() => {
40                if let Value::String(s) = value {
41                    result.style.set_currency_display_style(
42                        CurrencyDisplayStyle::from_str(s).unwrap_or_default(),
43                    );
44                }
45            }
46            "unitDisplay" if value.is_string() && result.style.is_unit() => {
47                if let Value::String(s) = value {
48                    result
49                        .style
50                        .set_unit_display_style(UnitDisplayStyle::from_str(s).unwrap_or_default());
51                }
52            }
53            "useGrouping" if value.is_string() => {
54                if let Value::String(s) = value {
55                    result.use_grouping = GroupingStyle::from_str(s).unwrap_or_default();
56                }
57            }
58            "minimumIntegerDigits" => result.minimum_integer_digits = read_digits(value, 1, 21),
59            "minimumFractionDigits" => result.minimum_fraction_digits = read_digits(value, 0, 100),
60            "maximumFractionDigits" => result.maximum_fraction_digits = read_digits(value, 0, 100),
61            "minimumSignificantDigits" => {
62                result.minimum_significant_digits = read_digits(value, 1, 21)
63            }
64            "maximumSignificantDigits" => {
65                result.maximum_significant_digits = read_digits(value, 1, 21)
66            }
67            _ => {}
68        }
69    }
70    result
71}
72
73fn read_digits<'a>(value: &Value<'a>, min: usize, max: usize) -> Option<usize> {
74    match value {
75        Value::String(s) => Number::from_str(s).ok().map(|n| clamp(&n, min, max)),
76        Value::Number { value, .. } => Some(clamp(value, min, max)),
77        _ => None,
78    }
79}
80
81fn clamp(value: &Number, min: usize, max: usize) -> usize {
82    match value {
83        Number::I64(val) => (*val).clamp(min as i64, max as i64) as usize,
84        Number::U64(val) => (*val).clamp(min as u64, max as u64) as usize,
85        Number::I128(val) => (*val).clamp(min as i128, max as i128) as usize,
86        Number::U128(val) => (*val).clamp(min as u128, max as u128) as usize,
87        Number::F64(val) => {
88            let clamped = val.clamp(min as f64, max as f64);
89            clamped.round() as usize
90        }
91    }
92}