///|
fn test_value1(
state : State,
args : Array[Value],
f : (Value) -> Bool,
) -> Value raise TemplateError {
let a = Args::new(state, args)
let v = a.value()
a.finish()
Value::from_bool(f(v))
}
///|
fn test_value2(
state : State,
args : Array[Value],
f : (Value, Value) -> Bool,
) -> Value raise TemplateError {
let a = Args::new(state, args)
let v = a.value()
let other = a.value()
a.finish()
Value::from_bool(f(v, other))
}
///|
fn test_is_odd(state : State, args : Array[Value]) -> Value raise TemplateError {
test_value1(state, args, v => {
match v.to_i128() {
Some(x) => !(x % 2N).is_zero()
None => false
}
})
}
///|
fn test_is_even(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
test_value1(state, args, v => {
match v.to_i128() {
Some(x) => (x % 2N).is_zero()
None => false
}
})
}
///|
fn test_is_divisibleby(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
test_value2(state, args, (v, other) => {
match coerce(v, other, false) {
Some(Int(a, b)) => b != 0L && a % b == 0L
Some(Big(a, b)) => !b.is_zero() && (a % b).is_zero()
Some(F64(a, b)) => a % b == 0.0
_ => false
}
})
}
///|
fn test_is_startingwith(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
let a = Args::new(state, args)
let v = a.string()
let other = a.string()
a.finish()
Value::from_bool(v.has_prefix(other))
}
///|
fn test_is_endingwith(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
let a = Args::new(state, args)
let v = a.string()
let other = a.string()
a.finish()
Value::from_bool(v.has_suffix(other))
}
///|
fn test_is_in(state : State, args : Array[Value]) -> Value raise TemplateError {
let a = Args::new(state, args)
let value = a.value()
let other = a.value()
a.finish()
state.undefined_behavior().assert_iterable(other)
let contained = value_contains(other, value) catch {
_ => Value::from_bool(false)
}
Value::from_bool(contained.is_true())
}
///|
fn test_is_filter(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
let a = Args::new(state, args)
let name = a.str()
a.finish()
Value::from_bool(state.env().get_filter(name) is Some(_))
}
///|
fn test_is_test(
state : State,
args : Array[Value],
) -> Value raise TemplateError {
let a = Args::new(state, args)
let name = a.str()
a.finish()
Value::from_bool(state.env().get_test(name) is Some(_))
}
///|
fn char_changes_case(ch : Char) -> Bool {
@unicode.char_to_lower(ch) != ch.to_string() ||
@unicode.char_to_upper(ch) != ch.to_string()
}
///|
fn is_lower_str(value : Value) -> Bool {
guard value.as_str() is Some(name) else { return false }
let mut has_cased = false
for ch in name {
if @unicode.is_uppercase(ch) ||
(!@unicode.is_lowercase(ch) && char_changes_case(ch)) {
return false
}
if @unicode.is_lowercase(ch) {
has_cased = true
}
}
has_cased
}
///|
fn is_upper_str(value : Value) -> Bool {
guard value.as_str() is Some(name) else { return false }
let mut has_cased = false
for ch in name {
if @unicode.is_lowercase(ch) ||
(!@unicode.is_uppercase(ch) && char_changes_case(ch)) {
return false
}
if @unicode.is_uppercase(ch) {
has_cased = true
}
}
has_cased
}
///|
fn is_sameas(value : Value, other : Value) -> Bool {
match (value.as_object(), other.as_object()) {
(Some(a), Some(b)) => a.is_same_object(b)
(None, Some(_)) | (Some(_), None) => false
(None, None) =>
if value.kind() != other.kind() ||
value.is_integer() != other.is_integer() {
false
} else {
value == other
}
}
}
///|
fn register_builtin_tests(rv : Map[String, Value]) -> Unit {
fn add1(
rv : Map[String, Value],
name : String,
path : String,
f : (Value) -> Bool,
) -> Unit {
rv[name] = Value::from_function(path, (state, args) => {
test_value1(state, args, f)
})
}
fn add2(
rv : Map[String, Value],
names : Array[String],
path : String,
f : (Value, Value) -> Bool,
) -> Unit {
let func = Value::from_function(path, (state, args) => {
test_value2(state, args, f)
})
for name in names {
rv[name] = func
}
}
fn add(
rv : Map[String, Value],
name : String,
path : String,
f : NativeFunction,
) -> Unit {
rv[name] = Value::from_function(path, f)
}
let t = "minijinja::tests::"
let b = "minijinja::tests::builtins::"
add1(rv, "undefined", t + "is_undefined", Value::is_undefined)
add1(rv, "defined", t + "is_defined", v => !v.is_undefined())
add1(rv, "none", t + "is_none", Value::is_none)
let is_safe = Value::from_function(t + "is_safe", (state, args) => {
test_value1(state, args, Value::is_safe)
})
rv["safe"] = is_safe
rv["escaped"] = is_safe
add1(rv, "boolean", b + "is_boolean", v => v.kind() == Bool)
add(rv, "odd", b + "is_odd", test_is_odd)
add(rv, "even", b + "is_even", test_is_even)
add(rv, "divisibleby", b + "is_divisibleby", test_is_divisibleby)
add1(rv, "number", b + "is_number", v => v.kind() is (Number | Bool))
let is_integer = Value::from_function(b + "is_integer", (state, args) => {
test_value1(state, args, Value::is_integer)
})
rv["integer"] = is_integer
rv["int"] = is_integer
add1(rv, "float", b + "is_float", v => v is F64(_))
add1(rv, "string", b + "is_string", v => v.kind() == String)
add1(rv, "sequence", b + "is_sequence", v => {
v.is_undefined() || v.kind() == Seq
})
add1(rv, "iterable", b + "is_iterable", v => {
let _ = v.try_iter() catch { _ => return false }
true
})
add1(rv, "mapping", b + "is_mapping", v => v.kind() == Map)
add(rv, "startingwith", b + "is_startingwith", test_is_startingwith)
add(rv, "endingwith", b + "is_endingwith", test_is_endingwith)
add1(rv, "lower", b + "is_lower", is_lower_str)
add1(rv, "upper", b + "is_upper", is_upper_str)
add2(rv, ["sameas"], b + "is_sameas", is_sameas)
add2(rv, ["eq", "equalto", "=="], b + "is_eq", (a, b) => a == b)
add2(rv, ["ne", "!="], b + "is_ne", (a, b) => a != b)
add2(rv, ["lt", "lessthan", "<"], b + "is_lt", (a, b) => a.cmp(b) < 0)
add2(rv, ["le", "<="], b + "is_le", (a, b) => a.cmp(b) <= 0)
add2(rv, ["gt", "greaterthan", ">"], b + "is_gt", (a, b) => a.cmp(b) > 0)
add2(rv, ["ge", ">="], b + "is_ge", (a, b) => a.cmp(b) >= 0)
add(rv, "in", b + "is_in", test_is_in)
add1(rv, "true", b + "is_true", v => v is Bool(true))
add1(rv, "false", b + "is_false", v => v is Bool(false))
add(rv, "filter", b + "is_filter", test_is_filter)
add(rv, "test", b + "is_test", test_is_test)
}