Skip to content

Commit

Permalink
✨ add helpers regex_captures and regex_is_match
Browse files Browse the repository at this point in the history
  • Loading branch information
davidB committed Jun 2, 2024
1 parent 9d13061 commit 6a9fdd8
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 8 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ log = "^0.4"
cruet = { version = "^0.14", optional = true }
jmespath = { version = "^0.3", optional = true }
jsonnet-rs = { version = "^0.17", optional = true }
regex = { version = "^1.10", optional = true }
reqwest = { version = "0.12", optional = true, default-features = false, features = [
"blocking",
"rustls-tls",
Expand All @@ -48,8 +49,7 @@ similar-asserts = "1.4"
unindent = "0.2"

[features]
default = ["string", "http_attohttpc", "json", "jsonnet"]
string = ["dep:cruet", "dep:enquote", "jsontype"]
default = ["string", "http_attohttpc", "json", "jsonnet", "regex"]
http_attohttpc = ["dep:attohttpc"]
http_reqwest = ["dep:reqwest"]
json = [
Expand All @@ -59,5 +59,7 @@ json = [
"dep:serde_yaml",
"dep:toml",
]
jsontype = ["dep:serde_json"]
jsonnet = ["dep:jsonnet-rs"]
jsontype = ["dep:serde_json"]
regex = ["dep:regex"]
string = ["dep:cruet", "dep:enquote", "jsontype"]
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ see [Handlebars templating language](https://handlebarsjs.com/)
To not "import" useless dependencies, use crate's features:

```toml
[features]
default = ["string", "http_attohttpc", "json", "jsonnet"]
string = ["dep:cruet", "dep:enquote", "jsontype"]
default = ["string", "http_attohttpc", "json", "jsonnet", "regex"]
http_attohttpc = ["dep:attohttpc"]
http_reqwest = ["dep:reqwest"]
json = [
Expand All @@ -47,13 +45,16 @@ json = [
"dep:serde_yaml",
"dep:toml",
]
jsontype = ["dep:serde_json"]
jsonnet = ["dep:jsonnet-rs"]
jsontype = ["dep:serde_json"]
regex = ["dep:regex"]
string = ["dep:cruet", "dep:enquote", "jsontype"]
```

<!-- TOC depthFrom:2 -->

- [String transformation](#string-transformation)
- [Regular expression](#regular-expression)
- [Http content](#http-content)
- [Path extraction](#path-extraction)
- [File](#file)
Expand Down Expand Up @@ -97,6 +98,16 @@ jsonnet = ["dep:jsonnet-rs"]
| `enquote symbol:String s:String` | `enquote "" "foo"` | `"\"foo\""` |
| `first_non_empty s:String+` | `first_non_empty "" null "foo" "bar"` | `"foo"` |

## Regular expression

| usage | output |
| ----------------------------------------- | -------------------------- |
| `{{ regex_is_match pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" }}` | `true` |
| `{{#if (regex_is_match pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}ok{{/if}}` | `ok` |
| `{{ regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" }}` | `[object]` |
| `{{ json_to_str( regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}` | `{"_0":"today","_1":"t","_2":"o","_3":"y","first":"t","last":"y"}` |
| `{{ set captures=( regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}{{ captures.last }}` | `y` |

## Http content

Helper able to render body response from an http request.
Expand Down
7 changes: 6 additions & 1 deletion src/assign_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ mod tests {
(r##"{{ set foo="{}" }}{{ foo }}"##, r##"{}"##),
(r##"{{ set foo={} }}{{ foo }}"##, r##"[object]"##),
(r##"{{ set foo={"bar": 33} }}{{ foo }}"##, r##"[object]"##,),
(
r##"{{ set foo={"bar": 33} }}{{ json_to_str foo }}"##,
r##"{"bar":33}"##,
),
(r##"{{ set foo={"bar": 33} }}{{ foo.bar }}"##, r##"33"##,),
(
r##"{{ set foo="hello world" }}{{ foo }}"##,
Expand All @@ -94,7 +98,8 @@ mod tests {
(
r##"{{ set foo="world" }}{{ set bar="hello" }}>{{ bar }} {{ foo }}<"##,
r##">hello world<"##,
)
),
(r##"{{ set foo=(eq 12 12) }}{{ foo }}"##, r##"true"##,)
]
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod json_helpers;
pub mod jsonnet_helpers;
pub mod outputs;
pub mod path_helpers;
#[cfg(feature = "regex")]
pub mod regex_helpers;
#[cfg(feature = "jsontype")]
pub mod region_helpers;
#[cfg(feature = "string")]
Expand Down Expand Up @@ -55,6 +57,8 @@ pub fn register(handlebars: &mut Handlebars) {
file_helpers::register(handlebars);
#[cfg(feature = "jsontype")]
region_helpers::register(handlebars);
#[cfg(feature = "regex")]
regex_helpers::register(handlebars);
}

#[allow(dead_code)]
Expand Down
125 changes: 125 additions & 0 deletions src/regex_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use handlebars::{Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, ScopedJson};
use regex::Regex;

#[allow(non_camel_case_types)]
pub struct regex_captures_fct;

impl HelperDef for regex_captures_fct {
fn call_inner<'reg: 'rc, 'rc>(
&self,
h: &Helper<'rc>,
_: &'reg Handlebars,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'reg>, RenderError> {
let on = h
.hash_get("on")
.and_then(|v| v.value().as_str())
.unwrap_or_default();
let pattern = h
.hash_get("pattern")
.and_then(|v| v.value().as_str())
.unwrap_or_default();
let re = Regex::new(pattern).map_err(|err| crate::to_other_error(err.to_string()))?;
if let Some(caps) = re.captures(on) {
let collected = re
.capture_names()
.filter_map(|v| {
v.and_then(|name| {
caps.name(name).map(|m| {
(
name.to_string(),
serde_json::Value::String(m.as_str().to_string()),
)
})
})
})
.chain(caps.iter().enumerate().filter_map(|(i, mm)| {
mm.map(|m| {
(
format!("_{}", i),
serde_json::Value::String(m.as_str().to_string()),
)
})
}))
.collect::<serde_json::Map<_, _>>();
Ok(ScopedJson::Derived(serde_json::Value::Object(collected)))
} else {
Ok(ScopedJson::Derived(serde_json::Value::Null))
}
}
}

#[allow(non_camel_case_types)]
pub struct regex_is_match_fct;

impl HelperDef for regex_is_match_fct {
fn call_inner<'reg: 'rc, 'rc>(
&self,
h: &Helper<'rc>,
_: &'reg Handlebars,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'reg>, RenderError> {
let on = h
.hash_get("on")
.and_then(|v| v.value().as_str())
.unwrap_or_default();
let pattern = h
.hash_get("pattern")
.and_then(|v| v.value().as_str())
.unwrap_or_default();
let re = Regex::new(pattern).map_err(|err| crate::to_other_error(err.to_string()))?;
Ok(ScopedJson::Derived(serde_json::Value::Bool(
re.is_match(on),
)))
}
}

pub fn register(handlebars: &mut Handlebars) {
handlebars.register_helper("regex_captures", Box::new(regex_captures_fct));
handlebars.register_helper("regex_is_match", Box::new(regex_is_match_fct));
}

#[cfg(test)]
mod tests {
use crate::assert_renders;
use std::error::Error;

#[test]
fn test_regex_captures() -> Result<(), Box<dyn Error>> {
assert_renders![
(r##"{{ regex_captures pattern="foo" on="" }}"##, r##""##),
(
r##"{{ regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" }}"##,
r##"[object]"##
),
(
r##"{{ json_to_str( regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}"##,
r##"{"_0":"today","_1":"t","_2":"o","_3":"y","first":"t","last":"y"}"##
),
(
r##"{{ set captures=( regex_captures pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}{{ captures.last }}"##,
r##"y"##
),
]
}

#[test]
fn test_regex_is_match() -> Result<(), Box<dyn Error>> {
assert_renders![
(
r##"{{ regex_is_match pattern="foo" on="" }}"##,
r##"false"##
),
(
r##"{{ regex_is_match pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" }}"##,
r##"true"##
),
(
r##"{{#if (regex_is_match pattern="(?<first>\\w)(\\w)(?:\\w)\\w(?<last>\\w)" on="today" ) }}ok{{/if}}"##,
r##"ok"##
),
]
}
}

0 comments on commit 6a9fdd8

Please sign in to comment.