return false;
};
+ auto test_is_in = [&]() -> bool {
+ func_args args(ctx);
+ args.push_back(left_val);
+ args.push_back(right_val);
+ return global_builtins().at("test_is_in")(args)->as_bool();
+ };
+
// Handle undefined and null values
if (is_val<value_undefined>(left_val) || is_val<value_undefined>(right_val)) {
if (is_val<value_undefined>(right_val) && (op.value == "in" || op.value == "not in")) {
return result;
}
} else if (is_val<value_array>(right_val)) {
- auto & arr = right_val->as_array();
- bool member = false;
- for (const auto & item : arr) {
- if (*left_val == *item) {
- member = true;
- break;
- }
- }
+ // case: 1 in [0, 1, 2]
+ bool member = test_is_in();
if (op.value == "in") {
- JJ_DEBUG("Checking membership: %s in Array is %d", left_val->type().c_str(), member);
return mk_val<value_bool>(member);
} else if (op.value == "not in") {
- JJ_DEBUG("Checking non-membership: %s not in Array is %d", left_val->type().c_str(), !member);
return mk_val<value_bool>(!member);
}
}
// String membership
if (is_val<value_string>(left_val) && is_val<value_string>(right_val)) {
- auto left_str = left_val->as_string().str();
- auto right_str = right_val->as_string().str();
+ // case: "a" in "abc"
+ bool member = test_is_in();
if (op.value == "in") {
- return mk_val<value_bool>(right_str.find(left_str) != std::string::npos);
+ return mk_val<value_bool>(member);
} else if (op.value == "not in") {
- return mk_val<value_bool>(right_str.find(left_str) == std::string::npos);
+ return mk_val<value_bool>(!member);
}
}
// Value key in object
if (is_val<value_object>(right_val)) {
- bool has_key = right_val->has_key(left_val);
+ // case: key in {key: value}
+ bool member = test_is_in();
if (op.value == "in") {
- return mk_val<value_bool>(has_key);
+ return mk_val<value_bool>(member);
} else if (op.value == "not in") {
- return mk_val<value_bool>(!has_key);
+ return mk_val<value_bool>(!member);
}
}
{"test_is_lt", test_compare_fn<value_compare_op::lt>},
{"test_is_lessthan", test_compare_fn<value_compare_op::lt>},
{"test_is_ne", test_compare_fn<value_compare_op::ne>},
+ {"test_is_in", [](const func_args & args) -> value {
+ args.ensure_count(2);
+ auto needle = args.get_pos(0);
+ auto haystack = args.get_pos(1);
+ if (is_val<value_undefined>(haystack)) {
+ return mk_val<value_bool>(false);
+ }
+ if (is_val<value_array>(haystack)) {
+ for (const auto & item : haystack->as_array()) {
+ if (*needle == *item) {
+ return mk_val<value_bool>(true);
+ }
+ }
+ return mk_val<value_bool>(false);
+ }
+ if (is_val<value_string>(haystack)) {
+ if (!is_val<value_string>(needle)) {
+ throw raised_exception("'in' test expects args[1] as string when args[0] is string, got args[1] as " + needle->type());
+ }
+ return mk_val<value_bool>(
+ haystack->as_string().str().find(needle->as_string().str()) != std::string::npos);
+ }
+ if (is_val<value_object>(haystack)) {
+ return mk_val<value_bool>(haystack->has_key(needle));
+ }
+ throw raised_exception("'in' test expects iterable as first argument, got " + haystack->type());
+ }},
{"test_is_test", [](const func_args & args) -> value {
args.ensure_vals<value_string>();
auto & builtins = global_builtins();
"negated"
);
- test_template(t, "in operator",
+ test_template(t, "in operator (element in array)",
"{% if 'x' in items %}found{% endif %}",
{{"items", json::array({"x", "y"})}},
"found"
);
+ test_template(t, "in operator (substring)",
+ "{% if 'bc' in 'abcd' %}found{% endif %}",
+ json::object(),
+ "found"
+ );
+
+ test_template(t, "in operator (object key)",
+ "{% if 'key' in obj %}found{% endif %}",
+ {{"obj", {{"key", 1}, {"other", 2}}}},
+ "found"
+ );
+
test_template(t, "is defined",
"{% if x is defined %}yes{% else %}no{% endif %}",
{{"x", 1}},
json::object(),
"yes"
);
+
+ test_template(t, "is in (array, true)",
+ "{{ 'yes' if 2 is in([1, 2, 3]) }}",
+ json::object(),
+ "yes"
+ );
+
+ test_template(t, "is in (array, false)",
+ "{{ 'yes' if 5 is in([1, 2, 3]) else 'no' }}",
+ json::object(),
+ "no"
+ );
+
+ test_template(t, "is in (string)",
+ "{{ 'yes' if 'bc' is in('abcde') }}",
+ json::object(),
+ "yes"
+ );
+
+ test_template(t, "is in (object keys)",
+ "{{ 'yes' if 'a' is in(obj) }}",
+ {{"obj", {{"a", 1}, {"b", 2}}}},
+ "yes"
+ );
+
+ test_template(t, "reject with in test",
+ "{{ items | reject('in', skip) | join(', ') }}",
+ {{"items", json::array({"a", "b", "c", "d"})}, {"skip", json::array({"b", "d"})}},
+ "a, c"
+ );
+
+ test_template(t, "select with in test",
+ "{{ items | select('in', keep) | join(', ') }}",
+ {{"items", json::array({"a", "b", "c", "d"})}, {"keep", json::array({"b", "c"})}},
+ "b, c"
+ );
}
static void test_string_methods(testing & t) {