in_definite/
lib.rs

1//! # in_definite
2//!
3//! Get the indefinite article ('a' or 'an') to match the given word. For example: an umbrella, a user.
4
5mod core_is_an;
6mod options;
7mod rules;
8mod utils;
9
10use utils::string_helper;
11
12pub use core_is_an::Is;
13pub use options::Options;
14
15/// Get 'a' or 'an' to match the given word.
16///
17/// # Examples
18///
19/// ```
20/// use in_definite;
21///
22/// let result = in_definite::get_a_or_an("alien");
23///
24/// assert_eq!("an", result);
25/// ```
26///
27/// ```
28/// // Irregular word
29/// let result = in_definite::get_a_or_an("unicorn");
30///
31/// assert_eq!("a", result);
32/// ```
33///
34/// ```
35/// // Title Case
36/// let result = in_definite::get_a_or_an("Ugly");
37///
38/// assert_eq!("An", result);
39/// ```
40pub fn get_a_or_an(word: &str) -> &str {
41    get_a_or_an_options(word, &Options::default())
42}
43
44/// Get 'a' or 'an' to match the given word, with options.
45///
46/// # Examples
47///
48/// ```
49/// use in_definite;
50///
51/// // 'eighteen hundred'
52/// let result = in_definite::get_a_or_an_options("1800", &in_definite::Options::with_colloquial());
53///
54/// assert_eq!("an", result);
55/// ```
56///
57/// ```
58/// // 'one thousand eight hundred'
59/// let result = in_definite::get_a_or_an_options("1800", &in_definite::Options::default());
60///
61/// assert_eq!("a", result);
62/// ```
63///
64/// ```
65/// // Title Case
66/// let result = in_definite::get_a_or_an_options("Ugly", &in_definite::Options::default());
67///
68/// assert_eq!("An", result);
69/// ```
70pub fn get_a_or_an_options(word: &str, options: &Options) -> &'static str {
71    if word.trim().len() == 0 {
72        return "";
73    }
74
75    let is_an = is_an_options(word, options);
76
77    core_is_an::a_or_an_capitalized_to_match(is_an, string_helper::get_first_word(word))
78}
79
80/// Returns true if the given word should be used with 'an' (not 'a').
81///
82/// # Examples
83///
84/// ```
85/// use in_definite;
86///
87/// let result = in_definite::is_an("alien");
88///
89/// assert_eq!(in_definite::Is::An, result);
90/// ```
91///
92/// ```
93/// let result = in_definite::is_an("unicorn");
94///
95/// assert_eq!(in_definite::Is::A, result);
96/// ```
97///
98/// ```
99/// let result = in_definite::is_an("");
100///
101/// assert_eq!(in_definite::Is::None, result);
102/// ```
103pub fn is_an(word: &str) -> Is {
104    is_an_options(word, &Options::default())
105}
106
107/// Get 'a' or 'an' to match the given word, with options.
108///
109/// # Examples
110///
111/// ```
112/// use in_definite;
113///
114/// let result = in_definite::is_an_options("1800", &in_definite::Options::with_colloquial()); // 'eighteen hundred'
115///
116/// assert_eq!(in_definite::Is::An, result);
117/// ```
118///
119/// ```
120/// let result = in_definite::is_an_options("1800", &in_definite::Options::default()); // 'one thousand eight hundred'
121///
122/// assert_eq!(in_definite::Is::A, result);
123/// ```
124///
125/// ```
126/// let result = in_definite::is_an_options(" ", &in_definite::Options::default());
127///
128/// assert_eq!(in_definite::Is::None, result);
129/// ```
130pub fn is_an_options(word: &str, options: &Options) -> Is {
131    if word.trim().len() == 0 {
132        return Is::None;
133    }
134
135    if core_is_an::is_an_options_bool(word, options) {
136        return Is::An;
137    }
138
139    Is::A
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn common_words() {
148        assert_eq!("an", get_a_or_an("antelope"));
149        assert_eq!("an", get_a_or_an("apple"));
150        assert_eq!("a", get_a_or_an("pear"));
151
152        assert_eq!(Is::An, is_an("antelope"));
153        assert_eq!(Is::An, is_an("apple"));
154        assert_eq!(Is::A, is_an("pear"));
155    }
156
157    #[test]
158    fn zero_length() {
159        assert_eq!("", get_a_or_an(""));
160        assert_eq!(Is::None, is_an(""));
161    }
162
163    #[test]
164    fn all_whitespace() {
165        assert_eq!("", get_a_or_an("   "));
166        assert_eq!(Is::None, is_an("   "));
167    }
168
169    macro_rules! tests {
170        ($($name:ident: $value:expr,)*) => {
171        $(
172            #[test]
173            fn $name() {
174                let (input, expected) = $value;
175                assert_eq!(expected, get_a_or_an(input));
176                assert_eq!(expected.to_lowercase() == "an", is_an(input) == Is::An);
177            }
178        )*
179        }
180    }
181
182    macro_rules! tests_options_with_colloquial {
183        ($($name:ident: $value:expr,)*) => {
184        $(
185            #[test]
186            fn $name() {
187                let options = &(Options::with_colloquial());
188
189                let (input, expected) = $value;
190                assert_eq!(expected, get_a_or_an_options(input, options));
191                assert_eq!(expected.to_lowercase() == "an", is_an_options(input, options) == Is::An);
192            }
193        )*
194        }
195    }
196
197    tests! {
198        // acronyms
199        test_ac0: ("CEO", "a"),
200        test_ac1: ("EU", "an"),
201        test_ac2a: ("FFA", "an"),
202        test_ac2b: ("FIFA", "an"),
203        test_ac2c: ("IOU", "an"),
204        test_ac3: ("MIA", "an"),
205        test_ac4: ("MNM", "an"),
206        test_ac5: ("UFO", "a"),
207        test_ac6: ("UN", "a"),
208        test_ac7: ("US", "a"),
209        test_ac8: ("USA", "a"),
210        // words
211        test_a1: ("alien", "an"),
212        test_a2a: ("american", "an"),
213        test_a2b: ("antelope", "an"),
214        test_a2c: ("apple", "an"),
215        test_a2d: ("banana", "a"),
216        test_e1: ("economic", "an"),
217        test_e2: ("economy", "an"),
218        // nouns eu-
219        test_eu1: ("euro", "a"),
220        test_eu2: ("european", "a"),
221        test_eu3: ("European", "A"),
222        test_eu4: ("eucalyptus", "a"),
223        test_eu5: ("eulogy", "a"),
224        // nouns that begin with ur-.
225        test_ur1: ("uranium", "a"),
226        test_ur2: ("urinal", "a"),
227        test_ur3: ("urologist", "a"),
228        // uni - Most nouns or adjectives that begin with uni– also require the indefinite article a.
229        test_uni1: ("unicorn", "a"),
230        test_uni2: ("uniform", "a"),
231        test_uni3: ("unit", "a"),
232        test_uni4: ("universal", "a"),
233        test_uni5: ("university", "a"),
234        // uni - adjectives that begin with uni– which follow the general rule.
235        test_uni_adj1: ("unidentified", "an"),
236        test_uni_adj2: ("unimportant", "an"),
237        test_uni_adj3: ("unintended", "an"),
238        test_uni_adj4: ("unintelligent", "an"),
239        // more words
240        test_h1: ("hair", "a"),
241        test_h2: ("heir", "an"),
242        test_h3: ("herb", "an"), // USA not UK
243        test_h4: ("hotel", "a"),
244        test_o0: ("ordinary", "an"),
245        test_o1: ("ouija", "a"),
246        test_r1: ("red", "a"),
247        test_r2: ("red rum", "a"),
248        test_r3: ("rum", "a"),
249        test_u0: ("ukelele", "a"),
250        test_u1: ("umbrella", "an"),
251        test_u2: ("user", "a"),
252        test_u2a: ("usurper", "a"),
253        test_u2b: ("usurped", "a"),
254        test_u3: ("Utah", "A"),
255        test_u4: ("utahn", "a"),
256        // numbers
257        test_n0: ("0", "a"),
258        test_n1: ("1", "a"),
259        test_n2: ("2", "a"),
260        test_n3: ("3", "a"),
261        test_n4: ("4", "a"),
262        test_n5: ("5", "a"),
263        test_n6: ("6", "a"),
264        test_n7: ("7", "a"),
265        test_n8: ("8", "an"),
266        test_n9: ("9", "a"),
267        test_n10: ("10", "a"),
268        test_n11: ("11", "an"),
269        test_n12: ("12", "a"),
270        test_n13: ("13", "a"),
271        test_n14: ("14", "a"),
272        test_n15: ("15", "a"),
273        test_n16: ("16", "a"),
274        test_n17: ("17", "a"),
275        test_n18: ("18", "an"),
276        test_n19: ("19", "a"),
277        test_n20: ("20", "a"),
278        // numbers - thousands with separator
279        test_n1_000: ("1,000", "a"),
280        test_n2_000: ("2,000", "a"),
281        test_n3_000: ("3,000", "a"),
282        test_n4_000: ("4,000", "a"),
283        test_n5_000: ("5,000", "a"),
284        test_n6_000: ("6,000", "a"),
285        test_n7_000: ("7,000", "a"),
286        test_n8_000: ("8,000", "an"),
287        test_n9_000: ("9,000", "a"),
288        test_n10_000: ("10,000", "a"),
289        test_n11_000: ("11,000", "an"),
290        test_n12_000: ("12,000", "a"),
291        test_n13_000: ("13,000", "a"),
292        test_n14_000: ("14,000", "a"),
293        test_n15_000: ("15,000", "a"),
294        test_n16_000: ("16,000", "a"),
295        test_n17_000: ("17,000", "a"),
296        test_n18_000: ("18,000", "an"),
297        test_n19_000: ("19,000", "a"),
298        test_n20_000: ("20,000", "a"),
299        // numbers - decimal point
300        test_n0_5: ("0.5", "a"),
301        test_n1_5: ("1.5", "a"),
302        test_n2_5: ("2.5", "a"),
303        test_n3_5: ("3.5", "a"),
304        test_n4_5: ("4.5", "a"),
305        test_n5_5: ("5.5", "a"),
306        test_n6_5: ("6.5", "a"),
307        test_n7_5: ("7.5", "a"),
308        test_n8_5: ("8.5", "an"),
309        test_n9_5: ("9.5", "a"),
310        test_n10_5: ("10.5", "a"),
311        test_n11_5: ("11.5", "an"),
312        test_n12_5: ("12.5", "a"),
313        test_n13_5: ("13.5", "a"),
314        test_n14_5: ("14.5", "a"),
315        test_n15_5: ("15.5", "a"),
316        test_n16_5: ("16.5", "a"),
317        test_n17_5: ("17.5", "a"),
318        test_n18_5: ("18.5", "an"),
319        test_n19_5: ("19.5", "a"),
320        test_n20_5: ("20.5", "a"),
321        // numbers - years
322        test_ny1000: ("1000", "a"),
323        test_ny1800: ("1800", "a"),
324        test_ny1892: ("1892", "a"),
325        // starting with 11
326        test_n11_01: ("11", "an"),
327        test_n11_02: ("110", "a"),
328        test_n11_03: ("1100", "a"), // 'one thousand one hundred'
329        test_n11_04: ("11000", "an"),
330        test_n11_05: ("110000", "a"),
331        test_n11_06: ("1100000", "a"),
332        test_n11_07: ("11000000", "an"),
333        test_n11_08: ("110000000", "a"),
334        test_n11_09: ("1100000000", "a"),
335        test_n11_10: ("11000000000", "an"),
336        test_n11_11: ("110000000000", "a"),
337        test_n11_12: ("1100000000000", "a"),
338        test_n11_13: ("11000000000000", "an"),
339        test_n11_14: ("110000000000000", "a"),
340        test_n11_15: ("1100000000000000", "a"),
341        test_n11_16: ("11000000000000000", "an"),
342        // starting with 18
343        test_ns18_01: ("18", "an"),
344        test_ns18_02: ("180", "a"),
345        test_ns18_03: ("18000", "an"),
346        test_ns18_04: ("180000", "a"),
347        test_ns18_05: ("1800000", "a"),
348        test_ns18_06: ("18000000", "an"),
349        test_ns18_07: ("180000000", "a"),
350        test_ns18_08: ("1800000000", "a"),
351        test_ns18_09: ("18000000000", "an"),
352        test_ns18_10: ("180000000000", "a"),
353        test_ns18_11: ("1800000000000", "a"),
354        test_ns18_12: ("18000000000000", "an"),
355        test_ns18_13: ("180000000000000", "a"),
356        test_ns18_14: ("1800000000000000", "a"),
357        test_ns18_15: ("18000000000000000", "an"),
358        // starting with 8
359        test_n8_01: ("8", "an"),
360        test_n8_02: ("80", "an"),
361        test_n8_03: ("800", "an"),
362        test_n8_04: ("8000", "an"),
363        test_n8_05: ("80000", "an"),
364        test_n8_06: ("800000", "an"),
365        test_n8_07: ("8000000", "an"),
366        test_n8_08: ("80000000", "an"),
367        test_n8_09: ("800000000", "an"),
368        // Mixed case
369        test_mc1: ("Alien", "An"), // Title Case
370        test_mc2: ("anteLoPe", "an"), // mixed case
371        test_mc3: ("haiR", "a"), // mixed case
372        test_mc4: ("HEIR", "an"), // acronym 'h -> an'
373        test_mc5: ("Heir", "An"), // Title Case, irregular
374        test_mc6: ("Ugly", "An"), // Title Case
375        ////////////////
376        // other: words with spaces or hyphens, plurals etc.
377        // 2 words
378        test_other1: ("ouija board", "a"),
379        // hyphenation
380        test_other2: ("apple-board", "an"),
381        test_other3: ("honor-bound", "an"),
382        test_other4: ("horror-bound", "a"),
383        // suffix
384        test_other_s1: ("heavenly", "a"),
385        test_other_s2: ("honored", "an"),
386        test_other_s3: ("hourly", "an"),
387        test_other_s4: ("heirly", "an"),
388        // plural
389        test_other6: ("heiresses", "an"),
390        test_other6b: ("heirs", "an"),
391        test_other7: ("honors", "an"),
392        // possessive
393        test_other8: ("heir's", "an"),
394        test_other9: ("horror's", "a"),
395        // Adverbs: u like y
396        test_other_adv1: ("ubiquitously", "a"),
397        test_other_adv2: ("ukelele", "a"),
398        test_other_adv3: ("unanimously", "a"),
399        test_other_adv4: ("unicamerally", "a"),
400        test_other_adv5: ("uniquely", "a"),
401        test_other_adv6: ("universally", "a"),
402        test_other_adv7: ("urologically", "a"),
403        test_other_adv8: ("usefully", "a"),
404        test_other_adv9: ("uselessly", "a"),
405        test_other_adv10: ("usuriously", "a"),
406    }
407
408    tests_options_with_colloquial! {
409        test_colloquial_ac1: ("EU", "an"),
410        test_colloquial_ac2: ("FIFA", "an"),
411        test_colloquial_ac3: ("MIA", "an"),
412        test_colloquial_ac4: ("MNM", "an"),
413        test_colloquial_ac4b: ("NATO", "an"),
414        test_colloquial_ac5: ("UFO", "a"),
415        test_colloquial_ac6: ("UN", "a"),
416        test_colloquial_a1: ("alien", "an"),
417        test_colloquial_a2: ("antelope", "an"),
418        test_colloquial_h1: ("hair", "a"),
419        test_colloquial_h2: ("heir", "an"),
420        test_colloquial_h3: ("herb", "an"), // USA not UK
421        test_colloquial_h4: ("hotel", "a"),
422        test_colloquial_u1: ("umbrella", "an"),
423        test_colloquial_u2: ("user", "a"),
424        // numbers
425        test_colloquial_n0: ("0", "a"),
426        test_colloquial_n1: ("1", "a"),
427        test_colloquial_n2: ("2", "a"),
428        test_colloquial_n3: ("3", "a"),
429        test_colloquial_n4: ("4", "a"),
430        test_colloquial_n5: ("5", "a"),
431        test_colloquial_n6: ("6", "a"),
432        test_colloquial_n7: ("7", "a"),
433        test_colloquial_n8: ("8", "an"),
434        test_colloquial_n9: ("9", "a"),
435        test_colloquial_n10: ("10", "a"),
436        // numbers - years: with colloquial on, they are as read out loud: "an eighteen hundred"
437        test_colloquial_ny1000: ("1000", "a"), // 'ten hundred'
438        test_colloquial_ny1100: ("1100", "an"), // 'eleven hundred'
439        test_colloquial_ny1800: ("1800", "an"),
440        test_colloquial_ny1892: ("1892", "an"),
441        // single letters - irregular
442        test_single_irregular_u: ("u", "a"),
443        test_single_irregular_f: ("f", "an"),
444        test_single_irregular_h: ("h", "an"),
445        test_single_irregular_l: ("l", "an"),
446        test_single_irregular_m: ("m", "an"),
447        test_single_irregular_n: ("n","an"),
448        test_single_irregular_r: ("r", "an"),
449        test_single_irregular_s: ("s", "an"),
450        test_single_irregular_x: ("x", "an"),
451        // single letters - regular
452        test_single_regular_a: ("a", "an"),
453        test_single_regular_b: ("b", "a"),
454    }
455}