• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)]
8 macro_rules! __enum_keyword_inner {
9     ($name:ident, $variant:ident) => {
10         $name::$variant
11     };
12     ($name:ident, $variant:ident, $s:ident, $v2:ident, $($subk:expr => $subv:ident),*) => {{
13         let sv = $s.get_subtag(1).and_then(|st| {
14             match st.as_str() {
15                 $(
16                     $subk => Some($v2::$subv),
17                 )*
18                 _ => None,
19             }
20         });
21         $name::$variant(sv)
22     }};
23 }
24 
25 /// Macro used to generate a preference keyword as an enum.
26 ///
27 /// The macro supports single and two subtag enums.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// use icu::locale::preferences::extensions::unicode::enum_keyword;
33 ///
34 /// enum_keyword!(
35 ///     EmojiPresentationStyle {
36 ///         ("emoji" => Emoji),
37 ///         ("text" => Text),
38 ///         ("default" => Default)
39 /// }, "em");
40 ///
41 /// enum_keyword!(
42 ///      MetaKeyword {
43 ///         ("normal" => Normal),
44 ///         ("emoji" => Emoji(EmojiPresentationStyle) {
45 ///             ("emoji" => Emoji),
46 ///             ("text" => Text),
47 ///             ("default" => Default)
48 ///         })
49 /// }, "mk");
50 /// ```
51 #[macro_export]
52 #[doc(hidden)]
53 macro_rules! __enum_keyword {
54     (
55         $(#[$doc:meta])*
56         $([$derive_attrs:ty])?
57         $name:ident {
58             $(
59                 $(#[$variant_doc:meta])*
60                 $([$variant_attr:ty])?
61                 $variant:ident $($v2:ident)?
62             ),*
63         }
64     ) => {
65         #[non_exhaustive]
66         #[derive(Debug, Clone, Eq, PartialEq, Copy, Hash)]
67         $(#[derive($derive_attrs)])?
68         $(#[$doc])*
69         pub enum $name {
70             $(
71                 $(#[$variant_doc])*
72                 $(#[$variant_attr])?
73                 $variant $((Option<$v2>))?
74             ),*
75         }
76     };
77     ($(#[$doc:meta])* $([$derive_attrs:ty])? $name:ident {
78         $(
79             $(#[$variant_doc:meta])*
80             $([$variant_attr:ty])?
81             ($key:expr => $variant:ident $(($v2:ident) {
82                 $(
83                     ($subk:expr => $subv:ident)
84                 ),*
85             })?)
86         ),* $(,)?
87     }, $ext_key:literal) => {
88         $crate::__enum_keyword!(
89             $(#[$doc])*
90             $([$derive_attrs])?
91             $name {
92                 $(
93                     $(#[$variant_doc])*
94                     $([$variant_attr])?
95                     $variant $($v2)?
96                 ),*
97             }
98         );
99 
100         impl $crate::preferences::PreferenceKey for $name {
101             fn unicode_extension_key() -> Option<$crate::extensions::unicode::Key> {
102                 Some($crate::extensions::unicode::key!($ext_key))
103             }
104 
105             fn try_from_key_value(
106                 key: &$crate::extensions::unicode::Key,
107                 value: &$crate::extensions::unicode::Value,
108             ) -> Result<Option<Self>, $crate::preferences::extensions::unicode::errors::PreferencesParseError> {
109                 if Self::unicode_extension_key() == Some(*key) {
110                     Self::try_from(value).map(Some)
111                 } else {
112                     Ok(None)
113                 }
114             }
115 
116             fn unicode_extension_value(&self) -> Option<$crate::extensions::unicode::Value> {
117                 Some((*self).into())
118             }
119         }
120 
121         impl TryFrom<&$crate::extensions::unicode::Value> for $name {
122             type Error = $crate::preferences::extensions::unicode::errors::PreferencesParseError;
123 
124             fn try_from(s: &$crate::extensions::unicode::Value) -> Result<Self, Self::Error> {
125                 let subtag = s.get_subtag(0)
126                                 // No subtag is equivalent to the "true" value.
127                                 .unwrap_or(&$crate::subtags::subtag!("true"));
128                 Ok(match subtag.as_str() {
129                     $(
130                         $key => {
131                             $crate::__enum_keyword_inner!($name, $variant$(, s, $v2, $($subk => $subv),*)?)
132                         }
133                     )*
134                     _ => {
135                         return Err(Self::Error::InvalidKeywordValue);
136                     }
137                 })
138             }
139         }
140 
141         impl From<$name>  for $crate::extensions::unicode::Value {
142             fn from(input: $name) -> $crate::extensions::unicode::Value {
143                 let mut result = $crate::extensions::unicode::Value::default();
144                 input.extend_value(&mut result);
145                 result
146             }
147         }
148 
149         impl $name {
150             pub(crate) fn extend_value(self, input: &mut $crate::extensions::unicode::Value) {
151                 match self {
152                     $(
153                         // This is circumventing a limitation of the macro_rules - we need to have a conditional
154                         // $()? case here for when the variant has a value, and macro_rules require us to
155                         // reference the $v2 inside it, but in match case it becomes a variable, so clippy
156                         // complaints.
157                         #[allow(non_snake_case)]
158                         Self::$variant $(($v2))? => {
159                             input.push_subtag($crate::subtags::subtag!($key));
160 
161                             $(
162                                 if let Some(v2) = $v2 {
163                                     match v2 {
164                                         $(
165                                             $v2::$subv => input.push_subtag($crate::subtags::subtag!($subk)),
166                                         )*
167                                     }
168                                 }
169                             )?
170                         },
171                     )*
172                 }
173             }
174 
175             /// A helper function for displaying as a `&str`.
176             pub const fn as_str(&self) -> &str {
177                 match self {
178                     $(
179                         // This is circumventing a limitation of the macro_rules - we need to have a conditional
180                         // $()? case here for when the variant has a value, and macro_rules require us to
181                         // reference the $v2 inside it, but in match case it becomes a variable, so clippy
182                         // complaints.
183                         #[allow(non_snake_case)]
184                         Self::$variant $(($v2))? => {
185                             $(
186                                 if let Some(v2) = $v2 {
187                                     return match v2 {
188                                         $(
189                                             $v2::$subv => concat!($key, '-', $subk),
190                                         )*
191                                     };
192                                 }
193                             )?
194                             return $key;
195                         },
196                     )*
197                 }
198             }
199         }
200     };
201 }
202 pub use __enum_keyword as enum_keyword;
203 
204 #[cfg(test)]
205 mod tests {
206     use super::*;
207     use crate::extensions::unicode;
208     use core::str::FromStr;
209 
210     #[test]
enum_keywords_test()211     fn enum_keywords_test() {
212         enum_keyword!(DummyKeyword {
213             ("standard" => Standard),
214             ("rare" => Rare),
215         }, "dk");
216 
217         let v = unicode::Value::from_str("standard").unwrap();
218         let dk = DummyKeyword::try_from(&v).unwrap();
219         assert_eq!(dk, DummyKeyword::Standard);
220         assert_eq!(unicode::Value::from(dk), v);
221 
222         let v = unicode::Value::from_str("rare").unwrap();
223         let dk = DummyKeyword::try_from(&v).unwrap();
224         assert_eq!(dk, DummyKeyword::Rare);
225         assert_eq!(unicode::Value::from(dk), v);
226 
227         let v = unicode::Value::from_str("foo").unwrap();
228         let dk = DummyKeyword::try_from(&v);
229         assert!(dk.is_err());
230 
231         assert_eq!(DummyKeyword::Standard.as_str(), "standard");
232     }
233 
234     #[test]
enum_keywords_nested_test()235     fn enum_keywords_nested_test() {
236         enum_keyword!(DummySubKeyword { Standard, Rare });
237 
238         enum_keyword!(DummyKeyword {
239             ("default" => Default),
240             ("sub" => Sub(DummySubKeyword) {
241                 ("standard" => Standard),
242                 ("rare" => Rare)
243             })
244         }, "dk");
245 
246         let v = unicode::Value::from_str("default").unwrap();
247         let dk = DummyKeyword::try_from(&v).unwrap();
248         assert_eq!(dk, DummyKeyword::Default);
249         assert_eq!(unicode::Value::from(dk), v);
250 
251         let v = unicode::Value::from_str("sub").unwrap();
252         let dk = DummyKeyword::try_from(&v).unwrap();
253         assert_eq!(dk, DummyKeyword::Sub(None));
254         assert_eq!(unicode::Value::from(dk), v);
255 
256         let v = unicode::Value::from_str("foo").unwrap();
257         let dk = DummyKeyword::try_from(&v);
258         assert!(dk.is_err());
259 
260         let v = unicode::Value::from_str("sub-standard").unwrap();
261         let dk = DummyKeyword::try_from(&v).unwrap();
262         assert_eq!(dk, DummyKeyword::Sub(Some(DummySubKeyword::Standard)));
263         assert_eq!(unicode::Value::from(dk), v);
264 
265         let v = unicode::Value::from_str("sub-rare").unwrap();
266         let dk = DummyKeyword::try_from(&v).unwrap();
267         assert_eq!(dk, DummyKeyword::Sub(Some(DummySubKeyword::Rare)));
268         assert_eq!(unicode::Value::from(dk), v);
269 
270         let v = unicode::Value::from_str("sub-foo").unwrap();
271         let dk = DummyKeyword::try_from(&v).unwrap();
272         assert_eq!(dk, DummyKeyword::Sub(None));
273         assert_eq!(unicode::Value::from(dk), unicode::value!("sub"));
274 
275         assert_eq!(
276             DummyKeyword::Sub(Some(DummySubKeyword::Rare)).as_str(),
277             "sub-rare"
278         );
279     }
280 }
281