Skip to content

Commit

Permalink
Support for case conversion helpers. (#619)
Browse files Browse the repository at this point in the history
Helpers for converting a text string to `camelCase`, `kebab-case`,
`snake_case` etc. Supports case conversions from the `heck` crate. This
is an opt-in feature which can be enabled using `string_helpers`
feature.
  • Loading branch information
gabhijit committed Nov 6, 2023
1 parent 94c84a1 commit ae5e6a5
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -31,6 +31,7 @@ serde_json = "1.0.39"
walkdir = { version = "2.2.3", optional = true }
rhai = { version = "1.16.1", optional = true, features = ["sync", "serde"] }
rust-embed = { version = "8.0.0", optional = true, features = ["include-exclude"] }
heck = { version = "0.4", optional = true }

[dev-dependencies]
env_logger = "0.10"
Expand All @@ -48,6 +49,7 @@ dir_source = ["walkdir"]
script_helper = ["rhai"]
no_logging = []
default = []
string_helpers = ["heck"]

[badges]
maintenance = { status = "actively-developed" }
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/mod.rs
Expand Up @@ -176,6 +176,9 @@ mod helper_with;
#[cfg(feature = "script_helper")]
pub(crate) mod scripting;

#[cfg(feature = "string_helpers")]
pub(crate) mod string_helpers;

// pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result<String, RenderError>>;
//
// pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result<String, RenderError> {
Expand Down
112 changes: 112 additions & 0 deletions src/helpers/string_helpers/mod.rs
@@ -0,0 +1,112 @@
//! A Set of string utilities often required during code generation.
//!
//! See also: [`heck`](https://docs.rs/heck/latest/heck)
//!
//! `lowerCamelCase`: Convert a string to lowerCamelCase
//! `upperCamelCase`: Convert a string to UpperCamelCase
//! `snakeCase`: Convert a string to snake_case
//! `kebabCase`: Conver a string to kebab-case
//! `shoutySnakeCase`: Convert a string to SHOUTY_SNAKE_CASE
//! `shoutyKebabCase`: Convert a string to SHOUTY-KEBAB-CASE
//! `titleCase`: Convert a string to Title Case
//! `trainCase`: Convert a string to Train-Case

use heck::{
ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase,
ToTrainCase, ToUpperCamelCase,
};

macro_rules! define_case_helper {
($helper_fn_name: ident, $heck_fn_name:ident) => {
pub(crate) fn $helper_fn_name(
h: &crate::render::Helper<'_>,
_: &crate::Handlebars<'_>,
_: &crate::context::Context,
_rc: &mut crate::render::RenderContext<'_, '_>,
out: &mut dyn crate::output::Output,
) -> crate::helpers::HelperResult {
let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
out.write(param.$heck_fn_name().as_ref())?;
Ok(())
}
};
}

define_case_helper!(lower_camel_case, to_lower_camel_case);
define_case_helper!(upper_camel_case, to_upper_camel_case);
define_case_helper!(snake_case, to_snake_case);
define_case_helper!(shouty_snake_case, to_shouty_snake_case);
define_case_helper!(kebab_case, to_kebab_case);
define_case_helper!(shouty_kebab_case, to_shouty_kebab_case);
define_case_helper!(title_case, to_title_case);
define_case_helper!(train_case, to_train_case);

#[cfg(test)]
mod tests {
macro_rules! define_case_helpers_test_cases {
($template_fn_name:literal, $helper_tc_fn_name:ident, $(($tc_input:literal, $tc_expected:literal),)+) => {

#[test]
fn $helper_tc_fn_name() {
let hbs = crate::registry::Registry::new();
let test_cases = vec![$(($tc_input, $tc_expected)),+];
for tc in test_cases {
let result =
hbs.render_template(
concat!("{{", $template_fn_name, " data}}"),
&json!({"data": tc.0}));
assert!(result.is_ok(), "{}", result.err().unwrap());
assert_eq!(result.unwrap(), tc.1.to_string());
}
}
}
}

define_case_helpers_test_cases!(
"lowerCamelCase",
test_lower_camel_case,
("lower camel case", "lowerCamelCase"),
("lower-camel-case", "lowerCamelCase"),
("lower_camel_case", "lowerCamelCase"),
);

define_case_helpers_test_cases!(
"upperCamelCase",
test_upper_camel_case,
("upper camel case", "UpperCamelCase"),
("upper-camel-case", "UpperCamelCase"),
("upper_camel_case", "UpperCamelCase"),
);

define_case_helpers_test_cases!(
"snakeCase",
test_snake_case,
("snake case", "snake_case"),
("snake-case", "snake_case"),
);

define_case_helpers_test_cases!(
"kebabCase",
test_kebab_case,
("kebab case", "kebab-case"),
("kebab_case", "kebab-case"),
);

define_case_helpers_test_cases!(
"shoutySnakeCase",
test_shouty_snake_case,
("shouty snake case", "SHOUTY_SNAKE_CASE"),
("shouty snake-case", "SHOUTY_SNAKE_CASE"),
);

define_case_helpers_test_cases!(
"shoutyKebabCase",
test_shouty_kebab_case,
("shouty kebab case", "SHOUTY-KEBAB-CASE"),
("shouty_kebab_case", "SHOUTY-KEBAB-CASE"),
);

define_case_helpers_test_cases!("titleCase", test_title_case, ("title case", "Title Case"),);

define_case_helpers_test_cases!("trainCase", test_train_case, ("train case", "Train-Case"),);
}
24 changes: 24 additions & 0 deletions src/lib.rs
Expand Up @@ -366,6 +366,30 @@
//! Handlebars.js' partial system is fully supported in this implementation.
//! Check [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs#L49) for details.
//!
//! ### String (or Case) Helpers
//!
//! [Handlebars] supports helpers for converting string cases for example converting a value to
//! 'camelCase or 'kebab-case' etc. This can be useful during generating code using Handlebars.
//! This can be enabled by selecting the feature-flag `string_helpers`. Currently the case
//! conversions from the [`heck`](https://docs.rs/heck/latest/heck) crate are supported.
//!
//! ```
//! # #[cfg(feature = "string_helpers")] {
//! # use std::error::Error;
//! # extern crate handlebars;
//! use handlebars::Handlebars;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//!
//! let mut handlebars = Handlebars::new();
//!
//! let data = serde_json::json!({"value": "lower camel case"});
//! assert_eq!(handlebars.render_template("This is {{lowerCamelCase value}}", &data)?,
//! "This is lowerCamelCase".to_owned());
//! # Ok(())
//! # }
//! # }
//! ```
//!

#![allow(dead_code, clippy::upper_case_acronyms)]
Expand Down
27 changes: 26 additions & 1 deletion src/registry.rs
Expand Up @@ -179,6 +179,9 @@ impl<'reg> Registry<'reg> {
self.register_helper("not", Box::new(helpers::helper_extras::not));
self.register_helper("len", Box::new(helpers::helper_extras::len));

#[cfg(feature = "string_helpers")]
self.register_string_helpers();

self.register_decorator("inline", Box::new(decorators::INLINE_DECORATOR));
self
}
Expand Down Expand Up @@ -800,6 +803,24 @@ impl<'reg> Registry<'reg> {
let ctx = Context::wraps(data)?;
self.render_template_with_context_to_write(template_string, &ctx, writer)
}

#[cfg(feature = "string_helpers")]
#[inline]
fn register_string_helpers(&mut self) {
use helpers::string_helpers::{
kebab_case, lower_camel_case, shouty_kebab_case, shouty_snake_case, snake_case,
title_case, train_case, upper_camel_case,
};

self.register_helper("lowerCamelCase", Box::new(lower_camel_case));
self.register_helper("upperCamelCase", Box::new(upper_camel_case));
self.register_helper("snakeCase", Box::new(snake_case));
self.register_helper("kebabCase", Box::new(kebab_case));
self.register_helper("shoutySnakeCase", Box::new(shouty_snake_case));
self.register_helper("shoutyKebabCase", Box::new(shouty_kebab_case));
self.register_helper("titleCase", Box::new(title_case));
self.register_helper("trainCase", Box::new(train_case));
}
}

#[cfg(test)]
Expand Down Expand Up @@ -858,9 +879,13 @@ mod test {
let num_helpers = 7;
let num_boolean_helpers = 10; // stuff like gt and lte
let num_custom_helpers = 1; // dummy from above
#[cfg(feature = "string_helpers")]
let string_helpers = 8;
#[cfg(not(feature = "string_helpers"))]
let string_helpers = 0;
assert_eq!(
r.helpers.len(),
num_helpers + num_boolean_helpers + num_custom_helpers
num_helpers + num_boolean_helpers + num_custom_helpers + string_helpers
);
}

Expand Down

0 comments on commit ae5e6a5

Please sign in to comment.