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}