Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::ffi;
use fluent::types::{FluentNumberOptions, FluentType, FluentValue};
use fluent::FluentArgs;
use intl_memoizer::IntlLangMemoizer;
use intl_memoizer::Memoizable;
use nsstring::nsCString;
use std::borrow::Cow;
use std::ptr::NonNull;
use unic_langid::LanguageIdentifier;
pub struct NumberFormat {
raw: Option<NonNull<ffi::RawNumberFormatter>>,
}
/**
* According to http://userguide.icu-project.org/design, as long as we constrain
* ourselves to const APIs ICU is const-correct.
*/
unsafe impl Send for NumberFormat {}
unsafe impl Sync for NumberFormat {}
impl NumberFormat {
pub fn new(locale: LanguageIdentifier, options: &FluentNumberOptions) -> Self {
let loc: String = locale.to_string();
Self {
raw: unsafe {
NonNull::new(ffi::FluentBuiltInNumberFormatterCreate(
&loc.into(),
&options.into(),
))
},
}
}
pub fn format(&self, input: f64) -> String {
if let Some(raw) = self.raw {
unsafe {
let mut byte_count = 0;
let mut capacity = 0;
let buffer = ffi::FluentBuiltInNumberFormatterFormat(
raw.as_ptr(),
input,
&mut byte_count,
&mut capacity,
);
if buffer.is_null() {
return String::new();
}
String::from_raw_parts(buffer, byte_count, capacity)
}
} else {
String::new()
}
}
}
impl Drop for NumberFormat {
fn drop(&mut self) {
if let Some(raw) = self.raw {
unsafe { ffi::FluentBuiltInNumberFormatterDestroy(raw.as_ptr()) };
}
}
}
impl Memoizable for NumberFormat {
type Args = (FluentNumberOptions,);
type Error = &'static str;
fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> {
Ok(NumberFormat::new(lang, &args.0))
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum FluentDateTimeStyle {
Full,
Long,
Medium,
Short,
None,
}
impl Default for FluentDateTimeStyle {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeStyle {
fn from(input: &str) -> Self {
match input {
"full" => Self::Full,
"long" => Self::Long,
"medium" => Self::Medium,
"short" => Self::Short,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FluentDateTimeHourCycle {
H24,
H23,
H12,
H11,
None,
}
impl Default for FluentDateTimeHourCycle {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeHourCycle {
fn from(input: &str) -> Self {
match input {
"h24" => Self::H24,
"h23" => Self::H23,
"h12" => Self::H12,
"h11" => Self::H11,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FluentDateTimeTextComponent {
Long,
Short,
Narrow,
None,
}
impl Default for FluentDateTimeTextComponent {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeTextComponent {
fn from(input: &str) -> Self {
match input {
"long" => Self::Long,
"short" => Self::Short,
"narrow" => Self::Narrow,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FluentDateTimeNumericComponent {
Numeric,
TwoDigit,
None,
}
impl Default for FluentDateTimeNumericComponent {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeNumericComponent {
fn from(input: &str) -> Self {
match input {
"numeric" => Self::Numeric,
"2-digit" => Self::TwoDigit,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FluentDateTimeMonthComponent {
Numeric,
TwoDigit,
Long,
Short,
Narrow,
None,
}
impl Default for FluentDateTimeMonthComponent {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeMonthComponent {
fn from(input: &str) -> Self {
match input {
"numeric" => Self::Numeric,
"2-digit" => Self::TwoDigit,
"long" => Self::Long,
"short" => Self::Short,
"narrow" => Self::Narrow,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum FluentDateTimeTimeZoneNameComponent {
Long,
Short,
None,
}
impl Default for FluentDateTimeTimeZoneNameComponent {
fn default() -> Self {
Self::None
}
}
impl From<&str> for FluentDateTimeTimeZoneNameComponent {
fn from(input: &str) -> Self {
match input {
"long" => Self::Long,
"short" => Self::Short,
_ => Self::None,
}
}
}
#[repr(C)]
#[derive(Default, Debug, Clone, Hash, PartialEq, Eq)]
pub struct FluentDateTimeOptions {
pub date_style: FluentDateTimeStyle,
pub time_style: FluentDateTimeStyle,
pub hour_cycle: FluentDateTimeHourCycle,
pub weekday: FluentDateTimeTextComponent,
pub era: FluentDateTimeTextComponent,
pub year: FluentDateTimeNumericComponent,
pub month: FluentDateTimeMonthComponent,
pub day: FluentDateTimeNumericComponent,
pub hour: FluentDateTimeNumericComponent,
pub minute: FluentDateTimeNumericComponent,
pub second: FluentDateTimeNumericComponent,
pub time_zone_name: FluentDateTimeTimeZoneNameComponent,
}
impl FluentDateTimeOptions {
pub fn merge(&mut self, opts: &FluentArgs) {
for (key, value) in opts.iter() {
match (key, value) {
("dateStyle", FluentValue::String(n)) => {
self.date_style = n.as_ref().into();
}
("timeStyle", FluentValue::String(n)) => {
self.time_style = n.as_ref().into();
}
("hourCycle", FluentValue::String(n)) => {
self.hour_cycle = n.as_ref().into();
}
("weekday", FluentValue::String(n)) => {
self.weekday = n.as_ref().into();
}
("era", FluentValue::String(n)) => {
self.era = n.as_ref().into();
}
("year", FluentValue::String(n)) => {
self.year = n.as_ref().into();
}
("month", FluentValue::String(n)) => {
self.month = n.as_ref().into();
}
("day", FluentValue::String(n)) => {
self.day = n.as_ref().into();
}
("hour", FluentValue::String(n)) => {
self.hour = n.as_ref().into();
}
("minute", FluentValue::String(n)) => {
self.minute = n.as_ref().into();
}
("second", FluentValue::String(n)) => {
self.second = n.as_ref().into();
}
("timeZoneName", FluentValue::String(n)) => {
self.time_zone_name = n.as_ref().into();
}
_ => {}
}
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FluentDateTime {
epoch: f64,
options: FluentDateTimeOptions,
}
impl FluentType for FluentDateTime {
fn duplicate(&self) -> Box<dyn FluentType + Send> {
Box::new(self.clone())
}
fn as_string(&self, intls: &IntlLangMemoizer) -> Cow<'static, str> {
let result = intls
.with_try_get::<DateTimeFormat, _, _>((self.options.clone(),), |dtf| {
dtf.format(self.epoch)
})
.expect("Failed to retrieve a DateTimeFormat instance.");
result.into()
}
fn as_string_threadsafe(
&self,
_: &intl_memoizer::concurrent::IntlLangMemoizer,
) -> Cow<'static, str> {
unimplemented!()
}
}
impl std::fmt::Display for FluentDateTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DATETIME: {}", self.epoch)
}
}
impl FluentDateTime {
pub fn new(epoch: f64, options: FluentDateTimeOptions) -> Self {
Self { epoch, options }
}
}
pub struct DateTimeFormat {
raw: Option<NonNull<ffi::RawDateTimeFormatter>>,
}
/**
* According to http://userguide.icu-project.org/design, as long as we constrain
* ourselves to const APIs ICU is const-correct.
*/
unsafe impl Send for DateTimeFormat {}
unsafe impl Sync for DateTimeFormat {}
impl DateTimeFormat {
pub fn new(locale: LanguageIdentifier, options: FluentDateTimeOptions) -> Self {
// ICU needs null-termination here, otherwise we could use nsCStr.
let loc: nsCString = locale.to_string().into();
Self {
raw: unsafe { NonNull::new(ffi::FluentBuiltInDateTimeFormatterCreate(&loc, options)) },
}
}
pub fn format(&self, input: f64) -> String {
if let Some(raw) = self.raw {
unsafe {
let mut byte_count = 0;
let buffer =
ffi::FluentBuiltInDateTimeFormatterFormat(raw.as_ptr(), input, &mut byte_count);
if buffer.is_null() {
return String::new();
}
String::from_raw_parts(buffer, byte_count as usize, byte_count as usize)
}
} else {
String::new()
}
}
}
impl Drop for DateTimeFormat {
fn drop(&mut self) {
if let Some(raw) = self.raw {
unsafe { ffi::FluentBuiltInDateTimeFormatterDestroy(raw.as_ptr()) };
}
}
}
impl Memoizable for DateTimeFormat {
type Args = (FluentDateTimeOptions,);
type Error = &'static str;
fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> {
Ok(DateTimeFormat::new(lang, args.0))
}
}