convert_case/
pattern.rs

1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3
4fn lowercase_word(word: &str) -> String {
5    word.to_lowercase()
6}
7
8fn uppercase_word(word: &str) -> String {
9    word.to_uppercase()
10}
11
12/// Applies capital pattern to a single word using graphemes.
13fn capital_word(word: &str) -> String {
14    let mut chars = word.chars();
15
16    if let Some(c) = chars.next() {
17        [c.to_uppercase().collect(), chars.as_str().to_lowercase()].concat()
18    } else {
19        String::new()
20    }
21}
22
23/// Transformations on a list of words.
24///
25/// A pattern is a function that maps a list of words into another list
26/// after changing the casing of each letter.  How a patterns mutates
27/// each letter can be dependent on the word the letters are present in.
28///
29/// ## Custom Pattern
30///
31/// A pattern is a function that maps from a borrowed list of words `&[&str]` to
32/// an owned list of owned words `Vec<String>` by applying a transformation.
33/// One example of custom behavior might be a pattern
34/// that detects a fixed list of two-letter acronyms, and capitalizes them
35/// appropriately on output.
36/// ```
37/// use convert_case::{Converter, Pattern};
38///
39/// fn pascal_upper_acronyms(words: &[&str]) -> Vec<String> {
40///     Pattern::Capital.mutate(words).into_iter()
41///         .map(|word| match word.as_ref() {
42///             "Io" | "Xml" => word.to_uppercase(),
43///             _ => word,
44///         })
45///         .collect()
46/// }
47///
48/// let acronym_converter = Converter::new()
49///     .set_pattern(Pattern::Custom(pascal_upper_acronyms));
50///
51/// assert_eq!(acronym_converter.convert("io_stream"), "IOStream");
52/// assert_eq!(acronym_converter.convert("xml request"), "XMLRequest");
53/// ```
54///
55/// Another example might be a one that explicitly adds leading
56/// and trailing double underscores.  We do this by modifying the words directly,
57/// which will get passed as-is to the join function.
58/// ```
59/// use convert_case::{Converter, Pattern};
60///
61/// fn snake_dunder(mut words: &[&str]) -> Vec<String> {
62///     words
63///         .into_iter()
64///         .map(|word| word.to_lowercase())
65///         .enumerate()
66///         .map(|(i, word)| {
67///             if words.len() == 1 {
68///                 format!("__{}__", word)
69///             } else if i == 0 {
70///                 format!("__{}", word)
71///             } else if i == words.len() - 1 {
72///                 format!("{}__", word)
73///             } else {
74///                 word
75///             }
76///         })
77///         .collect()
78/// }
79///
80/// let dunder_converter = Converter::new()
81///     .set_pattern(Pattern::Custom(snake_dunder))
82///     .set_delim("_");
83///
84/// assert_eq!(dunder_converter.convert("getAttr"), "__get_attr__");
85/// assert_eq!(dunder_converter.convert("ITER"), "__iter__");
86/// ```
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
88pub enum Pattern {
89    /// The no-op pattern performs no mutations.
90    /// ```
91    /// # use convert_case::Pattern;
92    /// assert_eq!(
93    ///     Pattern::Noop.mutate(&["Case", "CONVERSION", "library"]),
94    ///     vec!["Case", "CONVERSION", "library"],
95    /// );
96    /// ```
97    Noop,
98
99    /// Makes all words lowercase.
100    /// ```
101    /// # use convert_case::Pattern;
102    /// assert_eq!(
103    ///     Pattern::Lowercase.mutate(&["Case", "CONVERSION", "library"]),
104    ///     vec!["case", "conversion", "library"],
105    /// );
106    /// ```
107    Lowercase,
108
109    /// Makes all words uppercase.
110    /// ```
111    /// # use convert_case::Pattern;
112    /// assert_eq!(
113    ///     Pattern::Uppercase.mutate(&["Case", "CONVERSION", "library"]),
114    ///     vec!["CASE", "CONVERSION", "LIBRARY"],
115    /// );
116    /// ```
117    Uppercase,
118
119    /// Makes the first letter of each word uppercase
120    /// and the remaining letters of each word lowercase.
121    /// ```
122    /// # use convert_case::Pattern;
123    /// assert_eq!(
124    ///     Pattern::Capital.mutate(&["Case", "CONVERSION", "library"]),
125    ///     vec!["Case", "Conversion", "Library"],
126    /// );
127    /// ```
128    Capital,
129
130    /// Makes the first non-empty word lowercase and the
131    /// remaining capitalized.
132    /// ```
133    /// # use convert_case::Pattern;
134    /// assert_eq!(
135    ///     Pattern::Camel.mutate(&["Case", "CONVERSION", "library"]),
136    ///     vec!["case", "Conversion", "Library"],
137    /// );
138    /// ```
139    Camel,
140
141    /// Makes the first non-empty word capitalized and the
142    /// remaining lowercase.
143    /// ```
144    /// # use convert_case::Pattern;
145    /// assert_eq!(
146    ///     Pattern::Sentence.mutate(&["Case", "CONVERSION", "library"]),
147    ///     vec!["Case", "conversion", "library"],
148    /// );
149    /// ```
150    Sentence,
151
152    /// Define custom behavior to transform a set of words.
153    ///
154    /// See the [`Pattern`] documentation for examples.
155    Custom(fn(&[&str]) -> Vec<String>),
156}
157
158impl Pattern {
159    /// Applies the pattern transformation to a list of words.
160    pub fn mutate(&self, words: &[&str]) -> Vec<String> {
161        use Pattern::*;
162        match self {
163            Custom(transformation) => (transformation)(words),
164            Noop => words.iter().map(|word| word.to_string()).collect(),
165            Lowercase => words.iter().map(|word| lowercase_word(word)).collect(),
166            Uppercase => words.iter().map(|word| uppercase_word(word)).collect(),
167            Capital => words.iter().map(|word| capital_word(word)).collect(),
168            Camel => words
169                .iter()
170                .enumerate()
171                .map(|(i, &word)| {
172                    if i == 0 {
173                        lowercase_word(word)
174                    } else {
175                        capital_word(word)
176                    }
177                })
178                .collect(),
179            Sentence => words
180                .iter()
181                .enumerate()
182                .map(|(i, &word)| {
183                    if i == 0 {
184                        capital_word(word)
185                    } else {
186                        lowercase_word(word)
187                    }
188                })
189                .collect(),
190        }
191    }
192}
193
194#[cfg(test)]
195mod test {
196    use crate::Case;
197    use crate::Converter;
198
199    use super::*;
200
201    #[test]
202    fn mutate_empty_strings() {
203        for word_pattern in [lowercase_word, uppercase_word, capital_word] {
204            assert_eq!(String::new(), word_pattern(&String::new()))
205        }
206    }
207
208    #[test]
209    fn filtering_with_custom() {
210        // TODO: find a way to make this cleaner, then add in docs
211        let filter_camel_pattern = Pattern::Custom(|words| {
212            Pattern::Camel.mutate(
213                &words
214                    .into_iter()
215                    .filter(|word| word.len() > 0)
216                    .map(|word| *word)
217                    .collect::<Vec<&str>>(),
218            )
219        });
220
221        let conv = Converter::new()
222            .from_case(Case::Kebab)
223            .set_pattern(filter_camel_pattern);
224
225        assert_eq!(conv.convert("--leading-delims"), "leadingDelims");
226    }
227}