From d0e56037d186a2868e52c5ad0e347ab07d6ae086 Mon Sep 17 00:00:00 2001 From: Filip Filmar Date: Thu, 25 Jun 2020 18:09:11 -0700 Subject: [PATCH] Adds support for UFormattable. Using uformattable::UFormattable provides generalized value parsing for the particular locale. While this isn't the full spectrum of the underlying functions that ICU4C provides for value parsing, it's general enough to be usable for all the use cases where parsing is needed, *if* needed. However, those uses are rare. Issue #141. --- rust_icu_uformattable/src/lib.rs | 15 ++++ rust_icu_unum/Cargo.toml | 7 ++ rust_icu_unum/src/lib.rs | 150 +++++++++++++++++++++++++++++-- 3 files changed, 163 insertions(+), 9 deletions(-) diff --git a/rust_icu_uformattable/src/lib.rs b/rust_icu_uformattable/src/lib.rs index a57c7451..f594fbd3 100644 --- a/rust_icu_uformattable/src/lib.rs +++ b/rust_icu_uformattable/src/lib.rs @@ -96,6 +96,21 @@ impl<'a> crate::UFormattable<'a> { }) } + /// Reveals the underlying representation as a mutable pointer. + /// + /// **DO NOT USE UNLESS YOU HAVE NO OTHER CHOICE** + /// + /// The intended use of this method is for other crates that need to obtain + /// low-level representations of this type. + #[doc(hidden)] + pub fn as_mut_ptr(&mut self) -> *mut sys::UFormattable { + self.rep.as_ptr() + } + + pub fn as_ptr(&self) -> *const sys::UFormattable { + self.rep.as_ptr() + } + /// Returns `true` if this formattable is numeric. /// /// Implements `ufmt_isNumeric` diff --git a/rust_icu_unum/Cargo.toml b/rust_icu_unum/Cargo.toml index 696a90a2..76b4fd5a 100644 --- a/rust_icu_unum/Cargo.toml +++ b/rust_icu_unum/Cargo.toml @@ -20,6 +20,7 @@ log = "0.4.6" paste = "0.1.5" rust_icu_common = { path = "../rust_icu_common", version = "0.3.1", default-features = false } rust_icu_sys = { path = "../rust_icu_sys", version = "0.3.1", default-features = false } +rust_icu_uformattable = { path = "../rust_icu_uformattable", version = "0.3.1", default-features = false } rust_icu_uloc = { path = "../rust_icu_uloc", version = "0.3.1", default-features = false } rust_icu_ustring = { path = "../rust_icu_ustring", version = "0.3.1", default-features = false } anyhow = "1.0.25" @@ -34,36 +35,42 @@ default = ["use-bindgen", "renaming", "icu_config"] use-bindgen = [ "rust_icu_common/use-bindgen", "rust_icu_sys/use-bindgen", + "rust_icu_uformattable/use-bindgen", "rust_icu_uloc/use-bindgen", "rust_icu_ustring/use-bindgen", ] renaming = [ "rust_icu_common/renaming", "rust_icu_sys/renaming", + "rust_icu_uformattable/renaming", "rust_icu_uloc/renaming", "rust_icu_ustring/renaming", ] icu_config = [ "rust_icu_common/icu_config", "rust_icu_sys/icu_config", + "rust_icu_uformattable/icu_config", "rust_icu_uloc/icu_config", "rust_icu_ustring/icu_config", ] icu_version_in_env = [ "rust_icu_common/icu_version_in_env", "rust_icu_sys/icu_version_in_env", + "rust_icu_uformattable/icu_version_in_env", "rust_icu_uloc/icu_version_in_env", "rust_icu_ustring/icu_version_in_env", ] icu_version_64_plus = [ "rust_icu_common/icu_version_64_plus", "rust_icu_sys/icu_version_64_plus", + "rust_icu_uformattable/icu_version_64_plus", "rust_icu_uloc/icu_version_64_plus", "rust_icu_ustring/icu_version_64_plus", ] icu_version_67_plus = [ "rust_icu_common/icu_version_67_plus", "rust_icu_sys/icu_version_67_plus", + "rust_icu_uformattable/icu_version_67_plus", "rust_icu_uloc/icu_version_67_plus", "rust_icu_ustring/icu_version_67_plus", ] diff --git a/rust_icu_unum/src/lib.rs b/rust_icu_unum/src/lib.rs index 6a35d6b3..e2a7ce8a 100644 --- a/rust_icu_unum/src/lib.rs +++ b/rust_icu_unum/src/lib.rs @@ -21,7 +21,7 @@ use { paste, rust_icu_common as common, rust_icu_sys as sys, rust_icu_sys::versioned_function, rust_icu_sys::*, - rust_icu_uloc as uloc, rust_icu_ustring as ustring, + 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}, }; @@ -235,21 +235,29 @@ impl UNumberFormat { } /// Implements `unum_formatDoubleCurrency`. Since 0.3.1. - pub fn format_double_currency(&self, number: f64, currency: &str) -> Result { + pub fn format_double_currency( + &self, + number: f64, + currency: &str, + ) -> Result { let currency = ustring::UChar::try_from(currency)?; let result = self.format_double_currency_ustring(number, ¤cy)?; String::try_from(&result) } /// Implements `unum_formatDoubleCurrency`. Since 0.3.1 - pub fn format_double_currency_ustring(&self, number: f64, currency: &ustring::UChar) -> Result { + pub fn format_double_currency_ustring( + &self, + number: f64, + currency: &ustring::UChar, + ) -> Result { const CAPACITY: usize = 200; buffered_uchar_method_with_retry!( format_double_currency_impl, CAPACITY, [ format: *const sys::UNumberFormat, - number: f64, + number: f64, // NUL terminated! currency: *mut sys::UChar, ], @@ -263,12 +271,83 @@ impl UNumberFormat { format_double_currency_impl( versioned_function!(unum_formatDoubleCurrency), self.rep.as_ptr(), - number, + number, currencyz.as_mut_c_ptr(), 0 as *mut sys::UFieldPosition, ) } - + + /// Implements `unum_parseToUFormattable`. Since 0.3.1. + /// + /// > **WARNING** the `parse_position` parameter is with respect to the number index + /// in the `UChar` string. This won't work exactly for multibyte UTF8 values of + /// `text`. If you think you will have multibyte values, use instead + /// [UNumberFormat::parse_to_formattable_ustring]. + pub fn parse_to_formattable<'a>( + &'a self, + text: &str, + parse_position: Option, + ) -> Result, common::Error> { + let ustr = ustring::UChar::try_from(text)?; + self.parse_to_formattable_ustring(&ustr, parse_position) + } + + /// Implements `unum_parseToUFormattable`. Since 0.3.1. + pub fn parse_to_formattable_ustring<'a>( + &'a self, + text: &ustring::UChar, + parse_position: Option, + ) -> Result, 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) + } + + /// Implements `unum_formatUFormattable`. Since 0.3.1. + pub fn format_formattable<'a>( + &self, + fmt: &uformattable::UFormattable<'a>, + ) -> Result { + let result = self.format_formattable_ustring(fmt)?; + String::try_from(&result) + } + + /// Implements `unum_formatUFormattable`. Since 0.3.1. + pub fn format_formattable_ustring<'a>( + &self, + fmt: &uformattable::UFormattable<'a>, + ) -> Result { + 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, + ) + } } /// Used to iterate over the field positions. @@ -536,9 +615,7 @@ mod tests { let fmt = crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter"); - let s = fmt - .format_decimal(test.number) - .expect("format success"); + let s = fmt.format_decimal(test.number).expect("format success"); assert_eq!(test.expected, s); } @@ -573,4 +650,59 @@ mod tests { assert_eq!(test.expected, s); } } + + #[test] + fn format_and_parse_uformattable() { + #[derive(Debug)] + struct TestCase { + source_locale: &'static str, + number: &'static str, + position: Option, + 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); + } + } }