diff --git a/README.md b/README.md index 291b636f..d05f7067 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ coverage in the headers. | [rust_icu_udat](https://crates.io/crates/rust_icu_udat)| ICU date and time. Implements [`udat.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/udat_8h.html) C API header from the ICU library. | | [rust_icu_udata](https://crates.io/crates/rust_icu_udata)| ICU binary data. Implements [`udata.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/udata_8h.html) C API header from the ICU library. | | [rust_icu_uenum](https://crates.io/crates/rust_icu_uenum)| ICU enumerations. Implements [`uenum.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uenum_8h.html) C API header from the ICU library. Mainly `UEnumeration` and friends. | +| [rust_icu_uformattable](https://crates.io/crates/rust_icu_uformattable)| Locale-sensitive list formatting support. Implements [`uformattable.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uformattable_8h.html) C API header from the ICU library. Since 0.3.1. | | [rust_icu_ulistformatter](https://crates.io/crates/rust_icu_ulistformatter)| Locale-sensitive list formatting support. Implements [`ulistformatter.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/ulistformatter_8h.html) C API header from the ICU library. | | [rust_icu_uloc](https://crates.io/crates/rust_icu_uloc)| Locale support. Implements [`uloc.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uloc_8h.html) C API header from the ICU library. | | [rust_icu_umsg](https://crates.io/crates/rust_icu_umsg)| MessageFormat support. Implements [`umsg.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/umsg_8h.html) C API header from the ICU library. | diff --git a/build/showprogress.sh b/build/showprogress.sh index f715b949..de6f5c2c 100755 --- a/build/showprogress.sh +++ b/build/showprogress.sh @@ -13,10 +13,12 @@ C_API_HEADER_NAMES=( "udat" "udata" "uenum" + "uformattable" + "ulistformatter" "uloc" + "upluralrules" "umsg" "ustring" - "ustring" "utext" ) diff --git a/coverage/report.md b/coverage/report.md index d62bba9f..965a2a20 100644 --- a/coverage/report.md +++ b/coverage/report.md @@ -2,15 +2,17 @@ | Header | Implemented | | ------ | ----------- | -| `ucal.h` | 15 / 45 | +| `ucal.h` | 15 / 46 | | `ucol.h` | 2 / 50 | | `udat.h` | 6 / 38 | | `udata.h` | 2 / 8 | | `uenum.h` | 8 / 8 | -| `uloc.h` | 19 / 41 | +| `uformattable.h` | 7 / 13 | +| `ulistformatter.h` | 2 / 8 | +| `uloc.h` | 19 / 42 | +| `upluralrules.h` | 3 / 7 | | `umsg.h` | 6 / 20 | | `ustring.h` | 3 / 61 | -| `ustring.h` | 3 / 61 | | `utext.h` | 3 / 28 | # Unimplemented functions per header @@ -47,6 +49,7 @@ | `ucal_getDSTSavings` | | | `ucal_getFieldDifference` | | | `ucal_getGregorianChange` | | +| `ucal_getHostTimeZone` | | | `ucal_getKeywordValuesForLocale` | | | `ucal_getLimit` | | | `ucal_getLocaleByType` | | @@ -196,6 +199,38 @@ | `uenum_reset` | | | `uenum_unext` | | +# Header: `uformattable.h` + +| Unimplemented | Implemented | +| ------------- | ----------- | +| | `$impl_function_name` | +| | `ufmt_close` | +| | `ufmt_getArrayItemByIndex` | +| | `ufmt_getDecNumChars` | +| | `ufmt_getUChars` | +| | `ufmt_isNumeric` | +| | `ufmt_open` | +| `ufmt_getArrayLength` | | +| `ufmt_getDate` | | +| `ufmt_getDouble` | | +| `ufmt_getInt64` | | +| `ufmt_getLong` | | +| `ufmt_getObject` | | +| `ufmt_getType` | | + +# Header: `ulistformatter.h` + +| Unimplemented | Implemented | +| ------------- | ----------- | +| | `ulistfmt_format` | +| | `ulistfmt_openForType` | +| `ulistfmt_close` | | +| `ulistfmt_closeResult` | | +| `ulistfmt_formatStringsToResult` | | +| `ulistfmt_open` | | +| `ulistfmt_openResult` | | +| `ulistfmt_resultAsValue` | | + # Header: `uloc.h` | Unimplemented | Implemented | @@ -242,10 +277,23 @@ | `uloc_getName` | | | `uloc_getParent` | | | `uloc_isRightToLeft` | | +| `uloc_openAvailableByType` | | | `uloc_openKeywords` | | | `uloc_setKeywordValue` | | | `uloc_toLegacyType` | | +# Header: `upluralrules.h` + +| Unimplemented | Implemented | +| ------------- | ----------- | +| | `uplrules_getKeywords` | +| | `uplrules_openForType` | +| | `uplrules_select` | +| `uplrules_close` | | +| `uplrules_open` | | +| `uplrules_selectFormatted` | | +| `uplrules_selectWithFormat` | | + # Header: `umsg.h` | Unimplemented | Implemented | @@ -339,73 +387,6 @@ | `u_unescape` | | | `u_unescapeAt` | | -# Header: `ustring.h` - -| Unimplemented | Implemented | -| ------------- | ----------- | -| | `UChar*` | -| | `u_strFromUTF8` | -| | `u_strToUTF8` | -| `u_austrcpy` | | -| `u_austrncpy` | | -| `u_countChar32` | | -| `u_memcasecmp` | | -| `u_memchr` | | -| `u_memchr32` | | -| `u_memcmp` | | -| `u_memcmpCodePointOrder` | | -| `u_memcpy` | | -| `u_memmove` | | -| `u_memrchr` | | -| `u_memrchr32` | | -| `u_memset` | | -| `u_strcasecmp` | | -| `u_strCaseCompare` | | -| `u_strcat` | | -| `u_strchr` | | -| `u_strchr32` | | -| `u_strcmp` | | -| `u_strcmpCodePointOrder` | | -| `u_strCompare` | | -| `u_strCompareIter` | | -| `u_strcpy` | | -| `u_strcspn` | | -| `u_strFindFirst` | | -| `u_strFindLast` | | -| `u_strFoldCase` | | -| `u_strFromJavaModifiedUTF8WithSub` | | -| `u_strFromUTF32` | | -| `u_strFromUTF32WithSub` | | -| `u_strFromUTF8Lenient` | | -| `u_strFromUTF8WithSub` | | -| `u_strFromWCS` | | -| `u_strHasMoreChar32Than` | | -| `u_strlen` | | -| `u_strncasecmp` | | -| `u_strncat` | | -| `u_strncmp` | | -| `u_strncmpCodePointOrder` | | -| `u_strncpy` | | -| `u_strpbrk` | | -| `u_strrchr` | | -| `u_strrchr32` | | -| `u_strrstr` | | -| `u_strspn` | | -| `u_strstr` | | -| `u_strToJavaModifiedUTF8` | | -| `u_strtok_r` | | -| `u_strToLower` | | -| `u_strToTitle` | | -| `u_strToUpper` | | -| `u_strToUTF32` | | -| `u_strToUTF32WithSub` | | -| `u_strToUTF8WithSub` | | -| `u_strToWCS` | | -| `u_uastrcpy` | | -| `u_uastrncpy` | | -| `u_unescape` | | -| `u_unescapeAt` | | - # Header: `utext.h` | Unimplemented | Implemented | diff --git a/coverage/ucal_all.txt b/coverage/ucal_all.txt index 9b93ddd4..124f1336 100644 --- a/coverage/ucal_all.txt +++ b/coverage/ucal_all.txt @@ -14,6 +14,7 @@ ucal_getDefaultTimeZone ucal_getDSTSavings ucal_getFieldDifference ucal_getGregorianChange +ucal_getHostTimeZone ucal_getKeywordValuesForLocale ucal_getLimit ucal_getLocaleByType diff --git a/coverage/ufmt_all.txt b/coverage/ufmt_all.txt new file mode 100644 index 00000000..e69de29b diff --git a/coverage/uformattable_all.txt b/coverage/uformattable_all.txt new file mode 100644 index 00000000..1cb927c9 --- /dev/null +++ b/coverage/uformattable_all.txt @@ -0,0 +1,13 @@ +ufmt_close +ufmt_getArrayItemByIndex +ufmt_getArrayLength +ufmt_getDate +ufmt_getDecNumChars +ufmt_getDouble +ufmt_getInt64 +ufmt_getLong +ufmt_getObject +ufmt_getType +ufmt_getUChars +ufmt_isNumeric +ufmt_open diff --git a/coverage/uformattable_implemented.txt b/coverage/uformattable_implemented.txt new file mode 100644 index 00000000..f06811a4 --- /dev/null +++ b/coverage/uformattable_implemented.txt @@ -0,0 +1,7 @@ +$impl_function_name +ufmt_close +ufmt_getArrayItemByIndex +ufmt_getDecNumChars +ufmt_getUChars +ufmt_isNumeric +ufmt_open diff --git a/coverage/ulistformatter_all.txt b/coverage/ulistformatter_all.txt new file mode 100644 index 00000000..037550c7 --- /dev/null +++ b/coverage/ulistformatter_all.txt @@ -0,0 +1,8 @@ +ulistfmt_close +ulistfmt_closeResult +ulistfmt_format +ulistfmt_formatStringsToResult +ulistfmt_open +ulistfmt_openForType +ulistfmt_openResult +ulistfmt_resultAsValue diff --git a/coverage/ulistformatter_implemented.txt b/coverage/ulistformatter_implemented.txt new file mode 100644 index 00000000..63e3d003 --- /dev/null +++ b/coverage/ulistformatter_implemented.txt @@ -0,0 +1,2 @@ +ulistfmt_format +ulistfmt_openForType diff --git a/coverage/uloc_all.txt b/coverage/uloc_all.txt index 2ae43c5f..30bb06d1 100644 --- a/coverage/uloc_all.txt +++ b/coverage/uloc_all.txt @@ -31,6 +31,7 @@ uloc_getScript uloc_getVariant uloc_isRightToLeft uloc_minimizeSubtags +uloc_openAvailableByType uloc_openKeywords uloc_setDefault uloc_setKeywordValue diff --git a/coverage/upluralrules_all.txt b/coverage/upluralrules_all.txt new file mode 100644 index 00000000..9c32b705 --- /dev/null +++ b/coverage/upluralrules_all.txt @@ -0,0 +1,7 @@ +uplrules_close +uplrules_getKeywords +uplrules_open +uplrules_openForType +uplrules_select +uplrules_selectFormatted +uplrules_selectWithFormat diff --git a/coverage/upluralrules_implemented.txt b/coverage/upluralrules_implemented.txt new file mode 100644 index 00000000..4d369093 --- /dev/null +++ b/coverage/upluralrules_implemented.txt @@ -0,0 +1,3 @@ +uplrules_getKeywords +uplrules_openForType +uplrules_select diff --git a/rust_icu_common/src/lib.rs b/rust_icu_common/src/lib.rs index 5d70726e..470dafe5 100644 --- a/rust_icu_common/src/lib.rs +++ b/rust_icu_common/src/lib.rs @@ -169,7 +169,7 @@ impl Into for Error { /// BUFFER_CAPACITY, /// [before_arg_a: before_type_a, before_arg_b: before_type_b,], /// [after_arg_a: after_type_a, after_arg_b: after_type_b,] -/// ); +/// ); /// ``` /// /// the generated method has a signature of the form diff --git a/rust_icu_uformattable/src/lib.rs b/rust_icu_uformattable/src/lib.rs index 1940e4cd..f4b54ca3 100644 --- a/rust_icu_uformattable/src/lib.rs +++ b/rust_icu_uformattable/src/lib.rs @@ -19,6 +19,189 @@ use { rust_icu_sys::versioned_function, rust_icu_sys::*, rust_icu_ustring as ustring, - std::{convert::TryFrom, convert::TryInto, ffi, ptr}, + std::{convert::TryFrom, os::raw, ffi, ptr}, }; +// Implements the ICU type [`UFormattable`][ufmt]. +// +// [UFormattable] is a thin wrapper for primitive types used for number formatting. +// +// Note from the ICU4C API: +// +// > Underlying is a C interface to the class `icu::Formatable`. Static functions +// on this class convert to and from this interface (via `reinterpret_cast`). Many +// operations are not thread safe, and should not be shared between threads. +// +// [ufmt]: https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uformattable_8h.html +#[derive(Debug)] +pub struct UFormattable<'a> { + // The underlying representation. + rep: ptr::NonNull, + owner: Option<&'a Self>, +} + +impl<'a> Drop for crate::UFormattable<'a> { + /// Implements `ufmt_close`. + fn drop(&mut self) { + if let None = self.owner { + unsafe { versioned_function!(ufmt_close)(self.rep.as_ptr()) }; + } + } +} + +/// Generates a simple getter, which just shunts into an appropriately +/// named getter function of the sys crate and returns the appropriate type. +/// +/// Example: +/// +/// ``` +/// simple_getter!(get_array_length, ufmt_getArrayLength, i32); +/// ``` +/// +/// * `$method_name` is an identifier +macro_rules! simple_getter { + ($method_name:ident, $impl_function_name:ident, $return_type:ty) => { + /// Implements `$impl_function_name` + /// Use [UFormattable::get_type] to verify that the type matches. + pub fn $method_name(&self) -> Result<$return_type, common::Error> { + let mut status = common::Error::OK_CODE; + let ret = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!($impl_function_name)( + self.rep.as_ptr(), &mut status) + }; + common::Error::ok_or_warning(status)?; + Ok(ret) + } + } +} + +impl<'a> crate::UFormattable<'a> { + /// Initialize a [crate::UFormattable] to type `UNUM_LONG`, value 0 may + /// return error. + /// + /// Implements `ufmt_open`. + pub fn try_new<'b>() -> Result, common::Error> { + let mut status = common::Error::OK_CODE; + // We verify that status is OK on entry and on exit, and that the + // returned representation is not null. + let rep = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(ufmt_open)(&mut status) + }; + common::Error::ok_or_warning(status)?; + Ok(UFormattable { + rep: ptr::NonNull::new(rep).unwrap(), + owner: None, + }) + } + + /// Returns `true` if this formattable is numeric. + /// + /// Implements `ufmt_isNumeric` + pub fn is_numeric(&self) -> bool { + let ubool = unsafe { versioned_function!(ufmt_isNumeric)(self.rep.as_ptr()) }; + match ubool { + 0i8 => false, + _ => true, + } + } + + // Returns the type of this formattable. The comment here and below is + // used in coverage analysis; the macro `simple_getter!` generates + // user-visible documentation. + // + // Implements `ufmt_getType` + simple_getter!(get_type, ufmt_getType, sys::UFormattableType); + + // Implements `ufmt_getDate` + simple_getter!(get_date, ufmt_getDate, sys::UDate); + + // Implements `ufmt_getDouble` + simple_getter!(get_double, ufmt_getDouble, f64); + + // Implements `ufmt_getLong` + simple_getter!(get_i32, ufmt_getLong, i32); + + // Implements `ufmt_getInt64` + simple_getter!(get_i64, ufmt_getInt64, i64); + + // Implements `ufmt_getArrayLength` + simple_getter!(get_array_length, ufmt_getArrayLength, i32); + + // Implements `ufmt_getUChars` + pub fn get_ustring(&self) -> Result { + let mut status = common::Error::OK_CODE; + let mut ustrlen = 0i32; + let raw: *const sys::UChar = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(ufmt_getUChars)( + self.rep.as_ptr(), &mut ustrlen, &mut status) + }; + common::Error::ok_or_warning(status)?; + let ret = unsafe { + assert_ne!(raw, 0 as *const sys::UChar); + assert!(ustrlen >= 0); + ustring::UChar::clone_from_raw_parts(raw, ustrlen) + }; + Ok(ret) + } + + /// Implements `ufmt_getUChars` + pub fn get_str(&self) -> Result { + let ustr = self.get_ustring()?; + String::try_from(&ustr) + } + + /// Use [UFormattable::get_type] to ensure that this formattable is an array before using this + /// method. Otherwise you will get an error. The lifetime of the resulting formattable is tied + /// to this one. + /// + /// Implements `ufmt_getArrayItemByIndex` + pub fn get_array_item_by_index(&'a self, index: i32) -> Result, common::Error> { + let mut status = common::Error::OK_CODE; + let raw: *mut sys::UFormattable = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(ufmt_getArrayItemByIndex)( + self.rep.as_ptr(), index, &mut status) + }; + common::Error::ok_or_warning(status)?; + assert_ne!(raw, 0 as *mut sys::UFormattable); + Ok(UFormattable{ rep: ptr::NonNull::new(raw).unwrap(), owner: Some(&self) }) + } + + /// Implements `ufmt_getDecNumChars` + pub fn get_dec_num_chars(&self) -> Result { + let mut status = common::Error::OK_CODE; + let mut cstrlen = 0i32; + let raw: *const raw::c_char = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(ufmt_getDecNumChars)( + self.rep.as_ptr(), &mut cstrlen, &mut status) + }; + common::Error::ok_or_warning(status)?; + let ret = unsafe { + assert_ne!(raw, 0 as *const raw::c_char); + assert!(cstrlen >= 0); + ffi::CStr::from_ptr(raw) + }; + Ok(ret.to_str().map_err(|e: std::str::Utf8Error| common::Error::from(e))?.to_string()) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + // There doesn't seem to be a way to initialize a nonzero Numeric for testing all of this code. + // So it seems it would have to remain uncovered with tests, until some other code gets to + // use it. + + #[test] + fn basic() { + let n = crate::UFormattable::try_new().expect("try_new"); + assert_eq!(sys::UFormattableType::UFMT_LONG, n.get_type().expect("get_type")); + assert_eq!(0, n.get_i32().expect("get_i32")); + assert_eq!("0", n.get_dec_num_chars().expect("get_dec_num_chars")); + } +} diff --git a/rust_icu_ustring/src/lib.rs b/rust_icu_ustring/src/lib.rs index e26b5ca3..6e70e38b 100644 --- a/rust_icu_ustring/src/lib.rs +++ b/rust_icu_ustring/src/lib.rs @@ -172,6 +172,28 @@ impl crate::UChar { crate::UChar::from(rep) } + /// Creates a new [crate::UChar] from its low-level representation, a buffer + /// pointer and a buffer size. + /// + /// Does *not* take ownership of the buffer that was passed in. + /// + /// **DO NOT USE UNLESS YOU HAVE NO OTHER CHOICE.** + #[doc(hidden)] + pub unsafe fn clone_from_raw_parts(rep: *const sys::UChar, len: i32) -> crate::UChar { + assert!(len >= 0); + // Always works for len: i32 >= 0. + let cap = len as usize; + + // View the deconstructed buffer as a vector of UChars. Then make a + // copy of it to return. This is not efficient, but is always safe. + let original = Vec::from_raw_parts(rep as *mut sys::UChar, cap, cap); + let copy = original.clone(); + // Don't free the buffer we don't own. + std::mem::forget(original); + crate::UChar::from(copy) + } + + /// Converts into a zeroed-out string. /// /// This is a very weird ICU API thing, where there apparently exists a zero-terminated @@ -204,6 +226,7 @@ impl crate::UChar { pub fn resize(&mut self, new_size: usize) { self.rep.resize(new_size, 0); } + } #[cfg(test)]