#![feature(proc_macro_hygiene)]
use {
paste, rust_icu_common as common,
rust_icu_common::format_ustring_for_type,
rust_icu_common::generalized_fallible_getter,
rust_icu_common::generalized_fallible_setter,
rust_icu_common::simple_drop_impl,
rust_icu_sys as sys,
rust_icu_sys::versioned_function,
rust_icu_sys::*,
rust_icu_uformattable as uformattable, rust_icu_uloc as uloc, rust_icu_ustring as ustring,
rust_icu_ustring::buffered_uchar_method_with_retry,
std::{convert::TryFrom, convert::TryInto, ptr},
};
macro_rules! attribute{
($method_name:ident, $original_method_name:ident, $type_name:ty) => (
paste::item! {
pub fn [< get_ $method_name >](&self, attr: sys::UNumberFormatAttribute) -> $type_name {
unsafe {
versioned_function!([< unum_get $original_method_name >])(self.rep.as_ptr(), attr)
}
}
pub fn [< set_ $method_name >](&mut self, attr: sys::UNumberFormatAttribute, value: $type_name) {
unsafe {
versioned_function!([< unum_set $original_method_name >])(self.rep.as_ptr(), attr, value)
}
}
}
)
}
#[derive(Debug)]
pub struct UNumberFormat {
rep: ptr::NonNull<sys::UNumberFormat>,
}
simple_drop_impl!(UNumberFormat, unum_close);
impl UNumberFormat {
pub fn try_new_decimal_pattern_ustring(
pattern: &ustring::UChar,
locale: &uloc::ULoc,
) -> Result<UNumberFormat, common::Error> {
UNumberFormat::try_new_style_pattern_ustring(
sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL,
pattern,
locale,
)
}
pub fn try_new_decimal_rule_based_ustring(
rule: &ustring::UChar,
locale: &uloc::ULoc,
) -> Result<UNumberFormat, common::Error> {
UNumberFormat::try_new_style_pattern_ustring(
sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED,
rule,
locale,
)
}
pub fn try_new_with_style(
style: sys::UNumberFormatStyle,
locale: &uloc::ULoc,
) -> Result<UNumberFormat, common::Error> {
let rule = ustring::UChar::try_from("")?;
assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED);
assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL);
UNumberFormat::try_new_style_pattern_ustring(style, &rule, locale)
}
fn try_new_style_pattern_ustring(
style: sys::UNumberFormatStyle,
pattern: &ustring::UChar,
locale: &uloc::ULoc,
) -> Result<UNumberFormat, common::Error> {
let mut status = common::Error::OK_CODE;
let mut parse = common::NO_PARSE_ERROR.clone();
let loc = locale.as_c_str();
let rep = unsafe {
assert!(common::Error::is_ok(status));
assert!(common::parse_ok(parse).is_ok());
versioned_function!(unum_open)(
style,
pattern.as_c_ptr(),
pattern.len() as i32,
loc.as_ptr(),
&mut parse,
&mut status,
)
};
common::Error::ok_or_warning(status)?;
common::parse_ok(parse)?;
assert_ne!(rep, 0 as *mut sys::UNumberFormat);
Ok(UNumberFormat {
rep: ptr::NonNull::new(rep).unwrap(),
})
}
pub fn try_clone(&self) -> Result<UNumberFormat, common::Error> {
let mut status = common::Error::OK_CODE;
let rep = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(unum_clone)(self.rep.as_ptr(), &mut status)
};
common::Error::ok_or_warning(status)?;
Ok(UNumberFormat {
rep: ptr::NonNull::new(rep).unwrap(),
})
}
format_ustring_for_type!(format, unum_format, i32);
format_ustring_for_type!(format_i64, unum_formatInt64, i64);
format_ustring_for_type!(format_f64, unum_formatDouble, f64);
pub fn format_double_for_fields_ustring<'a>(
&'a self,
number: f64,
) -> Result<
(
ustring::UChar,
UFieldPositionIterator<'a, *const sys::UNumberFormat>,
),
common::Error,
> {
let mut iterator = UFieldPositionIterator::try_new_unowned()?;
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
format_for_fields_impl,
CAPACITY,
[format: *const sys::UNumberFormat, number: f64,],
[iter: *mut sys::UFieldPositionIterator,]
);
let result = format_for_fields_impl(
versioned_function!(unum_formatDoubleForFields),
self.rep.as_ptr(),
number,
iterator.as_mut_ptr(),
)?;
Ok((result, iterator))
}
pub fn format_decimal(&self, decimal: &str) -> Result<String, common::Error> {
let result = self.format_decimal_ustring(decimal)?;
String::try_from(&result)
}
pub fn format_decimal_ustring(&self, decimal: &str) -> Result<ustring::UChar, common::Error> {
use std::os::raw;
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
format_decimal_impl,
CAPACITY,
[
format: *const sys::UNumberFormat,
ptr: *const raw::c_char,
len: i32,
],
[pos: *mut sys::UFieldPosition,]
);
format_decimal_impl(
versioned_function!(unum_formatDecimal),
self.rep.as_ptr(),
decimal.as_ptr() as *const raw::c_char,
decimal.len() as i32,
0 as *mut sys::UFieldPosition,
)
}
pub fn format_double_currency(
&self,
number: f64,
currency: &str,
) -> Result<String, common::Error> {
let currency = ustring::UChar::try_from(currency)?;
let result = self.format_double_currency_ustring(number, ¤cy)?;
String::try_from(&result)
}
pub fn format_double_currency_ustring(
&self,
number: f64,
currency: &ustring::UChar,
) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
format_double_currency_impl,
CAPACITY,
[
format: *const sys::UNumberFormat,
number: f64,
currency: *mut sys::UChar,
],
[pos: *mut sys::UFieldPosition,]
);
let mut currencyz = currency.clone();
currencyz.make_z();
format_double_currency_impl(
versioned_function!(unum_formatDoubleCurrency),
self.rep.as_ptr(),
number,
currencyz.as_mut_c_ptr(),
0 as *mut sys::UFieldPosition,
)
}
pub fn parse_to_formattable<'a>(
&'a self,
text: &str,
parse_position: Option<i32>,
) -> Result<uformattable::UFormattable<'a>, common::Error> {
let ustr = ustring::UChar::try_from(text)?;
self.parse_to_formattable_ustring(&ustr, parse_position)
}
pub fn parse_to_formattable_ustring<'a>(
&'a self,
text: &ustring::UChar,
parse_position: Option<i32>,
) -> Result<uformattable::UFormattable<'a>, common::Error> {
let mut fmt = uformattable::UFormattable::try_new()?;
let mut status = common::Error::OK_CODE;
let mut pos = parse_position.unwrap_or(0);
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(unum_parseToUFormattable)(
self.rep.as_ptr(),
fmt.as_mut_ptr(),
text.as_c_ptr(),
text.len() as i32,
&mut pos,
&mut status,
)
};
common::Error::ok_or_warning(status)?;
Ok(fmt)
}
pub fn format_formattable<'a>(
&self,
fmt: &uformattable::UFormattable<'a>,
) -> Result<String, common::Error> {
let result = self.format_formattable_ustring(fmt)?;
String::try_from(&result)
}
pub fn format_formattable_ustring<'a>(
&self,
fmt: &uformattable::UFormattable<'a>,
) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
format_formattable_impl,
CAPACITY,
[
format: *const sys::UNumberFormat,
fmt: *const sys::UFormattable,
],
[pos: *mut sys::UFieldPosition,]
);
format_formattable_impl(
versioned_function!(unum_formatUFormattable),
self.rep.as_ptr(),
fmt.as_ptr(),
0 as *mut sys::UFieldPosition,
)
}
attribute!(attribute, Attribute, i32);
attribute!(double_attribute, DoubleAttribute, f64);
pub fn get_text_attribute(
&self,
tag: sys::UNumberFormatTextAttribute,
) -> Result<String, common::Error> {
let result = self.get_text_attribute_ustring(tag)?;
String::try_from(&result)
}
pub fn get_text_attribute_ustring(
&self,
tag: sys::UNumberFormatTextAttribute,
) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
get_text_attribute_impl,
CAPACITY,
[
rep: *const sys::UNumberFormat,
tag: sys::UNumberFormatTextAttribute,
],
[]
);
get_text_attribute_impl(
versioned_function!(unum_getTextAttribute),
self.rep.as_ptr(),
tag,
)
}
pub fn set_text_attribute(
&mut self,
tag: sys::UNumberFormatTextAttribute,
new_value: &str,
) -> Result<(), common::Error> {
let new_value = ustring::UChar::try_from(new_value)?;
self.set_text_attribute_ustring(tag, &new_value)?;
Ok(())
}
pub fn set_text_attribute_ustring(
&mut self,
tag: sys::UNumberFormatTextAttribute,
new_value: &ustring::UChar,
) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(unum_setTextAttribute)(
self.rep.as_ptr(),
tag,
new_value.as_c_ptr(),
new_value.len() as i32,
&mut status,
)
};
common::Error::ok_or_warning(status)?;
Ok(())
}
pub fn get_pattern(&self, is_localized: bool) -> Result<String, common::Error> {
let result = self.get_pattern_ustring(is_localized)?;
String::try_from(&result)
}
pub fn get_pattern_ustring(&self, is_localized: bool) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
get_pattern_ustring_impl,
CAPACITY,
[rep: *const sys::UNumberFormat, is_localized: sys::UBool,],
[]
);
let result = get_pattern_ustring_impl(
versioned_function!(unum_toPattern),
self.rep.as_ptr(),
is_localized as sys::UBool,
);
result
}
pub fn get_symbol(&self, symbol: sys::UNumberFormatSymbol) -> Result<String, common::Error> {
let result = self.get_symbol_ustring(symbol)?;
String::try_from(&result)
}
pub fn get_symbol_ustring(
&self,
symbol: sys::UNumberFormatSymbol,
) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
get_symbol_impl,
CAPACITY,
[
rep: *const sys::UNumberFormat,
symbol: sys::UNumberFormatSymbol,
],
[]
);
get_symbol_impl(
versioned_function!(unum_getSymbol),
self.rep.as_ptr(),
symbol,
)
}
pub fn set_symbol(
&mut self,
symbol: sys::UNumberFormatSymbol,
value: &str,
) -> Result<(), common::Error> {
let value = ustring::UChar::try_from(value)?;
self.set_symbol_ustring(symbol, &value)
}
pub fn set_symbol_ustring(
&mut self,
symbol: sys::UNumberFormatSymbol,
value: &ustring::UChar,
) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(unum_setSymbol)(
self.rep.as_ptr(),
symbol,
value.as_c_ptr(),
value.len() as i32,
&mut status,
);
};
common::Error::ok_or_warning(status)?;
Ok(())
}
pub fn get_locale_by_type<'a>(
&'a self,
data_loc_type: sys::ULocDataLocaleType,
) -> Result<&'a str, common::Error> {
let mut status = common::Error::OK_CODE;
let cptr = unsafe {
assert!(common::Error::is_ok(status));
let raw = versioned_function!(unum_getLocaleByType)(
self.rep.as_ptr(),
data_loc_type,
&mut status,
);
std::ffi::CStr::from_ptr(raw)
};
common::Error::ok_or_warning(status)?;
cptr.to_str().map_err(|e| e.into())
}
generalized_fallible_getter!(
get_context,
unum_getContext,
[context_type: sys::UDisplayContextType,],
sys::UDisplayContext
);
generalized_fallible_setter!(set_context, unum_setContext, [value: sys::UDisplayContext,]);
}
pub struct UFieldPositionIterator<'a, T> {
rep: ptr::NonNull<sys::UFieldPositionIterator>,
#[allow(dead_code)]
owner: Option<&'a T>,
}
impl<'a, T> Drop for UFieldPositionIterator<'a, T> {
fn drop(&mut self) {
unsafe { versioned_function!(ufieldpositer_close)(self.rep.as_ptr()) };
}
}
impl<'a, T: 'a> UFieldPositionIterator<'a, T> {
pub fn try_new_owned(owner: &'a T) -> Result<UFieldPositionIterator<'a, T>, common::Error> {
let raw = Self::new_raw()?;
Ok(UFieldPositionIterator {
rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
owner: Some(owner),
})
}
pub fn try_new_unowned<'b>() -> Result<UFieldPositionIterator<'b, T>, common::Error> {
let raw = Self::new_raw()?;
Ok(UFieldPositionIterator {
rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
owner: None,
})
}
fn new_raw() -> Result<*mut sys::UFieldPositionIterator, common::Error> {
let mut status = common::Error::OK_CODE;
let raw = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(ufieldpositer_open)(&mut status)
};
common::Error::ok_or_warning(status)?;
assert_ne!(raw, 0 as *mut sys::UFieldPositionIterator);
Ok(raw)
}
#[doc(hidden)]
pub fn as_mut_ptr(&mut self) -> *mut sys::UFieldPositionIterator {
self.rep.as_ptr()
}
}
#[derive(Debug, PartialEq)]
pub struct UFieldPositionType {
pub field_type: i32,
pub begin_index: i32,
pub past_end_index: i32,
}
impl<'a, T> Iterator for UFieldPositionIterator<'a, T> {
type Item = UFieldPositionType;
fn next(&mut self) -> Option<Self::Item> {
let mut begin = 0i32;
let mut end = 0i32;
let field_type = unsafe {
versioned_function!(ufieldpositer_next)(self.rep.as_ptr(), &mut begin, &mut end)
};
if field_type < 0 {
return None;
}
Some(UFieldPositionType {
field_type,
begin_index: begin,
past_end_index: end,
})
}
}
pub fn available_iter() -> UnumIter {
let max = get_num_available();
UnumIter { max, next: 0 }
}
fn get_num_available() -> usize {
let result = unsafe { versioned_function!(unum_countAvailable)() } as usize;
result
}
pub struct UnumIter {
max: usize,
next: usize,
}
impl Iterator for UnumIter {
type Item = String;
fn next(&mut self) -> Option<String> {
if self.max == 0 {
return None;
}
if self.max != get_num_available() {
return None;
}
if self.next >= self.max {
return None;
}
let cptr: *const std::os::raw::c_char =
unsafe { versioned_function!(unum_getAvailable)(self.next as i32) };
assert_ne!(
cptr,
std::ptr::null(),
"unum_getAvailable unexpectedly returned nullptr"
);
let cstr = unsafe { std::ffi::CStr::from_ptr(cptr) };
self.next = self.next + 1;
Some(cstr.to_str().expect("can be converted to str").to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_decimal_pattern_ustring() {
struct TestCase {
locale: &'static str,
number: i32,
pattern: &'static str,
expected: &'static str,
};
let tests = vec![TestCase {
locale: "sr-RS",
number: 42,
pattern: "",
expected: "42",
}];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let pattern = ustring::UChar::try_from(test.pattern).expect("pattern is set");
let fmt = crate::UNumberFormat::try_new_decimal_pattern_ustring(&pattern, &locale)
.expect("formatter");
let result = fmt
.try_clone()
.expect("clone")
.format(test.number)
.expect("format success");
assert_eq!(test.expected, result);
}
}
#[test]
#[should_panic(expected = "U_MEMORY_ALLOCATION_ERROR")]
fn format_decimal_rulebased_ustring() {
struct TestCase {
locale: &'static str,
number: i32,
rule: &'static str,
expected: &'static str,
};
let tests = vec![TestCase {
locale: "sr-RS",
number: 42,
rule: "",
expected: "42",
}];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let pattern = ustring::UChar::try_from(test.rule).expect("pattern is set");
let fmt = crate::UNumberFormat::try_new_decimal_rule_based_ustring(&pattern, &locale)
.expect("formatter");
let result = fmt
.try_clone()
.expect("clone")
.format(test.number)
.expect("format success");
assert_eq!(test.expected, result);
}
}
#[test]
fn format_style_ustring() {
struct TestCase {
locale: &'static str,
number: i32,
style: sys::UNumberFormatStyle,
expected: &'static str,
};
let tests = vec![
TestCase {
locale: "sr-RS",
number: 42,
style: sys::UNumberFormatStyle::UNUM_CURRENCY,
expected: "42\u{a0}RSD",
},
TestCase {
locale: "sr-RS",
number: 42,
style: sys::UNumberFormatStyle::UNUM_SPELLOUT,
expected: "четрдесет и два",
},
];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let fmt =
crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");
let result = fmt
.try_clone()
.expect("clone")
.format(test.number)
.expect("format success");
assert_eq!(test.expected, result);
}
}
#[test]
fn format_double_with_fields() {
struct TestCase {
locale: &'static str,
number: f64,
style: sys::UNumberFormatStyle,
expected: &'static str,
expected_iter: Vec<UFieldPositionType>,
};
let tests = vec![TestCase {
locale: "sr-RS",
number: 42.1,
style: sys::UNumberFormatStyle::UNUM_CURRENCY,
expected: "42\u{a0}RSD",
expected_iter: vec![
UFieldPositionType {
field_type: 0,
begin_index: 0,
past_end_index: 2,
},
UFieldPositionType {
field_type: 7,
begin_index: 3,
past_end_index: 6,
},
],
}];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let fmt =
crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");
let (ustring, iter) = fmt
.format_double_for_fields_ustring(test.number)
.expect("format success");
let s = String::try_from(&ustring)
.expect(&format!("string is convertible to utf8: {:?}", &ustring));
assert_eq!(test.expected, s);
let iter_values = iter.collect::<Vec<UFieldPositionType>>();
assert_eq!(test.expected_iter, iter_values);
}
}
#[test]
fn format_decimal() {
struct TestCase {
locale: &'static str,
number: &'static str,
style: sys::UNumberFormatStyle,
expected: &'static str,
};
let tests = vec![TestCase {
locale: "sr-RS",
number: "1300.55",
style: sys::UNumberFormatStyle::UNUM_CURRENCY,
expected: "1.301\u{a0}RSD",
}];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let fmt =
crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");
let s = fmt.format_decimal(test.number).expect("format success");
assert_eq!(test.expected, s);
}
}
#[test]
fn format_double_currency() {
struct TestCase {
locale: &'static str,
number: f64,
currency: &'static str,
style: sys::UNumberFormatStyle,
expected: &'static str,
};
let tests = vec![TestCase {
locale: "sr-RS",
number: 1300.55,
currency: "usd",
style: sys::UNumberFormatStyle::UNUM_CURRENCY,
expected: "1.300,55\u{a0}US$",
}];
for test in tests {
let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
let fmt =
crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");
let s = fmt
.format_double_currency(test.number, test.currency)
.expect("format success");
assert_eq!(test.expected, s);
}
}
#[test]
fn format_and_parse_uformattable() {
#[derive(Debug)]
struct TestCase {
source_locale: &'static str,
number: &'static str,
position: Option<i32>,
style: sys::UNumberFormatStyle,
target_locale: &'static str,
expected: &'static str,
};
let tests = vec![
TestCase {
source_locale: "sr-RS",
number: "123,44",
position: None,
style: sys::UNumberFormatStyle::UNUM_DECIMAL,
target_locale: "en-US",
expected: "123.44",
},
TestCase {
source_locale: "sr-RS",
number: "123,44",
position: Some(2),
style: sys::UNumberFormatStyle::UNUM_DECIMAL,
target_locale: "en-US",
expected: "3.44",
},
];
for test in tests {
let locale = uloc::ULoc::try_from(test.source_locale).expect("locale exists");
let fmt = crate::UNumberFormat::try_new_with_style(test.style, &locale)
.expect("source_locale formatter");
let formattable = fmt
.parse_to_formattable(test.number, test.position)
.expect(&format!("parse_to_formattable: {:?}", &test));
let locale = uloc::ULoc::try_from(test.target_locale).expect("locale exists");
let fmt = crate::UNumberFormat::try_new_with_style(test.style, &locale)
.expect("target_locale formatter");
let result = fmt
.format_formattable(&formattable)
.expect(&format!("format_formattable: {:?}", &test));
assert_eq!(test.expected, result);
}
}
#[test]
fn test_available() {
let all = super::available_iter().collect::<Vec<String>>();
let count = super::available_iter().count();
assert_ne!(
0, count,
"there should be at least some available locales: {:?}",
&all
);
let available = all
.into_iter()
.filter(|f| *f == "en_US")
.collect::<Vec<String>>();
assert_eq!(
vec!["en_US"],
available,
"missing a locale that likely should be there"
);
}
#[test]
fn pattern() {
#[derive(Debug)]
struct TestCase {
source_locale: &'static str,
is_localized: bool,
style: sys::UNumberFormatStyle,
expected: &'static str,
};
let tests = vec![
TestCase {
source_locale: "en-US",
is_localized: true,
style: sys::UNumberFormatStyle::UNUM_DECIMAL,
expected: "#,##0.###",
},
TestCase {
source_locale: "sr-RS",
is_localized: true,
style: sys::UNumberFormatStyle::UNUM_DECIMAL,
expected: "#.##0,###",
},
TestCase {
source_locale: "sr-RS",
is_localized: false,
style: sys::UNumberFormatStyle::UNUM_DECIMAL,
expected: "#,##0.###",
},
];
for test in tests {
let locale = uloc::ULoc::try_from(test.source_locale).expect("locale exists");
let fmt = crate::UNumberFormat::try_new_with_style(test.style, &locale)
.expect("source_locale formatter");
let pattern = fmt
.get_pattern(test.is_localized)
.expect(&format!("localized in test: {:?}", test));
assert_eq!(test.expected, pattern, "in test: {:?}", test);
}
}
}