unicode_locale_parser/
measure.rs

1use crate::constants::SEP;
2use crate::errors::ParserError;
3use crate::shared::split_str;
4
5use std::fmt::{self};
6use std::iter::Peekable;
7use std::str::FromStr;
8
9#[derive(Debug, PartialEq)]
10pub struct UnicodeMeasureUnit {
11    pub values: Vec<String>,
12}
13
14impl fmt::Display for UnicodeMeasureUnit {
15    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16        let mut messages = vec![];
17        for value in &self.values {
18            messages.push(value.to_string());
19        }
20        write!(f, "{}", messages.join(&SEP.to_string()))?;
21        Ok(())
22    }
23}
24
25impl FromStr for UnicodeMeasureUnit {
26    type Err = ParserError;
27
28    fn from_str(source: &str) -> Result<Self, Self::Err> {
29        parse_unicode_measure_unit(source)
30    }
31}
32
33/// Parse the given string as an Unicode Measure Unit
34///
35/// This function parses according to [`unicode_measure_unit` EBNF defined in UTS #35](https://unicode.org/reports/tr35/#unicode_measure_unit)
36///
37/// # Examples
38///
39/// ```
40/// use unicode_locale_parser::parse_measure_unit;
41///
42/// let measure = parse_measure_unit("area-hectare").unwrap();
43/// assert_eq!(vec!["area", "hectare"], measure.values);
44/// ```
45///
46/// # Errors
47///
48/// This function returns an error in the following cases:
49///
50/// - [`ParserError::Missing`] if the given measure unit is empty.
51/// - [`ParserError::InvalidSubtag`] if the given measure unit is not a valid.
52pub fn parse_unicode_measure_unit(measure_unit: &str) -> Result<UnicodeMeasureUnit, ParserError> {
53    if measure_unit.is_empty() {
54        return Err(ParserError::Missing);
55    }
56
57    parse_unicode_measure_unit_from_iter(&mut split_str(measure_unit).peekable())
58}
59
60fn parse_unicode_measure_unit_from_iter<'a>(
61    iter: &mut Peekable<impl Iterator<Item = &'a str>>,
62) -> Result<UnicodeMeasureUnit, ParserError> {
63    // unicode_measure_unit
64    // https://unicode.org/reports/tr35/#unicode_measure_unit
65    let mut values = vec![];
66
67    while let Some(subtag) = iter.peek() {
68        let subtag_bytes = subtag.as_bytes();
69
70        if !(3..=8).contains(&subtag_bytes.len())
71            || !subtag_bytes.iter().all(|b: &u8| b.is_ascii_alphanumeric())
72        {
73            return Err(ParserError::InvalidSubtag);
74        }
75
76        values.push(subtag.to_string());
77        iter.next();
78    }
79
80    let values = if values.is_empty() {
81        return Err(ParserError::Missing);
82    } else {
83        values
84    };
85
86    Ok(UnicodeMeasureUnit { values })
87}
88
89/*
90 * Unit tests
91 */
92
93#[test]
94fn success_parse_unicode_measure_unit() {
95    // basic
96    let measure = parse_unicode_measure_unit("area-hectare").unwrap();
97    assert_eq!(vec!["area", "hectare"], measure.values);
98
99    // Display trait implementation
100    assert_eq!(
101        "area-hectare",
102        format!("{}", parse_unicode_measure_unit("area-hectare").unwrap())
103    );
104
105    // PartialEq trait implementation
106    assert_eq!(
107        parse_unicode_measure_unit("area-hectare").unwrap(),
108        parse_unicode_measure_unit("area-hectare").unwrap()
109    );
110
111    // FromStr trait implementation
112    let measure: UnicodeMeasureUnit = "area-hectare".parse().unwrap();
113    assert_eq!(vec!["area", "hectare"], measure.values);
114}
115
116#[test]
117fn fail_parse_unicode_measure_unit() {
118    // missing
119    assert_eq!(
120        ParserError::Missing,
121        parse_unicode_measure_unit("").unwrap_err()
122    );
123
124    // invalid subtag
125    assert_eq!(
126        ParserError::InvalidSubtag,
127        parse_unicode_measure_unit("acceleration-g-force").unwrap_err()
128    );
129}