use pluralizer::pluralize;
use regex::Regex;
use std::collections::HashMap;
use std::sync::Arc;
use tera::to_value;
use inflector::Inflector;
use serde_json::Value;
use tera::Tera;
use tera::{try_get_value, Result as TeraResult};
pub mod bucket_counter {
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tera::{to_value, Function, Value};
#[derive(Default)]
pub struct MultiBucketCounter {
registry: Mutex<HashMap<String, HashMap<String, usize>>>,
}
pub fn get_bucket_count(counter: Arc<MultiBucketCounter>) -> impl Function {
move |args: &HashMap<String, Value>| -> tera::Result<Value> {
let bucket = args
.get("bucket")
.and_then(|v| v.as_str())
.unwrap_or("default");
let name = args
.get("name")
.and_then(|v| v.as_str())
.ok_or("Argument 'name' is required")?;
let mut root_map = counter
.registry
.lock()
.map_err(|_| tera::Error::msg("Failed to acquire lock on the counter"))?;
let bucket_map = root_map.entry(bucket.to_string()).or_default();
let entry = bucket_map.entry(name.to_string()).or_insert(0);
let current_count = *entry;
*entry += 1;
if current_count == 0 {
Ok(Value::Null)
} else {
Ok(to_value(entry).unwrap())
}
}
}
pub fn clear_bucket(counter: Arc<MultiBucketCounter>) -> impl Function {
move |args: &HashMap<String, Value>| -> tera::Result<Value> {
let bucket = args
.get("bucket")
.and_then(|v| v.as_str())
.ok_or("Argument 'bucket' is required")?;
let mut root_map = counter
.registry
.lock()
.map_err(|_| tera::Error::msg("Failed to acquire lock on the counter"))?;
root_map.remove(bucket);
Ok(Value::Null)
}
}
}
pub fn register(tera: &mut Tera) {
tera.register_filter("camelcase", camelcase);
tera.register_filter("pascalcase", pascalcase);
tera.register_filter("snakecase", snakecase);
tera.register_filter("upper_snakecase", upper_snakecase);
tera.register_filter("kebabcase", kebabcase);
tera.register_filter("traincase", traincase);
tera.register_filter("titlecase", titlecase);
tera.register_filter("lcfirst", lcfirst);
tera.register_filter("ucfirst", ucfirst);
tera.register_filter("nospaces", nospaces);
tera.register_filter("path_parts", path_parts);
tera.register_filter("when_numeric", when_numeric);
tera.register_filter("filter_not", filter_not);
tera.register_filter("filter_startswith", filter_startswith);
tera.register_filter("filter_inarray", filter_inarray);
tera.register_filter("filter_not_inarray", filter_not_inarray);
tera.register_filter("plural", plural);
let counter = Arc::new(bucket_counter::MultiBucketCounter::default());
tera.register_function(
"get_bucket_count",
bucket_counter::get_bucket_count(counter.clone()),
);
tera.register_function(
"clear_bucket",
bucket_counter::clear_bucket(counter.clone()),
);
}
pub fn pascalcase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("pascalcase", "value", String, value);
let case = s.to_pascal_case();
Ok(to_value(case).unwrap())
}
pub fn camelcase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("camelcase", "value", String, value);
let case = s.to_camel_case();
Ok(to_value(case).unwrap())
}
pub fn snakecase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("snakecase", "value", String, value);
let case = s.to_snake_case();
Ok(to_value(case).unwrap())
}
pub fn upper_snakecase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("upper_snakecase", "value", String, value);
let case = s.to_screaming_snake_case();
Ok(to_value(case).unwrap())
}
pub fn kebabcase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("kebabcase", "value", String, value);
let case = s.to_kebab_case();
Ok(to_value(case).unwrap())
}
pub fn traincase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("traincase", "value", String, value);
let case = s.to_train_case();
Ok(to_value(case).unwrap())
}
pub fn titlecase(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("titlecase", "value", String, value);
let case = s.to_title_case();
Ok(to_value(case).unwrap())
}
pub fn lcfirst(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("lcfirst", "value", String, value);
let lcfirst = s[..1].to_ascii_lowercase() + &s[1..];
Ok(to_value(lcfirst).unwrap())
}
pub fn ucfirst(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let o = try_get_value!("ucfirst", "value", String, value);
let ucfirst = o[..1].to_ascii_uppercase() + &o[1..];
Ok(to_value(ucfirst).unwrap())
}
pub fn nospaces(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let mut s = try_get_value!("nospaces", "value", String, value);
s.retain(|c| !c.is_whitespace());
Ok(to_value(s).unwrap())
}
pub fn path_parts(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let data = try_get_value!("path_parts", "value", String, value);
let to = match args.get("to") {
Some(val) => try_get_value!("path_parts", "to", String, val),
None => return Err(tera::Error::msg("Please provide to parameter")),
};
let path = Regex::new("\\{[A-z0-9\\-]+\\}")
.unwrap()
.replace_all(&data, to.as_str());
Ok(to_value(path).unwrap())
}
pub fn when_numeric(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let value = try_get_value!("when_numeric", "value", String, value);
if value.chars().next().is_some_and(char::is_numeric) {
let prefix = match args.get("prefix") {
Some(val) => try_get_value!("when_numeric", "prefix", String, val),
None => return Err(tera::Error::msg("Please provide prefix parameter")),
};
Ok(to_value(format!("{prefix}{value}")).unwrap())
} else {
Ok(to_value(value).unwrap())
}
}
pub fn filter_not(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let mut arr = try_get_value!("filter_not", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter_not", "attribute", String, val),
None => {
return Err(tera::Error::msg(
"The `filter_not` filter has to have an `attribute` argument",
))
}
};
let value = args.get("value").unwrap_or(&Value::Null);
let json_pointer = ["/", &key.replace('.', "/")].join("");
arr = arr
.into_iter()
.filter(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
val != value
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
}
pub fn filter_startswith(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let mut arr = try_get_value!("filter_startswith", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter_startswith", "attribute", String, val),
None => {
return Err(tera::Error::msg(
"The `filter_startswith` filter has to have an `attribute` argument",
))
}
};
let match_ = match args.get("match") {
Some(val) => try_get_value!("filter_startswith", "match", bool, val),
None => true,
};
let value = match args.get("value") {
Some(val) => try_get_value!("filter_startswith", "value", String, val),
None => {
return Err(tera::Error::msg(
"The `filter_startswith` filter has to have an `value` argument",
))
}
};
let json_pointer = ["/", &key.replace('.', "/")].join("");
arr = arr
.into_iter()
.filter(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
val.as_str()
.map(|s| (match_ && s.starts_with(&value)) || (!match_ && !s.starts_with(&value)))
.unwrap_or(match_)
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
}
pub fn filter_inarray(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let mut arr = try_get_value!("filter_inarray", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter_inarray", "attribute", String, val),
None => {
return Err(tera::Error::msg(
"The `filter_inarray` filter has to have an `attribute` argument",
))
}
};
let values = args.get("values").unwrap_or(&Value::Null);
if let Value::Array(accepted) = values {
let json_pointer = ["/", &key.replace('.', "/")].join("");
arr = arr
.into_iter()
.filter(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
accepted.contains(val)
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
} else {
Err(tera::Error::msg(
"The `filter_inarray` filter has to have an `values` argument, type: array",
))
}
}
pub fn filter_not_inarray(value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
let mut arr = try_get_value!("filter_inarray", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter_not_inarray", "attribute", String, val),
None => {
return Err(tera::Error::msg(
"The `filter_not_inarray` filter has to have an `attribute` argument",
))
}
};
let values = args.get("values").unwrap_or(&Value::Null);
if let Value::Array(rejected) = values {
let json_pointer = ["/", &key.replace('.', "/")].join("");
arr = arr
.into_iter()
.filter(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
!rejected.contains(val)
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
} else {
Err(tera::Error::msg(
"The `filter_inarray` filter has to have an `values` argument, type: array",
))
}
}
pub fn plural(value: &Value, _: &HashMap<String, Value>) -> TeraResult<Value> {
let s = try_get_value!("plural", "value", String, value);
let plural = pluralize(&s, 2, false);
Ok(to_value(plural).unwrap())
}