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. |
29 | pub mod operands; |
30 | #[cfg (not(tarpaulin_include))] |
31 | mod rules; |
32 | |
33 | use std::convert::TryInto; |
34 | |
35 | use unic_langid::LanguageIdentifier; |
36 | |
37 | use crate::operands::PluralOperands; |
38 | use 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)] |
43 | pub 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)] |
54 | pub 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. |
63 | pub 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)] |
80 | pub struct PluralRules { |
81 | locale: LanguageIdentifier, |
82 | function: PluralRule, |
83 | } |
84 | |
85 | impl 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)] |
188 | mod 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 | |