unicode_locale_parser/
extensions.rs

1pub mod other;
2pub mod pu;
3pub mod transformed;
4pub mod unicode_locale;
5
6use other::{parse_other_extensions, OtherExtensions};
7use pu::{parse_pu_extensions, PuExtensions};
8use transformed::{parse_transformed_extensions, TransformedExtensions};
9use unicode_locale::{parse_unicode_locale_extensions, UnicodeLocaleExtensions};
10
11use crate::constants::SEP;
12use crate::errors::ParserError;
13use crate::shared::split_str;
14
15use std::fmt::{self, Write};
16use std::iter::Peekable;
17
18#[derive(Debug, PartialEq)]
19enum ExtensionKind {
20    UnicodeLocale,
21    Transformed,
22    Pu,
23    Other(char),
24}
25
26impl ExtensionKind {
27    fn from_byte(key: u8) -> Result<Self, ParserError> {
28        let key = key.to_ascii_lowercase();
29        match key {
30            b'u' => Ok(ExtensionKind::UnicodeLocale),
31            b't' => Ok(ExtensionKind::Transformed),
32            b'x' => Ok(ExtensionKind::Pu),
33            other if other.is_ascii_alphanumeric() => Ok(ExtensionKind::Other(char::from(other))),
34            _ => Err(ParserError::InvalidExtension),
35        }
36    }
37}
38
39impl fmt::Display for ExtensionKind {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        let c = match self {
42            ExtensionKind::UnicodeLocale => 'u',
43            ExtensionKind::Transformed => 't',
44            ExtensionKind::Pu => 'x',
45            ExtensionKind::Other(c) => *c,
46        };
47        f.write_char(c)
48    }
49}
50
51#[derive(Debug)]
52pub struct Extensions {
53    pub unicode_locale: Option<Vec<UnicodeLocaleExtensions>>,
54    pub transformed: Option<Vec<TransformedExtensions>>,
55    pub other: Option<Vec<OtherExtensions>>,
56    pub pu: Option<PuExtensions>,
57}
58
59impl fmt::Display for Extensions {
60    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61        let mut messages = vec![];
62        if let Some(unicode_locale) = &self.unicode_locale {
63            for u in unicode_locale {
64                messages.push(format!("{}", u));
65            }
66        }
67        if let Some(transformed) = &self.transformed {
68            for t in transformed {
69                messages.push(format!("{}", t));
70            }
71        }
72        if let Some(other) = &self.other {
73            for o in other {
74                messages.push(format!("{}", o));
75            }
76        }
77        if let Some(pu) = &self.pu {
78            messages.push(format!("{}", pu));
79        }
80
81        if !messages.is_empty() {
82            f.write_str(&messages.join(&SEP.to_string()))?;
83        }
84        Ok(())
85    }
86}
87
88#[allow(dead_code)]
89pub fn parse_extensions(chunk: &str) -> Result<Extensions, ParserError> {
90    // check empty
91    if chunk.is_empty() {
92        return Err(ParserError::Missing);
93    }
94
95    parse_extensions_from_iter(&mut split_str(chunk).peekable())
96}
97
98pub fn parse_extensions_from_iter<'a>(
99    iter: &mut Peekable<impl Iterator<Item = &'a str>>,
100) -> Result<Extensions, ParserError> {
101    let mut unicode_locale = vec![];
102    let mut transformed = vec![];
103    let mut other = vec![];
104    let mut pu = None;
105
106    let mut chunk = iter.next();
107    while let Some(subtag) = chunk {
108        match subtag
109            .as_bytes()
110            .first()
111            .map(|c| ExtensionKind::from_byte(*c))
112        {
113            Some(Ok(ExtensionKind::UnicodeLocale)) => {
114                unicode_locale.push(parse_unicode_locale_extensions(iter)?);
115            }
116            Some(Ok(ExtensionKind::Transformed)) => {
117                transformed.push(parse_transformed_extensions(iter)?);
118            }
119            Some(Ok(ExtensionKind::Pu)) => {
120                if pu.is_some() {
121                    return Err(ParserError::Unexpected);
122                }
123                pu = Some(parse_pu_extensions(iter)?);
124            }
125            Some(Ok(ExtensionKind::Other(c))) => {
126                other.push(parse_other_extensions(iter, c)?);
127            }
128            None => {}
129            _ => unreachable!(),
130        }
131
132        chunk = iter.next();
133    }
134
135    // normalize unicode locale extensions
136    let unicode_locale = if unicode_locale.is_empty() {
137        None
138    } else {
139        Some(unicode_locale)
140    };
141
142    // normalize transformed extensions
143    let transformed = if transformed.is_empty() {
144        None
145    } else {
146        Some(transformed)
147    };
148
149    // normalize other extensions
150    let other = if other.is_empty() { None } else { Some(other) };
151
152    Ok(Extensions {
153        unicode_locale,
154        transformed,
155        pu,
156        other,
157    })
158}
159
160/**
161 * Tests
162 */
163
164#[test]
165fn success_parse_extensions() {
166    // basic
167    let extensions = parse_extensions(
168        "U-attr1-kz-value2-t-en-Latn-US-macos-t1-value1-value2-a-vue-rust-x-foo-123",
169    )
170    .unwrap();
171    let unicode_locale = extensions.unicode_locale.unwrap();
172    assert_eq!(
173        ["u-attr1-kz-value2"],
174        unicode_locale
175            .iter()
176            .map(|u| format!("{}", u))
177            .collect::<Vec<String>>()
178            .as_slice()
179    );
180    let transformed = extensions.transformed.unwrap();
181    assert_eq!(
182        ["t-en-Latn-US-macos-t1-value1-value2"],
183        transformed
184            .iter()
185            .map(|t| format!("{}", t))
186            .collect::<Vec<String>>()
187            .as_slice()
188    );
189    let other = extensions.other.unwrap();
190    assert_eq!(
191        ["a-vue-rust"],
192        other
193            .iter()
194            .map(|o| format!("{}", o))
195            .collect::<Vec<String>>()
196            .as_slice()
197    );
198    let pu = extensions.pu.unwrap();
199    assert_eq!("x-foo-123", format!("{}", pu));
200
201    // Display trait implementation
202    assert_eq!(
203        "u-attr1-kz-value2-t-en-Latn-US-macos-t1-value1-value2-a-vue-rust-x-foo-123",
204        format!(
205            "{}",
206            parse_extensions(
207                "U-attr1-kz-value2-t-en-Latn-US-macos-t1-value1-value2-a-vue-rust-x-foo-123",
208            )
209            .unwrap()
210        )
211    );
212}
213
214/*
215 * Unit tests
216 */
217
218#[test]
219fn fail_parse_unicode_extensions() {
220    // missing locale
221    assert_eq!(ParserError::Missing, parse_extensions("").unwrap_err());
222}
223
224#[test]
225fn success_extension_kind_from_byte() {
226    assert_eq!(
227        ExtensionKind::UnicodeLocale,
228        ExtensionKind::from_byte(b'u').unwrap()
229    );
230    assert_eq!(
231        ExtensionKind::Transformed,
232        ExtensionKind::from_byte(b't').unwrap()
233    );
234    assert_eq!(
235        ExtensionKind::Transformed,
236        ExtensionKind::from_byte(b'T').unwrap()
237    );
238    assert_eq!(ExtensionKind::Pu, ExtensionKind::from_byte(b'x').unwrap());
239    assert_eq!(
240        ExtensionKind::Other('a'),
241        ExtensionKind::from_byte(b'a').unwrap()
242    );
243    assert_eq!(
244        ExtensionKind::Other('1'),
245        ExtensionKind::from_byte(b'1').unwrap()
246    );
247}
248
249/**
250 * Unit tests
251 */
252
253#[test]
254fn fail_extension_kind_from_byte() {
255    assert_eq!(
256        ParserError::InvalidExtension,
257        ExtensionKind::from_byte(b'!').unwrap_err()
258    );
259    assert_eq!(
260        ParserError::InvalidExtension,
261        ExtensionKind::from_byte(b' ').unwrap_err()
262    );
263}
264
265#[test]
266fn extention_kind_display() {
267    assert_eq!("u", format!("{}", ExtensionKind::UnicodeLocale));
268    assert_eq!("t", format!("{}", ExtensionKind::Transformed));
269    assert_eq!("x", format!("{}", ExtensionKind::Pu));
270    assert_eq!("a", format!("{}", ExtensionKind::Other('a')));
271}