use lazy_static::lazy_static;
use regex::{Regex, Replacer};
mod title_case;
pub use title_case::title_case;
lazy_static! {
static ref RE_SPLIT_1: Regex = Regex::new(r"([a-z0-9])([A-Z])").unwrap();
static ref RE_SPLIT_2: Regex = Regex::new(r"([A-Z])([A-Z][a-z])").unwrap();
static ref RE_STRIP: Regex = Regex::new(r"(?i)[^A-Z0-9]+").unwrap();
}
type Fransform = dyn Fn(&str, usize) -> String;
pub struct Options {
split_regex: Vec<Regex>,
strip_regex: Vec<Regex>,
delimiter: String,
transform: Box<Fransform>,
}
impl Options {
pub fn split_regex(mut self, value: Vec<Regex>) -> Self {
self.split_regex = value;
self
}
pub fn strip_regex(mut self, value: Vec<Regex>) -> Self {
self.strip_regex = value;
self
}
pub fn delimiter(mut self, value: &str) -> Self {
self.delimiter = value.into();
self
}
pub fn transform(mut self, value: Box<Fransform>) -> Self {
self.transform = value;
self
}
}
impl Default for Options {
fn default() -> Self {
Self {
split_regex: vec![RE_SPLIT_1.clone(), RE_SPLIT_2.clone()],
strip_regex: vec![RE_STRIP.clone()],
delimiter: " ".into(),
transform: Box::new(|part: &str, _index: usize| part.to_lowercase()),
}
}
}
pub fn change_case(input: &str, options: Options) -> String {
let result = replace(
input,
options.split_regex.iter().map(|v| (v, "$1\0$2")).collect(),
);
let result = replace(
result.as_str(),
options.strip_regex.iter().map(|v| (v, "\0")).collect(),
);
let result = result.trim_start_matches("\0").trim_end_matches("\0");
let transform = options.transform;
let parts: Vec<String> = result
.split("\0")
.enumerate()
.map(|(index, part)| (transform)(part, index))
.collect();
parts.join(options.delimiter.as_str())
}
fn replace<R: Replacer>(input: &str, reps: Vec<(&Regex, R)>) -> String {
reps.into_iter().fold(input.to_string(), |acc, re| {
re.0.replace_all(acc.as_str(), re.1).to_string()
})
}
pub fn upper_case(input: &str) -> String {
input.to_uppercase()
}
pub fn upper_case_first(input: &str) -> String {
if input.len() == 0 {
return String::new();
}
let (first, last) = input.split_at(1);
format!("{}{}", upper_case(first), last)
}
pub fn lower_case(input: &str) -> String {
input.to_lowercase()
}
pub fn lower_case_first(input: &str) -> String {
if input.len() == 0 {
return String::new();
}
let (first, last) = input.split_at(1);
format!("{}{}", lower_case(first), last)
}
fn transform_pascal_case(input: &str, index: usize) -> String {
if input.len() == 0 {
return String::new();
}
let (first, last) = input.split_at(1);
let mut first = upper_case(first);
if index > 0 {
let first_char = first.chars().nth(0).unwrap();
if first_char >= '0' && first_char <= '9' {
first = format!("_{}", first)
}
}
format!("{}{}", first, lower_case(last))
}
pub fn pascal_case(input: &str) -> String {
let options = Options::default()
.delimiter("")
.transform(Box::new(transform_pascal_case));
change_case(input, options)
}
fn transform_camel_case(input: &str, index: usize) -> String {
if index == 0 {
return lower_case(input);
}
transform_pascal_case(input, index)
}
pub fn camel_case(input: &str) -> String {
let options = Options::default()
.delimiter("")
.transform(Box::new(transform_camel_case));
change_case(input, options)
}
fn transform_capital_case(input: &str, _index: usize) -> String {
upper_case_first(lower_case(input).as_str())
}
pub fn captial_case(input: &str) -> String {
let options = Options::default()
.delimiter(" ")
.transform(Box::new(transform_capital_case));
change_case(input, options)
}
fn transform_upper_case(input: &str, _index: usize) -> String {
upper_case(input)
}
pub fn constant_case(input: &str) -> String {
let options = Options::default()
.delimiter("_")
.transform(Box::new(transform_upper_case));
change_case(input, options)
}
fn transform_lower_case(input: &str, _index: usize) -> String {
lower_case(input)
}
pub fn dot_case(input: &str) -> String {
let options = Options::default()
.delimiter(".")
.transform(Box::new(transform_lower_case));
change_case(input, options)
}
pub fn header_case(input: &str) -> String {
let options = Options::default()
.delimiter("-")
.transform(Box::new(transform_capital_case));
change_case(input, options)
}
pub fn param_case(input: &str) -> String {
let options = Options::default()
.delimiter("-")
.transform(Box::new(transform_lower_case));
change_case(input, options)
}
pub fn path_case(input: &str) -> String {
let options = Options::default()
.delimiter("/")
.transform(Box::new(transform_lower_case));
change_case(input, options)
}
fn transform_sentence_case(input: &str, index: usize) -> String {
let input = lower_case(input);
if index == 0 {
upper_case_first(input.as_str())
} else {
input
}
}
pub fn sentence_case(input: &str) -> String {
let options = Options::default()
.delimiter(" ")
.transform(Box::new(transform_sentence_case));
change_case(input, options)
}
pub fn snake_case(input: &str) -> String {
let options = Options::default()
.delimiter("_")
.transform(Box::new(transform_lower_case));
change_case(input, options)
}
pub fn swap_case(input: &str) -> String {
input
.chars()
.into_iter()
.map(|v| {
if v.is_lowercase() {
v.to_uppercase().to_string()
} else {
v.to_lowercase().to_string()
}
})
.collect()
}