From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- intl/icu_capi/src/bidi.rs | 267 +++++++++ intl/icu_capi/src/calendar.rs | 148 +++++ intl/icu_capi/src/casemap.rs | 336 +++++++++++ intl/icu_capi/src/collator.rs | 237 ++++++++ intl/icu_capi/src/collections_sets.rs | 223 ++++++++ intl/icu_capi/src/common.rs | 16 + intl/icu_capi/src/data_struct.rs | 90 +++ intl/icu_capi/src/date.rs | 300 ++++++++++ intl/icu_capi/src/datetime.rs | 415 ++++++++++++++ intl/icu_capi/src/datetime_formatter.rs | 358 ++++++++++++ intl/icu_capi/src/decimal.rs | 109 ++++ intl/icu_capi/src/displaynames.rs | 156 +++++ intl/icu_capi/src/errors.rs | 407 +++++++++++++ intl/icu_capi/src/fallbacker.rs | 195 +++++++ intl/icu_capi/src/fixed_decimal.rs | 341 +++++++++++ intl/icu_capi/src/iana_bcp47_mapper.rs | 82 +++ intl/icu_capi/src/lib.rs | 145 +++++ intl/icu_capi/src/list.rs | 125 ++++ intl/icu_capi/src/locale.rs | 212 +++++++ intl/icu_capi/src/locale_directionality.rs | 101 ++++ intl/icu_capi/src/locid_transform.rs | 115 ++++ intl/icu_capi/src/logging.rs | 36 ++ intl/icu_capi/src/metazone_calculator.rs | 34 ++ intl/icu_capi/src/normalizer.rs | 152 +++++ intl/icu_capi/src/normalizer_properties.rs | 144 +++++ intl/icu_capi/src/pluralrules.rs | 150 +++++ intl/icu_capi/src/properties_iter.rs | 48 ++ intl/icu_capi/src/properties_maps.rs | 311 ++++++++++ intl/icu_capi/src/properties_names.rs | 259 +++++++++ intl/icu_capi/src/properties_sets.rs | 887 +++++++++++++++++++++++++++++ intl/icu_capi/src/properties_unisets.rs | 149 +++++ intl/icu_capi/src/provider.rs | 344 +++++++++++ intl/icu_capi/src/script.rs | 156 +++++ intl/icu_capi/src/segmenter_grapheme.rs | 154 +++++ intl/icu_capi/src/segmenter_line.rs | 271 +++++++++ intl/icu_capi/src/segmenter_sentence.rs | 141 +++++ intl/icu_capi/src/segmenter_word.rs | 213 +++++++ intl/icu_capi/src/time.rs | 68 +++ intl/icu_capi/src/timezone.rs | 328 +++++++++++ intl/icu_capi/src/timezone_formatter.rs | 298 ++++++++++ intl/icu_capi/src/week.rs | 93 +++ intl/icu_capi/src/zoned_formatter.rs | 198 +++++++ 42 files changed, 8812 insertions(+) create mode 100644 intl/icu_capi/src/bidi.rs create mode 100644 intl/icu_capi/src/calendar.rs create mode 100644 intl/icu_capi/src/casemap.rs create mode 100644 intl/icu_capi/src/collator.rs create mode 100644 intl/icu_capi/src/collections_sets.rs create mode 100644 intl/icu_capi/src/common.rs create mode 100644 intl/icu_capi/src/data_struct.rs create mode 100644 intl/icu_capi/src/date.rs create mode 100644 intl/icu_capi/src/datetime.rs create mode 100644 intl/icu_capi/src/datetime_formatter.rs create mode 100644 intl/icu_capi/src/decimal.rs create mode 100644 intl/icu_capi/src/displaynames.rs create mode 100644 intl/icu_capi/src/errors.rs create mode 100644 intl/icu_capi/src/fallbacker.rs create mode 100644 intl/icu_capi/src/fixed_decimal.rs create mode 100644 intl/icu_capi/src/iana_bcp47_mapper.rs create mode 100644 intl/icu_capi/src/lib.rs create mode 100644 intl/icu_capi/src/list.rs create mode 100644 intl/icu_capi/src/locale.rs create mode 100644 intl/icu_capi/src/locale_directionality.rs create mode 100644 intl/icu_capi/src/locid_transform.rs create mode 100644 intl/icu_capi/src/logging.rs create mode 100644 intl/icu_capi/src/metazone_calculator.rs create mode 100644 intl/icu_capi/src/normalizer.rs create mode 100644 intl/icu_capi/src/normalizer_properties.rs create mode 100644 intl/icu_capi/src/pluralrules.rs create mode 100644 intl/icu_capi/src/properties_iter.rs create mode 100644 intl/icu_capi/src/properties_maps.rs create mode 100644 intl/icu_capi/src/properties_names.rs create mode 100644 intl/icu_capi/src/properties_sets.rs create mode 100644 intl/icu_capi/src/properties_unisets.rs create mode 100644 intl/icu_capi/src/provider.rs create mode 100644 intl/icu_capi/src/script.rs create mode 100644 intl/icu_capi/src/segmenter_grapheme.rs create mode 100644 intl/icu_capi/src/segmenter_line.rs create mode 100644 intl/icu_capi/src/segmenter_sentence.rs create mode 100644 intl/icu_capi/src/segmenter_word.rs create mode 100644 intl/icu_capi/src/time.rs create mode 100644 intl/icu_capi/src/timezone.rs create mode 100644 intl/icu_capi/src/timezone_formatter.rs create mode 100644 intl/icu_capi/src/week.rs create mode 100644 intl/icu_capi/src/zoned_formatter.rs (limited to 'intl/icu_capi/src') diff --git a/intl/icu_capi/src/bidi.rs b/intl/icu_capi/src/bidi.rs new file mode 100644 index 0000000000..cf4c8af98b --- /dev/null +++ b/intl/icu_capi/src/bidi.rs @@ -0,0 +1,267 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::vec::Vec; + use diplomat_runtime::DiplomatWriteable; + + use core::fmt::Write; + use icu_properties::bidi::BidiClassAdapter; + use icu_properties::maps; + use icu_properties::BidiClass; + use unicode_bidi::BidiInfo; + use unicode_bidi::Level; + use unicode_bidi::Paragraph; + + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + + pub enum ICU4XBidiDirection { + Ltr, + Rtl, + Mixed, + } + + #[diplomat::opaque] + /// An ICU4X Bidi object, containing loaded bidi data + #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter, Struct)] + // #[diplomat::rust_link(icu::properties::maps::load_bidi_class, Struct)] + pub struct ICU4XBidi(pub maps::CodePointMapData); + + impl ICU4XBidi { + /// Creates a new [`ICU4XBidi`] from locale data. + #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter::new, FnInStruct)] + pub fn create(provider: &ICU4XDataProvider) -> Result, ICU4XError> { + Ok(Box::new(ICU4XBidi(call_constructor_unstable!( + maps::bidi_class [m => Ok(m.static_to_owned())], + maps::load_bidi_class, + provider, + )?))) + } + + /// Use the data loaded in this object to process a string and calculate bidi information + /// + /// Takes in a Level for the default level, if it is an invalid value it will default to LTR + #[diplomat::rust_link(unicode_bidi::BidiInfo::new_with_data_source, FnInStruct)] + #[diplomat::rust_link( + icu::properties::bidi::BidiClassAdapter::bidi_class, + FnInStruct, + hidden + )] + pub fn for_text<'text>( + &self, + text: &'text str, + default_level: u8, + ) -> Box> { + let data = self.0.as_borrowed(); + let adapter = BidiClassAdapter::new(data); + + Box::new(ICU4XBidiInfo(BidiInfo::new_with_data_source( + &adapter, + text, + Level::new(default_level).ok(), + ))) + } + /// Utility function for producing reorderings given a list of levels + /// + /// Produces a map saying which visual index maps to which source index. + /// + /// The levels array must not have values greater than 126 (this is the + /// Bidi maximum explicit depth plus one). + /// Failure to follow this invariant may lead to incorrect results, + /// but is still safe. + #[diplomat::rust_link(unicode_bidi::BidiInfo::reorder_visual, FnInStruct)] + pub fn reorder_visual(&self, levels: &[u8]) -> Box { + let levels = Level::from_slice_unchecked(levels); + Box::new(ICU4XReorderedIndexMap(BidiInfo::reorder_visual(levels))) + } + + /// Check if a Level returned by level_at is an RTL level. + /// + /// Invalid levels (numbers greater than 125) will be assumed LTR + #[diplomat::rust_link(unicode_bidi::Level::is_rtl, FnInStruct)] + pub fn level_is_rtl(level: u8) -> bool { + Level::new(level).unwrap_or_else(|_| Level::ltr()).is_rtl() + } + + /// Check if a Level returned by level_at is an LTR level. + /// + /// Invalid levels (numbers greater than 125) will be assumed LTR + #[diplomat::rust_link(unicode_bidi::Level::is_ltr, FnInStruct)] + pub fn level_is_ltr(level: u8) -> bool { + Level::new(level).unwrap_or_else(|_| Level::ltr()).is_ltr() + } + + /// Get a basic RTL Level value + #[diplomat::rust_link(unicode_bidi::Level::rtl, FnInStruct)] + pub fn level_rtl() -> u8 { + Level::rtl().number() + } + + /// Get a simple LTR Level value + #[diplomat::rust_link(unicode_bidi::Level::ltr, FnInStruct)] + pub fn level_ltr() -> u8 { + Level::ltr().number() + } + } + + /// Thin wrapper around a vector that maps visual indices to source indices + /// + /// `map[visualIndex] = sourceIndex` + /// + /// Produced by `reorder_visual()` on [`ICU4XBidi`]. + #[diplomat::opaque] + pub struct ICU4XReorderedIndexMap(pub Vec); + + impl ICU4XReorderedIndexMap { + /// Get this as a slice/array of indices + pub fn as_slice<'a>(&'a self) -> &'a [usize] { + &self.0 + } + + /// The length of this map + #[allow(clippy::len_without_is_empty)] + #[diplomat::attr(dart, rename = "length")] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Get element at `index`. Returns 0 when out of bounds + /// (note that 0 is also a valid in-bounds value, please use `len()` + /// to avoid out-of-bounds) + pub fn get(&self, index: usize) -> usize { + self.0.get(index).copied().unwrap_or(0) + } + } + + /// An object containing bidi information for a given string, produced by `for_text()` on `ICU4XBidi` + #[diplomat::rust_link(unicode_bidi::BidiInfo, Struct)] + #[diplomat::opaque] + pub struct ICU4XBidiInfo<'text>(pub BidiInfo<'text>); + + impl<'text> ICU4XBidiInfo<'text> { + /// The number of paragraphs contained here + pub fn paragraph_count(&self) -> usize { + self.0.paragraphs.len() + } + + /// Get the nth paragraph, returning `None` if out of bounds + pub fn paragraph_at(&'text self, n: usize) -> Option>> { + self.0 + .paragraphs + .get(n) + .map(|p| Box::new(ICU4XBidiParagraph(Paragraph::new(&self.0, p)))) + } + + /// The number of bytes in this full text + pub fn size(&self) -> usize { + self.0.levels.len() + } + + /// Get the BIDI level at a particular byte index in the full text. + /// This integer is conceptually a `unicode_bidi::Level`, + /// and can be further inspected using the static methods on ICU4XBidi. + /// + /// Returns 0 (equivalent to `Level::ltr()`) on error + pub fn level_at(&self, pos: usize) -> u8 { + if let Some(l) = self.0.levels.get(pos) { + l.number() + } else { + 0 + } + } + } + + /// Bidi information for a single processed paragraph + #[diplomat::opaque] + pub struct ICU4XBidiParagraph<'info>(pub Paragraph<'info, 'info>); + + impl<'info> ICU4XBidiParagraph<'info> { + /// Given a paragraph index `n` within the surrounding text, this sets this + /// object to the paragraph at that index. Returns `ICU4XError::OutOfBoundsError` when out of bounds. + /// + /// This is equivalent to calling `paragraph_at()` on `ICU4XBidiInfo` but doesn't + /// create a new object + pub fn set_paragraph_in_text(&mut self, n: usize) -> Result<(), ICU4XError> { + let para = self + .0 + .info + .paragraphs + .get(n) + .ok_or(ICU4XError::OutOfBoundsError)?; + self.0 = Paragraph::new(self.0.info, para); + Ok(()) + } + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + /// The primary direction of this paragraph + pub fn direction(&self) -> ICU4XBidiDirection { + self.0.direction().into() + } + + /// The number of bytes in this paragraph + #[diplomat::rust_link(unicode_bidi::ParagraphInfo::len, FnInStruct)] + pub fn size(&self) -> usize { + self.0.para.len() + } + + /// The start index of this paragraph within the source text + pub fn range_start(&self) -> usize { + self.0.para.range.start + } + + /// The end index of this paragraph within the source text + pub fn range_end(&self) -> usize { + self.0.para.range.end + } + + /// Reorder a line based on display order. The ranges are specified relative to the source text and must be contained + /// within this paragraph's range. + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + pub fn reorder_line( + &self, + range_start: usize, + range_end: usize, + out: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + if range_start < self.range_start() || range_end > self.range_end() { + return Err(ICU4XError::OutOfBoundsError); + } + + let info = self.0.info; + let para = self.0.para; + + let reordered = info.reorder_line(para, range_start..range_end); + + Ok(out.write_str(&reordered)?) + } + + /// Get the BIDI level at a particular byte index in this paragraph. + /// This integer is conceptually a `unicode_bidi::Level`, + /// and can be further inspected using the static methods on ICU4XBidi. + /// + /// Returns 0 (equivalent to `Level::ltr()`) on error + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + pub fn level_at(&self, pos: usize) -> u8 { + if pos >= self.size() { + return 0; + } + + self.0.level_at(pos).number() + } + } +} + +use unicode_bidi::Direction; + +impl From for ffi::ICU4XBidiDirection { + fn from(other: Direction) -> Self { + match other { + Direction::Ltr => Self::Ltr, + Direction::Rtl => Self::Rtl, + Direction::Mixed => Self::Mixed, + } + } +} diff --git a/intl/icu_capi/src/calendar.rs b/intl/icu_capi/src/calendar.rs new file mode 100644 index 0000000000..dd4b2dcbaa --- /dev/null +++ b/intl/icu_capi/src/calendar.rs @@ -0,0 +1,148 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + + use core::fmt::Write; + use icu_calendar::{AnyCalendar, AnyCalendarKind}; + + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + + /// The various calendar types currently supported by [`ICU4XCalendar`] + #[diplomat::enum_convert(AnyCalendarKind, needs_wildcard)] + #[diplomat::rust_link(icu::calendar::AnyCalendarKind, Enum)] + pub enum ICU4XAnyCalendarKind { + /// The kind of an Iso calendar + Iso = 0, + /// The kind of a Gregorian calendar + Gregorian = 1, + /// The kind of a Buddhist calendar + Buddhist = 2, + /// The kind of a Japanese calendar with modern eras + Japanese = 3, + /// The kind of a Japanese calendar with modern and historic eras + JapaneseExtended = 4, + /// The kind of an Ethiopian calendar, with Amete Mihret era + Ethiopian = 5, + /// The kind of an Ethiopian calendar, with Amete Alem era + EthiopianAmeteAlem = 6, + /// The kind of a Indian calendar + Indian = 7, + /// The kind of a Coptic calendar + Coptic = 8, + /// The kind of a Dangi calendar + Dangi = 9, + /// The kind of a Chinese calendar + Chinese = 10, + /// The kind of a Hebrew calendar + Hebrew = 11, + /// The kind of a Islamic civil calendar + IslamicCivil = 12, + /// The kind of a Islamic observational calendar + IslamicObservational = 13, + /// The kind of a Islamic tabular calendar + IslamicTabular = 14, + /// The kind of a Islamic Umm al-Qura calendar + IslamicUmmAlQura = 15, + /// The kind of a Persian calendar + Persian = 16, + /// The kind of a Roc calendar + Roc = 17, + } + + impl ICU4XAnyCalendarKind { + /// Read the calendar type off of the -u-ca- extension on a locale. + /// + /// Errors if there is no calendar on the locale or if the locale's calendar + /// is not known or supported. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::get_for_locale, FnInEnum)] + pub fn get_for_locale(locale: &ICU4XLocale) -> Result { + AnyCalendarKind::get_for_locale(&locale.0) + .map(Into::into) + .ok_or(()) + } + + /// Obtain the calendar type given a BCP-47 -u-ca- extension string. + /// + /// Errors if the calendar is not known or supported. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::get_for_bcp47_value, FnInEnum)] + #[diplomat::rust_link( + icu::calendar::AnyCalendarKind::get_for_bcp47_string, + FnInEnum, + hidden + )] + #[diplomat::rust_link( + icu::calendar::AnyCalendarKind::get_for_bcp47_bytes, + FnInEnum, + hidden + )] + pub fn get_for_bcp47(s: &str) -> Result { + let s = s.as_bytes(); // #2520 + AnyCalendarKind::get_for_bcp47_bytes(s) + .map(Into::into) + .ok_or(()) + } + + /// Obtain the string suitable for use in the -u-ca- extension in a BCP47 locale. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::as_bcp47_string, FnInEnum)] + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::as_bcp47_value, FnInEnum, hidden)] + pub fn bcp47( + self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let kind = AnyCalendarKind::from(self); + Ok(write.write_str(kind.as_bcp47_string())?) + } + } + + #[diplomat::opaque] + #[diplomat::transparent_convert] + #[diplomat::rust_link(icu::calendar::AnyCalendar, Enum)] + pub struct ICU4XCalendar(pub Arc); + + impl ICU4XCalendar { + /// Creates a new [`ICU4XCalendar`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::AnyCalendar::new_for_locale, FnInEnum)] + pub fn create_for_locale( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XCalendar(Arc::new(call_constructor!( + AnyCalendar::new_for_locale [r => Ok(r)], + AnyCalendar::try_new_for_locale_with_any_provider, + AnyCalendar::try_new_for_locale_with_buffer_provider, + provider, + &locale + )?)))) + } + + /// Creates a new [`ICU4XCalendar`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::AnyCalendar::new, FnInEnum)] + pub fn create_for_kind( + provider: &ICU4XDataProvider, + kind: ICU4XAnyCalendarKind, + ) -> Result, ICU4XError> { + Ok(Box::new(ICU4XCalendar(Arc::new(call_constructor!( + AnyCalendar::new [r => Ok(r)], + AnyCalendar::try_new_with_any_provider, + AnyCalendar::try_new_with_buffer_provider, + provider, + kind.into() + )?)))) + } + + /// Returns the kind of this calendar + #[diplomat::rust_link(icu::calendar::AnyCalendar::kind, FnInEnum)] + pub fn kind(&self) -> ICU4XAnyCalendarKind { + self.0.kind().into() + } + } +} diff --git a/intl/icu_capi/src/casemap.rs b/intl/icu_capi/src/casemap.rs new file mode 100644 index 0000000000..cc20ccccec --- /dev/null +++ b/intl/icu_capi/src/casemap.rs @@ -0,0 +1,336 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_casemap::titlecase::TitlecaseOptions; + +#[diplomat::bridge] +pub mod ffi { + use crate::{ + errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider, + }; + use alloc::boxed::Box; + use diplomat_runtime::DiplomatWriteable; + use icu_casemap::titlecase::{LeadingAdjustment, TrailingCase}; + use icu_casemap::{CaseMapCloser, CaseMapper, TitlecaseMapper}; + use writeable::Writeable; + + #[diplomat::enum_convert(LeadingAdjustment, needs_wildcard)] + #[diplomat::rust_link(icu::casemap::titlecase::LeadingAdjustment, Enum)] + pub enum ICU4XLeadingAdjustment { + Auto, + None, + ToCased, + } + + #[diplomat::enum_convert(TrailingCase, needs_wildcard)] + #[diplomat::rust_link(icu::casemap::titlecase::TrailingCase, Enum)] + pub enum ICU4XTrailingCase { + Lower, + Unchanged, + } + + #[diplomat::rust_link(icu::casemap::titlecase::TitlecaseOptions, Struct)] + pub struct ICU4XTitlecaseOptionsV1 { + pub leading_adjustment: ICU4XLeadingAdjustment, + pub trailing_case: ICU4XTrailingCase, + } + + impl ICU4XTitlecaseOptionsV1 { + #[diplomat::rust_link(icu::casemap::titlecase::TitlecaseOptions::default, FnInStruct)] + pub fn default_options() -> ICU4XTitlecaseOptionsV1 { + // named default_options to avoid keyword clashes + Self { + leading_adjustment: ICU4XLeadingAdjustment::Auto, + trailing_case: ICU4XTrailingCase::Lower, + } + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::CaseMapper, Struct)] + pub struct ICU4XCaseMapper(pub CaseMapper); + + impl ICU4XCaseMapper { + /// Construct a new ICU4XCaseMapper instance + #[diplomat::rust_link(icu::casemap::CaseMapper::new, FnInStruct)] + pub fn create(provider: &ICU4XDataProvider) -> Result, ICU4XError> { + Ok(Box::new(ICU4XCaseMapper(call_constructor!( + CaseMapper::new [r => Ok(r)], + CaseMapper::try_new_with_any_provider, + CaseMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Returns the full lowercase mapping of the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::lowercase, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::lowercase_to_string, FnInStruct, hidden)] + pub fn lowercase( + &self, + s: &str, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.lowercase(s, &locale.0.id).write_to(write)?; + + Ok(()) + } + + /// Returns the full uppercase mapping of the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::uppercase, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::uppercase_to_string, FnInStruct, hidden)] + pub fn uppercase( + &self, + s: &str, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.uppercase(s, &locale.0.id).write_to(write)?; + + Ok(()) + } + + /// Returns the full titlecase mapping of the given string, performing head adjustment without + /// loading additional data. + /// (if head adjustment is enabled in the options) + /// + /// The `v1` refers to the version of the options struct, which may change as we add more options + #[diplomat::rust_link( + icu::casemap::CaseMapper::titlecase_segment_with_only_case_data, + FnInStruct + )] + #[diplomat::rust_link( + icu::casemap::CaseMapper::titlecase_segment_with_only_case_data_to_string, + FnInStruct, + hidden + )] + pub fn titlecase_segment_with_only_case_data_v1( + &self, + s: &str, + locale: &ICU4XLocale, + options: ICU4XTitlecaseOptionsV1, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0 + .titlecase_segment_with_only_case_data(s, &locale.0.id, options.into()) + .write_to(write)?; + + Ok(()) + } + + /// Case-folds the characters in the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::fold, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_string, FnInStruct, hidden)] + pub fn fold(&self, s: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.fold(s).write_to(write)?; + + Ok(()) + } + /// Case-folds the characters in the given string + /// using Turkic (T) mappings for dotted/dotless I. + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_turkic, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_turkic_string, FnInStruct, hidden)] + pub fn fold_turkic( + &self, + s: &str, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.fold_turkic(s).write_to(write)?; + + Ok(()) + } + + /// Adds all simple case mappings and the full case folding for `c` to `builder`. + /// Also adds special case closure mappings. + /// + /// In other words, this adds all characters that this casemaps to, as + /// well as all characters that may casemap to this one. + /// + /// Note that since ICU4XCodePointSetBuilder does not contain strings, this will + /// ignore string mappings. + /// + /// Identical to the similarly named method on `ICU4XCaseMapCloser`, use that if you + /// plan on using string case closure mappings too. + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapper::add_case_closure_to, FnInStruct)] + pub fn add_case_closure_to( + &self, + c: char, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) { + self.0.add_case_closure_to(c, &mut builder.0) + } + + /// Returns the simple lowercase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::lowercase`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_lowercase, FnInStruct)] + pub fn simple_lowercase(&self, ch: char) -> char { + self.0.simple_lowercase(ch) + } + + /// Returns the simple uppercase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::uppercase`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_uppercase, FnInStruct)] + pub fn simple_uppercase(&self, ch: char) -> char { + self.0.simple_uppercase(ch) + } + + /// Returns the simple titlecase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::titlecase_segment`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_titlecase, FnInStruct)] + pub fn simple_titlecase(&self, ch: char) -> char { + self.0.simple_titlecase(ch) + } + + /// Returns the simple casefolding of the given character. + /// + /// This function only implements simple folding. + /// For full folding, use `ICU4XCaseMapper::fold`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_fold, FnInStruct)] + pub fn simple_fold(&self, ch: char) -> char { + self.0.simple_fold(ch) + } + /// Returns the simple casefolding of the given character in the Turkic locale + /// + /// This function only implements simple folding. + /// For full folding, use `ICU4XCaseMapper::fold_turkic`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_fold_turkic, FnInStruct)] + pub fn simple_fold_turkic(&self, ch: char) -> char { + self.0.simple_fold_turkic(ch) + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::CaseMapCloser, Struct)] + pub struct ICU4XCaseMapCloser(pub CaseMapCloser); + + impl ICU4XCaseMapCloser { + /// Construct a new ICU4XCaseMapper instance + #[diplomat::rust_link(icu::casemap::CaseMapCloser::new, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::new_with_mapper, FnInStruct, hidden)] + pub fn create(provider: &ICU4XDataProvider) -> Result, ICU4XError> { + Ok(Box::new(ICU4XCaseMapCloser(call_constructor!( + CaseMapCloser::new [r => Ok(r)], + CaseMapCloser::try_new_with_any_provider, + CaseMapCloser::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Adds all simple case mappings and the full case folding for `c` to `builder`. + /// Also adds special case closure mappings. + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::add_case_closure_to, FnInStruct)] + pub fn add_case_closure_to( + &self, + c: char, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) { + self.0.add_case_closure_to(c, &mut builder.0) + } + + /// Finds all characters and strings which may casemap to `s` as their full case folding string + /// and adds them to the set. + /// + /// Returns true if the string was found + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::add_string_case_closure_to, FnInStruct)] + pub fn add_string_case_closure_to( + &self, + s: &str, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) -> bool { + // #2520 + // In the future we should be able to make assumptions based on backend + let s = core::str::from_utf8(s.as_bytes()).unwrap_or(""); + self.0.add_string_case_closure_to(s, &mut builder.0) + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::TitlecaseMapper, Struct)] + pub struct ICU4XTitlecaseMapper(pub TitlecaseMapper); + + impl ICU4XTitlecaseMapper { + /// Construct a new `ICU4XTitlecaseMapper` instance + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::new, FnInStruct)] + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::new_with_mapper, FnInStruct, hidden)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result, ICU4XError> { + Ok(Box::new(ICU4XTitlecaseMapper(call_constructor!( + TitlecaseMapper::new [r => Ok(r)], + TitlecaseMapper::try_new_with_any_provider, + TitlecaseMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Returns the full titlecase mapping of the given string + /// + /// The `v1` refers to the version of the options struct, which may change as we add more options + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::titlecase_segment, FnInStruct)] + #[diplomat::rust_link( + icu::casemap::TitlecaseMapper::titlecase_segment_to_string, + FnInStruct, + hidden + )] + pub fn titlecase_segment_v1( + &self, + s: &str, + locale: &ICU4XLocale, + options: ICU4XTitlecaseOptionsV1, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0 + .titlecase_segment(s, &locale.0.id, options.into()) + .write_to(write)?; + + Ok(()) + } + } +} + +impl From for TitlecaseOptions { + fn from(other: ffi::ICU4XTitlecaseOptionsV1) -> Self { + let mut ret = Self::default(); + + ret.leading_adjustment = other.leading_adjustment.into(); + ret.trailing_case = other.trailing_case.into(); + ret + } +} diff --git a/intl/icu_capi/src/collator.rs b/intl/icu_capi/src/collator.rs new file mode 100644 index 0000000000..bbf56786b7 --- /dev/null +++ b/intl/icu_capi/src/collator.rs @@ -0,0 +1,237 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_collator::{Collator, CollatorOptions}; + + use crate::{ + common::ffi::ICU4XOrdering, errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + }; + + #[diplomat::opaque] + #[diplomat::rust_link(icu::collator::Collator, Struct)] + pub struct ICU4XCollator(pub Collator); + + #[diplomat::rust_link(icu::collator::CollatorOptions, Struct)] + #[diplomat::rust_link(icu::collator::CollatorOptions::new, FnInStruct, hidden)] + pub struct ICU4XCollatorOptionsV1 { + pub strength: ICU4XCollatorStrength, + pub alternate_handling: ICU4XCollatorAlternateHandling, + pub case_first: ICU4XCollatorCaseFirst, + pub max_variable: ICU4XCollatorMaxVariable, + pub case_level: ICU4XCollatorCaseLevel, + pub numeric: ICU4XCollatorNumeric, + pub backward_second_level: ICU4XCollatorBackwardSecondLevel, + } + + #[diplomat::rust_link(icu::collator::Strength, Enum)] + pub enum ICU4XCollatorStrength { + Auto = 0, + Primary = 1, + Secondary = 2, + Tertiary = 3, + Quaternary = 4, + Identical = 5, + } + + #[diplomat::rust_link(icu::collator::AlternateHandling, Enum)] + pub enum ICU4XCollatorAlternateHandling { + Auto = 0, + NonIgnorable = 1, + Shifted = 2, + } + + #[diplomat::rust_link(icu::collator::CaseFirst, Enum)] + pub enum ICU4XCollatorCaseFirst { + Auto = 0, + Off = 1, + LowerFirst = 2, + UpperFirst = 3, + } + + #[diplomat::rust_link(icu::collator::MaxVariable, Enum)] + pub enum ICU4XCollatorMaxVariable { + Auto = 0, + Space = 1, + Punctuation = 2, + Symbol = 3, + Currency = 4, + } + + #[diplomat::rust_link(icu::collator::CaseLevel, Enum)] + pub enum ICU4XCollatorCaseLevel { + Auto = 0, + Off = 1, + On = 2, + } + + #[diplomat::rust_link(icu::collator::Numeric, Enum)] + pub enum ICU4XCollatorNumeric { + Auto = 0, + Off = 1, + On = 2, + } + + #[diplomat::rust_link(icu::collator::BackwardSecondLevel, Enum)] + pub enum ICU4XCollatorBackwardSecondLevel { + Auto = 0, + Off = 1, + On = 2, + } + + impl ICU4XCollator { + /// Construct a new Collator instance. + #[diplomat::rust_link(icu::collator::Collator::try_new, FnInStruct)] + pub fn create_v1( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + options: ICU4XCollatorOptionsV1, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + let options = CollatorOptions::from(options); + + Ok(Box::new(ICU4XCollator(call_constructor!( + Collator::try_new, + Collator::try_new_with_any_provider, + Collator::try_new_with_buffer_provider, + provider, + &locale, + options, + )?))) + } + + /// Compare potentially ill-formed UTF-8 strings. + /// + /// Ill-formed input is compared + /// as if errors had been replaced with REPLACEMENT CHARACTERs according + /// to the WHATWG Encoding Standard. + #[diplomat::rust_link(icu::collator::Collator::compare_utf8, FnInStruct)] + #[diplomat::attr(dart, disable)] + pub fn compare(&self, left: &str, right: &str) -> ICU4XOrdering { + let left = left.as_bytes(); // #2520 + let right = right.as_bytes(); // #2520 + self.0.compare_utf8(left, right).into() + } + + /// Compare guaranteed well-formed UTF-8 strings. + /// + /// Note: In C++, passing ill-formed UTF-8 strings is undefined behavior + /// (and may be memory-unsafe to do so, too). + #[diplomat::rust_link(icu::collator::Collator::compare, FnInStruct)] + #[diplomat::attr(dart, rename = "compare")] + pub fn compare_valid_utf8(&self, left: &str, right: &str) -> ICU4XOrdering { + self.0.compare(left, right).into() + } + + /// Compare potentially ill-formed UTF-16 strings, with unpaired surrogates + /// compared as REPLACEMENT CHARACTER. + #[diplomat::rust_link(icu::collator::Collator::compare_utf16, FnInStruct)] + pub fn compare_utf16(&self, left: &[u16], right: &[u16]) -> ICU4XOrdering { + self.0.compare_utf16(left, right).into() + } + } +} + +use icu_collator::{ + AlternateHandling, BackwardSecondLevel, CaseFirst, CaseLevel, CollatorOptions, MaxVariable, + Numeric, Strength, +}; + +impl From for Option { + fn from(strength: ffi::ICU4XCollatorStrength) -> Option { + match strength { + ffi::ICU4XCollatorStrength::Auto => None, + ffi::ICU4XCollatorStrength::Primary => Some(Strength::Primary), + ffi::ICU4XCollatorStrength::Secondary => Some(Strength::Secondary), + ffi::ICU4XCollatorStrength::Tertiary => Some(Strength::Tertiary), + ffi::ICU4XCollatorStrength::Quaternary => Some(Strength::Quaternary), + ffi::ICU4XCollatorStrength::Identical => Some(Strength::Identical), + } + } +} + +impl From for Option { + fn from(alternate_handling: ffi::ICU4XCollatorAlternateHandling) -> Option { + match alternate_handling { + ffi::ICU4XCollatorAlternateHandling::Auto => None, + ffi::ICU4XCollatorAlternateHandling::NonIgnorable => { + Some(AlternateHandling::NonIgnorable) + } + ffi::ICU4XCollatorAlternateHandling::Shifted => Some(AlternateHandling::Shifted), + } + } +} + +impl From for Option { + fn from(case_first: ffi::ICU4XCollatorCaseFirst) -> Option { + match case_first { + ffi::ICU4XCollatorCaseFirst::Auto => None, + ffi::ICU4XCollatorCaseFirst::Off => Some(CaseFirst::Off), + ffi::ICU4XCollatorCaseFirst::LowerFirst => Some(CaseFirst::LowerFirst), + ffi::ICU4XCollatorCaseFirst::UpperFirst => Some(CaseFirst::UpperFirst), + } + } +} + +impl From for Option { + fn from(max_variable: ffi::ICU4XCollatorMaxVariable) -> Option { + match max_variable { + ffi::ICU4XCollatorMaxVariable::Auto => None, + ffi::ICU4XCollatorMaxVariable::Space => Some(MaxVariable::Space), + ffi::ICU4XCollatorMaxVariable::Punctuation => Some(MaxVariable::Punctuation), + ffi::ICU4XCollatorMaxVariable::Symbol => Some(MaxVariable::Symbol), + ffi::ICU4XCollatorMaxVariable::Currency => Some(MaxVariable::Currency), + } + } +} + +impl From for Option { + fn from(case_level: ffi::ICU4XCollatorCaseLevel) -> Option { + match case_level { + ffi::ICU4XCollatorCaseLevel::Auto => None, + ffi::ICU4XCollatorCaseLevel::Off => Some(CaseLevel::Off), + ffi::ICU4XCollatorCaseLevel::On => Some(CaseLevel::On), + } + } +} + +impl From for Option { + fn from(numeric: ffi::ICU4XCollatorNumeric) -> Option { + match numeric { + ffi::ICU4XCollatorNumeric::Auto => None, + ffi::ICU4XCollatorNumeric::Off => Some(Numeric::Off), + ffi::ICU4XCollatorNumeric::On => Some(Numeric::On), + } + } +} + +impl From for Option { + fn from( + backward_second_level: ffi::ICU4XCollatorBackwardSecondLevel, + ) -> Option { + match backward_second_level { + ffi::ICU4XCollatorBackwardSecondLevel::Auto => None, + ffi::ICU4XCollatorBackwardSecondLevel::Off => Some(BackwardSecondLevel::Off), + ffi::ICU4XCollatorBackwardSecondLevel::On => Some(BackwardSecondLevel::On), + } + } +} + +impl From for CollatorOptions { + fn from(options: ffi::ICU4XCollatorOptionsV1) -> CollatorOptions { + let mut result = CollatorOptions::new(); + result.strength = options.strength.into(); + result.alternate_handling = options.alternate_handling.into(); + result.case_first = options.case_first.into(); + result.max_variable = options.max_variable.into(); + result.case_level = options.case_level.into(); + result.numeric = options.numeric.into(); + result.backward_second_level = options.backward_second_level.into(); + + result + } +} diff --git a/intl/icu_capi/src/collections_sets.rs b/intl/icu_capi/src/collections_sets.rs new file mode 100644 index 0000000000..68ce1dfd1a --- /dev/null +++ b/intl/icu_capi/src/collections_sets.rs @@ -0,0 +1,223 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::properties_sets::ffi::ICU4XCodePointSetData; + use alloc::boxed::Box; + use core::mem; + use icu_collections::codepointinvlist::CodePointInversionListBuilder; + use icu_properties::sets::CodePointSetData; + + #[diplomat::opaque] + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder, + Struct + )] + pub struct ICU4XCodePointSetBuilder(pub CodePointInversionListBuilder); + + impl ICU4XCodePointSetBuilder { + /// Make a new set builder containing nothing + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::new, + FnInStruct + )] + pub fn create() -> Box { + Box::new(Self(CodePointInversionListBuilder::new())) + } + + /// Build this into a set + /// + /// This object is repopulated with an empty builder + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::build, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::from_code_point_inversion_list, + FnInStruct, + hidden + )] + pub fn build(&mut self) -> Box { + let inner = mem::take(&mut self.0); + let built = inner.build(); + let set = CodePointSetData::from_code_point_inversion_list(built); + Box::new(ICU4XCodePointSetData(set)) + } + + /// Complements this set + /// + /// (Elements in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement, + FnInStruct + )] + pub fn complement(&mut self) { + self.0.complement() + } + + /// Returns whether this set is empty + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::is_empty, + FnInStruct + )] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Add a single character to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_char, + FnInStruct + )] + pub fn add_char(&mut self, ch: char) { + self.0.add_char(ch) + } + + /// Add a single u32 value to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_u32, + FnInStruct + )] + #[diplomat::attr(dart, disable)] + pub fn add_u32(&mut self, ch: u32) { + self.0.add_u32(ch) + } + + /// Add an inclusive range of characters to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_range, + FnInStruct + )] + pub fn add_inclusive_range(&mut self, start: char, end: char) { + self.0.add_range(&(start..=end)) + } + + /// Add an inclusive range of u32s to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_range_u32, + FnInStruct + )] + #[diplomat::attr(dart, disable)] + pub fn add_inclusive_range_u32(&mut self, start: u32, end: u32) { + self.0.add_range_u32(&(start..=end)) + } + + /// Add all elements that belong to the provided set to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_set, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::as_code_point_inversion_list, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::to_code_point_inversion_list, + FnInStruct, + hidden + )] + pub fn add_set(&mut self, data: &ICU4XCodePointSetData) { + // This is a ZeroFrom and always cheap for a CPIL, may be expensive + // for other impls. In the future we can make this builder support multiple impls + // if we ever add them + let list = data.0.to_code_point_inversion_list(); + self.0.add_set(&list); + } + + /// Remove a single character to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_char, + FnInStruct + )] + pub fn remove_char(&mut self, ch: char) { + self.0.remove_char(ch) + } + + /// Remove an inclusive range of characters from the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_range, + FnInStruct + )] + pub fn remove_inclusive_range(&mut self, start: char, end: char) { + self.0.remove_range(&(start..=end)) + } + + /// Remove all elements that belong to the provided set from the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_set, + FnInStruct + )] + pub fn remove_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.remove_set(&list); + } + + /// Removes all elements from the set except a single character + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_char, + FnInStruct + )] + pub fn retain_char(&mut self, ch: char) { + self.0.retain_char(ch) + } + + /// Removes all elements from the set except an inclusive range of characters f + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_range, + FnInStruct + )] + pub fn retain_inclusive_range(&mut self, start: char, end: char) { + self.0.retain_range(&(start..=end)) + } + + /// Removes all elements from the set except all elements in the provided set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_set, + FnInStruct + )] + pub fn retain_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.retain_set(&list); + } + + /// Complement a single character to the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_char, + FnInStruct + )] + pub fn complement_char(&mut self, ch: char) { + self.0.complement_char(ch) + } + + /// Complement an inclusive range of characters from the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_range, + FnInStruct + )] + pub fn complement_inclusive_range(&mut self, start: char, end: char) { + self.0.complement_range(&(start..=end)) + } + + /// Complement all elements that belong to the provided set from the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_set, + FnInStruct + )] + pub fn complement_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.complement_set(&list); + } + } +} diff --git a/intl/icu_capi/src/common.rs b/intl/icu_capi/src/common.rs new file mode 100644 index 0000000000..d11ca26044 --- /dev/null +++ b/intl/icu_capi/src/common.rs @@ -0,0 +1,16 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + + #[diplomat::enum_convert(core::cmp::Ordering)] + #[diplomat::rust_link(core::cmp::Ordering, Enum)] + pub enum ICU4XOrdering { + Less = -1, + Equal = 0, + Greater = 1, + } +} diff --git a/intl/icu_capi/src/data_struct.rs b/intl/icu_capi/src/data_struct.rs new file mode 100644 index 0000000000..60765cec24 --- /dev/null +++ b/intl/icu_capi/src/data_struct.rs @@ -0,0 +1,90 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[cfg(feature = "icu_decimal")] +use alloc::borrow::{Cow, ToOwned}; + +#[diplomat::bridge] +pub mod ffi { + + #[cfg(feature = "icu_decimal")] + use crate::errors::ffi::ICU4XError; + use alloc::boxed::Box; + use icu_provider::AnyPayload; + #[cfg(feature = "icu_decimal")] + use icu_provider::DataPayload; + + #[diplomat::opaque] + /// A generic data struct to be used by ICU4X + /// + /// This can be used to construct a StructDataProvider. + #[diplomat::attr(dart, disable)] + pub struct ICU4XDataStruct(#[allow(dead_code)] AnyPayload); + + impl ICU4XDataStruct { + /// Construct a new DecimalSymbolsV1 data struct. + /// + /// C++ users: All string arguments must be valid UTF8 + #[diplomat::rust_link(icu::decimal::provider::DecimalSymbolsV1, Struct)] + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "icu_decimal")] + pub fn create_decimal_symbols_v1( + plus_sign_prefix: &str, + plus_sign_suffix: &str, + minus_sign_prefix: &str, + minus_sign_suffix: &str, + decimal_separator: &str, + grouping_separator: &str, + primary_group_size: u8, + secondary_group_size: u8, + min_group_size: u8, + digits: &[char], + ) -> Result, ICU4XError> { + use super::str_to_cow; + use icu_decimal::provider::{ + AffixesV1, DecimalSymbolsV1, DecimalSymbolsV1Marker, GroupingSizesV1, + }; + let digits = if digits.len() == 10 { + let mut new_digits = ['\0'; 10]; + new_digits.copy_from_slice(digits); + new_digits + } else { + return Err(ICU4XError::DataStructValidityError); + }; + let plus_sign_affixes = AffixesV1 { + prefix: str_to_cow(plus_sign_prefix), + suffix: str_to_cow(plus_sign_suffix), + }; + let minus_sign_affixes = AffixesV1 { + prefix: str_to_cow(minus_sign_prefix), + suffix: str_to_cow(minus_sign_suffix), + }; + let grouping_sizes = GroupingSizesV1 { + primary: primary_group_size, + secondary: secondary_group_size, + min_grouping: min_group_size, + }; + + let symbols = DecimalSymbolsV1 { + plus_sign_affixes, + minus_sign_affixes, + decimal_separator: str_to_cow(decimal_separator), + grouping_separator: str_to_cow(grouping_separator), + grouping_sizes, + digits, + }; + + let payload: DataPayload = DataPayload::from_owned(symbols); + Ok(Box::new(ICU4XDataStruct(payload.wrap_into_any_payload()))) + } + } +} +#[cfg(feature = "icu_decimal")] +fn str_to_cow(s: &str) -> Cow<'static, str> { + if s.is_empty() { + Cow::default() + } else { + Cow::from(s.to_owned()) + } +} diff --git a/intl/icu_capi/src/date.rs b/intl/icu_capi/src/date.rs new file mode 100644 index 0000000000..39fd03835c --- /dev/null +++ b/intl/icu_capi/src/date.rs @@ -0,0 +1,300 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + use core::fmt::Write; + use icu_calendar::types::IsoWeekday; + use icu_calendar::AnyCalendar; + use icu_calendar::{Date, Iso}; + use tinystr::TinyAsciiStr; + + use crate::calendar::ffi::ICU4XCalendar; + use crate::errors::ffi::ICU4XError; + + #[cfg(feature = "icu_calendar")] + use crate::week::ffi::ICU4XWeekCalculator; + + #[diplomat::enum_convert(IsoWeekday)] + pub enum ICU4XIsoWeekday { + Monday = 1, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, + } + #[diplomat::opaque] + #[diplomat::transparent_convert] + /// An ICU4X Date object capable of containing a ISO-8601 date + #[diplomat::rust_link(icu::calendar::Date, Struct)] + pub struct ICU4XIsoDate(pub Date); + + impl ICU4XIsoDate { + /// Creates a new [`ICU4XIsoDate`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::Date::try_new_iso_date, FnInStruct)] + pub fn create(year: i32, month: u8, day: u8) -> Result, ICU4XError> { + Ok(Box::new(ICU4XIsoDate(Date::try_new_iso_date( + year, month, day, + )?))) + } + + /// Creates a new [`ICU4XIsoDate`] representing January 1, 1970. + #[diplomat::rust_link(icu::calendar::Date::unix_epoch, FnInStruct)] + pub fn create_for_unix_epoch() -> Box { + Box::new(ICU4XIsoDate(Date::unix_epoch())) + } + + /// Convert this date to one in a different calendar + #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box { + Box::new(ICU4XDate(self.0.to_calendar(calendar.0.clone()))) + } + + #[diplomat::rust_link(icu::calendar::Date::to_any, FnInStruct)] + pub fn to_any(&self) -> Box { + Box::new(ICU4XDate(self.0.to_any().wrap_calendar_in_arc())) + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result { + Ok(self.0.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month(&self) -> u32 { + self.0.month().ordinal + } + + /// Returns the year number for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year(&self) -> i32 { + self.0.year().number + } + + /// Returns if the year is a leap year for this date + #[diplomat::rust_link(icu::calendar::Date::is_in_leap_year, FnInStruct)] + pub fn is_in_leap_year(&self) -> bool { + self.0.is_in_leap_year() + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.days_in_year() + } + } + + #[diplomat::opaque] + #[diplomat::transparent_convert] + /// An ICU4X Date object capable of containing a date and time for any calendar. + #[diplomat::rust_link(icu::calendar::Date, Struct)] + pub struct ICU4XDate(pub Date>); + + impl ICU4XDate { + /// Creates a new [`ICU4XDate`] representing the ISO date and time + /// given but in a given calendar + #[diplomat::rust_link(icu::calendar::Date::new_from_iso, FnInStruct)] + pub fn create_from_iso_in_calendar( + year: i32, + month: u8, + day: u8, + calendar: &ICU4XCalendar, + ) -> Result, ICU4XError> { + let cal = calendar.0.clone(); + Ok(Box::new(ICU4XDate( + Date::try_new_iso_date(year, month, day)?.to_calendar(cal), + ))) + } + + /// Creates a new [`ICU4XDate`] from the given codes, which are interpreted in the given calendar system + #[diplomat::rust_link(icu::calendar::Date::try_new_from_codes, FnInStruct)] + pub fn create_from_codes_in_calendar( + era_code: &str, + year: i32, + month_code: &str, + day: u8, + calendar: &ICU4XCalendar, + ) -> Result, ICU4XError> { + let era_code = era_code.as_bytes(); // #2520 + let month_code = month_code.as_bytes(); // #2520 + let era = TinyAsciiStr::from_bytes(era_code)?.into(); + let month = TinyAsciiStr::from_bytes(month_code)?.into(); + let cal = calendar.0.clone(); + Ok(Box::new(ICU4XDate(Date::try_new_from_codes( + era, year, month, day, cal, + )?))) + } + + /// Convert this date to one in a different calendar + #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box { + Box::new(ICU4XDate(self.0.to_calendar(calendar.0.clone()))) + } + + /// Converts this date to ISO + #[diplomat::rust_link(icu::calendar::Date::to_iso, FnInStruct)] + pub fn to_iso(&self) -> Box { + Box::new(ICU4XIsoDate(self.0.to_iso())) + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result { + Ok(self.0.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + /// + /// Note that for lunar calendars this may not lead to the same month + /// having the same ordinal month across years; use month_code if you care + /// about month identity. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn ordinal_month(&self) -> u32 { + self.0.month().ordinal + } + + /// Returns the month code for this date. Typically something + /// like "M01", "M02", but can be more complicated for lunar calendars. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month_code( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let code = self.0.month().code; + write.write_str(&code.0)?; + Ok(()) + } + + /// Returns the year number in the current era for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year_in_era(&self) -> i32 { + self.0.year().number + } + + /// Returns the era for this date, + #[diplomat::rust_link(icu::Date::year, FnInStruct)] + #[diplomat::rust_link(icu::types::Era, Struct, compact)] + pub fn era( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let era = self.0.year().era; + write.write_str(&era.0)?; + Ok(()) + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.days_in_year() + } + + /// Returns the [`ICU4XCalendar`] object backing this date + #[diplomat::rust_link(icu::calendar::Date::calendar, FnInStruct)] + #[diplomat::rust_link(icu::calendar::Date::calendar_wrapper, FnInStruct, hidden)] + pub fn calendar(&self) -> Box { + Box::new(ICU4XCalendar(self.0.calendar_wrapper().clone())) + } + } +} diff --git a/intl/icu_capi/src/datetime.rs b/intl/icu_capi/src/datetime.rs new file mode 100644 index 0000000000..627b9c16a3 --- /dev/null +++ b/intl/icu_capi/src/datetime.rs @@ -0,0 +1,415 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + use core::convert::TryInto; + use core::fmt::Write; + + use icu_calendar::types::Time; + use icu_calendar::AnyCalendar; + use icu_calendar::{DateTime, Iso}; + use tinystr::TinyAsciiStr; + + use crate::calendar::ffi::ICU4XCalendar; + use crate::date::ffi::{ICU4XDate, ICU4XIsoDate, ICU4XIsoWeekday}; + use crate::errors::ffi::ICU4XError; + use crate::time::ffi::ICU4XTime; + + #[cfg(feature = "icu_calendar")] + use crate::week::ffi::ICU4XWeekCalculator; + + #[diplomat::opaque] + /// An ICU4X DateTime object capable of containing a ISO-8601 date and time. + #[diplomat::rust_link(icu::calendar::DateTime, Struct)] + pub struct ICU4XIsoDateTime(pub DateTime); + + impl ICU4XIsoDateTime { + /// Creates a new [`ICU4XIsoDateTime`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::DateTime::try_new_iso_datetime, FnInStruct)] + pub fn create( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Result, ICU4XError> { + let mut dt = DateTime::try_new_iso_datetime(year, month, day, hour, minute, second)?; + dt.time.nanosecond = nanosecond.try_into()?; + Ok(Box::new(ICU4XIsoDateTime(dt))) + } + + /// Creates a new [`ICU4XIsoDateTime`] from an [`ICU4XIsoDate`] and [`ICU4XTime`] object + #[diplomat::rust_link(icu::calendar::DateTime::new, FnInStruct)] + #[diplomat::attr(dart, rename = "create_from_date_and_time")] + pub fn crate_from_date_and_time( + date: &ICU4XIsoDate, + time: &ICU4XTime, + ) -> Box { + let dt = DateTime::new(date.0, time.0); + Box::new(ICU4XIsoDateTime(dt)) + } + + /// Construct from the minutes since the local unix epoch for this date (Jan 1 1970, 00:00) + #[diplomat::rust_link( + icu::calendar::DateTime::from_minutes_since_local_unix_epoch, + FnInStruct + )] + pub fn create_from_minutes_since_local_unix_epoch(minutes: i32) -> Box { + Box::new(ICU4XIsoDateTime( + DateTime::from_minutes_since_local_unix_epoch(minutes), + )) + } + + /// Gets the date contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::date, StructField)] + pub fn date(&self) -> Box { + Box::new(ICU4XIsoDate(self.0.date)) + } + + /// Gets the time contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::time, StructField)] + pub fn time(&self) -> Box { + Box::new(ICU4XTime(self.0.time)) + } + + /// Converts this to an [`ICU4XDateTime`] capable of being mixed with dates of + /// other calendars + #[diplomat::rust_link(icu::calendar::DateTime::to_any, FnInStruct)] + #[diplomat::rust_link(icu::calendar::DateTime::new_from_iso, FnInStruct, hidden)] + pub fn to_any(&self) -> Box { + Box::new(ICU4XDateTime(self.0.to_any().wrap_calendar_in_arc())) + } + + /// Gets the minutes since the local unix epoch for this date (Jan 1 1970, 00:00) + #[diplomat::rust_link(icu::calendar::DateTime::minutes_since_local_unix_epoch, FnInStruct)] + pub fn minutes_since_local_unix_epoch(&self) -> i32 { + self.0.minutes_since_local_unix_epoch() + } + + /// Convert this datetime to one in a different calendar + #[diplomat::rust_link(icu::calendar::DateTime::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box { + Box::new(ICU4XDateTime(self.0.to_calendar(calendar.0.clone()))) + } + + /// Returns the hour in this time + #[diplomat::rust_link(icu::calendar::types::Time::hour, StructField)] + pub fn hour(&self) -> u8 { + self.0.time.hour.into() + } + /// Returns the minute in this time + #[diplomat::rust_link(icu::calendar::types::Time::minute, StructField)] + pub fn minute(&self) -> u8 { + self.0.time.minute.into() + } + /// Returns the second in this time + #[diplomat::rust_link(icu::calendar::types::Time::second, StructField)] + pub fn second(&self) -> u8 { + self.0.time.second.into() + } + /// Returns the nanosecond in this time + #[diplomat::rust_link(icu::calendar::types::Time::nanosecond, StructField)] + pub fn nanosecond(&self) -> u32 { + self.0.time.nanosecond.into() + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.date.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.date.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.date.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result { + Ok(self.0.date.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month(&self) -> u32 { + self.0.date.month().ordinal + } + + /// Returns the year number for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year(&self) -> i32 { + self.0.date.year().number + } + + /// Returns whether this date is in a leap year + #[diplomat::rust_link(icu::calendar::Date::is_in_leap_year, FnInStruct)] + pub fn is_in_leap_year(&self) -> bool { + self.0.date.is_in_leap_year() + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.date.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.date.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.date.days_in_year() + } + } + + #[diplomat::opaque] + /// An ICU4X DateTime object capable of containing a date and time for any calendar. + #[diplomat::rust_link(icu::calendar::DateTime, Struct)] + pub struct ICU4XDateTime(pub DateTime>); + + impl ICU4XDateTime { + /// Creates a new [`ICU4XDateTime`] representing the ISO date and time + /// given but in a given calendar + #[diplomat::rust_link(icu::DateTime::new_from_iso, FnInStruct)] + #[allow(clippy::too_many_arguments)] + pub fn create_from_iso_in_calendar( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + calendar: &ICU4XCalendar, + ) -> Result, ICU4XError> { + let cal = calendar.0.clone(); + let mut dt = DateTime::try_new_iso_datetime(year, month, day, hour, minute, second)?; + dt.time.nanosecond = nanosecond.try_into()?; + Ok(Box::new(ICU4XDateTime(dt.to_calendar(cal)))) + } + /// Creates a new [`ICU4XDateTime`] from the given codes, which are interpreted in the given calendar system + #[diplomat::rust_link(icu::calendar::DateTime::try_new_from_codes, FnInStruct)] + #[allow(clippy::too_many_arguments)] + pub fn create_from_codes_in_calendar( + era_code: &str, + year: i32, + month_code: &str, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + calendar: &ICU4XCalendar, + ) -> Result, ICU4XError> { + let era_code = era_code.as_bytes(); // #2520 + let month_code = month_code.as_bytes(); // #2520 + let era = TinyAsciiStr::from_bytes(era_code)?.into(); + let month = TinyAsciiStr::from_bytes(month_code)?.into(); + let cal = calendar.0.clone(); + let hour = hour.try_into()?; + let minute = minute.try_into()?; + let second = second.try_into()?; + let nanosecond = nanosecond.try_into()?; + let time = Time { + hour, + minute, + second, + nanosecond, + }; + Ok(Box::new(ICU4XDateTime(DateTime::try_new_from_codes( + era, year, month, day, time, cal, + )?))) + } + /// Creates a new [`ICU4XDateTime`] from an [`ICU4XDate`] and [`ICU4XTime`] object + #[diplomat::rust_link(icu::calendar::DateTime::new, FnInStruct)] + pub fn create_from_date_and_time(date: &ICU4XDate, time: &ICU4XTime) -> Box { + let dt = DateTime::new(date.0.clone(), time.0); + Box::new(ICU4XDateTime(dt)) + } + + /// Gets a copy of the date contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::date, StructField)] + pub fn date(&self) -> Box { + Box::new(ICU4XDate(self.0.date.clone())) + } + + /// Gets the time contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::time, StructField)] + pub fn time(&self) -> Box { + Box::new(ICU4XTime(self.0.time)) + } + + /// Converts this date to ISO + #[diplomat::rust_link(icu::calendar::DateTime::to_iso, FnInStruct)] + pub fn to_iso(&self) -> Box { + Box::new(ICU4XIsoDateTime(self.0.to_iso())) + } + + /// Convert this datetime to one in a different calendar + #[diplomat::rust_link(icu::calendar::DateTime::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box { + Box::new(ICU4XDateTime(self.0.to_calendar(calendar.0.clone()))) + } + + /// Returns the hour in this time + #[diplomat::rust_link(icu::calendar::types::Time::hour, StructField)] + pub fn hour(&self) -> u8 { + self.0.time.hour.into() + } + /// Returns the minute in this time + #[diplomat::rust_link(icu::calendar::types::Time::minute, StructField)] + pub fn minute(&self) -> u8 { + self.0.time.minute.into() + } + /// Returns the second in this time + #[diplomat::rust_link(icu::calendar::types::Time::second, StructField)] + pub fn second(&self) -> u8 { + self.0.time.second.into() + } + /// Returns the nanosecond in this time + #[diplomat::rust_link(icu::calendar::types::Time::nanosecond, StructField)] + pub fn nanosecond(&self) -> u32 { + self.0.time.nanosecond.into() + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.date.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.date.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.date.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result { + Ok(self.0.date.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + /// + /// Note that for lunar calendars this may not lead to the same month + /// having the same ordinal month across years; use month_code if you care + /// about month identity. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn ordinal_month(&self) -> u32 { + self.0.date.month().ordinal + } + + /// Returns the month code for this date. Typically something + /// like "M01", "M02", but can be more complicated for lunar calendars. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month_code( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let code = self.0.date.month().code; + write.write_str(&code.0)?; + Ok(()) + } + + /// Returns the year number in the current era for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year_in_era(&self) -> i32 { + self.0.date.year().number + } + + /// Returns the era for this date, + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn era( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let era = self.0.date.year().era; + write.write_str(&era.0)?; + Ok(()) + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.date.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.date.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.date.days_in_year() + } + + /// Returns the [`ICU4XCalendar`] object backing this date + #[diplomat::rust_link(icu::calendar::Date::calendar, FnInStruct)] + #[diplomat::rust_link(icu::calendar::Date::calendar_wrapper, FnInStruct, hidden)] + pub fn calendar(&self) -> Box { + Box::new(ICU4XCalendar(self.0.date.calendar_wrapper().clone())) + } + } +} diff --git a/intl/icu_capi/src/datetime_formatter.rs b/intl/icu_capi/src/datetime_formatter.rs new file mode 100644 index 0000000000..c68b3381e9 --- /dev/null +++ b/intl/icu_capi/src/datetime_formatter.rs @@ -0,0 +1,358 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_calendar::{Date, DateTime, Gregorian}; + use icu_datetime::{ + options::length, DateFormatter, DateTimeFormatter, TimeFormatter, TypedDateFormatter, + TypedDateTimeFormatter, + }; + + use crate::{ + date::ffi::{ICU4XDate, ICU4XIsoDate}, + datetime::ffi::ICU4XDateTime, + datetime::ffi::ICU4XIsoDateTime, + errors::ffi::ICU4XError, + locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + time::ffi::ICU4XTime, + }; + use writeable::Writeable; + + #[diplomat::opaque] + /// An ICU4X TimeFormatter object capable of formatting an [`ICU4XTime`] type (and others) as a string + #[diplomat::rust_link(icu::datetime::TimeFormatter, Struct)] + pub struct ICU4XTimeFormatter(pub TimeFormatter); + + #[diplomat::enum_convert(length::Time, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::options::length::Time, Enum)] + pub enum ICU4XTimeLength { + Full, + Long, + Medium, + Short, + } + + impl ICU4XTimeFormatter { + /// Creates a new [`ICU4XTimeFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TimeFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XTimeLength, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XTimeFormatter(call_constructor!( + TimeFormatter::try_new_with_length, + TimeFormatter::try_new_with_length_with_any_provider, + TimeFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + + /// Formats a [`ICU4XTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_time( + &self, + value: &ICU4XTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X TypedDateFormatter object capable of formatting a [`ICU4XIsoDateTime`] as a string, + /// using the Gregorian Calendar. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter, Struct)] + pub struct ICU4XGregorianDateFormatter(pub TypedDateFormatter); + + #[diplomat::enum_convert(length::Date, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::options::length::Date, Enum)] + pub enum ICU4XDateLength { + Full, + Long, + Medium, + Short, + } + + impl ICU4XGregorianDateFormatter { + /// Creates a new [`ICU4XGregorianDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XDateLength, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XGregorianDateFormatter(call_constructor!( + TypedDateFormatter::try_new_with_length, + TypedDateFormatter::try_new_with_length_with_any_provider, + TypedDateFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + + /// Formats a [`ICU4XIsoDate`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_date( + &self, + value: &ICU4XIsoDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = Date::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = DateTime::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X TypedDateTimeFormatter object capable of formatting a [`ICU4XIsoDateTime`] as a string, + /// using the Gregorian Calendar. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter, Struct)] + pub struct ICU4XGregorianDateTimeFormatter(pub TypedDateTimeFormatter); + + impl ICU4XGregorianDateTimeFormatter { + /// Creates a new [`ICU4XGregorianDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + let options = length::Bag::from_date_time_style(date_length.into(), time_length.into()); + + Ok(Box::new(ICU4XGregorianDateTimeFormatter( + call_constructor!( + TypedDateTimeFormatter::try_new, + TypedDateTimeFormatter::try_new_with_any_provider, + TypedDateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + options.into() + )?, + ))) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = DateTime::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X DateFormatter object capable of formatting a [`ICU4XDate`] as a string, + /// using some calendar specified at runtime in the locale. + #[diplomat::rust_link(icu::datetime::DateFormatter, Struct)] + pub struct ICU4XDateFormatter(pub DateFormatter); + + impl ICU4XDateFormatter { + /// Creates a new [`ICU4XDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::DateFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XDateFormatter(call_constructor!( + DateFormatter::try_new_with_length, + DateFormatter::try_new_with_length_with_any_provider, + DateFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + date_length.into() + )?))) + } + + /// Formats a [`ICU4XDate`] to a string. + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_date( + &self, + value: &ICU4XDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDate`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_date( + &self, + value: &ICU4XIsoDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X DateFormatter object capable of formatting a [`ICU4XDateTime`] as a string, + /// using some calendar specified at runtime in the locale. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter, Struct)] + pub struct ICU4XDateTimeFormatter(pub DateTimeFormatter); + + impl ICU4XDateTimeFormatter { + /// Creates a new [`ICU4XDateTimeFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + let options = length::Bag::from_date_time_style(date_length.into(), time_length.into()); + + Ok(Box::new(ICU4XDateTimeFormatter(call_constructor!( + DateTimeFormatter::try_new, + DateTimeFormatter::try_new_with_any_provider, + DateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + options.into() + )?))) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::DateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::DateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/decimal.rs b/intl/icu_capi/src/decimal.rs new file mode 100644 index 0000000000..290f1c5a63 --- /dev/null +++ b/intl/icu_capi/src/decimal.rs @@ -0,0 +1,109 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_decimal::{ + options::{FixedDecimalFormatterOptions, GroupingStrategy}, + provider::DecimalSymbolsV1Marker, + FixedDecimalFormatter, + }; + use icu_provider_adapters::any_payload::AnyPayloadProvider; + use writeable::Writeable; + + use crate::{ + data_struct::ffi::ICU4XDataStruct, errors::ffi::ICU4XError, + fixed_decimal::ffi::ICU4XFixedDecimal, locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + }; + + #[diplomat::opaque] + /// An ICU4X Fixed Decimal Format object, capable of formatting a [`ICU4XFixedDecimal`] as a string. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter, Struct)] + pub struct ICU4XFixedDecimalFormatter(pub FixedDecimalFormatter); + + #[diplomat::rust_link(icu::decimal::options::GroupingStrategy, Enum)] + pub enum ICU4XFixedDecimalGroupingStrategy { + Auto, + Never, + Always, + Min2, + } + + impl ICU4XFixedDecimalFormatter { + /// Creates a new [`ICU4XFixedDecimalFormatter`] from locale data. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter::try_new, FnInStruct)] + pub fn create_with_grouping_strategy( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + grouping_strategy: ICU4XFixedDecimalGroupingStrategy, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + + let grouping_strategy = match grouping_strategy { + ICU4XFixedDecimalGroupingStrategy::Auto => GroupingStrategy::Auto, + ICU4XFixedDecimalGroupingStrategy::Never => GroupingStrategy::Never, + ICU4XFixedDecimalGroupingStrategy::Always => GroupingStrategy::Always, + ICU4XFixedDecimalGroupingStrategy::Min2 => GroupingStrategy::Min2, + }; + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = grouping_strategy; + Ok(Box::new(ICU4XFixedDecimalFormatter(call_constructor!( + FixedDecimalFormatter::try_new, + FixedDecimalFormatter::try_new_with_any_provider, + FixedDecimalFormatter::try_new_with_buffer_provider, + provider, + &locale, + options, + )?))) + } + + /// Creates a new [`ICU4XFixedDecimalFormatter`] from preconstructed locale data in the form of an [`ICU4XDataStruct`] + /// constructed from `ICU4XDataStruct::create_decimal_symbols()`. + /// + /// The contents of the data struct will be consumed: if you wish to use the struct again it will have to be reconstructed. + /// Passing a consumed struct to this method will return an error. + #[diplomat::attr(dart, disable)] + pub fn create_with_decimal_symbols_v1( + data_struct: &ICU4XDataStruct, + grouping_strategy: ICU4XFixedDecimalGroupingStrategy, + ) -> Result, ICU4XError> { + let grouping_strategy = match grouping_strategy { + ICU4XFixedDecimalGroupingStrategy::Auto => GroupingStrategy::Auto, + ICU4XFixedDecimalGroupingStrategy::Never => GroupingStrategy::Never, + ICU4XFixedDecimalGroupingStrategy::Always => GroupingStrategy::Always, + ICU4XFixedDecimalGroupingStrategy::Min2 => GroupingStrategy::Min2, + }; + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = grouping_strategy; + Ok(Box::new(ICU4XFixedDecimalFormatter( + FixedDecimalFormatter::try_new_with_any_provider( + &AnyPayloadProvider::from_any_payload::( + // Note: This clone is free, since cloning AnyPayload is free. + data_struct.0.clone(), + ), + &Default::default(), + options, + )?, + ))) + } + + /// Formats a [`ICU4XFixedDecimal`] to a string. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::decimal::FixedDecimalFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format( + &self, + value: &ICU4XFixedDecimal, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/displaynames.rs b/intl/icu_capi/src/displaynames.rs new file mode 100644 index 0000000000..eec14ed839 --- /dev/null +++ b/intl/icu_capi/src/displaynames.rs @@ -0,0 +1,156 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use diplomat_runtime::DiplomatWriteable; + #[allow(unused_imports)] // feature-specific + use icu_displaynames::{DisplayNamesOptions, Fallback, LanguageDisplay}; + use icu_displaynames::{LocaleDisplayNamesFormatter, RegionDisplayNames}; + use icu_locid::subtags::Region; + use writeable::Writeable; + + // FFI version of `LocaleDisplayNamesFormatter`. + #[diplomat::opaque] + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter, Struct)] + pub struct ICU4XLocaleDisplayNamesFormatter(pub LocaleDisplayNamesFormatter); + + // FFI version of `RegionDisplayNames`. + #[diplomat::opaque] + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames, Struct)] + pub struct ICU4XRegionDisplayNames(pub RegionDisplayNames); + + // FFI version of `DisplayNamesOptions`. + #[diplomat::rust_link(icu::displaynames::options::DisplayNamesOptions, Struct)] + pub struct ICU4XDisplayNamesOptionsV1 { + /// The optional formatting style to use for display name. + pub style: ICU4XDisplayNamesStyle, + /// The fallback return when the system does not have the + /// requested display name, defaults to "code". + pub fallback: ICU4XDisplayNamesFallback, + /// The language display kind, defaults to "dialect". + pub language_display: ICU4XLanguageDisplay, + } + + // FFI version of `Style` option for the display names. + #[diplomat::rust_link(icu::displaynames::options::Style, Enum)] + pub enum ICU4XDisplayNamesStyle { + Auto, + Narrow, + Short, + Long, + Menu, + } + + // FFI version of `Fallback` option for the display names. + #[diplomat::rust_link(icu::displaynames::options::Fallback, Enum)] + #[diplomat::enum_convert(Fallback, needs_wildcard)] + pub enum ICU4XDisplayNamesFallback { + Code, + None, + } + + // FFI version of `LanguageDisplay`. + #[diplomat::rust_link(icu::displaynames::options::LanguageDisplay, Enum)] + #[diplomat::enum_convert(LanguageDisplay, needs_wildcard)] + pub enum ICU4XLanguageDisplay { + Dialect, + Standard, + } + + impl ICU4XLocaleDisplayNamesFormatter { + /// Creates a new `LocaleDisplayNamesFormatter` from locale data and an options bag. + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter::try_new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + options: ICU4XDisplayNamesOptionsV1, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + let options = DisplayNamesOptions::from(options); + + Ok(Box::new(ICU4XLocaleDisplayNamesFormatter( + call_constructor!( + LocaleDisplayNamesFormatter::try_new, + LocaleDisplayNamesFormatter::try_new_with_any_provider, + LocaleDisplayNamesFormatter::try_new_with_buffer_provider, + provider, + &locale, + options, + )?, + ))) + } + + /// Returns the locale-specific display name of a locale. + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter::of, FnInStruct)] + pub fn of( + &self, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.of(&locale.0).write_to(write)?; + Ok(()) + } + } + + impl ICU4XRegionDisplayNames { + /// Creates a new `RegionDisplayNames` from locale data and an options bag. + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames::try_new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XRegionDisplayNames(call_constructor!( + RegionDisplayNames::try_new, + RegionDisplayNames::try_new_with_any_provider, + RegionDisplayNames::try_new_with_buffer_provider, + provider, + &locale, + Default::default() + )?))) + } + + /// Returns the locale specific display name of a region. + /// Note that the funtion returns an empty string in case the display name for a given + /// region code is not found. + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames::of, FnInStruct)] + pub fn of(&self, region: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + self.0 + .of(region.parse::()?) + .unwrap_or("") + .write_to(write)?; + Ok(()) + } + } +} + +#[allow(unused_imports)] // feature-specific +use icu_displaynames::{DisplayNamesOptions, Fallback, LanguageDisplay, Style}; + +impl From for Option