intl_pluralrules/
lib.rs

1//! A crate for generating plural rule operands from numberical input.
2//!
3//! This crate generates plural operands according to the specifications outlined at [Unicode's website](https://unicode.org/reports/tr35/tr35-numbers.html#Operands).
4//!
5//! Input is supported for int, float, and &str.
6//!
7//! # Examples
8//!
9//! Plural rules example for Polish
10//!
11//! ```
12//! use intl_pluralrules::{PluralRules, PluralRuleType, PluralCategory};
13//! use unic_langid::LanguageIdentifier;
14//!
15//! let langid: LanguageIdentifier = "pl".parse().expect("Parsing failed.");
16//!
17//! assert!(PluralRules::get_locales(PluralRuleType::CARDINAL).contains(&langid));
18//!
19//! let pr = PluralRules::create(langid.clone(), PluralRuleType::CARDINAL).unwrap();
20//! assert_eq!(pr.select(1), Ok(PluralCategory::ONE));
21//! assert_eq!(pr.select("3"), Ok(PluralCategory::FEW));
22//! assert_eq!(pr.select(12), Ok(PluralCategory::MANY));
23//! assert_eq!(pr.select("5.0"), Ok(PluralCategory::OTHER));
24//!
25//! assert_eq!(pr.get_locale(), &langid);
26//! ```
27
28/// A public AST module for plural rule representations.
29pub mod operands;
30#[cfg(not(tarpaulin_include))]
31mod rules;
32
33use std::convert::TryInto;
34
35use unic_langid::LanguageIdentifier;
36
37use crate::operands::PluralOperands;
38use crate::rules::*;
39
40/// A public enum for handling the plural category.
41/// Each plural category will vary, depending on the language that is being used and whether that language has that plural category.
42#[derive(Debug, Eq, PartialEq)]
43pub enum PluralCategory {
44    ZERO,
45    ONE,
46    TWO,
47    FEW,
48    MANY,
49    OTHER,
50}
51
52/// A public enum for handling plural type.
53#[derive(Copy, Clone, Hash, PartialEq, Eq)]
54pub enum PluralRuleType {
55    /// Ordinal numbers express position or rank in a sequence. [More about oridinal numbers](https://en.wikipedia.org/wiki/Ordinal_number_(linguistics))
56    ORDINAL,
57    /// Cardinal numbers are natural numbers. [More about cardinal numbers](https://en.wikipedia.org/wiki/Cardinal_number)
58    CARDINAL,
59}
60
61// pub use rules::PluralRuleType;
62/// CLDR_VERSION is the version of CLDR extracted from the file used to generate rules.rs.
63pub use crate::rules::CLDR_VERSION;
64
65/// The main structure for selecting plural rules.
66///
67/// # Examples
68///
69/// ```
70/// use intl_pluralrules::{PluralRules, PluralRuleType, PluralCategory};
71/// use unic_langid::LanguageIdentifier;
72///
73/// let langid: LanguageIdentifier = "naq".parse().expect("Parsing failed.");
74/// let pr_naq = PluralRules::create(langid, PluralRuleType::CARDINAL).unwrap();
75/// assert_eq!(pr_naq.select(1), Ok(PluralCategory::ONE));
76/// assert_eq!(pr_naq.select("2"), Ok(PluralCategory::TWO));
77/// assert_eq!(pr_naq.select(5.0), Ok(PluralCategory::OTHER));
78/// ```
79#[derive(Clone)]
80pub struct PluralRules {
81    locale: LanguageIdentifier,
82    function: PluralRule,
83}
84
85impl PluralRules {
86    /// Returns an instance of PluralRules.
87    ///
88    /// # Examples
89    /// ```
90    /// use intl_pluralrules::{PluralRules, PluralRuleType, PluralCategory};
91    /// use unic_langid::LanguageIdentifier;
92    ///
93    /// let langid: LanguageIdentifier = "naq".parse().expect("Parsing failed.");
94    /// let pr_naq = PluralRules::create(langid, PluralRuleType::CARDINAL);
95    /// assert_eq!(pr_naq.is_ok(), !pr_naq.is_err());
96    ///
97    /// let langid: LanguageIdentifier = "xx".parse().expect("Parsing failed.");
98    /// let pr_broken = PluralRules::create(langid, PluralRuleType::CARDINAL);
99    /// assert_eq!(pr_broken.is_err(), !pr_broken.is_ok());
100    /// ```
101    pub fn create<L: Into<LanguageIdentifier>>(
102        langid: L,
103        prt: PluralRuleType,
104    ) -> Result<Self, &'static str> {
105        let langid = langid.into();
106        let returned_rule = match prt {
107            PluralRuleType::CARDINAL => {
108                let idx = rules::PRS_CARDINAL.binary_search_by_key(&&langid, |(l, _)| l);
109                idx.map(|idx| rules::PRS_CARDINAL[idx].1)
110            }
111            PluralRuleType::ORDINAL => {
112                let idx = rules::PRS_ORDINAL.binary_search_by_key(&&langid, |(l, _)| l);
113                idx.map(|idx| rules::PRS_ORDINAL[idx].1)
114            }
115        };
116        match returned_rule {
117            Ok(returned_rule) => Ok(Self {
118                locale: langid,
119                function: returned_rule,
120            }),
121            Err(_) => Err("unknown locale"),
122        }
123    }
124
125    /// Returns a result of the plural category for the given input.
126    ///
127    /// If the input is not numeric.
128    ///
129    /// # Examples
130    /// ```
131    /// use intl_pluralrules::{PluralRules, PluralRuleType, PluralCategory};
132    /// use unic_langid::LanguageIdentifier;
133    ///
134    /// let langid: LanguageIdentifier = "naq".parse().expect("Parsing failed.");
135    /// let pr_naq = PluralRules::create(langid, PluralRuleType::CARDINAL).unwrap();
136    /// assert_eq!(pr_naq.select(1), Ok(PluralCategory::ONE));
137    /// assert_eq!(pr_naq.select(2), Ok(PluralCategory::TWO));
138    /// assert_eq!(pr_naq.select(5), Ok(PluralCategory::OTHER));
139    /// ```
140    pub fn select<N: TryInto<PluralOperands>>(
141        &self,
142        number: N,
143    ) -> Result<PluralCategory, &'static str> {
144        let ops = number.try_into();
145        let pr = self.function;
146        match ops {
147            Ok(ops) => Ok(pr(&ops)),
148            Err(_) => Err("Argument can not be parsed to operands."),
149        }
150    }
151
152    /// Returns a list of the available locales.
153    ///
154    /// # Examples
155    /// ```
156    /// use intl_pluralrules::{PluralRules, PluralRuleType};
157    ///
158    /// assert_eq!(
159    ///     PluralRules::get_locales(PluralRuleType::CARDINAL).is_empty(),
160    ///     false
161    /// );
162    /// ```
163    pub fn get_locales(prt: PluralRuleType) -> Vec<LanguageIdentifier> {
164        let prs = match prt {
165            PluralRuleType::CARDINAL => rules::PRS_CARDINAL,
166            PluralRuleType::ORDINAL => rules::PRS_ORDINAL,
167        };
168        prs.iter().map(|(l, _)| l.clone()).collect()
169    }
170
171    /// Returns the locale name for this PluralRule instance.
172    ///
173    /// # Examples
174    /// ```
175    /// use intl_pluralrules::{PluralRules, PluralRuleType};
176    /// use unic_langid::LanguageIdentifier;
177    ///
178    /// let langid: LanguageIdentifier = "naq".parse().expect("Parsing failed.");
179    /// let pr_naq = PluralRules::create(langid.clone(), PluralRuleType::CARDINAL).unwrap();
180    /// assert_eq!(pr_naq.get_locale(), &langid);
181    /// ```
182    pub fn get_locale(&self) -> &LanguageIdentifier {
183        &self.locale
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::{PluralCategory, PluralRuleType, PluralRules, CLDR_VERSION};
190    use unic_langid::LanguageIdentifier;
191
192    #[test]
193    fn cardinals_test() {
194        let langid: LanguageIdentifier = "naq".parse().expect("Parsing failed.");
195        let pr_naq = PluralRules::create(langid, PluralRuleType::CARDINAL).unwrap();
196        assert_eq!(pr_naq.select(1), Ok(PluralCategory::ONE));
197        assert_eq!(pr_naq.select(2), Ok(PluralCategory::TWO));
198        assert_eq!(pr_naq.select(5), Ok(PluralCategory::OTHER));
199
200        let langid: LanguageIdentifier = "xx".parse().expect("Parsing failed.");
201        let pr_broken = PluralRules::create(langid, PluralRuleType::CARDINAL);
202        assert_eq!(pr_broken.is_err(), !pr_broken.is_ok());
203    }
204
205    #[test]
206    fn ordinals_rules() {
207        let langid: LanguageIdentifier = "uk".parse().expect("Parsing failed.");
208        let pr_naq = PluralRules::create(langid, PluralRuleType::ORDINAL).unwrap();
209        assert_eq!(pr_naq.select(33), Ok(PluralCategory::FEW));
210        assert_eq!(pr_naq.select(113), Ok(PluralCategory::OTHER));
211    }
212
213    #[test]
214    fn version_test() {
215        assert_eq!(CLDR_VERSION, 37);
216    }
217
218    #[test]
219    fn locale_test() {
220        assert_eq!(
221            PluralRules::get_locales(PluralRuleType::CARDINAL).is_empty(),
222            false
223        );
224    }
225}