builtins.rs (10510B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use crate::ffi; 6 use fluent::types::{FluentNumberOptions, FluentType, FluentValue}; 7 use fluent::FluentArgs; 8 use intl_memoizer::IntlLangMemoizer; 9 use intl_memoizer::Memoizable; 10 use nsstring::nsCString; 11 use std::borrow::Cow; 12 use std::ptr::NonNull; 13 use unic_langid::LanguageIdentifier; 14 15 pub struct NumberFormat { 16 raw: Option<NonNull<ffi::RawNumberFormatter>>, 17 } 18 19 /** 20 * According to http://userguide.icu-project.org/design, as long as we constrain 21 * ourselves to const APIs ICU is const-correct. 22 */ 23 unsafe impl Send for NumberFormat {} 24 unsafe impl Sync for NumberFormat {} 25 26 impl NumberFormat { 27 pub fn new(locale: LanguageIdentifier, options: &FluentNumberOptions) -> Self { 28 let loc: String = locale.to_string(); 29 Self { 30 raw: unsafe { 31 NonNull::new(ffi::FluentBuiltInNumberFormatterCreate( 32 &loc.into(), 33 &options.into(), 34 )) 35 }, 36 } 37 } 38 39 pub fn format(&self, input: f64) -> String { 40 if let Some(raw) = self.raw { 41 unsafe { 42 let mut byte_count = 0; 43 let mut capacity = 0; 44 let buffer = ffi::FluentBuiltInNumberFormatterFormat( 45 raw.as_ptr(), 46 input, 47 &mut byte_count, 48 &mut capacity, 49 ); 50 if buffer.is_null() { 51 return String::new(); 52 } 53 String::from_raw_parts(buffer, byte_count, capacity) 54 } 55 } else { 56 String::new() 57 } 58 } 59 } 60 61 impl Drop for NumberFormat { 62 fn drop(&mut self) { 63 if let Some(raw) = self.raw { 64 unsafe { ffi::FluentBuiltInNumberFormatterDestroy(raw.as_ptr()) }; 65 } 66 } 67 } 68 69 impl Memoizable for NumberFormat { 70 type Args = (FluentNumberOptions,); 71 type Error = &'static str; 72 fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> { 73 Ok(NumberFormat::new(lang, &args.0)) 74 } 75 } 76 77 #[repr(C)] 78 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 79 pub enum FluentDateTimeStyle { 80 Full, 81 Long, 82 Medium, 83 Short, 84 None, 85 } 86 87 impl Default for FluentDateTimeStyle { 88 fn default() -> Self { 89 Self::None 90 } 91 } 92 93 impl From<&str> for FluentDateTimeStyle { 94 fn from(input: &str) -> Self { 95 match input { 96 "full" => Self::Full, 97 "long" => Self::Long, 98 "medium" => Self::Medium, 99 "short" => Self::Short, 100 _ => Self::None, 101 } 102 } 103 } 104 105 #[repr(C)] 106 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 107 pub enum FluentDateTimeHourCycle { 108 H24, 109 H23, 110 H12, 111 H11, 112 None, 113 } 114 115 impl Default for FluentDateTimeHourCycle { 116 fn default() -> Self { 117 Self::None 118 } 119 } 120 121 impl From<&str> for FluentDateTimeHourCycle { 122 fn from(input: &str) -> Self { 123 match input { 124 "h24" => Self::H24, 125 "h23" => Self::H23, 126 "h12" => Self::H12, 127 "h11" => Self::H11, 128 _ => Self::None, 129 } 130 } 131 } 132 133 #[repr(C)] 134 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 135 pub enum FluentDateTimeTextComponent { 136 Long, 137 Short, 138 Narrow, 139 None, 140 } 141 142 impl Default for FluentDateTimeTextComponent { 143 fn default() -> Self { 144 Self::None 145 } 146 } 147 148 impl From<&str> for FluentDateTimeTextComponent { 149 fn from(input: &str) -> Self { 150 match input { 151 "long" => Self::Long, 152 "short" => Self::Short, 153 "narrow" => Self::Narrow, 154 _ => Self::None, 155 } 156 } 157 } 158 159 #[repr(C)] 160 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 161 pub enum FluentDateTimeNumericComponent { 162 Numeric, 163 TwoDigit, 164 None, 165 } 166 167 impl Default for FluentDateTimeNumericComponent { 168 fn default() -> Self { 169 Self::None 170 } 171 } 172 173 impl From<&str> for FluentDateTimeNumericComponent { 174 fn from(input: &str) -> Self { 175 match input { 176 "numeric" => Self::Numeric, 177 "2-digit" => Self::TwoDigit, 178 _ => Self::None, 179 } 180 } 181 } 182 183 #[repr(C)] 184 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 185 pub enum FluentDateTimeMonthComponent { 186 Numeric, 187 TwoDigit, 188 Long, 189 Short, 190 Narrow, 191 None, 192 } 193 194 impl Default for FluentDateTimeMonthComponent { 195 fn default() -> Self { 196 Self::None 197 } 198 } 199 200 impl From<&str> for FluentDateTimeMonthComponent { 201 fn from(input: &str) -> Self { 202 match input { 203 "numeric" => Self::Numeric, 204 "2-digit" => Self::TwoDigit, 205 "long" => Self::Long, 206 "short" => Self::Short, 207 "narrow" => Self::Narrow, 208 _ => Self::None, 209 } 210 } 211 } 212 213 #[repr(C)] 214 #[derive(Debug, Clone, Hash, PartialEq, Eq)] 215 pub enum FluentDateTimeTimeZoneNameComponent { 216 Long, 217 Short, 218 None, 219 } 220 221 impl Default for FluentDateTimeTimeZoneNameComponent { 222 fn default() -> Self { 223 Self::None 224 } 225 } 226 227 impl From<&str> for FluentDateTimeTimeZoneNameComponent { 228 fn from(input: &str) -> Self { 229 match input { 230 "long" => Self::Long, 231 "short" => Self::Short, 232 _ => Self::None, 233 } 234 } 235 } 236 237 #[repr(C)] 238 #[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] 239 pub struct FluentDateTimeOptions { 240 pub date_style: FluentDateTimeStyle, 241 pub time_style: FluentDateTimeStyle, 242 pub hour_cycle: FluentDateTimeHourCycle, 243 pub weekday: FluentDateTimeTextComponent, 244 pub era: FluentDateTimeTextComponent, 245 pub year: FluentDateTimeNumericComponent, 246 pub month: FluentDateTimeMonthComponent, 247 pub day: FluentDateTimeNumericComponent, 248 pub hour: FluentDateTimeNumericComponent, 249 pub minute: FluentDateTimeNumericComponent, 250 pub second: FluentDateTimeNumericComponent, 251 pub time_zone_name: FluentDateTimeTimeZoneNameComponent, 252 } 253 254 impl FluentDateTimeOptions { 255 pub fn merge(&mut self, opts: &FluentArgs) { 256 for (key, value) in opts.iter() { 257 match (key, value) { 258 ("dateStyle", FluentValue::String(n)) => { 259 self.date_style = n.as_ref().into(); 260 } 261 ("timeStyle", FluentValue::String(n)) => { 262 self.time_style = n.as_ref().into(); 263 } 264 ("hourCycle", FluentValue::String(n)) => { 265 self.hour_cycle = n.as_ref().into(); 266 } 267 ("weekday", FluentValue::String(n)) => { 268 self.weekday = n.as_ref().into(); 269 } 270 ("era", FluentValue::String(n)) => { 271 self.era = n.as_ref().into(); 272 } 273 ("year", FluentValue::String(n)) => { 274 self.year = n.as_ref().into(); 275 } 276 ("month", FluentValue::String(n)) => { 277 self.month = n.as_ref().into(); 278 } 279 ("day", FluentValue::String(n)) => { 280 self.day = n.as_ref().into(); 281 } 282 ("hour", FluentValue::String(n)) => { 283 self.hour = n.as_ref().into(); 284 } 285 ("minute", FluentValue::String(n)) => { 286 self.minute = n.as_ref().into(); 287 } 288 ("second", FluentValue::String(n)) => { 289 self.second = n.as_ref().into(); 290 } 291 ("timeZoneName", FluentValue::String(n)) => { 292 self.time_zone_name = n.as_ref().into(); 293 } 294 _ => {} 295 } 296 } 297 } 298 } 299 300 #[derive(Debug, PartialEq, Clone)] 301 pub struct FluentDateTime { 302 epoch: f64, 303 options: FluentDateTimeOptions, 304 } 305 306 impl FluentType for FluentDateTime { 307 fn duplicate(&self) -> Box<dyn FluentType + Send> { 308 Box::new(self.clone()) 309 } 310 fn as_string(&self, intls: &IntlLangMemoizer) -> Cow<'static, str> { 311 let result = intls 312 .with_try_get::<DateTimeFormat, _, _>((self.options.clone(),), |dtf| { 313 dtf.format(self.epoch) 314 }) 315 .expect("Failed to retrieve a DateTimeFormat instance."); 316 result.into() 317 } 318 fn as_string_threadsafe( 319 &self, 320 _: &intl_memoizer::concurrent::IntlLangMemoizer, 321 ) -> Cow<'static, str> { 322 unimplemented!() 323 } 324 } 325 326 impl std::fmt::Display for FluentDateTime { 327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 328 write!(f, "DATETIME: {}", self.epoch) 329 } 330 } 331 332 impl FluentDateTime { 333 pub fn new(epoch: f64, options: FluentDateTimeOptions) -> Self { 334 Self { epoch, options } 335 } 336 } 337 338 pub struct DateTimeFormat { 339 raw: Option<NonNull<ffi::RawDateTimeFormatter>>, 340 } 341 342 /** 343 * According to http://userguide.icu-project.org/design, as long as we constrain 344 * ourselves to const APIs ICU is const-correct. 345 */ 346 unsafe impl Send for DateTimeFormat {} 347 unsafe impl Sync for DateTimeFormat {} 348 349 impl DateTimeFormat { 350 pub fn new(locale: LanguageIdentifier, options: FluentDateTimeOptions) -> Self { 351 // ICU needs null-termination here, otherwise we could use nsCStr. 352 let loc: nsCString = locale.to_string().into(); 353 Self { 354 raw: unsafe { NonNull::new(ffi::FluentBuiltInDateTimeFormatterCreate(&loc, options)) }, 355 } 356 } 357 358 pub fn format(&self, input: f64) -> String { 359 if let Some(raw) = self.raw { 360 unsafe { 361 let mut byte_count = 0; 362 let buffer = 363 ffi::FluentBuiltInDateTimeFormatterFormat(raw.as_ptr(), input, &mut byte_count); 364 if buffer.is_null() { 365 return String::new(); 366 } 367 String::from_raw_parts(buffer, byte_count as usize, byte_count as usize) 368 } 369 } else { 370 String::new() 371 } 372 } 373 } 374 375 impl Drop for DateTimeFormat { 376 fn drop(&mut self) { 377 if let Some(raw) = self.raw { 378 unsafe { ffi::FluentBuiltInDateTimeFormatterDestroy(raw.as_ptr()) }; 379 } 380 } 381 } 382 383 impl Memoizable for DateTimeFormat { 384 type Args = (FluentDateTimeOptions,); 385 type Error = &'static str; 386 fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> { 387 Ok(DateTimeFormat::new(lang, args.0)) 388 } 389 }