use convert_case::{Boundary, Case, Casing};
use crate::compiler::function::EnumVariant;
use crate::prelude::*;
use crate::value;
cfg_if::cfg_if! {
if #[cfg(feature = "stdlib-base")] {
pub(crate) mod camelcase;
pub(crate) mod pascalcase;
pub(crate) mod snakecase;
pub(crate) mod screamingsnakecase;
pub(crate) mod kebabcase;
}
}
pub(crate) static ORIGINAL_CASE: Parameter = Parameter::optional(
"original_case",
kind::BYTES,
"Optional hint on the original case type. Must be one of: kebab-case, camelCase, PascalCase, SCREAMING_SNAKE, snake_case",
)
.enum_variants(&[
EnumVariant {
value: "kebab-case",
description: "[kebab-case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case)",
},
EnumVariant {
value: "camelCase",
description: "[camelCase](https://en.wikipedia.org/wiki/Camel_case)",
},
EnumVariant {
value: "PascalCase",
description: "[PascalCase](https://en.wikipedia.org/wiki/Camel_case)",
},
EnumVariant {
value: "SCREAMING_SNAKE",
description: "[SCREAMING_SNAKE](https://en.wikipedia.org/wiki/Snake_case)",
},
EnumVariant {
value: "snake_case",
description: "[snake_case](https://en.wikipedia.org/wiki/Snake_case)",
},
]);
pub(crate) fn variants() -> Vec<Value> {
vec![
value!("camelCase"),
value!("PascalCase"),
value!("SCREAMING_SNAKE"),
value!("snake_case"),
value!("kebab-case"),
]
}
pub(crate) fn boundaries() -> Vec<Value> {
vec![
value!("lower_upper"), value!("upper_lower"), value!("acronym"), value!("lower_digit"), value!("upper_digit"), value!("digit_lower"), value!("digit_upper"), ]
}
pub(crate) fn boundaries_msg() -> String {
boundaries()
.into_iter()
.filter_map(|v| Some(v.as_str()?.into_owned()))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn variants_msg() -> String {
variants()
.into_iter()
.filter_map(|v| Some(v.as_str()?.into_owned()))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn into_case(s: &str) -> Result<Case, Box<dyn DiagnosticMessage>> {
match s {
"camelCase" => Ok(Case::Camel),
"PascalCase" => Ok(Case::Pascal),
"SREAMING_SNAKE" => Ok(Case::Constant),
"snake_case" => Ok(Case::Snake),
"kebab-case" => Ok(Case::Kebab),
_ => Err(Box::new(ExpressionError::from(format!(
"case must match one of: {}",
variants_msg()
))) as Box<dyn DiagnosticMessage>),
}
}
pub(crate) fn into_boundary(s: &str) -> Result<convert_case::Boundary, Box<dyn DiagnosticMessage>> {
match s {
"lower_upper" => Ok(convert_case::Boundary::LOWER_UPPER),
"upper_lower" => Ok(convert_case::Boundary::UPPER_LOWER),
"acronym" => Ok(convert_case::Boundary::ACRONYM),
"lower_digit" => Ok(convert_case::Boundary::LOWER_DIGIT),
"upper_digit" => Ok(convert_case::Boundary::UPPER_DIGIT),
"digit_lower" => Ok(convert_case::Boundary::DIGIT_LOWER),
"digit_upper" => Ok(convert_case::Boundary::DIGIT_UPPER),
_ => Err(Box::new(ExpressionError::from(format!(
"boundary must match one of: {}",
boundaries_msg()
))) as Box<dyn DiagnosticMessage>),
}
}
#[inline]
pub(crate) fn convert_case(value: &Value, to_case: Case, from_case: Option<Case>) -> Resolved {
let string_value = value.try_bytes_utf8_lossy()?;
match from_case {
Some(case) => Ok(string_value.from_case(case).to_case(to_case).into()),
None => Ok(string_value.to_case(to_case).into()),
}
}
#[inline]
pub(crate) fn convert_case_with_excluded_boundaries(
string_value: &str,
to_case: Case,
from_case: Option<Case>,
excluded_boundaries: &[Boundary],
) -> Value {
match from_case {
Some(case) => string_value
.from_case(case)
.without_boundaries(excluded_boundaries)
.to_case(to_case)
.into(),
None => string_value
.without_boundaries(excluded_boundaries)
.to_case(to_case)
.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_var_msg() {
assert_eq!(
"camelCase, PascalCase, SCREAMING_SNAKE, snake_case, kebab-case",
variants_msg()
);
}
}