use {
rust_icu_common as common, rust_icu_sys as sys, rust_icu_sys::*, rust_icu_uloc as uloc,
rust_icu_ustring as ustring, std::convert::TryFrom,
};
use sealed::Sealed;
#[doc(hidden)]
pub use {rust_icu_sys as __sys, rust_icu_ustring as __ustring, std as __std};
#[derive(Debug)]
pub struct UMessageFormat {
rep: std::rc::Rc<Rep>,
}
#[derive(Debug)]
struct Rep {
rep: *mut sys::UMessageFormat,
}
impl Drop for Rep {
fn drop(&mut self) {
unsafe {
versioned_function!(umsg_close)(self.rep);
}
}
}
impl Clone for UMessageFormat {
fn clone(&self) -> Self {
UMessageFormat {
rep: self.rep.clone(),
}
}
}
impl UMessageFormat {
pub fn try_from(
pattern: &ustring::UChar,
locale: &uloc::ULoc,
) -> Result<UMessageFormat, common::Error> {
let pstr = pattern.as_c_ptr();
let loc = locale.as_c_str();
let mut status = common::Error::OK_CODE;
let mut parse_status = common::NO_PARSE_ERROR;
let rep = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(umsg_open)(
pstr,
pattern.len() as i32,
loc.as_ptr(),
&mut parse_status,
&mut status,
)
};
common::Error::ok_or_warning(status)?;
common::parse_ok(parse_status)?;
Ok(UMessageFormat {
rep: std::rc::Rc::new(Rep { rep }),
})
}
}
#[macro_export]
macro_rules! message_format {
($dest:expr $(,)?) => {
$crate::__std::compile_error!("you should not format a message without parameters")
};
($dest:expr, $( {$arg:expr => $t:ident} ),+ $(,)?) => {
unsafe {
$crate::format_args(&$dest, ($($crate::checkarg!($arg, $t),)*))
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! checkarg {
($e:expr, Double) => {{
let x: $crate::__std::primitive::f64 = $e;
x
}};
($e:expr, String) => {{
let x: $crate::__ustring::UChar = $e;
x
}};
($e:expr, Integer) => {{
let x: $crate::__std::primitive::i32 = $e;
x
}};
($e:expr, Long) => {{
let x: $crate::__std::primitive::i64 = $e;
x
}};
($e:expr, Date) => {{
let x: $crate::__sys::UDate = $e;
x
}};
}
#[doc(hidden)]
pub unsafe fn format_args(
fmt: &UMessageFormat,
args: impl FormatArgs,
) -> Result<String, common::Error> {
const CAP: usize = 1024;
let mut status = common::Error::OK_CODE;
let mut result = ustring::UChar::new_with_capacity(CAP);
let total_size =
args.format(fmt.rep.rep, result.as_mut_c_ptr(), CAP as i32, &mut status) as usize;
common::Error::ok_or_warning(status)?;
result.resize(total_size);
if total_size > CAP {
args.format(
fmt.rep.rep,
result.as_mut_c_ptr(),
total_size as i32,
&mut status,
);
common::Error::ok_or_warning(status)?;
}
String::try_from(&result)
}
mod sealed {
pub trait Sealed {}
}
#[doc(hidden)]
pub trait FormatArg: Sealed {
type Raw;
fn to_raw(&self) -> Self::Raw;
}
impl Sealed for f64 {}
impl FormatArg for f64 {
type Raw = f64;
fn to_raw(&self) -> Self::Raw {
*self
}
}
impl Sealed for ustring::UChar {}
impl FormatArg for ustring::UChar {
type Raw = *const UChar;
fn to_raw(&self) -> Self::Raw {
self.as_c_ptr()
}
}
impl Sealed for i32 {}
impl FormatArg for i32 {
type Raw = i32;
fn to_raw(&self) -> Self::Raw {
*self
}
}
impl Sealed for i64 {}
impl FormatArg for i64 {
type Raw = i64;
fn to_raw(&self) -> Self::Raw {
*self
}
}
#[doc(hidden)]
pub trait FormatArgs: Sealed {
#[doc(hidden)]
unsafe fn format(
&self,
fmt: *const sys::UMessageFormat,
result: *mut UChar,
result_length: i32,
status: *mut UErrorCode,
) -> i32;
}
macro_rules! impl_format_args_for_tuples {
($(($($param:ident),*),)*) => {
$(
impl<$($param: FormatArg,)*> Sealed for ($($param,)*) {}
impl<$($param: FormatArg,)*> FormatArgs for ($($param,)*) {
unsafe fn format(
&self,
fmt: *const sys::UMessageFormat,
result: *mut UChar,
result_length: i32,
status: *mut UErrorCode,
) -> i32 {
#[allow(non_snake_case)]
let ($($param,)*) = self;
$(
#[allow(non_snake_case)]
let $param = $crate::FormatArg::to_raw($param);
)*
versioned_function!(umsg_format)(
fmt,
result,
result_length,
status,
$($param,)*
)
}
}
)*
}
}
impl_format_args_for_tuples! {
(A),
(A, B),
(A, B, C),
(A, B, C, D),
(A, B, C, D, E),
(A, B, C, D, E, F),
(A, B, C, D, E, F, G),
(A, B, C, D, E, F, G, H),
(A, B, C, D, E, F, G, H, I),
(A, B, C, D, E, F, G, H, I, J),
(A, B, C, D, E, F, G, H, I, J, K),
(A, B, C, D, E, F, G, H, I, J, K, L),
(A, B, C, D, E, F, G, H, I, J, K, L, M),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y),
(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z),
}
#[cfg(test)]
mod tests {
use super::*;
use rust_icu_ucal as ucal;
struct TzSave(String);
impl Drop for TzSave {
fn drop(&mut self) {
ucal::set_default_time_zone(&self.0).expect("timezone set success");
}
}
#[test]
fn tzsave() -> Result<(), common::Error> {
let _ = TzSave(ucal::get_default_time_zone()?);
ucal::set_default_time_zone("Europe/Amsterdam")?;
Ok(())
}
#[test]
fn basic() -> Result<(), common::Error> {
let _ = TzSave(ucal::get_default_time_zone()?);
ucal::set_default_time_zone("Europe/Amsterdam")?;
let loc = uloc::ULoc::try_from("en-US")?;
let msg = ustring::UChar::try_from(
r"Formatted double: {0,number,##.#},
Formatted integer: {1,number,integer},
Formatted string: {2},
Date: {3,date,full}",
)?;
let fmt = crate::UMessageFormat::try_from(&msg, &loc)?;
let hello = ustring::UChar::try_from("Hello! Добар дан!")?;
let value: i32 = 31337;
let result = message_format!(
fmt,
{ 43.4 => Double },
{ value => Integer },
{ hello => String },
{ 0.0 => Date }
)?;
assert_eq!(
r"Formatted double: 43.4,
Formatted integer: 31,337,
Formatted string: Hello! Добар дан!,
Date: Thursday, January 1, 1970",
result
);
Ok(())
}
#[test]
fn clone() -> Result<(), common::Error> {
let loc = uloc::ULoc::try_from("en-US-u-tz-uslax")?;
let msg = ustring::UChar::try_from(r"Formatted double: {0,number,##.#}")?;
let fmt = crate::UMessageFormat::try_from(&msg, &loc)?;
#[allow(clippy::redundant_clone)]
let result = message_format!(fmt.clone(), { 43.43 => Double })?;
assert_eq!(r"Formatted double: 43.4", result);
Ok(())
}
}