use std::convert::From;
use itertools::Itertools;
#[cfg(feature = "regex")]
use regex::Regex;
use crate::case::*;
pub struct Words<'a> {
w: Vec<&'a str>,
}
impl<'a> From<&'a str> for Words<'a> {
fn from(s: &'a str) -> Self {
let s = s.trim();
let split_fn = select_split_fn(s);
Words { w: split_fn(s) }
}
}
impl<'a> Words<'a> {
#[cfg(feature = "regex")]
pub fn with_separator(s: &'a str, sep: &Regex) -> Words<'a> {
Words {
w: sep.split(s.trim()).collect(),
}
}
pub fn camel(&self) -> String {
let mut ws = self.w.iter();
match ws.next() {
Some(first_w) => {
first_w.to_lowercase() + &ws.map(|w| to_titlecase(w)).collect::<String>()
}
None => String::new(),
}
}
pub fn pascal(&self) -> String {
self.build(|w| to_titlecase(w), "")
}
pub fn kebab(&self) -> String {
self.build(|w| w.to_lowercase(), "-")
}
pub fn screaming_kebab(&self) -> String {
self.build(|w| w.to_uppercase(), "-")
}
pub fn lower(&self) -> String {
self.build(|w| w.to_lowercase(), " ")
}
pub fn upper(&self) -> String {
self.build(|w| w.to_uppercase(), " ")
}
pub fn snake(&self) -> String {
self.build(|w| w.to_lowercase(), "_")
}
pub fn screaming_snake(&self) -> String {
self.build(|w| w.to_uppercase(), "_")
}
pub fn title(&self) -> String {
self.build(|w| to_titlecase(w), " ")
}
pub fn toggle(&self) -> String {
self.build(|w| to_togglecase(w), " ")
}
#[cfg(feature = "rand")]
pub fn random(&self) -> String {
to_randomcase(&self.w.join(" "))
}
fn build(&self, case_fn: fn(&&str) -> String, sep: &str) -> String {
self.w.iter().map(case_fn).collect::<Vec<_>>().join(sep)
}
}
fn select_split_fn(s: &str) -> fn(&str) -> Vec<&str> {
if s.contains(' ') {
split_whitespace
} else {
let num_both = s.chars().filter(|&c| c == '-' || c == '_').count();
if num_both > 0 {
let num_hyphen = s.chars().filter(|&c| c == '-').count();
if num_hyphen * 2 >= num_both {
split_hyphen
} else {
split_underscore
}
} else {
split_case
}
}
}
fn split_whitespace(s: &str) -> Vec<&str> {
s.split_whitespace().collect()
}
fn split_hyphen(s: &str) -> Vec<&str> {
s.split('-').collect()
}
fn split_underscore(s: &str) -> Vec<&str> {
s.split('_').collect()
}
fn split_case(s: &str) -> Vec<&str> {
s.char_indices()
.zip(s.char_indices().skip(1))
.filter_map(
|((i, c0), (j, c1))| match (c0.is_uppercase(), c1.is_uppercase()) {
(true, false) if i > 0 => Some(i),
(false, true) => Some(j),
_ => None,
},
)
.dedup()
.chain(std::iter::once(s.len()))
.scan(0, |i, j| {
let ss = &s[*i..j];
*i = j;
Some(ss)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn select_split_fn_r_whitespace() {
let s = "this is-a_testString";
let act = select_split_fn(s);
assert_eq!(split_whitespace as usize, act as usize);
}
#[test]
fn select_split_fn_r_hyphen() {
let s = "this-is-a-test_string";
let act = select_split_fn(s);
assert_eq!(split_hyphen as usize, act as usize);
}
#[test]
fn select_split_fn_r_hyphen_over_underscore() {
let s = "this_is-a_test-string";
let act = select_split_fn(s);
assert_eq!(split_hyphen as usize, act as usize);
}
#[test]
fn select_split_fn_r_underscore() {
let s = "this-is_a_test_string";
let act = select_split_fn(s);
assert_eq!(split_underscore as usize, act as usize);
}
#[test]
fn select_split_fn_r_case() {
let s = "ThisIsATestString";
let act = select_split_fn(s);
assert_eq!(split_case as usize, act as usize);
}
proptest! {
#[test]
fn p_split_case_drops_no_chars(s in r"\PC*") {
let v = split_case(&s);
prop_assert_eq!(&s, &v.join(""));
}
#[test]
fn p_split_case_tail_words_are_title_or_upper(s in r"\PC+") {
let v = split_case(&s);
for w in &v[1..] {
prop_assert!(is_titlecase(w) || is_uppercase(w));
}
}
#[test]
fn p_split_case_1_upper_word_in_row(s in r"\PC*") {
let v = split_case(&s);
let it = v.iter().map(|w| is_uppercase(w));
let it1 = it.clone().skip(1);
for (p0, p1) in it.zip(it1) {
prop_assert!(!p0 || !p1);
}
}
}
fn is_titlecase(s: &str) -> bool {
s.starts_with(char::is_uppercase) && !s.chars().skip(1).any(char::is_uppercase)
}
fn is_uppercase(s: &str) -> bool {
s.chars().all(char::is_uppercase)
}
}