#![forbid(unsafe_code)]
#![doc(html_root_url = "https://docs.rs/humanize-string/0.1.0")]
use decamelize::decamelize;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
#[must_use]
pub fn humanize_string(string: &str) -> String {
humanize_string_with(string, false)
}
#[must_use]
pub fn humanize_string_with(string: &str, preserve_case: bool) -> String {
let mut s = if preserve_case {
string.to_string()
} else {
decamelize(string).to_lowercase()
};
s = replace_underscore_dash(&s);
s = collapse_whitespace(&s);
let s = js_trim(&s);
capitalize_first(s)
}
fn replace_underscore_dash(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut in_run = false;
for c in s.chars() {
if c == '_' || c == '-' {
if !in_run {
out.push(' ');
in_run = true;
}
} else {
out.push(c);
in_run = false;
}
}
out
}
fn collapse_whitespace(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while i < chars.len() {
if is_js_whitespace(chars[i]) {
let start = i;
while i < chars.len() && is_js_whitespace(chars[i]) {
i += 1;
}
if i - start >= 2 {
out.push(' ');
} else {
out.push(chars[start]);
}
} else {
out.push(chars[i]);
i += 1;
}
}
out
}
fn js_trim(s: &str) -> &str {
s.trim_matches(is_js_whitespace)
}
fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
}
fn is_js_whitespace(c: char) -> bool {
matches!(
c,
'\u{0009}'
| '\u{000A}'
| '\u{000B}'
| '\u{000C}'
| '\u{000D}'
| '\u{0020}'
| '\u{00A0}'
| '\u{1680}'
| '\u{2000}'
..='\u{200A}'
| '\u{2028}'
| '\u{2029}'
| '\u{202F}'
| '\u{205F}'
| '\u{3000}'
| '\u{FEFF}'
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_mode() {
assert_eq!(humanize_string("fooBar"), "Foo bar");
assert_eq!(humanize_string("background-color"), "Background color");
assert_eq!(humanize_string("_id"), "Id");
assert_eq!(humanize_string("fooBar123"), "Foo bar123");
assert_eq!(humanize_string("BowmoreIslay"), "Bowmore islay");
assert_eq!(humanize_string("XMLHttpRequest"), "Xml http request");
assert_eq!(humanize_string("my_url_string"), "My url string");
assert_eq!(humanize_string("HELLO_WORLD"), "Hello world");
assert_eq!(
humanize_string("backgroundColorURL"),
"Background color url"
);
}
#[test]
fn preserve_case() {
assert_eq!(humanize_string_with("fooBar", true), "FooBar");
assert_eq!(humanize_string_with("HELLO_WORLD", true), "HELLO WORLD");
assert_eq!(humanize_string_with("my-url", true), "My url");
}
#[test]
fn whitespace_and_sharp_s() {
assert_eq!(humanize_string(" spaced out "), "Spaced out");
assert_eq!(humanize_string("a_b-c__d"), "A b c d");
assert_eq!(humanize_string("ßeta"), "SSeta");
}
#[test]
fn edge_cases() {
assert_eq!(humanize_string(""), "");
assert_eq!(humanize_string("a"), "A");
assert_eq!(humanize_string("UPPERCASE"), "Uppercase");
assert_eq!(humanize_string("___"), "");
}
}