jinja : undefined should be treated as sequence/iterable (return string/array) by filters/tests (#19147)
* undefined is treated as iterable (string/array) by filters `tojson` is not a supported `undefined` filter * add tests * add sequence and iterable tests keep it DRY and fix some types
This commit is contained in:
parent
88d23ad515
commit
60368e1d73
|
|
@ -114,6 +114,18 @@ static T slice(const T & array, int64_t start, int64_t stop, int64_t step = 1) {
|
|||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static value empty_value_fn(const func_args &) {
|
||||
if constexpr (std::is_same_v<T, value_int>) {
|
||||
return mk_val<T>(0);
|
||||
} else if constexpr (std::is_same_v<T, value_float>) {
|
||||
return mk_val<T>(0.0);
|
||||
} else if constexpr (std::is_same_v<T, value_bool>) {
|
||||
return mk_val<T>(false);
|
||||
} else {
|
||||
return mk_val<T>();
|
||||
}
|
||||
}
|
||||
template<typename T>
|
||||
static value test_type_fn(const func_args & args) {
|
||||
args.ensure_count(1);
|
||||
|
|
@ -128,6 +140,13 @@ static value test_type_fn(const func_args & args) {
|
|||
JJ_DEBUG("test_type_fn: type=%s or %s result=%d", typeid(T).name(), typeid(U).name(), is_type ? 1 : 0);
|
||||
return mk_val<value_bool>(is_type);
|
||||
}
|
||||
template<typename T, typename U, typename V>
|
||||
static value test_type_fn(const func_args & args) {
|
||||
args.ensure_count(1);
|
||||
bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)) || is_val<V>(args.get_pos(0));
|
||||
JJ_DEBUG("test_type_fn: type=%s, %s or %s result=%d", typeid(T).name(), typeid(U).name(), typeid(V).name(), is_type ? 1 : 0);
|
||||
return mk_val<value_bool>(is_type);
|
||||
}
|
||||
template<value_compare_op op>
|
||||
static value test_compare_fn(const func_args & args) {
|
||||
args.ensure_count(2, 2);
|
||||
|
|
@ -347,8 +366,8 @@ const func_builtins & global_builtins() {
|
|||
{"test_is_integer", test_type_fn<value_int>},
|
||||
{"test_is_float", test_type_fn<value_float>},
|
||||
{"test_is_number", test_type_fn<value_int, value_float>},
|
||||
{"test_is_iterable", test_type_fn<value_array, value_string>},
|
||||
{"test_is_sequence", test_type_fn<value_array, value_string>},
|
||||
{"test_is_iterable", test_type_fn<value_array, value_string, value_undefined>},
|
||||
{"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>},
|
||||
{"test_is_mapping", test_type_fn<value_object>},
|
||||
{"test_is_lower", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_string>();
|
||||
|
|
@ -1003,7 +1022,12 @@ const func_builtins & value_none_t::get_builtins() const {
|
|||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
{"tojson", tojson},
|
||||
{"string", [](const func_args &) -> value { return mk_val<value_string>("None"); }}
|
||||
{"string", [](const func_args &) -> value {
|
||||
return mk_val<value_string>("None");
|
||||
}},
|
||||
{"safe", [](const func_args &) -> value {
|
||||
return mk_val<value_string>("None");
|
||||
}},
|
||||
};
|
||||
return builtins;
|
||||
}
|
||||
|
|
@ -1012,10 +1036,33 @@ const func_builtins & value_none_t::get_builtins() const {
|
|||
const func_builtins & value_undefined_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
{"tojson", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_undefined>();
|
||||
return mk_val<value_string>("null");
|
||||
}},
|
||||
{"capitalize", empty_value_fn<value_string>},
|
||||
{"first", empty_value_fn<value_undefined>},
|
||||
{"items", empty_value_fn<value_array>},
|
||||
{"join", empty_value_fn<value_string>},
|
||||
{"last", empty_value_fn<value_undefined>},
|
||||
{"length", empty_value_fn<value_int>},
|
||||
{"list", empty_value_fn<value_array>},
|
||||
{"lower", empty_value_fn<value_string>},
|
||||
{"map", empty_value_fn<value_array>},
|
||||
{"max", empty_value_fn<value_undefined>},
|
||||
{"min", empty_value_fn<value_undefined>},
|
||||
{"reject", empty_value_fn<value_array>},
|
||||
{"rejectattr", empty_value_fn<value_array>},
|
||||
{"replace", empty_value_fn<value_string>},
|
||||
{"reverse", empty_value_fn<value_array>},
|
||||
{"safe", empty_value_fn<value_string>},
|
||||
{"select", empty_value_fn<value_array>},
|
||||
{"selectattr", empty_value_fn<value_array>},
|
||||
{"sort", empty_value_fn<value_array>},
|
||||
{"string", empty_value_fn<value_string>},
|
||||
{"strip", empty_value_fn<value_string>},
|
||||
{"sum", empty_value_fn<value_int>},
|
||||
{"title", empty_value_fn<value_string>},
|
||||
{"truncate", empty_value_fn<value_string>},
|
||||
{"unique", empty_value_fn<value_array>},
|
||||
{"upper", empty_value_fn<value_string>},
|
||||
{"wordcount", empty_value_fn<value_int>},
|
||||
};
|
||||
return builtins;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,6 +329,12 @@ static void test_loops(testing & t) {
|
|||
"empty"
|
||||
);
|
||||
|
||||
test_template(t, "for undefined empty",
|
||||
"{% for i in items %}{{ i }}{% else %}empty{% endfor %}",
|
||||
json::object(),
|
||||
"empty"
|
||||
);
|
||||
|
||||
test_template(t, "nested for",
|
||||
"{% for i in a %}{% for j in b %}{{ i }}{{ j }}{% endfor %}{% endfor %}",
|
||||
{{"a", json::array({1, 2})}, {"b", json::array({"x", "y"})}},
|
||||
|
|
@ -1018,6 +1024,18 @@ static void test_tests(testing & t) {
|
|||
{{"x", {{"a", 1}}}},
|
||||
"yes"
|
||||
);
|
||||
|
||||
test_template(t, "undefined is sequence",
|
||||
"{{ 'yes' if x is sequence }}",
|
||||
json::object(),
|
||||
"yes"
|
||||
);
|
||||
|
||||
test_template(t, "undefined is iterable",
|
||||
"{{ 'yes' if x is iterable }}",
|
||||
json::object(),
|
||||
"yes"
|
||||
);
|
||||
}
|
||||
|
||||
static void test_string_methods(testing & t) {
|
||||
|
|
@ -1122,6 +1140,54 @@ static void test_string_methods(testing & t) {
|
|||
{{"s", "banana"}},
|
||||
"bXnXna"
|
||||
);
|
||||
|
||||
test_template(t, "undefined|capitalize",
|
||||
"{{ arr|capitalize }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|title",
|
||||
"{{ arr|title }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|truncate",
|
||||
"{{ arr|truncate(9) }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|upper",
|
||||
"{{ arr|upper }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|lower",
|
||||
"{{ arr|lower }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|replace",
|
||||
"{{ arr|replace('a', 'b') }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|trim",
|
||||
"{{ arr|trim }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|wordcount",
|
||||
"{{ arr|wordcount }}",
|
||||
json::object(),
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
static void test_array_methods(testing & t) {
|
||||
|
|
@ -1289,6 +1355,108 @@ static void test_array_methods(testing & t) {
|
|||
// {{"arr", json::array({"a", "b", "c"})}},
|
||||
// "a,x,b,c"
|
||||
// );
|
||||
|
||||
test_template(t, "undefined|select",
|
||||
"{% for item in items|select('odd') %}{{ item.name }} {% endfor %}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|selectattr",
|
||||
"{% for item in items|selectattr('active') %}{{ item.name }} {% endfor %}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|reject",
|
||||
"{% for item in items|reject('even') %}{{ item.name }} {% endfor %}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|rejectattr",
|
||||
"{% for item in items|rejectattr('active') %}{{ item.name }} {% endfor %}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|list",
|
||||
"{{ arr|list|string }}",
|
||||
json::object(),
|
||||
"[]"
|
||||
);
|
||||
|
||||
test_template(t, "undefined|string",
|
||||
"{{ arr|string }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|first",
|
||||
"{{ arr|first }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|last",
|
||||
"{{ arr|last }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|length",
|
||||
"{{ arr|length }}",
|
||||
json::object(),
|
||||
"0"
|
||||
);
|
||||
|
||||
test_template(t, "undefined|join",
|
||||
"{{ arr|join }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|sort",
|
||||
"{{ arr|sort|string }}",
|
||||
json::object(),
|
||||
"[]"
|
||||
);
|
||||
|
||||
test_template(t, "undefined|reverse",
|
||||
"{{ arr|reverse|join }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|map",
|
||||
"{% for v in arr|map(attribute='age') %}{{ v }} {% endfor %}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|min",
|
||||
"{{ arr|min }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|max",
|
||||
"{{ arr|max }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|unique",
|
||||
"{{ arr|unique|join }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
|
||||
test_template(t, "undefined|sum",
|
||||
"{{ arr|sum }}",
|
||||
json::object(),
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
static void test_object_methods(testing & t) {
|
||||
|
|
@ -1393,6 +1561,12 @@ static void test_object_methods(testing & t) {
|
|||
json::object(),
|
||||
"True"
|
||||
);
|
||||
|
||||
test_template(t, "undefined|items",
|
||||
"{{ arr|items|join }}",
|
||||
json::object(),
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
static void test_hasher(testing & t) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue