recase/lib.rs
1//! # ReCase
2//!
3//! `recase` is a text processing utility that changes the input text into desired convention cases.
4
5use crate::utils::WordSplit;
6
7mod utils;
8
9/// An instance that holds the text to be re-cased.
10/// # Example
11/// ```
12/// let recase = recase::ReCase::new("Example String");
13/// assert_eq!(recase.snake_case(), "example_string");
14/// assert_eq!(recase.upper_snake_case(), "EXAMPLE_STRING");
15/// ```
16#[derive(Debug)]
17pub struct ReCase<'a> {
18 original_text: &'a str,
19}
20
21// Push lowercase of c into s
22macro_rules! push_lowercase {
23 ($s:expr, $c:expr) => {
24 for lc in $c.to_lowercase() {
25 $s.push(lc);
26 }
27 };
28}
29
30// Push uppercase of chars into s
31macro_rules! push_uppercase {
32 ($s:expr, $c:expr) => {
33 for uc in $c.to_uppercase() {
34 $s.push(uc);
35 }
36 };
37}
38
39impl<'a> ReCase<'a> {
40 pub fn new(original_text: &'a str) -> Self {
41 ReCase { original_text }
42 }
43
44 fn words_iter(&self) -> impl Iterator<Item = &str> {
45 WordSplit::new(self.original_text).map(|(x, y)| &self.original_text[x..y])
46 }
47
48 #[inline(always)]
49 fn allocate_buffer(&self) -> String {
50 String::with_capacity(self.original_text.len())
51 }
52
53 fn lowercase_with_delim(&self, delim: &str) -> String {
54 self.words_iter()
55 .fold(self.allocate_buffer(), |mut acc, s| {
56 if !acc.is_empty() {
57 acc.push_str(delim);
58 }
59 for c in s.chars() {
60 push_lowercase!(acc, c);
61 }
62 acc
63 })
64 }
65
66 /// Returns a `normal case` version of the input text as a new String
67 /// ## Example
68 /// ```
69 /// let recase = recase::ReCase::new("Example String");
70 /// assert_eq!(recase.normal_case(), "example string");
71 /// ```
72 pub fn normal_case(&self) -> String {
73 self.lowercase_with_delim(" ")
74 }
75
76 /// Returns a `camelCase` version of the input text as a new String
77 /// ## Example
78 /// ```
79 /// let recase = recase::ReCase::new("Example String");
80 /// assert_eq!(recase.camel_case(), "exampleString");
81 /// ```
82 pub fn camel_case(&self) -> String {
83 let words_iter = self.words_iter();
84 let mut acc = self.allocate_buffer();
85
86 for (i, word) in words_iter.enumerate() {
87 let mut chars = word.chars();
88 // Push first character
89 if let Some(first_char) = chars.next() {
90 if i == 0 {
91 push_lowercase!(acc, first_char);
92 } else {
93 push_uppercase!(acc, first_char);
94 }
95 }
96 // Push the rest
97 chars.for_each(|c| {
98 push_lowercase!(acc, c);
99 });
100 }
101
102 acc
103 }
104
105 /// Returns a `PascalCase` version of the input text as a new String
106 /// ## Example
107 /// ```
108 /// let recase = recase::ReCase::new("Example String");
109 /// assert_eq!(recase.pascal_case(), "ExampleString");
110 /// ```
111 pub fn pascal_case(&self) -> String {
112 let words_iter = self.words_iter();
113 words_iter.fold(self.allocate_buffer(), |mut acc, word| {
114 let mut chars = word.chars();
115 if let Some(first_char) = chars.next() {
116 push_uppercase!(acc, first_char);
117 }
118 // Push the rest
119 chars.for_each(|c| {
120 push_lowercase!(acc, c);
121 });
122 acc
123 })
124 }
125
126 /// Returns a `snake_case` version of the input text as a new String
127 /// ## Example
128 /// ```
129 /// let recase = recase::ReCase::new("Example String");
130 /// assert_eq!(recase.snake_case(), "example_string");
131 /// ```
132 pub fn snake_case(&self) -> String {
133 self.lowercase_with_delim("_")
134 }
135
136 /// Returns a `kebab-case` version of the input text as a new String
137 /// ## Example
138 /// ```
139 /// let recase = recase::ReCase::new("Example String");
140 /// assert_eq!(recase.kebab_case(), "example-string");
141 /// ```
142 pub fn kebab_case(&self) -> String {
143 self.lowercase_with_delim("-")
144 }
145
146 /// Returns a `dot.case` version of the input text as a new String
147 /// ## Example
148 /// ```
149 /// let recase = recase::ReCase::new("Example String");
150 /// assert_eq!(recase.dot_case(), "example.string");
151 /// ```
152 pub fn dot_case(&self) -> String {
153 self.lowercase_with_delim(".")
154 }
155
156 /// Returns a `path/case` version of the input text as a new String
157 /// ## Example
158 /// ```
159 /// let recase = recase::ReCase::new("Example String");
160 /// assert_eq!(recase.path_case(), "example/string");
161 /// ```
162 pub fn path_case(&self) -> String {
163 self.lowercase_with_delim("/")
164 }
165
166 /// Returns a `windows\path\case` version of the input text as a new String
167 /// ## Example
168 /// ```
169 /// let recase = recase::ReCase::new("Example String");
170 /// assert_eq!(recase.windows_path_case(), "example\\string");
171 /// ```
172 pub fn windows_path_case(&self) -> String {
173 self.lowercase_with_delim("\\")
174 }
175
176 /// Returns a `Sentence case` version of the input text as a new String
177 /// ## Example
178 /// ```
179 /// let recase = recase::ReCase::new("Example String");
180 /// assert_eq!(recase.sentence_case(), "Example string");
181 /// ```
182 pub fn sentence_case(&self) -> String {
183 let words_iter = self.words_iter();
184 let mut acc = self.allocate_buffer();
185
186 for (i, word) in words_iter.enumerate() {
187 let mut chars = word.chars();
188 if i != 0 {
189 acc.push_str(" ");
190 } else {
191 // Push first character
192 if let Some(first_char) = chars.next() {
193 push_uppercase!(acc, first_char);
194 }
195 }
196 // Push the rest
197 chars.for_each(|c| {
198 push_lowercase!(acc, c);
199 });
200 }
201
202 acc
203 }
204
205 /// Returns a `Title Case` version of the input text as a new String
206 /// ## Example
207 /// ```
208 /// let recase = recase::ReCase::new("Example String");
209 /// assert_eq!(recase.title_case(), "Example String");
210 /// ```
211 pub fn title_case(&self) -> String {
212 let words_iter = self.words_iter();
213 let mut acc = self.allocate_buffer();
214
215 for (i, word) in words_iter.enumerate() {
216 let mut chars = word.chars();
217 if i != 0 {
218 acc.push_str(" ");
219 }
220
221 // Push first character
222 if let Some(first_char) = chars.next() {
223 push_uppercase!(acc, first_char);
224 }
225
226 // Push the rest
227 chars.for_each(|c| {
228 push_lowercase!(acc, c);
229 });
230 }
231
232 acc
233 }
234
235 /// Returns a `Header-Case` version of the input text as a new String
236 /// ## Example
237 /// ```
238 /// let recase = recase::ReCase::new("Example String");
239 /// assert_eq!(recase.header_case(), "Example-String");
240 /// ```
241 pub fn header_case(&self) -> String {
242 let words_iter = self.words_iter();
243 let mut acc = self.allocate_buffer();
244
245 for (i, word) in words_iter.enumerate() {
246 let mut chars = word.chars();
247 if i != 0 {
248 acc.push_str("-");
249 }
250
251 // Push first character
252 if let Some(first_char) = chars.next() {
253 push_uppercase!(acc, first_char);
254 }
255
256 // Push the rest
257 chars.for_each(|c| {
258 push_lowercase!(acc, c);
259 });
260 }
261
262 acc
263 }
264
265 /// Returns a `UPPER_SNAKE_CASE` version of the input text as a new String
266 /// ## Example
267 /// ```
268 /// let recase = recase::ReCase::new("Example String");
269 /// assert_eq!(recase.upper_snake_case(), "EXAMPLE_STRING");
270 /// ```
271 pub fn upper_snake_case(&self) -> String {
272 let mut acc = self.allocate_buffer();
273
274 for word in self.words_iter() {
275 if !acc.is_empty() {
276 acc.push_str("_");
277 }
278 let chars = word.chars();
279 for c in chars {
280 push_uppercase!(acc, c);
281 }
282 }
283
284 acc
285 }
286
287 /// Returns a `AlTeRnAtInG cAsE` version of the input text as a new String
288 /// ## Example
289 /// ```
290 /// let recase = recase::ReCase::new("Example String");
291 /// assert_eq!(recase.alternating_case(), "eXaMpLe StRiNg");
292 /// ```
293 pub fn alternating_case(&self) -> String {
294 let mut should_uppercase = false;
295 let mut acc = self.allocate_buffer();
296
297 for word in self.words_iter() {
298 if !acc.is_empty() {
299 acc.push_str(" ");
300 }
301
302 let chars = word.chars();
303 for c in chars {
304 if should_uppercase {
305 push_uppercase!(acc, c);
306 } else {
307 push_lowercase!(acc, c);
308 }
309 should_uppercase = !should_uppercase;
310 }
311 }
312
313 acc
314 }
315}
316
317pub trait Casing {
318 /// Returns a `normal case` version of the input text as a new String
319 /// ## Example
320 /// ```
321 /// use recase::Casing;
322 /// assert_eq!("Example String".to_normal_case(), "example string");
323 /// ```
324 fn to_normal_case(&self) -> String;
325
326 /// Returns a `camelCase` version of the input text as a new String
327 /// ## Example
328 /// ```
329 /// use recase::Casing;
330 /// assert_eq!("Example String".to_camel_case(), "exampleString");
331 /// ```
332 fn to_camel_case(&self) -> String;
333
334 /// Returns a `PascalCase` version of the input text as a new String
335 /// ## Example
336 /// ```
337 /// use recase::Casing;
338 /// assert_eq!("Example String".to_pascal_case(), "ExampleString");
339 /// ```
340 fn to_pascal_case(&self) -> String;
341
342 /// Returns a `snake_case` version of the input text as a new String
343 /// ## Example
344 /// ```
345 /// use recase::Casing;
346 /// assert_eq!("Example String".to_snake_case(), "example_string");
347 /// ```
348 fn to_snake_case(&self) -> String;
349
350 /// Returns a `kebab-case` version of the input text as a new String
351 /// ## Example
352 /// ```
353 /// use recase::Casing;
354 /// assert_eq!("Example String".to_kebab_case(), "example-string");
355 /// ```
356 fn to_kebab_case(&self) -> String;
357
358 /// Returns a `dot.case` version of the input text as a new String
359 /// ## Example
360 /// ```
361 /// use recase::Casing;
362 /// assert_eq!("Example String".to_dot_case(), "example.string");
363 /// ```
364 fn to_dot_case(&self) -> String;
365
366 /// Returns a `path/case` version of the input text as a new String
367 /// ## Example
368 /// ```
369 /// use recase::Casing;
370 /// assert_eq!("Example String".to_path_case(), "example/string");
371 /// ```
372 fn to_path_case(&self) -> String;
373
374 /// Returns a `windows\path\case` version of the input text as a new String
375 /// ## Example
376 /// ```
377 /// use recase::Casing;
378 /// assert_eq!("Example String".to_windows_path_case(), "example\\string");
379 /// ```
380 fn to_windows_path_case(&self) -> String;
381
382 /// Returns a `Sentence case` version of the input text as a new String
383 /// ## Example
384 /// ```
385 /// use recase::Casing;
386 /// assert_eq!("Example String".to_sentence_case(), "Example string");
387 /// ```
388 fn to_sentence_case(&self) -> String;
389
390 /// Returns a `Title Case` version of the input text as a new String
391 /// ## Example
392 /// ```
393 /// use recase::Casing;
394 /// assert_eq!("Example String".to_title_case(), "Example String");
395 /// ```
396 fn to_title_case(&self) -> String;
397
398 /// Returns a `Header-Case` version of the input text as a new String
399 /// ## Example
400 /// ```
401 /// use recase::Casing;
402 /// assert_eq!("Example String".to_header_case(), "Example-String");
403 /// ```
404 fn to_header_case(&self) -> String;
405
406 /// Returns a `UPPER_SNAKE_CASE` version of the input text as a new String
407 /// ## Example
408 /// ```
409 /// use recase::Casing;
410 /// assert_eq!("Example String".to_upper_snake_case(), "EXAMPLE_STRING");
411 /// ```
412 fn to_upper_snake_case(&self) -> String;
413
414 /// Returns a `AlTeRnAtInG cAsE` version of the input text as a new String
415 /// ## Example
416 /// ```
417 /// use recase::Casing;
418 /// assert_eq!("Example String".to_alternating_case(), "eXaMpLe StRiNg");
419 /// ```
420 fn to_alternating_case(&self) -> String;
421}
422
423impl Casing for str {
424 fn to_normal_case(&self) -> String {
425 ReCase::new(self).normal_case()
426 }
427
428 fn to_camel_case(&self) -> String {
429 ReCase::new(self).camel_case()
430 }
431
432 fn to_pascal_case(&self) -> String {
433 ReCase::new(self).pascal_case()
434 }
435
436 fn to_snake_case(&self) -> String {
437 ReCase::new(self).snake_case()
438 }
439
440 fn to_kebab_case(&self) -> String {
441 ReCase::new(self).kebab_case()
442 }
443
444 fn to_dot_case(&self) -> String {
445 ReCase::new(self).dot_case()
446 }
447
448 fn to_path_case(&self) -> String {
449 ReCase::new(self).path_case()
450 }
451
452 fn to_windows_path_case(&self) -> String {
453 ReCase::new(self).windows_path_case()
454 }
455
456 fn to_sentence_case(&self) -> String {
457 ReCase::new(self).sentence_case()
458 }
459
460 fn to_title_case(&self) -> String {
461 ReCase::new(self).title_case()
462 }
463
464 fn to_header_case(&self) -> String {
465 ReCase::new(self).header_case()
466 }
467
468 fn to_upper_snake_case(&self) -> String {
469 ReCase::new(self).upper_snake_case()
470 }
471
472 fn to_alternating_case(&self) -> String {
473 ReCase::new(self).alternating_case()
474 }
475}
476
477#[cfg(test)]
478mod recase_tests {
479 use crate::{Casing, ReCase};
480
481 #[test]
482 fn test_normal_case() {
483 let recase = ReCase::new("long_random_text");
484 assert_eq!(recase.normal_case(), "long random text");
485
486 let recase = ReCase::new("誰_long_random_text");
487 assert_eq!(recase.normal_case(), "誰 long random text");
488
489 let recase = ReCase::new("LONG_random_text");
490 assert_eq!(recase.normal_case(), "long random text");
491
492 let recase = ReCase::new("ßlong_random_text");
493 assert_eq!(recase.normal_case(), "ßlong random text");
494 }
495
496 #[test]
497 fn test_camel_case() {
498 let recase = ReCase::new("random_text");
499 assert_eq!(recase.camel_case(), "randomText");
500
501 let recase = ReCase::new("誰_randomText");
502 assert_eq!(recase.camel_case(), "誰RandomText");
503
504 let recase = ReCase::new("RanDom text");
505 assert_eq!(recase.camel_case(), "ranDomText");
506
507 let recase = ReCase::new("ßändom ßext");
508 assert_eq!(recase.camel_case(), "ßändomSSext");
509 }
510
511 #[test]
512 fn test_upper_snake_case() {
513 let recase = ReCase::new("HTML_parser");
514 assert_eq!(recase.upper_snake_case(), "HTML_PARSER");
515 }
516
517 #[test]
518 fn test_alternating_case() {
519 let recase = ReCase::new("random Text");
520 assert_eq!(recase.alternating_case(), "rAnDoM tExT");
521
522 let recase = ReCase::new("誰_random Text");
523 assert_eq!(recase.alternating_case(), "誰 RaNdOm TeXt");
524 }
525
526 #[test]
527 fn test_casing_trait() {
528 let s = "Hello World";
529
530 assert_eq!(s.to_kebab_case(), "hello-world");
531 assert_eq!(s.to_snake_case(), "hello_world");
532 assert_eq!(s.to_alternating_case(), "hElLo WoRlD");
533 }
534}