use heck::{
ToShoutyKebabCase,
ToShoutySnakeCase,
ToTitleCase, };
use inflector::{
cases::{
camelcase, classcase, kebabcase, pascalcase, sentencecase, snakecase, titlecase, traincase,
},
numbers::{deordinalize, ordinalize},
string::{deconstantize, demodulize, pluralize, singularize},
suffix::foreignkey,
};
use std::collections::HashMap;
use tera::{Context, Result as TeraResult, Tera, Value};
pub fn register_all(tera: &mut Tera) {
reg_str(tera, "camel", camelcase::to_camel_case);
reg_str(tera, "pascal", pascalcase::to_pascal_case);
reg_str(tera, "snake", snakecase::to_snake_case);
reg_str(tera, "kebab", kebabcase::to_kebab_case);
reg_str(tera, "class", classcase::to_class_case);
reg_str(tera, "title", titlecase::to_title_case);
reg_str(tera, "sentence", sentencecase::to_sentence_case);
reg_str(tera, "train", traincase::to_train_case);
reg_str(tera, "pluralize", pluralize::to_plural);
reg_str(tera, "singularize", singularize::to_singular);
reg_str(tera, "deconstantize", deconstantize::deconstantize);
reg_str(tera, "demodulize", demodulize::demodulize);
reg_str(tera, "ordinalize", ordinalize::ordinalize);
reg_str(tera, "deordinalize", deordinalize::deordinalize);
reg_str(tera, "foreign_key", foreignkey::to_foreign_key);
reg_str(tera, "shouty_snake", |s| s.to_shouty_snake_case());
reg_str(tera, "shouty_kebab", |s| s.to_shouty_kebab_case());
reg_str(tera, "titlecase", |s| s.to_title_case());
reg_str(tera, "param", kebabcase::to_kebab_case);
reg_str(tera, "constant", |s| s.to_shouty_snake_case());
reg_str(tera, "upper", |s| s.to_uppercase());
reg_str(tera, "lower", |s| s.to_lowercase());
reg_str(tera, "lcfirst", |s| {
let mut c = s.chars();
match c.next() {
Some(f) => f.to_lowercase().collect::<String>() + c.as_str(),
None => String::new(),
}
});
reg_str(tera, "ucfirst", |s| {
let mut c = s.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
});
}
pub fn bless_context(context: &mut Context) {
if let Some(name_value) = context.get("name") {
if let Some(name_str) = name_value.as_str() {
let pascal_name = pascalcase::to_pascal_case(name_str);
context.insert("Name", &pascal_name);
}
}
context.insert("locals", &HashMap::<String, Value>::new());
}
fn reg_str<F>(tera: &mut Tera, name: &str, f: F)
where
F: Fn(&str) -> String + Send + Sync + 'static,
{
tera.register_filter(
name,
move |v: &Value, _a: &HashMap<String, Value>| -> TeraResult<Value> {
let input_str = match v.as_str() {
Some(s) => s.to_string(),
None => v.to_string(),
};
Ok(Value::String(f(&input_str)))
},
);
}
#[cfg(test)]
mod tests {
use super::*;
use tera::Context;
fn create_test_tera() -> Tera {
let mut tera = Tera::default();
register_all(&mut tera);
tera
}
#[test]
fn test_case_conversions() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("name", "hello_world_example");
let result = tera.render_str("{{ name | camel }}", &ctx).unwrap();
assert_eq!(result, "helloWorldExample");
let result = tera.render_str("{{ name | pascal }}", &ctx).unwrap();
assert_eq!(result, "HelloWorldExample");
let result = tera.render_str("{{ name | snake }}", &ctx).unwrap();
assert_eq!(result, "hello_world_example");
let result = tera.render_str("{{ name | kebab }}", &ctx).unwrap();
assert_eq!(result, "hello-world-example");
let result = tera.render_str("{{ name | class }}", &ctx).unwrap();
assert_eq!(result, "HelloWorldExample");
let result = tera.render_str("{{ name | title }}", &ctx).unwrap();
assert_eq!(result, "Hello World Example");
let result = tera.render_str("{{ name | sentence }}", &ctx).unwrap();
assert_eq!(result, "Hello world example");
let result = tera.render_str("{{ name | train }}", &ctx).unwrap();
assert_eq!(result, "Hello-World-Example");
}
#[test]
fn test_heck_fill_ins() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("name", "hello_world_example");
let result = tera.render_str("{{ name | shouty_snake }}", &ctx).unwrap();
assert_eq!(result, "HELLO_WORLD_EXAMPLE");
let result = tera.render_str("{{ name | shouty_kebab }}", &ctx).unwrap();
assert_eq!(result, "HELLO-WORLD-EXAMPLE");
let result = tera.render_str("{{ name | titlecase }}", &ctx).unwrap();
assert_eq!(result, "Hello World Example");
}
#[test]
fn test_ordinalization() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("num", "1");
let result = tera.render_str("{{ num | ordinalize }}", &ctx).unwrap();
assert_eq!(result, "1st");
ctx.insert("num", "2");
let result = tera.render_str("{{ num | ordinalize }}", &ctx).unwrap();
assert_eq!(result, "2nd");
ctx.insert("num", "3");
let result = tera.render_str("{{ num | ordinalize }}", &ctx).unwrap();
assert_eq!(result, "3rd");
ctx.insert("num", "4");
let result = tera.render_str("{{ num | ordinalize }}", &ctx).unwrap();
assert_eq!(result, "4th");
ctx.insert("num", "11");
let result = tera.render_str("{{ num | ordinalize }}", &ctx).unwrap();
assert_eq!(result, "11th");
ctx.insert("num", "1st");
let result = tera.render_str("{{ num | deordinalize }}", &ctx).unwrap();
assert_eq!(result, "1");
ctx.insert("num", "2nd");
let result = tera.render_str("{{ num | deordinalize }}", &ctx).unwrap();
assert_eq!(result, "2");
}
#[test]
fn test_string_manipulation() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("path", "ActiveRecord::Base");
let result = tera.render_str("{{ path | deconstantize }}", &ctx).unwrap();
assert_eq!(result, "ActiveRecord");
ctx.insert("path", "ActiveRecord::Base");
let result = tera.render_str("{{ path | demodulize }}", &ctx).unwrap();
assert_eq!(result, "Base");
ctx.insert("name", "Message");
let result = tera.render_str("{{ name | foreign_key }}", &ctx).unwrap();
assert_eq!(result, "message_id");
}
#[test]
fn test_complex_combinations() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("class_name", "UserProfile");
let result = tera
.render_str("{{ class_name | snake | pluralize }}", &ctx)
.unwrap();
assert_eq!(result, "user_profiles");
let result = tera
.render_str("{{ class_name | kebab | shouty_kebab }}", &ctx)
.unwrap();
assert_eq!(result, "USER-PROFILE");
let result = tera
.render_str("{{ class_name | demodulize | snake }}", &ctx)
.unwrap();
assert_eq!(result, "user_profile");
}
#[test]
fn test_edge_cases() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("empty", "");
let result = tera.render_str("{{ empty | camel }}", &ctx).unwrap();
assert_eq!(result, "");
ctx.insert("single", "A");
let result = tera.render_str("{{ single | snake }}", &ctx).unwrap();
assert_eq!(result, "a");
ctx.insert("number", "123");
let result = tera.render_str("{{ number | camel }}", &ctx).unwrap();
assert_eq!(result, "123");
ctx.insert("special", "hello@world#test");
let result = tera.render_str("{{ special | kebab }}", &ctx).unwrap();
assert_eq!(result, "hello-world-test");
}
#[test]
fn test_non_string_input() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("num", &42);
let result = tera.render_str("{{ num | camel }}", &ctx).unwrap();
assert_eq!(result, "42");
ctx.insert("flag", &true);
let result = tera.render_str("{{ flag | snake }}", &ctx).unwrap();
assert_eq!(result, "true");
}
#[test]
fn test_real_world_scenarios() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("model", "UserAccount");
let table_name = tera
.render_str("{{ model | snake | pluralize }}", &ctx)
.unwrap();
assert_eq!(table_name, "user_accounts");
ctx.insert("resource", "UserProfile");
let endpoint = tera
.render_str("{{ resource | kebab | pluralize }}", &ctx)
.unwrap();
assert_eq!(endpoint, "user-profiles");
ctx.insert("name", "max_retry_count");
let constant = tera.render_str("{{ name | shouty_snake }}", &ctx).unwrap();
assert_eq!(constant, "MAX_RETRY_COUNT");
ctx.insert("action", "get_user_profile");
let method = tera.render_str("{{ action | camel }}", &ctx).unwrap();
assert_eq!(method, "getUserProfile");
}
#[test]
fn test_change_case_aliases() {
let mut tera = create_test_tera();
let mut ctx = Context::new();
ctx.insert("name", "hello_world_example");
let result = tera.render_str("{{ name | param }}", &ctx).unwrap();
assert_eq!(result, "hello-world-example");
let result = tera.render_str("{{ name | constant }}", &ctx).unwrap();
assert_eq!(result, "HELLO_WORLD_EXAMPLE");
let result = tera.render_str("{{ name | upper }}", &ctx).unwrap();
assert_eq!(result, "HELLO_WORLD_EXAMPLE");
ctx.insert("name", "HELLO_WORLD_EXAMPLE");
let result = tera.render_str("{{ name | lower }}", &ctx).unwrap();
assert_eq!(result, "hello_world_example");
ctx.insert("name", "HelloWorld");
let result = tera.render_str("{{ name | lcfirst }}", &ctx).unwrap();
assert_eq!(result, "helloWorld");
ctx.insert("name", "helloWorld");
let result = tera.render_str("{{ name | ucfirst }}", &ctx).unwrap();
assert_eq!(result, "HelloWorld");
ctx.insert("name", "");
let result = tera.render_str("{{ name | lcfirst }}", &ctx).unwrap();
assert_eq!(result, "");
let result = tera.render_str("{{ name | ucfirst }}", &ctx).unwrap();
assert_eq!(result, "");
ctx.insert("name", "ñáéíóú");
let result = tera.render_str("{{ name | ucfirst }}", &ctx).unwrap();
assert_eq!(result, "Ñáéíóú");
}
#[test]
fn test_all_filters_registered() {
let mut tera = create_test_tera();
let expected_filters = vec![
"camel",
"pascal",
"snake",
"kebab",
"class",
"title",
"sentence",
"train",
"pluralize",
"singularize",
"deconstantize",
"demodulize",
"ordinalize",
"deordinalize",
"foreign_key",
"shouty_snake",
"shouty_kebab",
"titlecase",
"param",
"constant",
"upper",
"lower",
"lcfirst",
"ucfirst",
];
for filter in expected_filters {
let mut ctx = Context::new();
ctx.insert("test", "hello_world");
let result = tera.render_str(&format!("{{{{ test | {} }}}}", filter), &ctx);
assert!(result.is_ok(), "Filter '{}' should be registered", filter);
}
}
#[test]
fn test_bless_context_name() {
let mut ctx = Context::new();
ctx.insert("name", "hello_world");
bless_context(&mut ctx);
assert_eq!(ctx.get("Name").unwrap().as_str().unwrap(), "HelloWorld");
assert_eq!(ctx.get("name").unwrap().as_str().unwrap(), "hello_world");
}
#[test]
fn test_bless_context_no_name() {
let mut ctx = Context::new();
ctx.insert("other", "value");
bless_context(&mut ctx);
assert!(ctx.get("Name").is_none());
assert!(ctx.get("locals").is_some());
}
#[test]
fn test_bless_context_non_string_name() {
let mut ctx = Context::new();
ctx.insert("name", &42);
bless_context(&mut ctx);
assert!(ctx.get("Name").is_none());
}
}