Repository: sunng87/handlebars-rust (rust)

Issue:
Support for named args in `handlebars_helper` macro?
It seems it's currently not possible to use the `handlebars_helper` macro for defining helpers with optional named args.
Could support be added by using `hash_get`? :)

The syntax for these args could be `arg: Option<Type>` or `arg=Option<Type>`.

Pull request description:
Hash support for helper macro
Fixes #335 

This patch added support for named argument in helper macro `handlebars_helper!`.  The syntax is like:

```rust
handlebars_helper!(is_above, |x: u64, { compare: u64 = 10 }| x > compare);
```

Multiple named arguments are supported with `,` separated. 

When using this helper, you can either 

```handlebars
{{is_above 100 compare=99}} or {{is_above 100}}
```



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a270ca..c25be5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## [Unreleased](https://github.com/sunng87/handlebars-rust/compare/3.1.0...Unreleased) - ReleaseDate
 
 * [Added] API to register an pre-processed template [#331]
+* [Added] Helper macro now has support for named argument and helepr hash [#338]
 * [Changed] Update rhai to 0.15
 
 ## [3.1.0](https://github.com/sunng87/handlebars-rust/compare/3.0.1...3.1.0) - 2020-06-01
diff --git a/src/macros.rs b/src/macros.rs
index b56b045..65d417c 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -1,5 +1,7 @@
 /// Macro that allows you to quickly define a handlebars helper by passing a
-/// name and a closure.
+/// name and a closure. The closure arguments are mapped to helper parameters
+/// one by one. Named argument with default value is also supported and mapped
+/// to helper hash.
 ///
 /// # Examples
 ///
@@ -8,21 +10,30 @@
 /// #[macro_use] extern crate serde_json;
 ///
 /// handlebars_helper!(is_above_10: |x: u64| x > 10);
+/// handlebars_helper!(is_above: |x: u64, { compare: u64 = 10 }| x > compare);
 ///
 /// # fn main() {
 /// #
 /// let mut handlebars = handlebars::Handlebars::new();
 /// handlebars.register_helper("is-above-10", Box::new(is_above_10));
+/// handlebars.register_helper("is-above", Box::new(is_above));
 ///
 /// let result = handlebars
 ///     .render_template("{{#if (is-above-10 12)}}great!{{else}}okay{{/if}}", &json!({}))
 ///     .unwrap();
 ///  assert_eq!(&result, "great!");
+/// let result2 = handlebars
+///     .render_template("{{#if (is-above 12 compare=10)}}great!{{else}}okay{{/if}}", &json!({}))
+///     .unwrap();
+///  assert_eq!(&result2, "great!");
 /// # }
 /// ```
+
 #[macro_export]
 macro_rules! handlebars_helper {
-    ($struct_name:ident: |$($name:ident: $tpe:tt),*| $body:expr ) => {
+    ($struct_name:ident: |$($name:ident: $tpe:tt),*
+                          $($(,)?{$($hash_name:ident: $hash_tpe:tt=$dft_val:literal),*})?|
+                            $body:expr ) => {
         #[allow(non_camel_case_types)]
         pub struct $struct_name;
 
@@ -42,20 +53,37 @@ macro_rules! handlebars_helper {
                         .map(|x| x.value())
                         .ok_or_else(|| $crate::RenderError::new(&format!(
                             "`{}` helper: Couldn't read parameter {}",
-                            stringify!($fn_name), stringify!($name),
+                            stringify!($struct_name), stringify!($name),
                         )))
                         .and_then(|x|
                                   handlebars_helper!(@as_json_value x, $tpe)
                                   .ok_or_else(|| $crate::RenderError::new(&format!(
                                       "`{}` helper: Couldn't convert parameter {} to type `{}`. \
                                        It's {:?} as JSON. Got these params: {:?}",
-                                      stringify!($fn_name), stringify!($name), stringify!($tpe),
+                                      stringify!($struct_name), stringify!($name), stringify!($tpe),
                                       x, h.params(),
                                   )))
                         )?;
                     param_idx += 1;
                 )*
 
+                    $(
+                        $(
+                            let $hash_name = h.hash_get(stringify!($hash_name))
+                                .map(|x| x.value())
+                                .map(|x|
+                                     handlebars_helper!(@as_json_value x, $hash_tpe)
+                                     .ok_or_else(|| $crate::RenderError::new(&format!(
+                                         "`{}` helper: Couldn't convert hash {} to type `{}`. \
+                                          It's {:?} as JSON. Got these hash: {:?}",
+                                         stringify!($struct_name), stringify!($hash_name), stringify!($hash_tpe),
+                                         x, h.hash(),
+                                     )))
+                                )
+                                .unwrap_or_else(|| Ok($dft_val))?;
+                        )*
+                    )?
+
                 let result = $body;
                 Ok(Some($crate::ScopedJson::Derived($crate::JsonValue::from(result))))
             }


Test patch:
diff --git a/tests/helper_macro.rs b/tests/helper_macro.rs
index 9198db6..cebdca4 100644
--- a/tests/helper_macro.rs
+++ b/tests/helper_macro.rs
@@ -8,6 +8,8 @@ use handlebars::Handlebars;
 handlebars_helper!(lower: |s: str| s.to_lowercase());
 handlebars_helper!(upper: |s: str| s.to_uppercase());
 handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));
+handlebars_helper!(money: |v: i64, {cur: str="$"}| format!("{}{}.00", cur, v));
+handlebars_helper!(all_hash: |{cur: str="$"}| cur);
 
 #[test]
 fn test_macro_helper() {
@@ -16,6 +18,7 @@ fn test_macro_helper() {
     hbs.register_helper("lower", Box::new(lower));
     hbs.register_helper("upper", Box::new(upper));
     hbs.register_helper("hex", Box::new(hex));
+    hbs.register_helper("money", Box::new(money));
 
     let data = json!("Teixeira");
 
@@ -28,4 +31,14 @@ fn test_macro_helper() {
         "TEIXEIRA"
     );
     assert_eq!(hbs.render_template("{{hex 16}}", &()).unwrap(), "0x10");
+
+    assert_eq!(
+        hbs.render_template("{{money 5000}}", &()).unwrap(),
+        "$5000.00"
+    );
+    assert_eq!(
+        hbs.render_template("{{money 5000 cur=\"£\"}}", &())
+            .unwrap(),
+        "£5000.00"
+    );
 }