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}
226