use handlebars::{
Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, RenderErrorReason,
ScopedJson,
};
use serde_json::Value as Json;
use unicode_width::UnicodeWidthStr;
#[derive(Debug)]
pub(super) struct LineCounter;
impl LineCounter {
pub(super) const NAME: &'static str = "count_lines";
}
impl HelperDef for LineCounter {
fn call_inner<'reg: 'rc, 'rc>(
&self,
helper: &Helper<'rc>,
_: &'reg Handlebars<'reg>,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'rc>, RenderError> {
#[cfg(feature = "tracing")]
let _entered_span = helper_span!(helper);
let string = helper
.param(0)
.ok_or(RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 0))?;
let string = string.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"string".to_owned(),
)
})?;
let mut lines = bytecount::count(string.as_bytes(), b'\n');
if !string.is_empty() && !string.ends_with('\n') {
lines += 1;
}
let lines = u64::try_from(lines)
.map_err(|err| RenderErrorReason::Other(format!("cannot convert length: {err}")))?;
Ok(ScopedJson::Derived(lines.into()))
}
}
#[derive(Debug)]
pub(super) struct LineSplitter;
impl LineSplitter {
pub(super) const NAME: &'static str = "split_lines";
}
impl HelperDef for LineSplitter {
fn call_inner<'reg: 'rc, 'rc>(
&self,
helper: &Helper<'rc>,
_: &'reg Handlebars<'reg>,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'rc>, RenderError> {
#[cfg(feature = "tracing")]
let _entered_span = helper_span!(helper);
let string = helper
.param(0)
.ok_or_else(|| RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 0))?;
let string = string.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"string".to_owned(),
)
})?;
let lines = string.split('\n');
let mut lines: Vec<_> = lines.map(Json::from).collect();
if let Some(Json::String(s)) = lines.last() {
if s.is_empty() {
lines.pop();
}
}
Ok(ScopedJson::Derived(lines.into()))
}
}
#[derive(Debug)]
pub(super) struct RepeatHelper;
impl RepeatHelper {
pub(super) const NAME: &'static str = "repeat";
}
impl HelperDef for RepeatHelper {
fn call_inner<'reg: 'rc, 'rc>(
&self,
helper: &Helper<'rc>,
_: &'reg Handlebars<'reg>,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'rc>, RenderError> {
#[cfg(feature = "tracing")]
let _entered_span = helper_span!(helper);
let repeated_str = helper
.param(0)
.ok_or(RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 0))?;
let repeated_str = repeated_str.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"string".to_owned(),
)
})?;
let quantity = helper
.param(1)
.ok_or(RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 1))?;
let quantity = quantity.value().as_u64().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"integer".to_owned(),
)
})?;
let quantity = quantity
.try_into()
.map_err(|_| RenderErrorReason::Other("quantity is too large".to_owned()))?;
let output = repeated_str.repeat(quantity);
Ok(ScopedJson::Derived(output.into()))
}
}
#[derive(Debug)]
pub(super) struct TrimHelper;
impl TrimHelper {
pub(super) const NAME: &'static str = "trim";
}
impl HelperDef for TrimHelper {
fn call_inner<'reg: 'rc, 'rc>(
&self,
helper: &Helper<'rc>,
_: &'reg Handlebars<'reg>,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'rc>, RenderError> {
#[cfg(feature = "tracing")]
let _entered_span = helper_span!(helper);
let val = helper
.param(0)
.ok_or(RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 0))?;
let val = val.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"string".to_owned(),
)
})?;
let side = helper
.hash_get("side")
.map(|val| {
val.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"side".to_owned(),
"one of `start`, `end`, or `both`".to_owned(),
)
})
})
.transpose()?;
let trimmed = match side {
None | Some("both") => val.trim(),
Some("start") => val.trim_start(),
Some("end") => val.trim_end(),
_ => {
let err = RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"side".to_owned(),
"one of `start`, `end`, or `both`".to_owned(),
);
return Err(err.into());
}
};
Ok(ScopedJson::Derived(trimmed.into()))
}
}
#[derive(Debug)]
pub(super) struct CharWidthHelper;
impl CharWidthHelper {
pub(super) const NAME: &'static str = "char_width";
}
impl HelperDef for CharWidthHelper {
fn call_inner<'reg: 'rc, 'rc>(
&self,
helper: &Helper<'rc>,
_: &'reg Handlebars<'reg>,
_: &'rc Context,
_: &mut RenderContext<'reg, 'rc>,
) -> Result<ScopedJson<'rc>, RenderError> {
#[cfg(feature = "tracing")]
let _entered_span = helper_span!(helper);
let val = helper
.param(0)
.ok_or(RenderErrorReason::ParamNotFoundForIndex(Self::NAME, 0))?;
let val = val.value().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
Self::NAME,
"0".to_owned(),
"string".to_owned(),
)
})?;
let width = val.width();
Ok(ScopedJson::Derived(width.into()))
}
}