1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*!
Fetch locale-specific number formatting settings.

This module provides basic formatting rules for most rational
numbers; floating point numbers in scientific notation, fractional
numbers, and imaginary numbers are not covered.

## Example

```
use locale_settings::locale::{Category, get_locale};
use locale_settings::numeric::get_numeric_format;
use locale_types::Locale;

if get_locale(&Category::Currency).unwrap() == Locale::POSIX {
    let format = get_numeric_format();
    assert_eq!(format.decimal_separator, ".");
} else {
    panic!("expecting POSIX locale");
}
```
*/

use crate::ffi::localeconv;
use crate::ffi::utils::*;
use crate::Category;
use locale_types::{Locale, LocaleResult};

// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------

/// Numeric formatting settings.
#[derive(Debug, Clone)]
pub struct NumericFormat {
    /// The symbol to use for positive values.
    pub positive_sign: String,
    /// The symbol to use for negative values.
    pub negative_sign: String,
    /// The radix character.
    pub decimal_separator: String, // TODO: is radix_character better?
    /// The separator between groups.
    pub thousands_separator: String, // TODO: is group_separator better?
    /// The grouping rules.
    pub grouping: Vec<usize>,
}

// ------------------------------------------------------------------------------------------------
// Public Functions
// ------------------------------------------------------------------------------------------------

/// Fetch the numeric formatting settings for the current locale.
pub fn get_numeric_format() -> NumericFormat {
    unsafe {
        let lconv = localeconv();
        NumericFormat {
            positive_sign: cstr_to_string((*lconv).positive_sign),
            negative_sign: cstr_to_string((*lconv).negative_sign),
            decimal_separator: cstr_to_string((*lconv).decimal_point),
            thousands_separator: cstr_to_string((*lconv).thousands_sep),
            grouping: grouping_vector((*lconv).grouping),
        }
    }
}

/// Fetch the numeric formatting rules for a specified `Locale`.
///
/// # Arguments
///
/// * `locale` - The locale to query.
/// * `inherit_current` - Whether the specified locale should inherit
///   from the current locale.
///
/// If `inherit_current` is `false` the `locale` specified will be treated
/// as an entirely new and complete locale when calling the C
/// [`newlocale`](https://man.openbsd.org/newlocale.3) function. If it is
/// `true` the `locale` is assumed to be a partially specified one and inherits
/// any unspecified components from the current locale. For example, if the
/// current locale is `en_US.UTF-8` and the parameters passed are `_NZ` and
/// `true` then the resulting locale will be `en_NZ.UTF-8`.
pub fn get_numeric_format_for_locale(
    locale: Locale,
    inherit_current: bool,
) -> LocaleResult<NumericFormat> {
    get_format_for_locale(
        Category::Numeric,
        locale,
        &get_numeric_format,
        inherit_current,
    )
}

#[cfg(experimental)]
pub mod fmt {
    use std::fmt::Display;

    pub fn format<T>(value: T) -> String
    where
        T: Display,
    {
        let initial = value.to_string();
        if initial.chars().all(|c| c.is_digit(10) || c == '.') {
            initial
        } else {
            panic!("doesn't look like a nunber");
        }
    }
}

// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use crate::locale::{set_locale, Category};
    use crate::numeric::get_numeric_format;
    use locale_types::Locale;

    // --------------------------------------------------------------------------------------------
    #[test]
    fn test_numeric_settings() {
        if set_locale(&Locale::POSIX, &Category::Numeric) {
            let format = get_numeric_format();
            println!("{:#?}", format);
            assert_eq!(format.decimal_separator, ".");
        } else {
            panic!("set_locale returned false");
        }
    }
}