change_case/
title_case.rs1use 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
12pub 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 !RE_MANUAL_CASE.is_match(token).unwrap() &&
28 (!RE_SMALL_WORDS.is_match(token).unwrap() || index == 0 || index2 == input.len()) &&
30 (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}