use std::collections::{HashSet};
use std::iter::FromIterator as _;
use lazy_static::lazy_static;
lazy_static! {
static ref COMMON_INITIALISM: HashSet<String> = {
let mut s = HashSet::new();
let x = vec![
"acl",
"api",
"ascii",
"cpu",
"css",
"dns",
"eof",
"guid",
"html",
"http",
"https",
"id",
"ip",
"json",
"lhs",
"qps",
"ram",
"rhs",
"rpc",
"sla",
"smtp",
"sql",
"ssh",
"tcp",
"tls",
"ttl",
"udp",
"ui",
"uid",
"uuid",
"uri",
"url",
"utf8",
"vm",
"xml",
"xmpp",
"xsrf",
"xss"
];
for v in x {
s.insert(v.to_string());
}
s
};
}
enum State {
Upper,
Lower,
Digit,
Split,
}
impl From<&char> for State {
fn from(b: &char) -> Self {
match b {
'0'..='9' => State::Digit,
'a'..='z' => State::Lower,
'A'..='Z' => State::Upper,
_ => State::Split,
}
}
}
impl From<&u8> for State {
fn from(b: &u8) -> Self {
match *b as char {
'0'..='9' => State::Digit,
'a'..='z' => State::Lower,
'A'..='Z' => State::Upper,
_ => State::Split,
}
}
}
#[inline]
fn split_via_special_chars(s: &str) -> Vec<String> {
assert_ne!(s.len(), 0, "input string should not be empty");
let mut idx = 0;
let mut ans: Vec<String> = vec![String::new()];
for v in s.as_bytes() {
match State::from(v) {
State::Split => {
if !ans[idx].is_empty() {
idx += 1;
ans.push(String::new());
}
}
_ => {
ans[idx].push(*v as char)
}
}
}
ans
}
fn split_via_upper_chars(s: &str) -> Vec<String> {
assert_ne!(s.len(), 0, "input string should not be empty");
let mut idx = 0;
let mut ans: Vec<String> = vec![String::new()];
let bytes = s.as_bytes();
for i in 0..bytes.len() {
let ch = bytes[i] as char;
if i == 0 {
ans[idx].push(ch);
continue;
}
match State::from(&ch) {
State::Upper => {
match State::from(&bytes[i - 1]) {
State::Split => {
ans[idx].push(ch);
}
State::Lower | State::Digit => {
ans.push(String::new());
idx += 1;
ans[idx].push(ch);
}
State::Upper => {
if i != bytes.len() - 1 {
match State::from(&bytes[i + 1]) {
State::Lower => {
ans.push(String::new());
idx += 1;
ans[idx].push(ch);
}
_ => {
ans[idx].push(ch);
}
}
} else {
ans[idx].push(ch);
}
}
}
}
_ => {
ans[idx].push(ch)
}
}
}
ans
}
fn split_string_in_parts(s: &str) -> Vec<String> {
let mut ans: Vec<String> = Vec::new();
for v in split_via_special_chars(s) {
if v.is_empty() {
continue;
}
let mut x = split_via_upper_chars(&v);
for i in x.iter_mut() {
ans.push(i.to_lowercase())
}
}
ans
}
pub fn to_camel(s: &str) -> String {
let x = split_string_in_parts(s);
let mut ans = String::from(&x[0]);
ans.push_str(
x.iter().skip(0)
.map(|v| {
if COMMON_INITIALISM.contains(v) {
to_upper(v)
} else {
to_upper_first(v)
}
})
.collect::<Vec<String>>()
.join("").as_str()
);
ans
}
pub fn to_pascal(s: &str) -> String {
split_string_in_parts(s).iter().map(|v| {
if COMMON_INITIALISM.contains(v) {
to_upper(v)
} else {
to_upper_first(v)
}
}).collect::<Vec<String>>().join("")
}
pub fn to_snack(s: &str) -> String {
split_string_in_parts(s).join("_")
}
pub trait ToKebab {
fn to_kebab(&self) -> String;
}
impl ToKebab for String {
fn to_kebab(&self) -> String {
to_kebab(&self)
}
}
pub fn to_kebab(s: &str) -> String {
split_string_in_parts(s).join("-")
}
pub fn to_upper(s: &str) -> String {
s.to_uppercase()
}
pub fn to_upper_first(s: &str) -> String {
assert_ne!(s.len(), 0);
let mut x: Vec<char> = Vec::from_iter(s.as_bytes().iter().map(|x| *x as char));
x[0] = x[0].to_ascii_uppercase();
String::from_iter(x)
}
#[cfg(test)]
mod tests;