message_format/ast/
plural_format.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use std::collections::HashMap;
8use std::fmt;
9
10use super::{Format, MessagePart};
11use super::english_cardinal_classifier;
12use {Args, Message};
13
14/// The set of [grammatical numbers] that we support.
15///
16/// [grammatical numbers]: https://en.wikipedia.org/wiki/Grammatical_number
17#[derive(Clone,Copy,Debug,PartialEq)]
18pub enum PluralCategory {
19    /// Value is `0`.
20    Zero,
21    /// Value is `1`. In English, this corresponds to the "singular" form.
22    One,
23    /// Value is `2`.
24    Two,
25    /// Value is a few, more than `2`, but less than `many`. The exact
26    /// range depends upon the locale.
27    Few,
28    /// Value is many, more than `few`. The exact range depends
29    /// upon the locale.
30    Many,
31    /// Not one of the others. In English, this is used for the "plural"
32    /// form.
33    Other,
34}
35
36/// Format a value taking pluralization rules into account.
37pub struct PluralFormat {
38    /// The name of the variable whose value should be formatted.
39    #[allow(dead_code)]
40    variable_name: String,
41    classifier: fn(i64) -> PluralCategory,
42    literals: HashMap<i64, Message>,
43    offset: i64,
44    zero: Option<Message>,
45    one: Option<Message>,
46    two: Option<Message>,
47    few: Option<Message>,
48    many: Option<Message>,
49    other: Message,
50}
51
52impl PluralFormat {
53    /// Construct a `PluralFormat`.
54    pub fn new(variable_name: &str, other: Message) -> Self {
55        PluralFormat {
56            variable_name: variable_name.to_string(),
57            classifier: english_cardinal_classifier,
58            literals: HashMap::new(),
59            offset: 0,
60            zero: None,
61            one: None,
62            two: None,
63            few: None,
64            many: None,
65            other: other,
66        }
67    }
68
69    /// Set the `message` to be used for a literal value.
70    pub fn literal(mut self, literal: i64, message: Message) -> Self {
71        self.literals.insert(literal, message);
72        self
73    }
74
75    /// Apply an `offset`.
76    pub fn offset(mut self, offset: i64) -> Self {
77        self.offset = offset;
78        self
79    }
80
81    /// Set the `message` for `PluralCategory::Zero`.
82    pub fn zero(mut self, message: Message) -> Self {
83        self.zero = Some(message);
84        self
85    }
86
87    /// Set the `message` for `PluralCategory::One`.
88    pub fn one(mut self, message: Message) -> Self {
89        self.one = Some(message);
90        self
91    }
92
93    /// Set the `message` for `PluralCategory::Two`.
94    pub fn two(mut self, message: Message) -> Self {
95        self.two = Some(message);
96        self
97    }
98
99    /// Set the `message` for `PluralCategory::Few`.
100    pub fn few(mut self, message: Message) -> Self {
101        self.few = Some(message);
102        self
103    }
104
105    /// Set the `message` for `PluralCategory::Many`.
106    pub fn many(mut self, message: Message) -> Self {
107        self.many = Some(message);
108        self
109    }
110
111    /// Given a value adjusted by the `offset`, determine which `Message` to use.
112    fn format_from_classifier(&self, offset_value: i64) -> &Message {
113        let category = (self.classifier)(offset_value);
114        match category {
115            PluralCategory::Zero => self.zero.as_ref().unwrap_or(&self.other),
116            PluralCategory::One => self.one.as_ref().unwrap_or(&self.other),
117            PluralCategory::Two => self.two.as_ref().unwrap_or(&self.other),
118            PluralCategory::Few => self.few.as_ref().unwrap_or(&self.other),
119            PluralCategory::Many => self.many.as_ref().unwrap_or(&self.other),
120            PluralCategory::Other => &self.other,
121        }
122    }
123
124    /// Handle specialized behavior for `MessagePart::Placeholder` when formatting
125    /// a `PluralFormat`.
126    fn format_plural_message(&self,
127                             stream: &mut fmt::Write,
128                             message: &Message,
129                             offset_value: i64,
130                             args: &Args)
131                             -> fmt::Result {
132        for part in &message.message_parts {
133            match *part {
134                MessagePart::String(ref string) => {
135                    try!(write!(stream, "{}", string));
136                }
137                MessagePart::Placeholder => {
138                    try!(write!(stream, "{}", offset_value));
139                }
140                MessagePart::Format(ref format) => try!(format.format_message_part(stream, args)),
141            }
142        }
143        Ok(())
144    }
145}
146
147impl Format for PluralFormat {
148    fn format_message_part(&self, stream: &mut fmt::Write, args: &Args) -> fmt::Result {
149        let value = 0;
150        let offset_value = value - self.offset;
151        let message = if !self.literals.is_empty() {
152            if let Some(literal) = self.literals.get(&offset_value) {
153                literal
154            } else {
155                self.format_from_classifier(offset_value)
156            }
157        } else {
158            self.format_from_classifier(offset_value)
159        };
160        try!(self.format_plural_message(stream, message, offset_value, args));
161        Ok(())
162    }
163}