#![cfg_attr(not(any(test, doc)), no_std)]
#![cfg_attr(
not(test),
deny(
clippy::indexing_slicing,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
)
)]
#![warn(missing_docs)]
extern crate alloc;
mod operands;
mod options;
pub mod provider;
#[cfg(feature = "unstable")]
mod raw_operands;
#[cfg(feature = "unstable")]
pub use raw_operands::RawPluralOperands;
use core::cmp::{Ord, PartialOrd};
use core::convert::Infallible;
use icu_locale_core::preferences::define_preferences;
use icu_provider::marker::ErasedMarker;
use icu_provider::prelude::*;
pub use operands::PluralOperands;
pub use options::*;
use provider::PluralRulesData;
use provider::PluralsCardinalV1;
use provider::PluralsOrdinalV1;
use provider::rules::runtime::test_rule;
#[cfg(feature = "unstable")]
use provider::PluralsRangesV1;
#[cfg(feature = "unstable")]
use provider::UnvalidatedPluralRange;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_plurals))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[repr(u8)]
#[zerovec::make_ule(PluralCategoryULE)]
#[allow(clippy::exhaustive_enums)] pub enum PluralCategory {
Zero = 0,
One = 1,
Two = 2,
Few = 3,
Many = 4,
Other = 5,
}
impl PluralCategory {
pub fn all() -> impl ExactSizeIterator<Item = Self> {
[
Self::Few,
Self::Many,
Self::One,
Self::Other,
Self::Two,
Self::Zero,
]
.iter()
.copied()
}
pub fn get_for_cldr_string(category: &str) -> Option<PluralCategory> {
Self::get_for_cldr_bytes(category.as_bytes())
}
pub fn get_for_cldr_bytes(category: &[u8]) -> Option<PluralCategory> {
match category {
b"zero" => Some(PluralCategory::Zero),
b"one" => Some(PluralCategory::One),
b"two" => Some(PluralCategory::Two),
b"few" => Some(PluralCategory::Few),
b"many" => Some(PluralCategory::Many),
b"other" => Some(PluralCategory::Other),
_ => None,
}
}
}
define_preferences!(
[Copy]
PluralRulesPreferences,
{}
);
#[derive(Debug)]
pub struct PluralRules(DataPayload<ErasedMarker<PluralRulesData<'static>>>);
impl AsRef<PluralRules> for PluralRules {
fn as_ref(&self) -> &PluralRules {
self
}
}
impl PluralRules {
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences, options: PluralRulesOptions) -> error: DataError,
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
pub fn try_new_unstable(
provider: &(impl DataProvider<PluralsCardinalV1> + DataProvider<PluralsOrdinalV1> + ?Sized),
prefs: PluralRulesPreferences,
options: PluralRulesOptions,
) -> Result<Self, DataError> {
match options.rule_type.unwrap_or_default() {
PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, prefs),
PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, prefs),
}
}
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences) -> error: DataError,
functions: [
try_new_cardinal,
try_new_cardinal_with_buffer_provider,
try_new_cardinal_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
pub fn try_new_cardinal_unstable(
provider: &(impl DataProvider<PluralsCardinalV1> + ?Sized),
prefs: PluralRulesPreferences,
) -> Result<Self, DataError> {
let locale = PluralsCardinalV1::make_locale(prefs.locale_preferences);
Ok(Self(
provider
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
..Default::default()
})?
.payload
.cast(),
))
}
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences) -> error: DataError,
functions: [
try_new_ordinal,
try_new_ordinal_with_buffer_provider,
try_new_ordinal_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
pub fn try_new_ordinal_unstable(
provider: &(impl DataProvider<PluralsOrdinalV1> + ?Sized),
prefs: PluralRulesPreferences,
) -> Result<Self, DataError> {
let locale = PluralsOrdinalV1::make_locale(prefs.locale_preferences);
Ok(Self(
provider
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
..Default::default()
})?
.payload
.cast(),
))
}
pub fn category_for<I: Into<PluralOperands>>(&self, input: I) -> PluralCategory {
let rules = self.0.get();
let input = input.into();
macro_rules! test_rule {
($rule:ident, $cat:ident) => {
rules
.$rule
.as_ref()
.and_then(|r| test_rule(r, &input).then(|| PluralCategory::$cat))
};
}
test_rule!(zero, Zero)
.or_else(|| test_rule!(one, One))
.or_else(|| test_rule!(two, Two))
.or_else(|| test_rule!(few, Few))
.or_else(|| test_rule!(many, Many))
.unwrap_or(PluralCategory::Other)
}
pub fn categories(&self) -> impl Iterator<Item = PluralCategory> + '_ {
let rules = self.0.get();
macro_rules! test_rule {
($rule:ident, $cat:ident) => {
rules
.$rule
.as_ref()
.map(|_| PluralCategory::$cat)
.into_iter()
};
}
test_rule!(zero, Zero)
.chain(test_rule!(one, One))
.chain(test_rule!(two, Two))
.chain(test_rule!(few, Few))
.chain(test_rule!(many, Many))
.chain(Some(PluralCategory::Other))
}
}
#[cfg(feature = "unstable")]
#[derive(Debug)]
pub struct PluralRulesWithRanges<R> {
rules: R,
ranges: DataPayload<PluralsRangesV1>,
}
#[cfg(feature = "unstable")]
impl PluralRulesWithRanges<PluralRules> {
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences, options: PluralRulesOptions) -> error: DataError,
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
pub fn try_new_unstable(
provider: &(
impl DataProvider<PluralsRangesV1>
+ DataProvider<PluralsCardinalV1>
+ DataProvider<PluralsOrdinalV1>
+ ?Sized
),
prefs: PluralRulesPreferences,
options: PluralRulesOptions,
) -> Result<Self, DataError> {
match options.rule_type.unwrap_or_default() {
PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, prefs),
PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, prefs),
}
}
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences) -> error: DataError,
functions: [
try_new_cardinal,
try_new_cardinal_with_buffer_provider,
try_new_cardinal_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
pub fn try_new_cardinal_unstable(
provider: &(impl DataProvider<PluralsCardinalV1> + DataProvider<PluralsRangesV1> + ?Sized),
prefs: PluralRulesPreferences,
) -> Result<Self, DataError> {
let rules = PluralRules::try_new_cardinal_unstable(provider, prefs)?;
PluralRulesWithRanges::try_new_with_rules_unstable(provider, prefs, rules)
}
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences) -> error: DataError,
functions: [
try_new_ordinal,
try_new_ordinal_with_buffer_provider,
try_new_ordinal_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
pub fn try_new_ordinal_unstable(
provider: &(impl DataProvider<PluralsOrdinalV1> + DataProvider<PluralsRangesV1> + ?Sized),
prefs: PluralRulesPreferences,
) -> Result<Self, DataError> {
let rules = PluralRules::try_new_ordinal_unstable(provider, prefs)?;
PluralRulesWithRanges::try_new_with_rules_unstable(provider, prefs, rules)
}
}
#[cfg(feature = "unstable")]
impl<R> PluralRulesWithRanges<R>
where
R: AsRef<PluralRules>,
{
icu_provider::gen_buffer_data_constructors!(
(prefs: PluralRulesPreferences, rules: R) -> error: DataError,
functions: [
try_new_with_rules,
try_new_with_rules_with_buffer_provider,
try_new_with_rules_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_rules)]
pub fn try_new_with_rules_unstable(
provider: &(impl DataProvider<PluralsRangesV1> + ?Sized),
prefs: PluralRulesPreferences,
rules: R,
) -> Result<Self, DataError> {
let locale = PluralsRangesV1::make_locale(prefs.locale_preferences);
let ranges = provider
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
..Default::default()
})?
.payload;
Ok(Self { rules, ranges })
}
pub fn rules(&self) -> &PluralRules {
self.rules.as_ref()
}
pub fn category_for_range<S: Into<PluralOperands>, E: Into<PluralOperands>>(
&self,
start: S,
end: E,
) -> PluralCategory {
let rules = self.rules.as_ref();
let start = rules.category_for(start);
let end = rules.category_for(end);
self.resolve_range(start, end)
}
pub fn resolve_range(&self, start: PluralCategory, end: PluralCategory) -> PluralCategory {
self.ranges
.get()
.ranges
.get_copied(&UnvalidatedPluralRange::from_range(
start.into(),
end.into(),
))
.map(PluralCategory::from)
.unwrap_or(end)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluralElements<T>(PluralElementsInner<T>);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
pub(crate) struct PluralElementsInner<T> {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
zero: Option<T>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
one: Option<T>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
two: Option<T>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
few: Option<T>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
many: Option<T>,
other: T,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
explicit_zero: Option<T>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
explicit_one: Option<T>,
}
impl<'a, T, C> zerofrom::ZeroFrom<'a, PluralElementsInner<C>> for PluralElementsInner<T>
where
T: zerofrom::ZeroFrom<'a, C>,
{
fn zero_from(other: &'a PluralElementsInner<C>) -> Self {
other.as_ref().map(|x| zerofrom::ZeroFrom::zero_from(x))
}
}
impl<T> PluralElementsInner<T> {
pub fn as_ref(&self) -> PluralElementsInner<&T> {
PluralElementsInner {
other: &self.other,
zero: self.zero.as_ref(),
one: self.one.as_ref(),
two: self.two.as_ref(),
few: self.few.as_ref(),
many: self.many.as_ref(),
explicit_zero: self.explicit_zero.as_ref(),
explicit_one: self.explicit_one.as_ref(),
}
}
pub fn map<B, F: FnMut(T) -> B>(self, mut f: F) -> PluralElementsInner<B> {
let Ok(x) = self.try_map(move |x| Ok::<B, Infallible>(f(x)));
x
}
pub fn try_map<B, E, F: FnMut(T) -> Result<B, E>>(
self,
mut f: F,
) -> Result<PluralElementsInner<B>, E> {
Ok(PluralElementsInner {
other: f(self.other)?,
zero: self.zero.map(&mut f).transpose()?,
one: self.one.map(&mut f).transpose()?,
two: self.two.map(&mut f).transpose()?,
few: self.few.map(&mut f).transpose()?,
many: self.many.map(&mut f).transpose()?,
explicit_zero: self.explicit_zero.map(&mut f).transpose()?,
explicit_one: self.explicit_one.map(&mut f).transpose()?,
})
}
}
impl<'a, T, C> zerofrom::ZeroFrom<'a, PluralElements<C>> for PluralElements<T>
where
T: zerofrom::ZeroFrom<'a, C>,
{
fn zero_from(other: &'a PluralElements<C>) -> Self {
other.as_ref().map(|x| zerofrom::ZeroFrom::zero_from(x))
}
}
impl<T> PluralElements<T> {
pub fn new(other: T) -> Self {
Self(PluralElementsInner {
other,
zero: None,
one: None,
two: None,
few: None,
many: None,
explicit_zero: None,
explicit_one: None,
})
}
pub fn zero(&self) -> &T {
self.0.zero.as_ref().unwrap_or(&self.0.other)
}
pub fn one(&self) -> &T {
self.0.one.as_ref().unwrap_or(&self.0.other)
}
pub fn two(&self) -> &T {
self.0.two.as_ref().unwrap_or(&self.0.other)
}
pub fn few(&self) -> &T {
self.0.few.as_ref().unwrap_or(&self.0.other)
}
pub fn many(&self) -> &T {
self.0.many.as_ref().unwrap_or(&self.0.other)
}
pub fn other(&self) -> &T {
&self.0.other
}
pub fn try_into_other(self) -> Option<T> {
match self.0 {
PluralElementsInner {
zero: None,
one: None,
two: None,
few: None,
many: None,
other,
explicit_zero: None,
explicit_one: None,
} => Some(other),
_ => None,
}
}
pub fn explicit_zero(&self) -> Option<&T> {
self.0.explicit_zero.as_ref()
}
pub fn explicit_one(&self) -> Option<&T> {
self.0.explicit_one.as_ref()
}
pub fn map<B, F: FnMut(T) -> B>(self, f: F) -> PluralElements<B> {
PluralElements(self.0.map(f))
}
pub fn try_map<B, E, F: FnMut(T) -> Result<B, E>>(self, f: F) -> Result<PluralElements<B>, E> {
self.0.try_map(f).map(PluralElements)
}
pub fn for_each<F: FnMut(&T)>(&self, mut f: F) {
#[expect(clippy::unit_arg)] let Ok(()) = self.try_for_each(move |x| Ok::<(), Infallible>(f(x)));
}
pub fn try_for_each<E, F: FnMut(&T) -> Result<(), E>>(&self, mut f: F) -> Result<(), E> {
let _ = PluralElements(PluralElementsInner {
other: f(&self.0.other)?,
zero: self.0.zero.as_ref().map(&mut f).transpose()?,
one: self.0.one.as_ref().map(&mut f).transpose()?,
two: self.0.two.as_ref().map(&mut f).transpose()?,
few: self.0.few.as_ref().map(&mut f).transpose()?,
many: self.0.many.as_ref().map(&mut f).transpose()?,
explicit_zero: self.0.explicit_zero.as_ref().map(&mut f).transpose()?,
explicit_one: self.0.explicit_one.as_ref().map(&mut f).transpose()?,
});
Ok(())
}
pub fn for_each_mut<F: FnMut(&mut T)>(&mut self, mut f: F) {
#[expect(clippy::unit_arg)] let Ok(()) = self.try_for_each_mut(move |x| Ok::<(), Infallible>(f(x)));
}
pub fn try_for_each_mut<E, F: FnMut(&mut T) -> Result<(), E>>(
&mut self,
mut f: F,
) -> Result<(), E> {
let _ = PluralElements(PluralElementsInner {
other: f(&mut self.0.other)?,
zero: self.0.zero.as_mut().map(&mut f).transpose()?,
one: self.0.one.as_mut().map(&mut f).transpose()?,
two: self.0.two.as_mut().map(&mut f).transpose()?,
few: self.0.few.as_mut().map(&mut f).transpose()?,
many: self.0.many.as_mut().map(&mut f).transpose()?,
explicit_zero: self.0.explicit_zero.as_mut().map(&mut f).transpose()?,
explicit_one: self.0.explicit_one.as_mut().map(&mut f).transpose()?,
});
Ok(())
}
pub fn as_ref(&self) -> PluralElements<&T> {
PluralElements(self.0.as_ref())
}
pub fn get<'a>(&'a self, op: PluralOperands, rules: &PluralRules) -> &'a T {
let category = rules.category_for(op);
if op.is_exactly_zero()
&& let Some(value) = self.0.explicit_zero.as_ref()
{
return value;
}
if op.is_exactly_one()
&& let Some(value) = self.0.explicit_one.as_ref()
{
return value;
}
match category {
PluralCategory::Zero => self.0.zero.as_ref(),
PluralCategory::One => self.0.one.as_ref(),
PluralCategory::Two => self.0.two.as_ref(),
PluralCategory::Few => self.0.few.as_ref(),
PluralCategory::Many => self.0.many.as_ref(),
PluralCategory::Other => return &self.0.other,
}
.unwrap_or(&self.0.other)
}
}
impl<T: PartialEq> PluralElements<T> {
pub fn with_zero_value(self, zero: Option<T>) -> Self {
Self(PluralElementsInner {
zero: zero.filter(|t| *t != self.0.other),
..self.0
})
}
pub fn with_one_value(self, one: Option<T>) -> Self {
Self(PluralElementsInner {
one: one.filter(|t| *t != self.0.other),
..self.0
})
}
pub fn with_two_value(self, two: Option<T>) -> Self {
Self(PluralElementsInner {
two: two.filter(|t| *t != self.0.other),
..self.0
})
}
pub fn with_few_value(self, few: Option<T>) -> Self {
Self(PluralElementsInner {
few: few.filter(|t| *t != self.0.other),
..self.0
})
}
pub fn with_many_value(self, many: Option<T>) -> Self {
Self(PluralElementsInner {
many: many.filter(|t| *t != self.0.other),
..self.0
})
}
pub fn with_explicit_zero_value(self, explicit_zero: Option<T>) -> Self {
Self(PluralElementsInner {
explicit_zero,
..self.0
})
}
pub fn with_explicit_one_value(self, explicit_one: Option<T>) -> Self {
Self(PluralElementsInner {
explicit_one,
..self.0
})
}
}