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