change_case/
lib.rs

1//!  Transform a string between `camelCase`, `PascalCase`, `Capital Case`, `snake_case`, `param-case`, `CONSTANT_CASE` and others.
2//! # Examples
3//!
4//! ```
5//! use change_case::*;
6//!
7//! assert_eq!(camel_case("Test String"), "testString");
8//! assert_eq!(captial_case("test string"), "Test String");
9//! assert_eq!(constant_case("test string"), "TEST_STRING");
10//! assert_eq!(dot_case("test string"), "test.string");
11//! assert_eq!(header_case("test string"), "Test-String");
12//! assert_eq!(param_case("test string"), "test-string");
13//! assert_eq!(pascal_case("test string"), "TestString");
14//! assert_eq!(path_case("test string"), "test/string");
15//! assert_eq!(sentence_case("Test String"), "Test string");
16//! assert_eq!(snake_case("Test String"), "test_string");
17//! assert_eq!(swap_case("Test String"), "tEST sTRING");
18//! assert_eq!(title_case("this vs that"), "This vs That");
19//! ```
20
21use lazy_static::lazy_static;
22use regex::{Regex, Replacer};
23
24mod title_case;
25pub use title_case::title_case;
26
27lazy_static! {
28    static ref RE_SPLIT_1: Regex = Regex::new(r"([a-z0-9])([A-Z])").unwrap();
29    static ref RE_SPLIT_2: Regex = Regex::new(r"([A-Z])([A-Z][a-z])").unwrap();
30    static ref RE_STRIP: Regex = Regex::new(r"(?i)[^A-Z0-9]+").unwrap();
31}
32
33type Fransform = dyn Fn(&str, usize) -> String;
34
35/// Control the behavier of change case
36pub struct Options {
37    split_regex: Vec<Regex>,
38    strip_regex: Vec<Regex>,
39    delimiter: String,
40    transform: Box<Fransform>,
41}
42
43impl Options {
44    /// Change regex used to split into word segments
45    pub fn split_regex(mut self, value: Vec<Regex>) -> Self {
46        self.split_regex = value;
47        self
48    }
49    /// Change regex used to remove extraneous characters
50    pub fn strip_regex(mut self, value: Vec<Regex>) -> Self {
51        self.strip_regex = value;
52        self
53    }
54    /// Change value used between words (e.g. " ")
55    pub fn delimiter(mut self, value: &str) -> Self {
56        self.delimiter = value.into();
57        self
58    }
59    /// Change the transform function used to transform each word segment
60    pub fn transform(mut self, value: Box<Fransform>) -> Self {
61        self.transform = value;
62        self
63    }
64}
65
66impl Default for Options {
67    fn default() -> Self {
68        Self {
69            split_regex: vec![RE_SPLIT_1.clone(), RE_SPLIT_2.clone()],
70            strip_regex: vec![RE_STRIP.clone()],
71            delimiter: " ".into(),
72            transform: Box::new(|part: &str, _index: usize| part.to_lowercase()),
73        }
74    }
75}
76
77/// Core function to change case
78/// ```rust
79/// use regex::Regex;
80/// use change_case::{change_case, Options};
81/// let options = Options::default()
82///     .split_regex(vec![Regex::new("([a-z])([A-Z0-9])").unwrap()]);
83/// assert_eq!(change_case("camel2019", options), "camel 2019");
84/// assert_eq!(change_case("camel2019", Options::default()), "camel2019");
85/// ```
86
87pub fn change_case(input: &str, options: Options) -> String {
88    let result = replace(
89        input,
90        options.split_regex.iter().map(|v| (v, "$1\0$2")).collect(),
91    );
92    let result = replace(
93        result.as_str(),
94        options.strip_regex.iter().map(|v| (v, "\0")).collect(),
95    );
96    let result = result.trim_start_matches("\0").trim_end_matches("\0");
97    let transform = options.transform;
98
99    let parts: Vec<String> = result
100        .split("\0")
101        .enumerate()
102        .map(|(index, part)| (transform)(part, index))
103        .collect();
104    parts.join(options.delimiter.as_str())
105}
106
107fn replace<R: Replacer>(input: &str, reps: Vec<(&Regex, R)>) -> String {
108    reps.into_iter().fold(input.to_string(), |acc, re| {
109        re.0.replace_all(acc.as_str(), re.1).to_string()
110    })
111}
112
113/// Change to upper case
114/// ```rust
115/// use change_case::upper_case;
116/// assert_eq!(upper_case(""), "");
117/// assert_eq!(upper_case("test"), "TEST");
118/// assert_eq!(upper_case("test string"), "TEST STRING");
119/// assert_eq!(upper_case("Test String"), "TEST STRING");
120/// assert_eq!(upper_case("\u{0131}"), "I");
121/// ```
122pub fn upper_case(input: &str) -> String {
123    input.to_uppercase()
124}
125
126/// Only change the first charactor to upper case
127/// ```rust
128/// use change_case::upper_case_first;
129/// assert_eq!(upper_case_first(""), "");
130/// assert_eq!(upper_case_first("test"), "Test");
131/// assert_eq!(upper_case_first("TEST"), "TEST");
132/// ```
133pub fn upper_case_first(input: &str) -> String {
134    if input.len() == 0 {
135        return String::new();
136    }
137    let (first, last) = input.split_at(1);
138    format!("{}{}", upper_case(first), last)
139}
140
141/// Change to lower case
142/// ```rust
143/// use change_case::lower_case;
144/// assert_eq!(lower_case(""), "");
145/// assert_eq!(lower_case("test"), "test");
146/// assert_eq!(lower_case("TEST"), "test");
147/// assert_eq!(lower_case("test string"), "test string");
148/// assert_eq!(lower_case("TEST STRING"), "test string");
149/// ```
150pub fn lower_case(input: &str) -> String {
151    input.to_lowercase()
152}
153
154/// Only change the first charactor to lower case
155/// ```rust
156/// use change_case::lower_case_first;
157/// assert_eq!(lower_case_first(""), "");
158/// assert_eq!(lower_case_first("Test"), "test");
159/// assert_eq!(lower_case_first("TEST"), "tEST");
160/// ```
161pub fn lower_case_first(input: &str) -> String {
162    if input.len() == 0 {
163        return String::new();
164    }
165    let (first, last) = input.split_at(1);
166    format!("{}{}", lower_case(first), last)
167}
168
169fn transform_pascal_case(input: &str, index: usize) -> String {
170    if input.len() == 0 {
171        return String::new();
172    }
173    let (first, last) = input.split_at(1);
174    let mut first = upper_case(first);
175    if index > 0 {
176        let first_char = first.chars().nth(0).unwrap();
177        if first_char >= '0' && first_char <= '9' {
178            first = format!("_{}", first)
179        }
180    }
181    format!("{}{}", first, lower_case(last))
182}
183
184/// Change to pascal case
185/// ```rust
186/// use change_case::pascal_case;
187/// assert_eq!(pascal_case(""), "");
188/// assert_eq!(pascal_case("test"), "Test");
189/// assert_eq!(pascal_case("test string"), "TestString");
190/// assert_eq!(pascal_case("Test String"), "TestString");
191/// assert_eq!(pascal_case("TestV2"), "TestV2");
192/// assert_eq!(pascal_case("version 1.2.10"), "Version_1_2_10");
193/// assert_eq!(pascal_case("version 1.21.0"), "Version_1_21_0");
194/// ```
195pub fn pascal_case(input: &str) -> String {
196    let options = Options::default()
197        .delimiter("")
198        .transform(Box::new(transform_pascal_case));
199    change_case(input, options)
200}
201
202fn transform_camel_case(input: &str, index: usize) -> String {
203    if index == 0 {
204        return lower_case(input);
205    }
206    transform_pascal_case(input, index)
207}
208
209/// Change to camel case
210/// ```rust
211/// use change_case::camel_case;
212/// assert_eq!(camel_case(""), "");
213/// assert_eq!(camel_case("test"), "test");
214/// assert_eq!(camel_case("test string"), "testString");
215/// assert_eq!(camel_case("Test String"), "testString");
216/// assert_eq!(camel_case("TestV2"), "testV2");
217/// assert_eq!(camel_case("_foo_bar_"), "fooBar");
218/// assert_eq!(camel_case("version 1.2.10"), "version_1_2_10");
219/// assert_eq!(camel_case("version 1.21.0"), "version_1_21_0");
220/// ```
221pub fn camel_case(input: &str) -> String {
222    let options = Options::default()
223        .delimiter("")
224        .transform(Box::new(transform_camel_case));
225    change_case(input, options)
226}
227
228fn transform_capital_case(input: &str, _index: usize) -> String {
229    upper_case_first(lower_case(input).as_str())
230}
231
232/// Change to capital case
233/// ```rust
234/// use change_case::captial_case;
235/// assert_eq!(captial_case(""), "");
236/// assert_eq!(captial_case("test"), "Test");
237/// assert_eq!(captial_case("test string"), "Test String");
238/// assert_eq!(captial_case("Test String"), "Test String");
239/// assert_eq!(captial_case("TestV2"), "Test V2");
240/// assert_eq!(captial_case("version 1.2.10"), "Version 1 2 10");
241/// assert_eq!(captial_case("version 1.21.0"), "Version 1 21 0");
242/// ```
243pub fn captial_case(input: &str) -> String {
244    let options = Options::default()
245        .delimiter(" ")
246        .transform(Box::new(transform_capital_case));
247    change_case(input, options)
248}
249
250fn transform_upper_case(input: &str, _index: usize) -> String {
251    upper_case(input)
252}
253
254/// Change to constant case
255/// ```rust
256/// use change_case::constant_case;
257/// assert_eq!(constant_case(""), "");
258/// assert_eq!(constant_case("test"), "TEST");
259/// assert_eq!(constant_case("test string"), "TEST_STRING");
260/// assert_eq!(constant_case("Test String"), "TEST_STRING");
261/// assert_eq!(constant_case("dot.case"), "DOT_CASE");
262/// assert_eq!(constant_case("path/case"), "PATH_CASE");
263/// assert_eq!(constant_case("TestV2"), "TEST_V2");
264/// assert_eq!(constant_case("version 1.2.10"), "VERSION_1_2_10");
265/// assert_eq!(constant_case("version 1.21.0"), "VERSION_1_21_0");
266/// ```
267pub fn constant_case(input: &str) -> String {
268    let options = Options::default()
269        .delimiter("_")
270        .transform(Box::new(transform_upper_case));
271    change_case(input, options)
272}
273
274fn transform_lower_case(input: &str, _index: usize) -> String {
275    lower_case(input)
276}
277
278/// Change to dot case
279/// ```rust
280/// use change_case::dot_case;
281/// assert_eq!(dot_case(""), "");
282/// assert_eq!(dot_case("test"), "test");
283/// assert_eq!(dot_case("test string"), "test.string");
284/// assert_eq!(dot_case("Test String"), "test.string");
285/// assert_eq!(dot_case("dot.case"), "dot.case");
286/// assert_eq!(dot_case("path/case"), "path.case");
287/// assert_eq!(dot_case("TestV2"), "test.v2");
288/// assert_eq!(dot_case("version 1.2.10"), "version.1.2.10");
289/// assert_eq!(dot_case("version 1.21.0"), "version.1.21.0");
290/// ```
291pub fn dot_case(input: &str) -> String {
292    let options = Options::default()
293        .delimiter(".")
294        .transform(Box::new(transform_lower_case));
295    change_case(input, options)
296}
297
298/// Change to header case
299/// ```rust
300/// use change_case::header_case;
301/// assert_eq!(header_case(""), "");
302/// assert_eq!(header_case("test"), "Test");
303/// assert_eq!(header_case("test string"), "Test-String");
304/// assert_eq!(header_case("Test String"), "Test-String");
305/// assert_eq!(header_case("TestV2"), "Test-V2");
306/// assert_eq!(header_case("version 1.2.10"), "Version-1-2-10");
307/// assert_eq!(header_case("version 1.21.0"), "Version-1-21-0");
308/// ```
309pub fn header_case(input: &str) -> String {
310    let options = Options::default()
311        .delimiter("-")
312        .transform(Box::new(transform_capital_case));
313    change_case(input, options)
314}
315
316/// Change to param case
317/// ```rust
318/// use change_case::param_case;
319/// assert_eq!(param_case(""), "");
320/// assert_eq!(param_case("test"), "test");
321/// assert_eq!(param_case("test string"), "test-string");
322/// assert_eq!(param_case("Test String"), "test-string");
323/// assert_eq!(param_case("TestV2"), "test-v2");
324/// assert_eq!(param_case("version 1.2.10"), "version-1-2-10");
325/// assert_eq!(param_case("version 1.21.0"), "version-1-21-0");
326/// ```
327pub fn param_case(input: &str) -> String {
328    let options = Options::default()
329        .delimiter("-")
330        .transform(Box::new(transform_lower_case));
331    change_case(input, options)
332}
333
334/// Change to path case
335/// ```rust
336/// use change_case::path_case;
337/// assert_eq!(path_case(""), "");
338/// assert_eq!(path_case("test"), "test");
339/// assert_eq!(path_case("test string"), "test/string");
340/// assert_eq!(path_case("Test String"), "test/string");
341/// assert_eq!(path_case("TestV2"), "test/v2");
342/// assert_eq!(path_case("version 1.2.10"), "version/1/2/10");
343/// assert_eq!(path_case("version 1.21.0"), "version/1/21/0");
344/// ```
345pub fn path_case(input: &str) -> String {
346    let options = Options::default()
347        .delimiter("/")
348        .transform(Box::new(transform_lower_case));
349    change_case(input, options)
350}
351
352fn transform_sentence_case(input: &str, index: usize) -> String {
353    let input = lower_case(input);
354    if index == 0 {
355        upper_case_first(input.as_str())
356    } else {
357        input
358    }
359}
360
361/// Change to sentence case
362/// ```rust
363/// use change_case::sentence_case;
364/// assert_eq!(sentence_case(""), "");
365/// assert_eq!(sentence_case("test"), "Test");
366/// assert_eq!(sentence_case("test string"), "Test string");
367/// assert_eq!(sentence_case("Test String"), "Test string");
368/// assert_eq!(sentence_case("TestV2"), "Test v2");
369/// assert_eq!(sentence_case("version 1.2.10"), "Version 1 2 10");
370/// assert_eq!(sentence_case("version 1.21.0"), "Version 1 21 0");
371/// ```
372pub fn sentence_case(input: &str) -> String {
373    let options = Options::default()
374        .delimiter(" ")
375        .transform(Box::new(transform_sentence_case));
376    change_case(input, options)
377}
378
379/// Change to snake case
380/// ```rust
381/// use change_case::snake_case;
382/// assert_eq!(snake_case(""), "");
383/// assert_eq!(snake_case("_id"), "id");
384/// assert_eq!(snake_case("test"), "test");
385/// assert_eq!(snake_case("test string"), "test_string");
386/// assert_eq!(snake_case("Test String"), "test_string");
387/// assert_eq!(snake_case("TestV2"), "test_v2");
388/// assert_eq!(snake_case("version 1.2.10"), "version_1_2_10");
389/// assert_eq!(snake_case("version 1.21.0"), "version_1_21_0");
390/// ```
391pub fn snake_case(input: &str) -> String {
392    let options = Options::default()
393        .delimiter("_")
394        .transform(Box::new(transform_lower_case));
395    change_case(input, options)
396}
397
398/// Change to swap case
399/// ```rust
400/// use change_case::swap_case;
401/// assert_eq!(swap_case(""), "");
402/// assert_eq!(swap_case("test"), "TEST");
403/// assert_eq!(swap_case("test string"), "TEST STRING");
404/// assert_eq!(swap_case("Test String"), "tEST sTRING");
405/// assert_eq!(swap_case("TestV2"), "tESTv2");
406/// assert_eq!(swap_case("sWaP cAsE"), "SwAp CaSe");
407/// ```
408pub fn swap_case(input: &str) -> String {
409    input
410        .chars()
411        .into_iter()
412        .map(|v| {
413            if v.is_lowercase() {
414                v.to_uppercase().to_string()
415            } else {
416                v.to_lowercase().to_string()
417            }
418        })
419        .collect()
420}