#![cfg_attr(coverage_nightly, coverage(off))]
use minijinja::Value;
pub fn register_helpers(env: &mut minijinja::Environment<'_>) {
env.add_filter("snake_case", snake_case_filter);
env.add_filter("kebab_case", kebab_case_filter);
env.add_filter("pascal_case", pascal_case_filter);
env.add_function("current_year", current_year_fn);
env.add_function("current_date", current_date_fn);
}
fn snake_case_filter(value: &str) -> String {
to_snake_case(value)
}
fn kebab_case_filter(value: &str) -> String {
to_kebab_case(value)
}
fn pascal_case_filter(value: &str) -> String {
to_pascal_case(value)
}
fn current_year_fn() -> Value {
Value::from(chrono::Utc::now().format("%Y").to_string())
}
fn current_date_fn() -> Value {
Value::from(chrono::Utc::now().format("%Y-%m-%d").to_string())
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::with_capacity(1024);
let mut prev_is_upper = false;
for (i, ch) in s.chars().enumerate() {
if ch.is_uppercase() && i > 0 && !prev_is_upper {
result.push('_');
}
result.push(
ch.to_lowercase()
.next()
.expect("to_lowercase() always yields at least one char"),
);
prev_is_upper = ch.is_uppercase();
}
result.replace(['-', ' '], "_")
}
fn to_kebab_case(s: &str) -> String {
to_snake_case(s).replace('_', "-")
}
pub fn to_pascal_case(s: &str) -> String {
s.split(['_', '-', ' '])
.filter(|s| !s.is_empty())
.map(|s| {
let mut chars = s.chars();
match chars.next() {
None => String::with_capacity(1024),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_snake_case_basic() {
assert_eq!(to_snake_case("MyProjectName"), "my_project_name");
assert_eq!(to_snake_case("camelCase"), "camel_case");
assert_eq!(to_snake_case("PascalCase"), "pascal_case");
assert_eq!(to_snake_case("snake_case"), "snake_case");
assert_eq!(to_snake_case("kebab-case"), "kebab_case");
assert_eq!(to_snake_case("With Spaces"), "with__spaces"); assert_eq!(to_snake_case(""), "");
}
#[test]
fn test_to_snake_case_edge_cases() {
assert_eq!(to_snake_case("XMLHttpRequest"), "xmlhttp_request");
assert_eq!(to_snake_case("IOError"), "ioerror");
assert_eq!(to_snake_case("123Numbers"), "123_numbers");
assert_eq!(to_snake_case("UPPERCASE"), "uppercase");
assert_eq!(to_snake_case("lowercase"), "lowercase");
}
#[test]
fn test_to_kebab_case_basic() {
assert_eq!(to_kebab_case("MyProjectName"), "my-project-name");
assert_eq!(to_kebab_case("snake_case"), "snake-case");
assert_eq!(to_kebab_case("With Spaces"), "with--spaces"); }
#[test]
fn test_to_pascal_case_basic() {
assert_eq!(to_pascal_case("my_project_name"), "MyProjectName");
assert_eq!(to_pascal_case("kebab-case-name"), "KebabCaseName");
assert_eq!(to_pascal_case("with spaces here"), "WithSpacesHere");
assert_eq!(to_pascal_case(""), "");
assert_eq!(to_pascal_case("single"), "Single");
}
#[test]
fn test_snake_case_filter_with_minijinja() {
let mut env = minijinja::Environment::new();
register_helpers(&mut env);
let template = env.template_from_str("{{ name|snake_case }}").unwrap();
let result = template
.render(minijinja::context! { name => "MyProjectName" })
.unwrap();
assert_eq!(result, "my_project_name");
}
#[test]
fn test_kebab_case_filter_with_minijinja() {
let mut env = minijinja::Environment::new();
register_helpers(&mut env);
let template = env.template_from_str("{{ name|kebab_case }}").unwrap();
let result = template
.render(minijinja::context! { name => "MyProjectName" })
.unwrap();
assert_eq!(result, "my-project-name");
}
#[test]
fn test_pascal_case_filter_with_minijinja() {
let mut env = minijinja::Environment::new();
register_helpers(&mut env);
let template = env.template_from_str("{{ name|pascal_case }}").unwrap();
let result = template
.render(minijinja::context! { name => "my_project_name" })
.unwrap();
assert_eq!(result, "MyProjectName");
}
#[test]
fn test_current_year_fn_with_minijinja() {
let mut env = minijinja::Environment::new();
register_helpers(&mut env);
let template = env.template_from_str("{{ current_year() }}").unwrap();
let result = template.render(minijinja::context! {}).unwrap();
let year: u32 = result.parse().expect("Should be a valid year");
assert!((2024..=2100).contains(&year));
}
#[test]
fn test_current_date_fn_with_minijinja() {
let mut env = minijinja::Environment::new();
register_helpers(&mut env);
let template = env.template_from_str("{{ current_date() }}").unwrap();
let result = template.render(minijinja::context! {}).unwrap();
assert_eq!(result.len(), 10);
assert_eq!(result.chars().nth(4), Some('-'));
assert_eq!(result.chars().nth(7), Some('-'));
}
#[test]
fn test_to_pascal_case_empty_segment() {
assert_eq!(to_pascal_case("__double__underscore__"), "DoubleUnderscore");
assert_eq!(to_pascal_case("--double--dash--"), "DoubleDash");
assert_eq!(to_pascal_case(" double space "), "DoubleSpace");
}
#[test]
fn test_to_pascal_case_single_char_segments() {
assert_eq!(to_pascal_case("a_b_c"), "ABC");
assert_eq!(to_pascal_case("x-y-z"), "XYZ");
}
#[test]
fn test_to_snake_case_unicode() {
assert_eq!(to_snake_case("CaféName"), "café_name");
assert_eq!(to_snake_case("ÜberDriver"), "über_driver");
assert_eq!(to_snake_case("ΑlphaΒeta"), "αlpha_βeta");
assert_eq!(to_snake_case("MyProject🚀Name"), "my_project🚀_name");
assert_eq!(to_snake_case("日本語Name"), "日本語_name");
}
#[test]
fn test_to_snake_case_special_chars() {
assert_eq!(to_snake_case("V8Engine"), "v8_engine");
assert_eq!(to_snake_case("C++Parser"), "c++_parser");
assert_eq!(to_snake_case("HTTPSConnection"), "httpsconnection");
assert_eq!(to_snake_case("URLParser"), "urlparser");
assert_eq!(to_snake_case("A"), "a");
assert_eq!(to_snake_case("Z"), "z");
}
#[test]
fn test_to_snake_case_boundaries() {
assert_eq!(to_snake_case(""), "");
assert_eq!(to_snake_case("a"), "a");
assert_eq!(to_snake_case("A"), "a");
assert_eq!(to_snake_case("alllowercase"), "alllowercase");
assert_eq!(to_snake_case("ALLUPPERCASE"), "alluppercase");
let mut long_name = "A".repeat(500);
long_name.push_str(&"B".repeat(500));
let result = to_snake_case(&long_name);
assert!(result.len() > 500);
assert!(result.chars().all(|c| c == 'a' || c == 'b'));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}