icu_datetime/provider/skeleton/
plural.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::provider::fields::{components, Field, FieldSymbol, Week};
6use crate::provider::pattern::{runtime::Pattern, PatternError, PatternItem};
7use either::Either;
8use icu_plurals::PluralCategory;
9use icu_provider::prelude::*;
10
11/// A collection of plural variants of a pattern.
12#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
13#[allow(missing_docs)]
14pub struct PluralPattern<'data> {
15    /// The field that 'variants' are predicated on.
16    pub pivot_field: Week,
17
18    pub zero: Option<Pattern<'data>>,
19    pub one: Option<Pattern<'data>>,
20    pub two: Option<Pattern<'data>>,
21    pub few: Option<Pattern<'data>>,
22    pub many: Option<Pattern<'data>>,
23    pub other: Pattern<'data>,
24}
25
26#[allow(missing_docs)]
27impl<'data> PluralPattern<'data> {
28    /// Creates a [`PluralPattern`] from a pattern containing a pivot field.
29    pub fn new(pattern: Pattern<'data>) -> Result<Self, PatternError> {
30        let pivot_field = pattern
31            .items
32            .iter()
33            .find_map(|pattern_item| match pattern_item {
34                PatternItem::Field(Field {
35                    symbol: FieldSymbol::Week(w),
36                    ..
37                }) => Some(w),
38                _ => None,
39            })
40            .ok_or(PatternError::UnsupportedPluralPivot)?;
41
42        Ok(Self {
43            pivot_field,
44            zero: None,
45            one: None,
46            two: None,
47            few: None,
48            many: None,
49            other: pattern,
50        })
51    }
52
53    /// Returns which week field determines the [icu_plurals::PluralCategory] used to select a pattern variant for a given date.
54    pub fn pivot_field(&self) -> Week {
55        self.pivot_field
56    }
57
58    pub fn maybe_set_variant(&mut self, category: PluralCategory, pattern: Pattern<'data>) {
59        if pattern == self.other {
60            return;
61        }
62        match category {
63            PluralCategory::Zero => self.zero = Some(pattern),
64            PluralCategory::One => self.one = Some(pattern),
65            PluralCategory::Two => self.two = Some(pattern),
66            PluralCategory::Few => self.few = Some(pattern),
67            PluralCategory::Many => self.many = Some(pattern),
68            PluralCategory::Other => unreachable!("You can't override other"),
69        }
70    }
71
72    pub fn patterns_iter(&self) -> impl Iterator<Item = &Pattern<'data>> {
73        PluralCategory::all().filter_map(move |cat| match cat {
74            PluralCategory::Zero => self.zero.as_ref(),
75            PluralCategory::One => self.one.as_ref(),
76            PluralCategory::Two => self.two.as_ref(),
77            PluralCategory::Few => self.few.as_ref(),
78            PluralCategory::Many => self.many.as_ref(),
79            PluralCategory::Other => Some(&self.other),
80        })
81    }
82
83    pub fn for_each_mut<F>(&mut self, f: &F)
84    where
85        F: Fn(&mut Pattern<'data>),
86    {
87        self.zero.iter_mut().for_each(f);
88        self.one.iter_mut().for_each(f);
89        self.two.iter_mut().for_each(f);
90        self.few.iter_mut().for_each(f);
91        self.many.iter_mut().for_each(f);
92        f(&mut self.other);
93    }
94
95    pub fn into_owned(self) -> PluralPattern<'static> {
96        PluralPattern {
97            pivot_field: self.pivot_field,
98            zero: self.zero.map(|p| p.into_owned()),
99            one: self.one.map(|p| p.into_owned()),
100            two: self.two.map(|p| p.into_owned()),
101            few: self.few.map(|p| p.into_owned()),
102            many: self.many.map(|p| p.into_owned()),
103            other: self.other.into_owned(),
104        }
105    }
106}
107
108/// Either a [`Pattern`] single pattern or a [`PluralPattern`] collection of
109/// patterns when there are plural variants.
110///
111/// Currently, the plural forms are only based on the week number.
112#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
113#[allow(clippy::large_enum_variant)]
114pub enum PatternPlurals<'data> {
115    /// A collection of pattern variants for when plurals differ.
116    MultipleVariants(PluralPattern<'data>),
117    /// A single pattern.
118    SinglePattern(Pattern<'data>),
119}
120
121/// Get the resolved components for a FixedCalendarDateTimeFormatter, via the PatternPlurals. In the case of
122/// plurals resolve off of the required `other` pattern.
123impl From<&PatternPlurals<'_>> for components::Bag {
124    fn from(other: &PatternPlurals) -> Self {
125        let pattern = match other {
126            PatternPlurals::SinglePattern(pattern) => pattern,
127            PatternPlurals::MultipleVariants(plural_pattern) => &plural_pattern.other,
128        };
129        Self::from(pattern)
130    }
131}
132
133#[allow(missing_docs)]
134impl<'data> PatternPlurals<'data> {
135    pub fn into_owned(self) -> PatternPlurals<'static> {
136        match self {
137            Self::SinglePattern(pattern) => PatternPlurals::SinglePattern(pattern.into_owned()),
138            Self::MultipleVariants(plural_pattern) => {
139                PatternPlurals::MultipleVariants(plural_pattern.into_owned())
140            }
141        }
142    }
143
144    pub fn patterns_iter(&self) -> impl Iterator<Item = &Pattern<'data>> {
145        match self {
146            Self::SinglePattern(pattern) => Either::Left(core::iter::once(pattern)),
147            Self::MultipleVariants(plural_pattern) => Either::Right(plural_pattern.patterns_iter()),
148        }
149    }
150
151    pub fn for_each_mut<F>(&mut self, f: F)
152    where
153        F: Fn(&mut Pattern<'data>),
154    {
155        match self {
156            Self::SinglePattern(pattern) => f(pattern),
157            Self::MultipleVariants(variants) => variants.for_each_mut(&f),
158        }
159    }
160
161    pub fn expect_pattern(self, msg: &str) -> Pattern<'data> {
162        match self {
163            Self::SinglePattern(pattern) => pattern,
164
165            Self::MultipleVariants(patterns) => {
166                // Potentially change to log::warn! in #2648
167                debug_assert!(
168                    false,
169                    "expect_pattern called with bad data (falling back to `other` pattern): {msg}"
170                );
171                patterns.other
172            }
173        }
174    }
175
176    /// Removes redundant patterns & transforms singleton [PatternPlurals::MultipleVariants] into a [PatternPlurals::SinglePattern].
177    pub fn normalize(&mut self) {
178        if let Self::MultipleVariants(patterns) = self {
179            if patterns.patterns_iter().count() == 1 {
180                *self = Self::SinglePattern(core::mem::take(&mut patterns.other));
181            }
182        }
183    }
184}
185
186impl<'data> From<Pattern<'data>> for PatternPlurals<'data> {
187    fn from(pattern: Pattern<'data>) -> Self {
188        Self::SinglePattern(pattern)
189    }
190}
191
192impl<'data> From<PluralPattern<'data>> for PatternPlurals<'data> {
193    fn from(pattern: PluralPattern<'data>) -> Self {
194        Self::MultipleVariants(pattern)
195    }
196}
197
198impl Default for PatternPlurals<'_> {
199    fn default() -> Self {
200        PatternPlurals::SinglePattern(Pattern::default())
201    }
202}
203
204#[cfg(test)]
205mod test {
206    use super::*;
207
208    #[test]
209    #[ignore] // TODO(#5643)
210    #[allow(unreachable_code, unused_variables, unused_mut)]
211    fn build_plural_pattern() {
212        let red_pattern: Pattern = "'red' w".parse().unwrap();
213        let blue_pattern: Pattern = "'blue' w".parse().unwrap();
214        let mut patterns =
215            PluralPattern::new(blue_pattern.clone()).expect("PluralPattern::new failed");
216        patterns.maybe_set_variant(PluralCategory::Zero, red_pattern.clone());
217        patterns.maybe_set_variant(PluralCategory::One, blue_pattern.clone());
218        patterns.maybe_set_variant(PluralCategory::Two, red_pattern.clone());
219        patterns.maybe_set_variant(PluralCategory::Few, red_pattern.clone());
220        patterns.maybe_set_variant(PluralCategory::Many, blue_pattern.clone());
221
222        // assert_eq!(patterns.pivot_field, Week::WeekOfYear);
223        assert_eq!(patterns.zero, Some(red_pattern.clone()));
224        assert_eq!(patterns.one, None); // duplicate `other
225        assert_eq!(patterns.two, Some(red_pattern));
226        assert_eq!(patterns.other, blue_pattern);
227    }
228
229    #[test]
230    #[ignore] // TODO(#5643)
231    #[allow(unreachable_code, unused_variables)]
232    fn normalize_pattern_plurals_switches_singletons_to_single_pattern() {
233        let pattern: Pattern = "'red' w".parse().unwrap();
234        let patterns = PluralPattern::new(pattern.clone()).expect("PluralPattern::new failed");
235        let mut plural_patterns: PatternPlurals = PatternPlurals::MultipleVariants(patterns);
236
237        plural_patterns.normalize();
238
239        assert_eq!(plural_patterns, PatternPlurals::SinglePattern(pattern));
240    }
241}