Skip to content

Commit

Permalink
Documenting templating functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jzbor committed Jul 9, 2024
1 parent 93b76fa commit a6e0b47
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 39 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ jobs:
You can find more information on the available options [in the source documentation](`crate::Zinnfile`).


## Templating Functions
Zinn provides custom functions for the templating language:
- `cat <s1> <s2>...`: Concatenate all parameters
- `joinlines <var>`: Join lines and connect them with a regular whitespace
- `lst <s1> <s2>...`: Create a space-separated list from all input parameters
- `lst-prefix <prefix> <list>`: Add a prefix to each element in a space-separated list
- `lst-re <list> <pattern> <replacement>`: Apply a regex replacement operation to each item in a space-separated list
- `lst-suffix <prefix> <list>`: Add a suffix to each element in a space-separated list
- `lst-without <list> <remove1> <remove2>...`: Create copy of a space-separated list without certain elements
- `re <base> <pattern> <replacement>`: Apply a regex replacement operation to an input string
- `shell <cmd>`: Create a string from the output of a shell command
- `subst <base> <pattern> <replacement>`: Replace all occurrences of a substring
178 changes: 139 additions & 39 deletions src/hbextensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,139 @@ use std::process::Command;
use handlebars::{Context, Handlebars, Helper, HelperResult, JsonRender, Output, RenderContext, RenderErrorReason};


