use crate::utils::WordSplit;
mod utils;
#[derive(Debug)]
pub struct ReCase<'a> {
original_text: &'a str,
}
macro_rules! push_lowercase {
($s:expr, $c:expr) => {
for lc in $c.to_lowercase() {
$s.push(lc);
}
};
}
macro_rules! push_uppercase {
($s:expr, $c:expr) => {
for uc in $c.to_uppercase() {
$s.push(uc);
}
};
}
impl<'a> ReCase<'a> {
pub fn new(original_text: &'a str) -> Self {
ReCase { original_text }
}
fn words_iter(&self) -> impl Iterator<Item = &str> {
WordSplit::new(self.original_text).map(|(x, y)| &self.original_text[x..y])
}
#[inline(always)]
fn allocate_buffer(&self) -> String {
String::with_capacity(self.original_text.len())
}
fn lowercase_with_delim(&self, delim: &str) -> String {
self.words_iter()
.fold(self.allocate_buffer(), |mut acc, s| {
if !acc.is_empty() {
acc.push_str(delim);
}
for c in s.chars() {
push_lowercase!(acc, c);
}
acc
})
}
pub fn normal_case(&self) -> String {
self.lowercase_with_delim(" ")
}
pub fn camel_case(&self) -> String {
let words_iter = self.words_iter();
let mut acc = self.allocate_buffer();
for (i, word) in words_iter.enumerate() {
let mut chars = word.chars();
if let Some(first_char) = chars.next() {
if i == 0 {
push_lowercase!(acc, first_char);
} else {
push_uppercase!(acc, first_char);
}
}
chars.for_each(|c| {
push_lowercase!(acc, c);
});
}
acc
}
pub fn pascal_case(&self) -> String {
let words_iter = self.words_iter();
words_iter.fold(self.allocate_buffer(), |mut acc, word| {
let mut chars = word.chars();
if let Some(first_char) = chars.next() {
push_uppercase!(acc, first_char);
}
chars.for_each(|c| {
push_lowercase!(acc, c);
});
acc
})
}
pub fn snake_case(&self) -> String {
self.lowercase_with_delim("_")
}
pub fn kebab_case(&self) -> String {
self.lowercase_with_delim("-")
}
pub fn dot_case(&self) -> String {
self.lowercase_with_delim(".")
}
pub fn path_case(&self) -> String {
self.lowercase_with_delim("/")
}
pub fn windows_path_case(&self) -> String {
self.lowercase_with_delim("\\")
}
pub fn sentence_case(&self) -> String {
let words_iter = self.words_iter();
let mut acc = self.allocate_buffer();
for (i, word) in words_iter.enumerate() {
let mut chars = word.chars();
if i != 0 {
acc.push_str(" ");
} else {
if let Some(first_char) = chars.next() {
push_uppercase!(acc, first_char);
}
}
chars.for_each(|c| {
push_lowercase!(acc, c);
});
}
acc
}
pub fn title_case(&self) -> String {
let words_iter = self.words_iter();
let mut acc = self.allocate_buffer();
for (i, word) in words_iter.enumerate() {
let mut chars = word.chars();
if i != 0 {
acc.push_str(" ");
}
if let Some(first_char) = chars.next() {
push_uppercase!(acc, first_char);
}
chars.for_each(|c| {
push_lowercase!(acc, c);
});
}
acc
}
pub fn header_case(&self) -> String {
let words_iter = self.words_iter();
let mut acc = self.allocate_buffer();
for (i, word) in words_iter.enumerate() {
let mut chars = word.chars();
if i != 0 {
acc.push_str("-");
}
if let Some(first_char) = chars.next() {
push_uppercase!(acc, first_char);
}
chars.for_each(|c| {
push_lowercase!(acc, c);
});
}
acc
}
pub fn upper_snake_case(&self) -> String {
let mut acc = self.allocate_buffer();
for word in self.words_iter() {
if !acc.is_empty() {
acc.push_str("_");
}
let chars = word.chars();
for c in chars {
push_uppercase!(acc, c);
}
}
acc
}
pub fn alternating_case(&self) -> String {
let mut should_uppercase = false;
let mut acc = self.allocate_buffer();
for word in self.words_iter() {
if !acc.is_empty() {
acc.push_str(" ");
}
let chars = word.chars();
for c in chars {
if should_uppercase {
push_uppercase!(acc, c);
} else {
push_lowercase!(acc, c);
}
should_uppercase = !should_uppercase;
}
}
acc
}
}
pub trait Casing {
fn to_normal_case(&self) -> String;
fn to_camel_case(&self) -> String;
fn to_pascal_case(&self) -> String;
fn to_snake_case(&self) -> String;
fn to_kebab_case(&self) -> String;
fn to_dot_case(&self) -> String;
fn to_path_case(&self) -> String;
fn to_windows_path_case(&self) -> String;
fn to_sentence_case(&self) -> String;
fn to_title_case(&self) -> String;
fn to_header_case(&self) -> String;
fn to_upper_snake_case(&self) -> String;
fn to_alternating_case(&self) -> String;
}
impl Casing for str {
fn to_normal_case(&self) -> String {
ReCase::new(self).normal_case()
}
fn to_camel_case(&self) -> String {
ReCase::new(self).camel_case()
}
fn to_pascal_case(&self) -> String {
ReCase::new(self).pascal_case()
}
fn to_snake_case(&self) -> String {
ReCase::new(self).snake_case()
}
fn to_kebab_case(&self) -> String {
ReCase::new(self).kebab_case()
}
fn to_dot_case(&self) -> String {
ReCase::new(self).dot_case()
}
fn to_path_case(&self) -> String {
ReCase::new(self).path_case()
}
fn to_windows_path_case(&self) -> String {
ReCase::new(self).windows_path_case()
}
fn to_sentence_case(&self) -> String {
ReCase::new(self).sentence_case()
}
fn to_title_case(&self) -> String {
ReCase::new(self).title_case()
}
fn to_header_case(&self) -> String {
ReCase::new(self).header_case()
}
fn to_upper_snake_case(&self) -> String {
ReCase::new(self).upper_snake_case()
}
fn to_alternating_case(&self) -> String {
ReCase::new(self).alternating_case()
}
}
#[cfg(test)]
mod recase_tests {
use crate::{Casing, ReCase};
#[test]
fn test_normal_case() {
let recase = ReCase::new("long_random_text");
assert_eq!(recase.normal_case(), "long random text");
let recase = ReCase::new("誰_long_random_text");
assert_eq!(recase.normal_case(), "誰 long random text");
let recase = ReCase::new("LONG_random_text");
assert_eq!(recase.normal_case(), "long random text");
let recase = ReCase::new("ßlong_random_text");
assert_eq!(recase.normal_case(), "ßlong random text");
}
#[test]
fn test_camel_case() {
let recase = ReCase::new("random_text");
assert_eq!(recase.camel_case(), "randomText");
let recase = ReCase::new("誰_randomText");
assert_eq!(recase.camel_case(), "誰RandomText");
let recase = ReCase::new("RanDom text");
assert_eq!(recase.camel_case(), "ranDomText");
let recase = ReCase::new("ßändom ßext");
assert_eq!(recase.camel_case(), "ßändomSSext");
}
#[test]
fn test_upper_snake_case() {
let recase = ReCase::new("HTML_parser");
assert_eq!(recase.upper_snake_case(), "HTML_PARSER");
}
#[test]
fn test_alternating_case() {
let recase = ReCase::new("random Text");
assert_eq!(recase.alternating_case(), "rAnDoM tExT");
let recase = ReCase::new("誰_random Text");
assert_eq!(recase.alternating_case(), "誰 RaNdOm TeXt");
}
#[test]
fn test_casing_trait() {
let s = "Hello World";
assert_eq!(s.to_kebab_case(), "hello-world");
assert_eq!(s.to_snake_case(), "hello_world");
assert_eq!(s.to_alternating_case(), "hElLo WoRlD");
}
}