git_release_name/
phrase.rs

1use adjectives;
2use adverbs;
3use case::Case;
4use nouns;
5use sha::{ParseShaError, Sha};
6use std::fmt::{Display, Error, Formatter};
7use std::str::FromStr;
8
9/// A phrase that is made up of an adverb, adjective, noun.
10///
11/// When parsed from a slice it will lookup the sha parts in the dictionary.
12/// It knows how to properly format itself if a different case is selected.
13#[derive(Debug, Eq, PartialEq, Clone)]
14pub struct Phrase {
15    adj: String,
16    adv: String,
17    noun: String,
18    format: Case,
19}
20
21impl Phrase {
22    /// Consumes the current phrase and returns a new one with a different
23    /// case.
24    ///
25    /// # Example
26    ///
27    /// ```
28    /// use std::str::FromStr;
29    /// use git_release_name::{Phrase, Case};
30    ///
31    /// let phrase = "1234".parse::<Phrase>().unwrap().with_case(Case::Upper);
32    /// assert_eq!(phrase.case(), Case::Upper);
33    /// ```
34    pub fn with_case(mut self, f: Case) -> Self {
35        self.format = f;
36        self
37    }
38
39    /// The adjective component of this phrase
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// use std::str::FromStr;
45    /// use git_release_name::Phrase;
46    ///
47    /// let phrase: Phrase = "1234".parse().unwrap();
48    /// assert_eq!(phrase.adjective(), "courant");
49    /// ```
50    pub fn adjective(&self) -> &str {
51        &self.adj
52    }
53
54    /// The adverb component of this phrase
55    ///
56    /// # Example
57    ///
58    /// ```
59    /// use std::str::FromStr;
60    /// use git_release_name::Phrase;
61    ///
62    /// let phrase: Phrase = "1234".parse().unwrap();
63    /// assert_eq!(phrase.adverb(), "ambitiously");
64    /// ```
65    pub fn adverb(&self) -> &str {
66        &self.adv
67    }
68
69    /// The noun component of this phrase
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use std::str::FromStr;
75    /// use git_release_name::Phrase;
76    ///
77    /// let phrase: Phrase = "1234".parse().unwrap();
78    /// assert_eq!(phrase.noun(), "gantlines");
79    /// ```
80    pub fn noun(&self) -> &str {
81        &self.noun
82    }
83
84    /// The case the phrase will be formated with
85    ///
86    /// # Example
87    ///
88    /// ```
89    /// use std::str::FromStr;
90    /// use git_release_name::{Phrase, Case};
91    ///
92    /// let phrase: Phrase = "1234".parse().unwrap();
93    /// assert_eq!(phrase.case(), Case::Lower);
94    /// ```
95    pub fn case(&self) -> Case {
96        self.format
97    }
98}
99
100/// Represents failures during parsing.
101#[derive(Debug, Eq, PartialEq, Clone, Copy)]
102pub enum ParsePhraseError {
103    /// The word was not found in the dictionary
104    WordNotFound,
105    Sha(ParseShaError),
106    #[doc(hidden)]
107    __NonExhaustive,
108}
109
110fn lookup(index: usize, words: &[&str]) -> Result<String, ParsePhraseError> {
111    words
112        .get(index % words.len())
113        .map(|s| s.to_string())
114        .ok_or(ParsePhraseError::WordNotFound)
115}
116
117impl FromStr for Phrase {
118    type Err = ParsePhraseError;
119
120    fn from_str(sha: &str) -> Result<Phrase, Self::Err> {
121        let sha = if sha.len() < 8 { &sha } else { &sha[..8] };
122        let sha: Sha = sha.parse().map_err(|e| ParsePhraseError::Sha(e))?;
123
124        let adv = lookup(sha.adverb(), &adverbs::WORDS)?;
125        let adj = lookup(sha.adjective(), &adjectives::WORDS)?;
126        let noun = lookup(sha.noun(), &nouns::WORDS)?;
127
128        Ok(Phrase {
129            adv,
130            adj,
131            noun,
132            format: Case::Lower,
133        })
134    }
135}
136
137impl Display for Phrase {
138    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
139        use inflector::Inflector;
140
141        let ret = format!("{} {} {}", self.adv, self.adj, self.noun);
142        match self.format {
143            Case::Snake => write!(f, "{}", ret.to_snake_case()),
144            Case::Kebab => write!(f, "{}", ret.to_kebab_case()),
145            Case::Pascal => write!(f, "{}", ret.to_pascal_case()),
146            Case::Camel => write!(f, "{}", ret.to_camel_case()),
147            Case::Title => write!(f, "{}", ret.to_title_case()),
148            Case::Sentence => write!(f, "{}", ret.to_sentence_case()),
149            Case::Lower => write!(f, "{}", ret),
150            Case::Upper => write!(f, "{}", ret.to_uppercase()),
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    fn make_simple_phrase() -> Phrase {
160        "0a00a00a".parse::<Phrase>().expect("Invalid phrase")
161    }
162
163    #[test]
164    fn a_phrase_can_be_generated_from_a_str() {
165        let phrase = make_simple_phrase();
166        assert_eq!("immeasurably endways borings", format!("{}", phrase));
167    }
168
169    #[test]
170    fn it_pads_the_string() {
171        let unpadded = "abc".parse::<Phrase>().expect("Invalid phrase");
172        let padded = "00000abc".parse::<Phrase>().expect("Invalid phrase");
173        assert_eq!(padded, unpadded);
174    }
175
176    #[test]
177    fn it_only_respects_first_eight() {
178        let overflow = "00000abcffff".parse::<Phrase>().expect("Invalid phrase");
179        let underflow = "abc".parse::<Phrase>().expect("Invalid phrase");
180        assert_eq!(overflow, underflow);
181    }
182
183    #[test]
184    fn a_phrase_can_be_formatted_as_snake_case() {
185        let phrase = make_simple_phrase().with_case(Case::Snake);
186        assert_eq!("immeasurably_endways_borings", format!("{}", phrase));
187    }
188
189    #[test]
190    fn a_phrase_can_be_formatted_as_kebab_case() {
191        let phrase = make_simple_phrase().with_case(Case::Kebab);
192        assert_eq!("immeasurably-endways-borings", format!("{}", phrase));
193    }
194
195    #[test]
196    fn a_phrase_can_be_formatted_as_camel_case() {
197        let phrase = make_simple_phrase().with_case(Case::Camel);
198        assert_eq!("immeasurablyEndwaysBorings", format!("{}", phrase));
199    }
200
201    #[test]
202    fn a_phrase_can_be_formatted_as_pascal_case() {
203        let phrase = make_simple_phrase().with_case(Case::Pascal);
204        assert_eq!("ImmeasurablyEndwaysBorings", format!("{}", phrase));
205    }
206
207    #[test]
208    fn a_phrase_can_be_formatted_as_title_case() {
209        let phrase = make_simple_phrase().with_case(Case::Title);
210        assert_eq!("Immeasurably Endways Borings", format!("{}", phrase));
211    }
212
213    #[test]
214    fn a_phrase_can_be_formatted_as_capital_case() {
215        let phrase = make_simple_phrase().with_case(Case::Sentence);
216        assert_eq!("Immeasurably endways borings", format!("{}", phrase));
217    }
218
219    #[test]
220    fn a_phrase_can_be_formatted_as_upper_case() {
221        let phrase = make_simple_phrase().with_case(Case::Upper);
222        assert_eq!("IMMEASURABLY ENDWAYS BORINGS", format!("{}", phrase));
223    }
224
225    #[test]
226    fn a_phrase_can_be_formatted_as_lower_case() {
227        let phrase = make_simple_phrase().with_case(Case::Lower);
228        assert_eq!("immeasurably endways borings", format!("{}", phrase));
229    }
230}