use std::sync::Arc;
use tera::Function;
use tera::TeraResult;
use tera::Value;
use crate::language::{LanguageEngine, UnicodeLanguageIdentifier, variable_description::Variable};
pub type TextFunctionContextGetter =
fn(args: &tera::Kwargs, state: &tera::State<'_>) -> TeraResult<TextFunctionContext>;
#[derive(Debug, Clone)]
pub struct TextFunctionContext {
pub language: Option<UnicodeLanguageIdentifier>,
pub escape_function: tera::EscapeFn,
pub safe_suffix: Option<String>,
}
impl TextFunctionContext {
pub fn matches_safe_suffix(&self, text: &str) -> bool {
if let Some(safe_suffix) = &self.safe_suffix {
text.ends_with(safe_suffix)
} else {
false
}
}
}
pub struct TextFunction {
pub(crate) language_engine: Arc<LanguageEngine>,
pub(crate) context_getter: TextFunctionContextGetter,
}
impl TextFunction {
pub fn new(
language_engine: Arc<LanguageEngine>,
context_getter: TextFunctionContextGetter,
) -> Self {
Self {
language_engine,
context_getter,
}
}
}
impl Function<tera::TeraResult<Value>> for TextFunction {
fn call(&self, args: tera::Kwargs, state: &tera::State<'_>) -> TeraResult<Value> {
let message_id: &str = args.must_get("id")?;
let context = (self.context_getter)(&args, state)?;
let is_safe_suffix = context.matches_safe_suffix(&message_id.replace("-", "_"));
let lang: UnicodeLanguageIdentifier = if let Some(lang) = args.get::<String>("lang")? {
lang.parse()
.map_err(|e| tera::Error::message(format!("Unable to parse lang argument: {e}")))?
} else if let Some(lang) = &context.language {
lang.clone()
} else {
return Err(tera::Error::message(
"Could neither source the language from outside the templating engine, nor was the lang argument set. If you are using the default HumusEngine this is most likely a bug. Otherwise check your `language_getter` implementation.",
));
};
let mut variables: Vec<(String, Variable)> = vec![];
for (key, value) in args.iter() {
let Some(key) = key.as_str() else {
return Err(tera::Error::message(format!(
"Key for text() must be a string, got {key:?} instead."
)));
};
if matches!(key, "id" | "lang") {
continue;
}
if let Some(value) = value.as_bool() {
variables.push((key.to_owned(), Variable::Bool(value)))
} else if let Some(value) = value.as_number() {
variables.push((key.to_owned(), Variable::Number(value.as_float())))
} else if let Some(value) = value.as_str() {
if is_safe_suffix && !context.matches_safe_suffix(key) {
let mut escaped_value = Vec::<u8>::default();
(context.escape_function)(value, &mut escaped_value).map_err(|e| {
tera::Error::chain(
format!("While escaping argument {key:?} for text(id={message_id:?})."),
e,
)
})?;
variables.push((
key.to_owned(),
Variable::Text(String::from_utf8_lossy(&escaped_value).to_string()),
));
} else {
variables.push((key.to_owned(), Variable::Text(value.to_owned())));
}
} else if value.is_undefined() || value.is_none() {
continue; } else {
return Err(tera::Error::message(format!(
"Value arguments for text() must be a bool, number, string or null got {key:?}={value:?} instead."
)));
}
}
match self
.language_engine
.get_text_with_args(Some(lang), message_id, variables)
{
Ok(text) => {
let text = text.replace("\"\u{2068}", "\"").replace("\u{2069}\"", "\"");
if is_safe_suffix {
Ok(Value::from(text).mark_safe())
} else {
Ok(Value::from(text))
}
}
Err(e) => Err(tera::Error::message(format!(
"Problem rendering message in template: {e}"
))),
}
}
fn is_safe(&self) -> bool {
false
}
}