change_case/
title_case.rs

1use lazy_static::lazy_static;
2use regex::{Captures, Regex};
3
4lazy_static! {
5    static ref RE_TOKENS: Regex = Regex::new(r"[^\s:–—-]+|.").unwrap();
6    static ref RE_MANUAL_CASE: fancy_regex::Regex = fancy_regex::Regex::new(r".(?=[A-Z]|\..)").unwrap();
7    static ref RE_SMALL_WORDS: fancy_regex::Regex = fancy_regex::Regex::new(r"\b(?:an?d?|a[st]|because|but|by|en|for|i[fn]|neither|nor|o[fnr]|only|over|per|so|some|tha[tn]|the|to|up|upon|vs?\.?|versus|via|when|with|without|yet)\b").unwrap();
8    static ref RE_WHITESPACE: Regex = Regex::new(r"\s").unwrap();
9    static ref RE_ALPHANUMERIC: Regex = Regex::new(r"[A-Za-z0-9\u00C0-\u00FF]").unwrap();
10}
11
12/// Change to title case
13/// ```rust
14/// use change_case::title_case;
15/// assert_eq!(title_case("test"), "Test");
16/// assert_eq!(title_case("two words"), "Two Words");
17/// assert_eq!(title_case("we keep NASA capitalized"), "We Keep NASA Capitalized");
18/// ```
19pub fn title_case(input: &str) -> String {
20    let mut result = String::new();
21    for ma in RE_TOKENS.find_iter(input) {
22        let token = ma.as_str();
23        let index = ma.start();
24        let index2 = index + token.len();
25        if
26        // Ignore already capitalized words.
27        !RE_MANUAL_CASE.is_match(token).unwrap() &&
28            // Ignore small words except at beginning or end.
29            (!RE_SMALL_WORDS.is_match(token).unwrap() || index == 0 || index2 == input.len()) &&
30            // Ignore URLs
31            (input.chars().nth(index2).map_or(true, |v| v != ':') ||
32                input.chars().nth(index2 + 1).map_or(false, |v| RE_WHITESPACE.is_match(v.to_string().as_str())))
33        {
34            let new_token =
35                RE_ALPHANUMERIC.replace(token, |v: &Captures| format!("{}", &v[0].to_uppercase()));
36            result.push_str(new_token.as_ref())
37        } else {
38            result.push_str(token)
39        }
40    }
41    result
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_title_case() {
50        assert_eq!(title_case(""), "");
51        assert_eq!(title_case("2019"), "2019");
52        assert_eq!(title_case("test"), "Test");
53        assert_eq!(title_case("two words"), "Two Words");
54        assert_eq!(title_case("one. two."), "One. Two.");
55        assert_eq!(title_case("a small word starts"), "A Small Word Starts");
56        assert_eq!(title_case("small word ends on"), "Small Word Ends On");
57        assert_eq!(
58            title_case("we keep NASA capitalized"),
59            "We Keep NASA Capitalized"
60        );
61        assert_eq!(
62            title_case("pass camelCase through"),
63            "Pass camelCase Through"
64        );
65        assert_eq!(
66            title_case("follow step-by-step instructions"),
67            "Follow Step-by-Step Instructions"
68        );
69        assert_eq!(
70            title_case("your hair[cut] looks (nice)"),
71            "Your Hair[cut] Looks (Nice)"
72        );
73        assert_eq!(title_case("leave Q&A unscathed"), "Leave Q&A Unscathed");
74        assert_eq!(
75            title_case("piña colada while you listen to ænima"),
76            "Piña Colada While You Listen to Ænima"
77        );
78        assert_eq!(
79            title_case("start title – end title"),
80            "Start Title – End Title"
81        );
82        assert_eq!(title_case("start title–end title"), "Start Title–End Title");
83        assert_eq!(
84            title_case("start title — end title"),
85            "Start Title — End Title"
86        );
87        assert_eq!(title_case("start title—end title"), "Start Title—End Title");
88        assert_eq!(
89            title_case("start title - end title"),
90            "Start Title - End Title"
91        );
92        assert_eq!(title_case("don't break"), "Don't Break");
93        assert_eq!(title_case(r#""double quotes""#), r#""Double Quotes""#);
94        assert_eq!(
95            title_case(r#"double quotes "inner" word"#),
96            r#"Double Quotes "Inner" Word"#
97        );
98        assert_eq!(
99            title_case("fancy double quotes “inner” word"),
100            "Fancy Double Quotes “Inner” Word"
101        );
102        assert_eq!(
103            title_case("have you read “The Lottery”?"),
104            "Have You Read “The Lottery”?"
105        );
106        assert_eq!(title_case("one: two"), "One: Two");
107        assert_eq!(title_case("one two: three four"), "One Two: Three Four");
108        assert_eq!(
109            title_case(r#"one two: "Three Four""#),
110            r#"One Two: "Three Four""#
111        );
112        assert_eq!(
113            title_case("email email@example.com address"),
114            "Email email@example.com Address"
115        );
116        assert_eq!(
117            title_case("you have an https://example.com/ title"),
118            "You Have an https://example.com/ Title"
119        );
120        assert_eq!(
121            title_case("_underscores around words_"),
122            "_Underscores Around Words_"
123        );
124        assert_eq!(
125            title_case("*asterisks around words*"),
126            "*Asterisks Around Words*"
127        );
128        assert_eq!(title_case("this vs. that"), "This vs. That");
129        assert_eq!(title_case("this vs that"), "This vs That");
130        assert_eq!(title_case("this v. that"), "This v. That");
131        assert_eq!(title_case("this v that"), "This v That");
132        assert_eq!(
133            title_case("Scott Moritz and TheStreet.com’s million iPhone la-la land"),
134            "Scott Moritz and TheStreet.com’s Million iPhone La-La Land"
135        );
136        assert_eq!(title_case("Notes and observations regarding Apple’s announcements from ‘The Beat Goes On’ special event"), "Notes and Observations Regarding Apple’s Announcements From ‘The Beat Goes On’ Special Event");
137        assert_eq!(
138            title_case("the quick brown fox jumps over the lazy dog"),
139            "The Quick Brown Fox Jumps over the Lazy Dog"
140        );
141        assert_eq!(title_case("newcastle upon tyne"), "Newcastle upon Tyne");
142        assert_eq!(title_case("newcastle *upon* tyne"), "Newcastle *upon* Tyne");
143    }
144}