Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions common/jinja/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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>();
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
174 changes: 174 additions & 0 deletions tests/test-jinja.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"})}},
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Loading