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:
+54
-7
@@ -114,6 +114,18 @@ static T slice(const T & array, int64_t start, int64_t stop, int64_t step = 1) {
|
|||||||
return result;
|
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>
|
template<typename T>
|
||||||
static value test_type_fn(const func_args & args) {
|
static value test_type_fn(const func_args & args) {
|
||||||
args.ensure_count(1);
|
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);
|
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);
|
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>
|
template<value_compare_op op>
|
||||||
static value test_compare_fn(const func_args & args) {
|
static value test_compare_fn(const func_args & args) {
|
||||||
args.ensure_count(2, 2);
|
args.ensure_count(2, 2);
|
||||||
@@ -347,8 +366,8 @@ const func_builtins & global_builtins() {
|
|||||||
{"test_is_integer", test_type_fn<value_int>},
|
{"test_is_integer", test_type_fn<value_int>},
|
||||||
{"test_is_float", test_type_fn<value_float>},
|
{"test_is_float", test_type_fn<value_float>},
|
||||||
{"test_is_number", test_type_fn<value_int, value_float>},
|
{"test_is_number", test_type_fn<value_int, value_float>},
|
||||||
{"test_is_iterable", 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>},
|
{"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>},
|
||||||
{"test_is_mapping", test_type_fn<value_object>},
|
{"test_is_mapping", test_type_fn<value_object>},
|
||||||
{"test_is_lower", [](const func_args & args) -> value {
|
{"test_is_lower", [](const func_args & args) -> value {
|
||||||
args.ensure_vals<value_string>();
|
args.ensure_vals<value_string>();
|
||||||
@@ -1003,7 +1022,12 @@ const func_builtins & value_none_t::get_builtins() const {
|
|||||||
static const func_builtins builtins = {
|
static const func_builtins builtins = {
|
||||||
{"default", default_value},
|
{"default", default_value},
|
||||||
{"tojson", tojson},
|
{"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;
|
return builtins;
|
||||||
}
|
}
|
||||||
@@ -1012,10 +1036,33 @@ const func_builtins & value_none_t::get_builtins() const {
|
|||||||
const func_builtins & value_undefined_t::get_builtins() const {
|
const func_builtins & value_undefined_t::get_builtins() const {
|
||||||
static const func_builtins builtins = {
|
static const func_builtins builtins = {
|
||||||
{"default", default_value},
|
{"default", default_value},
|
||||||
{"tojson", [](const func_args & args) -> value {
|
{"capitalize", empty_value_fn<value_string>},
|
||||||
args.ensure_vals<value_undefined>();
|
{"first", empty_value_fn<value_undefined>},
|
||||||
return mk_val<value_string>("null");
|
{"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;
|
return builtins;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,6 +329,12 @@ static void test_loops(testing & t) {
|
|||||||
"empty"
|
"empty"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_template(t, "for undefined empty",
|
||||||
|
"{% for i in items %}{{ i }}{% else %}empty{% endfor %}",
|
||||||
|
json::object(),
|
||||||
|
"empty"
|
||||||
|
);
|
||||||
|
|
||||||
test_template(t, "nested for",
|
test_template(t, "nested for",
|
||||||
"{% for i in a %}{% for j in b %}{{ i }}{{ j }}{% endfor %}{% endfor %}",
|
"{% for i in a %}{% for j in b %}{{ i }}{{ j }}{% endfor %}{% endfor %}",
|
||||||
{{"a", json::array({1, 2})}, {"b", json::array({"x", "y"})}},
|
{{"a", json::array({1, 2})}, {"b", json::array({"x", "y"})}},
|
||||||
@@ -1018,6 +1024,18 @@ static void test_tests(testing & t) {
|
|||||||
{{"x", {{"a", 1}}}},
|
{{"x", {{"a", 1}}}},
|
||||||
"yes"
|
"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) {
|
static void test_string_methods(testing & t) {
|
||||||
@@ -1122,6 +1140,54 @@ static void test_string_methods(testing & t) {
|
|||||||
{{"s", "banana"}},
|
{{"s", "banana"}},
|
||||||
"bXnXna"
|
"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) {
|
static void test_array_methods(testing & t) {
|
||||||
@@ -1289,6 +1355,108 @@ static void test_array_methods(testing & t) {
|
|||||||
// {{"arr", json::array({"a", "b", "c"})}},
|
// {{"arr", json::array({"a", "b", "c"})}},
|
||||||
// "a,x,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) {
|
static void test_object_methods(testing & t) {
|
||||||
@@ -1393,6 +1561,12 @@ static void test_object_methods(testing & t) {
|
|||||||
json::object(),
|
json::object(),
|
||||||
"True"
|
"True"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_template(t, "undefined|items",
|
||||||
|
"{{ arr|items|join }}",
|
||||||
|
json::object(),
|
||||||
|
""
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_hasher(testing & t) {
|
static void test_hasher(testing & t) {
|
||||||
|
|||||||
Reference in New Issue
Block a user