fn shell(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
fn parse_list(s: &str) -> Vec<String> {
s.split(char::is_whitespace)
.filter(|s| !s.is_empty())
.map(|s| s.to_owned())
.collect()
}

fn encode_list(l: &Vec<String>) -> String {
l.join(" ")
}


/// Concatenate all parameters
fn cat(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
let mut string = String::new();

for param in h.params() {
string.push_str(&param.value().render())
}

out.write(&string)?;
Ok(())
}

/// Join lines and connect them with a regular whitespace
fn joinlines(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().is_empty() {
return Err(RenderErrorReason::ParamNotFoundForIndex("shell", h.params().len()).into())
return Err(RenderErrorReason::ParamNotFoundForIndex("joinlines", h.params().len()).into())
}

let cmd = h.params().iter().fold(String::new(), |s, p| format!("{} {}", s, p.value().render()));
let output = Command::new("sh")
.arg("-c")
.arg(cmd)
.output()?;
let base = h.params().first().unwrap().value().render();
out.write(&base.replace('\n', " "))?;

if let Ok(string) = String::from_utf8(output.stdout) {
if let Some(trimmed_string) = string.strip_suffix('\n') {
out.write(trimmed_string)?;
} else {
out.write(&string)?;
}
Ok(())
}

/// Create a space-separated list from all input parameters
fn lst(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().is_empty() {
return Err(RenderErrorReason::ParamNotFoundForIndex("joinlines", h.params().len()).into())
}

let params: Vec<String> = h.params()
.iter()
.flat_map(|p| parse_list(&p.value().render()))
.collect();

out.write(&encode_list(&params))?;

Ok(())
}

fn subst(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
/// Add a prefix to each element in a space-separated list
fn lst_prefix(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().len() < 2 {
return Err(RenderErrorReason::ParamNotFoundForIndex("lst-prefix", h.params().len()).into())
}

let prefix = h.params().first().unwrap().value().render();
let list = parse_list(&h.params().get(1).unwrap().value().render());

let result = list.iter()
.map(|e| format!("{}{}", &prefix, e))
.collect();

out.write(&encode_list(&result))?;

Ok(())
}

/// Apply a regex replacement operation to each element in a space-separated list
#[cfg(feature = "regex")]
fn lst_re(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().len() < 3 {
return Err(RenderErrorReason::ParamNotFoundForIndex("subst", h.params().len()).into())
return Err(RenderErrorReason::ParamNotFoundForIndex("lst-re", h.params().len()).into())
}

let base = h.params().first().unwrap().value().render();
let list = parse_list(&base);
let pattern = h.params().get(1).unwrap().value().render();
let replacement = h.params().get(2).unwrap().value().render();
out.write(&base.replace(&pattern, &replacement))?;

let re = regex::Regex::new(&pattern)
.map_err(|e| RenderErrorReason::Other(format!("regex error - {e}")))?;

let result = list.iter()
.map(|e| re.replace_all(e, &replacement).to_string())
.collect();

out.write(&encode_list(&result))?;

Ok(())
}

fn joinlines(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().is_empty() {
return Err(RenderErrorReason::ParamNotFoundForIndex("joinlines", h.params().len()).into())
/// Add a suffix to each element in a space-separated list
fn lst_suffix(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().len() < 2 {
return Err(RenderErrorReason::ParamNotFoundForIndex("lst-suffix", h.params().len()).into())
}

let base = h.params().first().unwrap().value().render();
out.write(&base.replace('\n', " "))?;
let suffix = h.params().first().unwrap().value().render();
let list = parse_list(&h.params().get(1).unwrap().value().render());

let result = list.iter()
.map(|e| format!("{}{}", e, &suffix))
.collect();

out.write(&encode_list(&result))?;

Ok(())
}

fn without(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
/// Create copy of a space-separated list without certain elements
fn lst_without(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().len() < 2 {
return Err(RenderErrorReason::ParamNotFoundForIndex("without", h.params().len()).into())
return Err(RenderErrorReason::ParamNotFoundForIndex("lst-without", h.params().len()).into())
}

let mut temp = h.params().first().unwrap().value().render();
for par in h.params().iter().skip(1) {
temp = temp.replace(&par.value().render(), "");
}
out.write(&temp)?;
let list = parse_list(&h.params().first().unwrap().value().render());
let remove_list: Vec<String> = h.params().iter().skip(1).map(|p| p.value().render()).collect();

let result = list.into_iter()
.filter(|e| !remove_list.contains(&e))
.collect();

out.write(&encode_list(&result))?;

Ok(())
}

/// Apply a regex replacement operation to an input string
#[cfg(feature = "regex")]
fn re(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {

if h.params().len() < 3 {
return Err(RenderErrorReason::ParamNotFoundForIndex("re", h.params().len()).into())
}
Expand All @@ -78,31 +147,62 @@ fn re(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut
let re = regex::Regex::new(&pattern)
.map_err(|e| RenderErrorReason::Other(format!("regex error - {e}")))?;

let result = re.replace_all(&base, replacement);
let result = re.replace_all(&base, &replacement);

out.write(&result)?;

Ok(())
}

fn cat(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
let mut string = String::new();
/// Create a string from the output of a shell command
fn shell(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().is_empty() {
return Err(RenderErrorReason::ParamNotFoundForIndex("shell", h.params().len()).into())
}

for param in h.params() {
string.push_str(&param.value().render())
let cmd = h.params().iter().fold(String::new(), |s, p| format!("{} {}", s, p.value().render()));
let output = Command::new("sh")
.arg("-c")
.arg(cmd)
.output()?;

if let Ok(string) = String::from_utf8(output.stdout) {
if let Some(trimmed_string) = string.strip_suffix('\n') {
out.write(trimmed_string)?;
} else {
out.write(&string)?;
}
}

out.write(&string)?;
Ok(())
}

/// Replace all occurrences of a substring
fn subst(h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
if h.params().len() < 3 {
return Err(RenderErrorReason::ParamNotFoundForIndex("subst", h.params().len()).into())
}

let base = h.params().first().unwrap().value().render();
let pattern = h.params().get(1).unwrap().value().render();
let replacement = h.params().get(2).unwrap().value().render();
out.write(&base.replace(&pattern, &replacement))?;

Ok(())
}


pub fn register_helpers(handlebars: &mut Handlebars) {
handlebars.register_helper("shell", Box::new(shell));
handlebars.register_helper("subst", Box::new(subst));
handlebars.register_helper("joinlines", Box::new(joinlines));
handlebars.register_helper("cat", Box::new(cat));
handlebars.register_helper("without", Box::new(without));
handlebars.register_helper("joinlines", Box::new(joinlines));
handlebars.register_helper("lst", Box::new(lst));
handlebars.register_helper("lst-prefix", Box::new(lst_prefix));
#[cfg(feature = "regex")]
handlebars.register_helper("lst-re", Box::new(lst_re));
handlebars.register_helper("lst-suffix", Box::new(lst_suffix));
handlebars.register_helper("lst-without", Box::new(lst_without));
#[cfg(feature = "regex")]
handlebars.register_helper("re", Box::new(re));
handlebars.register_helper("shell", Box::new(shell));
handlebars.register_helper("subst", Box::new(subst));
}

0 comments on commit a6e0b47

Please sign in to comment.