Skip to main content

icu_locale_core/preferences/extensions/unicode/macros/
enum_keyword.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5/// Internal macro used by `enum_keyword` for nesting.
6#[macro_export]
7#[doc(hidden)]
8macro_rules! __enum_keyword_inner {
9    ($key:expr, $name:ident, $variant:ident, $value:ident) => {{
10        if $value.get_subtag(1).is_some() {
11            return Err(Self::Error::InvalidKeywordValue);
12        }
13        $name::$variant
14    }};
15    ($key:expr, $name:ident, $variant:ident, $value:ident, $v2:ident, $($subk:expr => $subv:ident),*) => {{
16        const _: () = assert!(!matches!($crate::subtags::subtag!($key), TRUE), "true value not allowed with second level");
17        if $value.get_subtag(2).is_some() {
18            return Err(Self::Error::InvalidKeywordValue);
19        }
20        $name::$variant(match $value.get_subtag(1) {
21            None => None,
22            $(
23                Some(s) if s == &$crate::subtags::subtag!($subk) => Some($v2::$subv),
24            )*
25            _ => return Err(Self::Error::InvalidKeywordValue),
26        })
27    }};
28}
29
30/// Macro used to generate a preference keyword as an enum.
31///
32/// The macro supports single and two subtag enums.
33///
34/// # Examples
35///
36/// ```
37/// use icu::locale::preferences::extensions::unicode::enum_keyword;
38///
39/// enum_keyword!(
40///     EmojiPresentationStyle {
41///         ("emoji" => Emoji),
42///         ("text" => Text),
43///         ("default" => Default)
44/// }, "em");
45///
46/// enum_keyword!(
47///      MetaKeyword {
48///         ("normal" => Normal),
49///         ("emoji" => Emoji(EmojiPresentationStyle) {
50///             ("emoji" => Emoji),
51///             ("text" => Text),
52///             ("default" => Default)
53///         })
54/// }, "mk");
55/// ```
56#[macro_export]
57#[doc(hidden)]
58macro_rules! __enum_keyword {
59    (
60        $(#[$doc:meta])*
61        $([$derive_attrs:ty])?
62        $name:ident {
63            $(
64                $(#[$variant_doc:meta])*
65                $([$variant_attr:ty])?
66                $variant:ident $($v2:ident)?
67            ),*
68        }
69    ) => {
70        #[non_exhaustive]
71        #[derive(Debug, Clone, Eq, PartialEq, Copy, Hash)]
72        $(#[derive($derive_attrs)])?
73        $(#[$doc])*
74        #[cfg_attr(feature = "databake", derive(databake::Bake))]
75        #[cfg_attr(feature = "databake", databake(path = icu_locale_core::preferences::extensions::unicode::keywords))]
76        pub enum $name {
77            $(
78                $(#[$variant_doc])*
79                $(#[$variant_attr])?
80                $variant $((Option<$v2>))?
81            ),*
82        }
83    };
84    ($(#[$doc:meta])*
85    $([$derive_attrs:ty])?
86    $name:ident {
87        $(
88            $(#[$variant_doc:meta])*
89            $([$variant_attr:ty])?
90            ($key:literal => $variant:ident $(($v2:ident) {
91                $(
92                    ($subk:literal => $subv:ident)
93                ),*
94            })?)
95        ),* $(,)?
96    },
97    $ext_key:literal
98    $(, $input:ident, $aliases:stmt)?
99    ) => {
100        $crate::__enum_keyword!(
101            $(#[$doc])*
102            $([$derive_attrs])?
103            $name {
104                $(
105                    $(#[$variant_doc])*
106                    $([$variant_attr])?
107                    $variant $($v2)?
108                ),*
109            }
110        );
111
112        impl $crate::preferences::PreferenceKey for $name {
113            fn unicode_extension_key() -> Option<$crate::extensions::unicode::Key> {
114                Some(Self::UNICODE_EXTENSION_KEY)
115            }
116
117            fn try_from_key_value(
118                key: &$crate::extensions::unicode::Key,
119                value: &$crate::extensions::unicode::Value,
120            ) -> Result<Option<Self>, $crate::preferences::extensions::unicode::errors::PreferencesParseError> {
121                if Self::UNICODE_EXTENSION_KEY == *key {
122                    Self::try_from(value).map(Some)
123                } else {
124                    Ok(None)
125                }
126            }
127
128            fn unicode_extension_value(&self) -> Option<$crate::extensions::unicode::Value> {
129                Some((*self).into())
130            }
131        }
132
133        impl $name {
134            pub(crate) const UNICODE_EXTENSION_KEY: $crate::extensions::unicode::Key = $crate::extensions::unicode::key!($ext_key);
135        }
136
137        impl TryFrom<&$crate::extensions::unicode::Value> for $name {
138            type Error = $crate::preferences::extensions::unicode::errors::PreferencesParseError;
139
140            fn try_from(value: &$crate::extensions::unicode::Value) -> Result<Self, Self::Error> {
141                const TRUE: $crate::subtags::Subtag = $crate::subtags::subtag!("true");
142
143                #[allow(unused_imports)]
144                use $crate::extensions::unicode::value;
145                $(
146                    let $input = value;
147                    $aliases
148                )?
149                Ok(match value.get_subtag(0).copied().unwrap_or(TRUE) {
150                    $(
151                        s if s == $crate::subtags::subtag!($key) => $crate::__enum_keyword_inner!($key, $name, $variant, value$(, $v2, $($subk => $subv),*)?),
152                    )*
153                    _ => return Err(Self::Error::InvalidKeywordValue),
154                })
155            }
156        }
157
158        impl From<$name>  for $crate::extensions::unicode::Value {
159            fn from(input: $name) -> $crate::extensions::unicode::Value {
160                let f;
161                #[allow(unused_mut)]
162                let mut s = None;
163                match input {
164                    $(
165                        // This is circumventing a limitation of the macro_rules - we need to have a conditional
166                        // $()? case here for when the variant has a value, and macro_rules require us to
167                        // reference the $v2 inside it, but in match case it becomes a variable, so clippy
168                        // complaints.
169                        #[allow(non_snake_case)]
170                        $name::$variant $(($v2))? => {
171                            f = $crate::subtags::subtag!($key);
172
173                            $(
174                                if let Some(v2) = $v2 {
175                                    match v2 {
176                                        $(
177                                            $v2::$subv => s = Some($crate::subtags::subtag!($subk)),
178                                        )*
179                                    }
180                                }
181                            )?
182                        },
183                    )*
184                }
185                if let Some(s) = s {
186                    $crate::extensions::unicode::Value::from_two_subtags(f, s)
187                } else {
188                    $crate::extensions::unicode::Value::from_subtag(Some(f))
189                }
190            }
191        }
192
193        impl $name {
194            /// A helper function for displaying as a `&str`.
195            pub const fn as_str(&self) -> &'static str {
196                match self {
197                    $(
198                        // This is circumventing a limitation of the macro_rules - we need to have a conditional
199                        // $()? case here for when the variant has a value, and macro_rules require us to
200                        // reference the $v2 inside it, but in match case it becomes a variable, so clippy
201                        // complaints.
202                        #[allow(non_snake_case)]
203                        Self::$variant $(($v2))? => {
204                            $(
205                                if let Some(v2) = $v2 {
206                                    return match v2 {
207                                        $(
208                                            $v2::$subv => concat!($key, '-', $subk),
209                                        )*
210                                    };
211                                }
212                            )?
213                            return $key;
214                        },
215                    )*
216                }
217            }
218        }
219    };
220}
221pub use __enum_keyword as enum_keyword;
222
223/// ```compile_fail,E0080
224/// use icu_locale_core::preferences::extensions::unicode::enum_keyword;
225///
226/// enum_keyword!(DummySubKeyword { Standard, Rare });
227///
228/// icu_locale_core::preferences::extensions::unicode::enum_keyword!(DummyKeyword {
229///     ("default" => Default),
230///     ("true" => Sub(DummySubKeyword) {
231///         ("standard" => Standard),
232///         ("rare" => Rare)
233///     })
234/// }, "dk");
235/// ```
236fn _nested_true() {}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use crate::extensions::unicode;
242    use core::str::FromStr;
243
244    #[test]
245    fn enum_keywords_test() {
246        enum_keyword!(DummyKeyword {
247            ("standard" => Standard),
248            ("rare" => Rare),
249        }, "dk");
250
251        let v = unicode::Value::from_str("standard").unwrap();
252        let dk = DummyKeyword::try_from(&v).unwrap();
253        assert_eq!(dk, DummyKeyword::Standard);
254        assert_eq!(unicode::Value::from(dk), v);
255
256        let v = unicode::Value::from_str("rare").unwrap();
257        let dk = DummyKeyword::try_from(&v).unwrap();
258        assert_eq!(dk, DummyKeyword::Rare);
259        assert_eq!(unicode::Value::from(dk), v);
260
261        let v = unicode::Value::from_str("foo").unwrap();
262        let dk = DummyKeyword::try_from(&v);
263        dk.unwrap_err();
264
265        assert_eq!(DummyKeyword::Standard.as_str(), "standard");
266    }
267
268    #[test]
269    fn enum_keywords_test_alias() {
270        enum_keyword!(DummyKeyword {
271            ("standard" => Standard),
272            ("rare" => Rare),
273        }, "dk", s, if *s == value!("std") { return Ok(Self::Standard) });
274
275        let v = unicode::Value::from_str("standard").unwrap();
276        let dk = DummyKeyword::try_from(&v).unwrap();
277        assert_eq!(dk, DummyKeyword::Standard);
278        assert_eq!(unicode::Value::from(dk), v);
279
280        let v_alias = unicode::Value::from_str("std").unwrap();
281        let dk = DummyKeyword::try_from(&v_alias).unwrap();
282        assert_eq!(dk, DummyKeyword::Standard);
283        assert_eq!(unicode::Value::from(dk), v);
284
285        let v = unicode::Value::from_str("rare").unwrap();
286        let dk = DummyKeyword::try_from(&v).unwrap();
287        assert_eq!(dk, DummyKeyword::Rare);
288        assert_eq!(unicode::Value::from(dk), v);
289
290        let v = unicode::Value::from_str("foo").unwrap();
291        let dk = DummyKeyword::try_from(&v);
292        dk.unwrap_err();
293
294        assert_eq!(DummyKeyword::Standard.as_str(), "standard");
295    }
296
297    #[test]
298    fn enum_keywords_nested_test() {
299        enum_keyword!(DummySubKeyword { Standard, Rare });
300
301        enum_keyword!(DummyKeyword {
302            ("default" => Default),
303            ("sub" => Sub(DummySubKeyword) {
304                ("standard" => Standard),
305                ("rare" => Rare)
306            })
307        }, "dk");
308
309        let v = unicode::Value::from_str("default").unwrap();
310        let dk = DummyKeyword::try_from(&v).unwrap();
311        assert_eq!(dk, DummyKeyword::Default);
312        assert_eq!(unicode::Value::from(dk), v);
313
314        let v = unicode::Value::from_str("sub").unwrap();
315        let dk = DummyKeyword::try_from(&v).unwrap();
316        assert_eq!(dk, DummyKeyword::Sub(None));
317        assert_eq!(unicode::Value::from(dk), v);
318
319        let v = unicode::Value::from_str("foo").unwrap();
320        let dk = DummyKeyword::try_from(&v);
321        dk.unwrap_err();
322
323        let v = unicode::Value::from_str("sub-standard").unwrap();
324        let dk = DummyKeyword::try_from(&v).unwrap();
325        assert_eq!(dk, DummyKeyword::Sub(Some(DummySubKeyword::Standard)));
326        assert_eq!(unicode::Value::from(dk), v);
327
328        let v = unicode::Value::from_str("sub-rare").unwrap();
329        let dk = DummyKeyword::try_from(&v).unwrap();
330        assert_eq!(dk, DummyKeyword::Sub(Some(DummySubKeyword::Rare)));
331        assert_eq!(unicode::Value::from(dk), v);
332
333        let v = unicode::Value::from_str("sub-foo").unwrap();
334        let dk = DummyKeyword::try_from(&v);
335        dk.unwrap_err();
336
337        assert_eq!(
338            DummyKeyword::Sub(Some(DummySubKeyword::Rare)).as_str(),
339            "sub-rare"
340        );
341    }
342}