mod case;
mod words;
pub use case::Case;
use words::Words;
pub trait Casing {
fn to_case(&self, case: Case) -> String;
fn from_case(&self, case: Case) -> FromCasing;
}
impl Casing for str {
fn to_case(&self, case: Case) -> String {
Words::new(self).into_case(case)
}
fn from_case(&self, case: Case) -> FromCasing {
FromCasing::new(self.to_string(), case)
}
}
impl Casing for String {
fn to_case(&self, case: Case) -> String {
Words::new(self).into_case(case)
}
fn from_case(&self, case: Case) -> FromCasing {
FromCasing::new(self.to_string(), case)
}
}
pub struct FromCasing {
name: String,
case: Case,
}
impl FromCasing {
fn new(name: String, case: Case) -> Self {
Self { name, case }
}
}
impl Casing for FromCasing {
fn to_case(&self, case: Case) -> String {
Words::from_casing(&self.name, self.case).into_case(case)
}
fn from_case(&self, case: Case) -> Self {
Self::new(self.name.to_string(), case)
}
}
#[cfg(test)]
mod test {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn lossless_against_lossless() {
let examples = vec![
(Case::Lower, "my variable 22 name"),
(Case::Upper, "MY VARIABLE 22 NAME"),
(Case::Title, "My Variable 22 Name"),
(Case::Camel, "myVariable22Name"),
(Case::Pascal, "MyVariable22Name"),
(Case::Snake, "my_variable_22_name"),
(Case::ScreamingSnake, "MY_VARIABLE_22_NAME"),
(Case::Kebab, "my-variable-22-name"),
(Case::Cobol, "MY-VARIABLE-22-NAME"),
(Case::Toggle, "mY vARIABLE 22 nAME"),
(Case::Train, "My-Variable-22-Name"),
(Case::Alternating, "mY vArIaBlE 22 nAmE"),
];
for (case_a, str_a) in examples.iter() {
for (case_b, str_b) in examples.iter() {
assert_eq!(*str_a, str_b.from_case(*case_b).to_case(*case_a))
}
}
}
#[test]
fn obvious_default_parsing() {
let examples = vec![
"SuperMario64Game",
"super-mario64-game",
"superMario64 game",
"Super Mario 64_game",
"SUPERMario 64-game",
"super_mario-64 game",
];
for example in examples {
assert_eq!("super_mario_64_game", example.to_case(Case::Snake));
}
}
#[test]
fn camel_case_acroynms() {
assert_eq!(
"xml_http_request",
"XMLHttpRequest".from_case(Case::Camel).to_case(Case::Snake)
);
assert_eq!(
"xml_http_request",
"XMLHttpRequest"
.from_case(Case::UpperCamel)
.to_case(Case::Snake)
);
assert_eq!(
"xml_http_request",
"XMLHttpRequest"
.from_case(Case::Pascal)
.to_case(Case::Snake)
);
}
#[test]
fn leading_tailing_delimeters() {
assert_eq!(
"leading_underscore",
"_leading_underscore"
.from_case(Case::Snake)
.to_case(Case::Snake)
);
assert_eq!(
"tailing_underscore",
"tailing_underscore_"
.from_case(Case::Snake)
.to_case(Case::Snake)
);
assert_eq!(
"leading_hyphen",
"-leading-hyphen"
.from_case(Case::Kebab)
.to_case(Case::Snake)
);
assert_eq!(
"tailing_hyphen",
"tailing-hyphen-"
.from_case(Case::Kebab)
.to_case(Case::Snake)
);
}
#[test]
fn double_delimeters() {
assert_eq!(
"many_underscores",
"many___underscores"
.from_case(Case::Snake)
.to_case(Case::Snake)
);
assert_eq!(
"many-underscores",
"many---underscores"
.from_case(Case::Kebab)
.to_case(Case::Kebab)
);
}
#[test]
fn early_word_boundaries() {
assert_eq!(
"a_bagel",
"aBagel".from_case(Case::Camel).to_case(Case::Snake)
);
}
#[test]
fn late_word_boundaries() {
assert_eq!(
"team_a",
"teamA".from_case(Case::Camel).to_case(Case::Snake)
);
}
#[test]
fn empty_string() {
for (case_a, case_b) in Case::iter().zip(Case::iter()) {
assert_eq!("", "".from_case(case_a).to_case(case_b));
}
}
#[test]
fn owned_string() {
assert_eq!(
"test_variable",
String::from("TestVariable").to_case(Case::Snake)
)
}
#[test]
fn default_all_boundaries() {
assert_eq!(
"abc_abc_abc_abc_abc_abc",
"ABC-abc_abcAbc ABCAbc".to_case(Case::Snake)
);
}
}