locale_decoder/
lib.rs

1#[derive(Debug, PartialEq)]
2pub struct Locale {
3    pub language: String,
4    pub country: Option<String>,
5    pub encoding: Option<String>,
6    pub modifier: Option<String>,
7}
8
9#[derive(thiserror::Error, Debug)]
10pub enum Error {
11    #[error("found unexpected character '{0}' in \"{2}\" at {1}")]
12    UnexpectedCharacterRecieved(char, usize, String),
13}
14pub type Result<T> = std::result::Result<T, Error>;
15
16impl Locale {
17    pub fn parse<'a, T: AsRef<str>>(text: T) -> Result<Locale> {
18        // lang_COUNTRY.ENCODING@MODIFIER
19        let mut locale = Locale {
20            language: String::new(),
21            country: None,
22            encoding: None,
23            modifier: None,
24        };
25        let text = text.as_ref();
26        for (idx, character) in text.chars().enumerate() {
27            match character {
28                '_' => {
29                    if locale.country.is_some()
30                        || locale.encoding.is_some()
31                        || locale.modifier.is_some()
32                    {
33                        return Err(Error::UnexpectedCharacterRecieved(
34                            character,
35                            idx,
36                            text.to_string(),
37                        ));
38                    }
39                    locale.country = Some(String::new());
40                }
41                '.' => {
42                    if locale.encoding.is_some() || locale.modifier.is_some() {
43                        return Err(Error::UnexpectedCharacterRecieved(
44                            character,
45                            idx,
46                            text.to_string(),
47                        ));
48                    }
49                    locale.encoding = Some(String::new());
50                }
51                '@' => {
52                    if locale.modifier.is_some() {
53                        return Err(Error::UnexpectedCharacterRecieved(
54                            character,
55                            idx,
56                            text.to_string(),
57                        ));
58                    }
59                    locale.modifier = Some(String::new());
60                }
61                character => match (&locale.country, &locale.encoding, &locale.modifier) {
62                    (None, None, None) => locale.language.push(character),
63                    (Some(country), None, None) => {
64                        locale.country = Some(format!("{country}{character}"))
65                    }
66                    (_, Some(encoding), None) => {
67                        locale.encoding = Some(format!("{encoding}{character}"))
68                    }
69                    (_, _, Some(modifier)) => {
70                        locale.modifier = Some(format!("{modifier}{character}"))
71                    }
72                },
73            }
74        }
75        Ok(locale)
76    }
77}
78impl TryFrom<String> for Locale {
79    fn try_from(text: String) -> Result<Self> {
80        Locale::parse(text)
81    }
82    type Error = Error;
83}
84
85#[cfg(test)]
86mod tests {
87    use super::{Locale, Result};
88    #[test]
89    fn parse_full() -> Result<()> {
90        let result = super::Locale::parse("lang_COUNTRY.ENCODING@MODIFIER")?;
91        assert_eq!(result.language, "lang");
92        assert_eq!(result.country, Some("COUNTRY".into()));
93        assert_eq!(result.encoding, Some("ENCODING".into()));
94        assert_eq!(result.modifier, Some("MODIFIER".into()));
95        Ok(())
96    }
97
98    #[test]
99    fn parse_rest() {
100        let tests = vec![
101            (
102                "lang_COUNTRY",
103                Locale {
104                    language: "lang".into(),
105                    country: Some("COUNTRY".into()),
106                    encoding: None,
107                    modifier: None,
108                },
109            ),
110            (
111                "lang@MODIFIER",
112                Locale {
113                    language: "lang".into(),
114                    country: None,
115                    encoding: None,
116                    modifier: Some("MODIFIER".into()),
117                },
118            ),
119            (
120                "justLang",
121                Locale {
122                    language: "justLang".into(),
123                    country: None,
124                    encoding: None,
125                    modifier: None,
126                },
127            ),
128        ];
129        for (example, expected_result) in tests {
130            assert_eq!(Locale::parse(example).unwrap(), expected_result);
131        }
132    }
133}