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 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}