use crate::{
fields::{Field, FieldSymbol, Week},
pattern::{runtime::Pattern, PatternError, PatternItem},
};
use either::Either;
use icu_plurals::PluralCategory;
use icu_provider::prelude::*;
#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
#[cfg_attr(
feature = "datagen",
derive(serde::Serialize, databake::Bake),
databake(path = icu_datetime::pattern::runtime),
)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[allow(clippy::exhaustive_structs)] pub struct PluralPattern<'data> {
pub pivot_field: Week,
#[cfg_attr(feature = "serde", serde(borrow))]
pub zero: Option<Pattern<'data>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub one: Option<Pattern<'data>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub two: Option<Pattern<'data>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub few: Option<Pattern<'data>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub many: Option<Pattern<'data>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub other: Pattern<'data>,
}
impl<'data> PluralPattern<'data> {
pub fn new(pattern: Pattern<'data>) -> Result<Self, PatternError> {
let pivot_field = pattern
.items
.iter()
.find_map(|pattern_item| match pattern_item {
PatternItem::Field(Field {
symbol: FieldSymbol::Week(w),
..
}) => Some(w),
_ => None,
})
.ok_or(PatternError::UnsupportedPluralPivot)?;
Ok(Self {
pivot_field,
zero: None,
one: None,
two: None,
few: None,
many: None,
other: pattern,
})
}
pub fn pivot_field(&self) -> Week {
self.pivot_field
}
pub fn maybe_set_variant(&mut self, category: PluralCategory, pattern: Pattern<'data>) {
if pattern == self.other {
return;
}
match category {
PluralCategory::Zero => self.zero = Some(pattern),
PluralCategory::One => self.one = Some(pattern),
PluralCategory::Two => self.two = Some(pattern),
PluralCategory::Few => self.few = Some(pattern),
PluralCategory::Many => self.many = Some(pattern),
PluralCategory::Other => unreachable!("You can't override other"),
}
}
pub(crate) fn variant(&self, category: PluralCategory) -> &Pattern<'data> {
let variant = match category {
PluralCategory::Zero => &self.zero,
PluralCategory::One => &self.one,
PluralCategory::Two => &self.two,
PluralCategory::Few => &self.few,
PluralCategory::Many => &self.many,
PluralCategory::Other => return &self.other,
};
variant.as_ref().unwrap_or(&self.other)
}
pub fn patterns_iter(&self) -> impl Iterator<Item = &Pattern<'data>> {
PluralCategory::all().filter_map(move |cat| match cat {
PluralCategory::Zero => self.zero.as_ref(),
PluralCategory::One => self.one.as_ref(),
PluralCategory::Two => self.two.as_ref(),
PluralCategory::Few => self.few.as_ref(),
PluralCategory::Many => self.many.as_ref(),
PluralCategory::Other => Some(&self.other),
})
}
pub fn for_each_mut<F>(&mut self, f: &F)
where
F: Fn(&mut Pattern<'data>),
{
self.zero.iter_mut().for_each(f);
self.one.iter_mut().for_each(f);
self.two.iter_mut().for_each(f);
self.few.iter_mut().for_each(f);
self.many.iter_mut().for_each(f);
f(&mut self.other);
}
pub fn into_owned(self) -> PluralPattern<'static> {
PluralPattern {
pivot_field: self.pivot_field,
zero: self.zero.map(|p| p.into_owned()),
one: self.one.map(|p| p.into_owned()),
two: self.two.map(|p| p.into_owned()),
few: self.few.map(|p| p.into_owned()),
many: self.many.map(|p| p.into_owned()),
other: self.other.into_owned(),
}
}
}
#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
#[allow(clippy::large_enum_variant)]
#[allow(clippy::exhaustive_enums)] #[cfg_attr(
feature = "datagen",
derive(databake::Bake),
databake(path = icu_datetime::pattern::runtime),
)]
pub enum PatternPlurals<'data> {
MultipleVariants(PluralPattern<'data>),
SinglePattern(Pattern<'data>),
}
impl<'data> PatternPlurals<'data> {
pub fn into_owned(self) -> PatternPlurals<'static> {
match self {
Self::SinglePattern(pattern) => PatternPlurals::SinglePattern(pattern.into_owned()),
Self::MultipleVariants(plural_pattern) => {
PatternPlurals::MultipleVariants(plural_pattern.into_owned())
}
}
}
pub fn patterns_iter(&self) -> impl Iterator<Item = &Pattern<'data>> {
match self {
Self::SinglePattern(pattern) => Either::Left(core::iter::once(pattern)),
Self::MultipleVariants(plural_pattern) => Either::Right(plural_pattern.patterns_iter()),
}
}
pub fn for_each_mut<F>(&mut self, f: F)
where
F: Fn(&mut Pattern<'data>),
{
match self {
Self::SinglePattern(pattern) => f(pattern),
Self::MultipleVariants(variants) => variants.for_each_mut(&f),
}
}
pub fn expect_pattern(self, msg: &str) -> Pattern<'data> {
match self {
Self::SinglePattern(pattern) => pattern,
Self::MultipleVariants(patterns) => {
debug_assert!(
false,
"expect_pattern called with bad data (falling back to `other` pattern): {msg}"
);
patterns.other
}
}
}
pub fn normalize(&mut self) {
if let Self::MultipleVariants(patterns) = self {
if patterns.patterns_iter().count() == 1 {
*self = Self::SinglePattern(core::mem::take(&mut patterns.other));
}
}
}
}
impl<'data> From<Pattern<'data>> for PatternPlurals<'data> {
fn from(pattern: Pattern<'data>) -> Self {
Self::SinglePattern(pattern)
}
}
impl<'data> From<PluralPattern<'data>> for PatternPlurals<'data> {
fn from(pattern: PluralPattern<'data>) -> Self {
Self::MultipleVariants(pattern)
}
}
impl Default for PatternPlurals<'_> {
fn default() -> Self {
PatternPlurals::SinglePattern(Pattern::default())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn build_plural_pattern() {
let red_pattern: Pattern = "'red' w".parse().unwrap();
let blue_pattern: Pattern = "'blue' w".parse().unwrap();
let mut patterns =
PluralPattern::new(blue_pattern.clone()).expect("PluralPattern::new failed");
patterns.maybe_set_variant(PluralCategory::Zero, red_pattern.clone());
patterns.maybe_set_variant(PluralCategory::One, blue_pattern.clone());
patterns.maybe_set_variant(PluralCategory::Two, red_pattern.clone());
patterns.maybe_set_variant(PluralCategory::Few, red_pattern.clone());
patterns.maybe_set_variant(PluralCategory::Many, blue_pattern.clone());
assert_eq!(patterns.pivot_field, Week::WeekOfYear);
assert_eq!(patterns.zero, Some(red_pattern.clone()));
assert_eq!(patterns.one, None); assert_eq!(patterns.two, Some(red_pattern));
assert_eq!(patterns.other, blue_pattern);
}
#[test]
fn normalize_pattern_plurals_switches_singletons_to_single_pattern() {
let pattern: Pattern = "'red' w".parse().unwrap();
let patterns = PluralPattern::new(pattern.clone()).expect("PluralPattern::new failed");
let mut plural_patterns: PatternPlurals = PatternPlurals::MultipleVariants(patterns);
plural_patterns.normalize();
assert_eq!(plural_patterns, PatternPlurals::SinglePattern(pattern));
}
}