Skip to content

Commit f9c1743

Browse files
author
ochafik
committed
minja: fix iterables
1 parent 8299fac commit f9c1743

File tree

2 files changed

+33
-6
lines changed

2 files changed

+33
-6
lines changed

common/minja.hpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ class Value : public std::enable_shared_from_this<Value> {
249249
bool is_number_float() const { return primitive_.is_number_float(); }
250250
bool is_number() const { return primitive_.is_number(); }
251251
bool is_string() const { return primitive_.is_string(); }
252+
bool is_iterable() const { return is_array() || is_object() || is_string(); }
252253

253254
bool is_primitive() const { return !array_ && !object_ && !callable_; }
254255
bool is_hashable() const { return is_primitive(); }
@@ -262,6 +263,28 @@ class Value : public std::enable_shared_from_this<Value> {
262263
return false;
263264
}
264265

266+
void for_each(const std::function<void(Value &)> & callback) const {
267+
if (is_null())
268+
throw std::runtime_error("Undefined value or reference");
269+
if (array_) {
270+
for (auto& item : *array_) {
271+
callback(item);
272+
}
273+
} else if (object_) {
274+
for (auto & item : *object_) {
275+
Value key(item.first);
276+
callback(key);
277+
}
278+
} else if (is_string()) {
279+
for (char c : primitive_.get<std::string>()) {
280+
auto val = Value(std::string(1, c));
281+
callback(val);
282+
}
283+
} else {
284+
throw std::runtime_error("Value is not iterable: " + dump());
285+
}
286+
}
287+
265288
bool to_bool() const {
266289
if (is_null()) return false;
267290
if (is_boolean()) return get<bool>();
@@ -829,16 +852,15 @@ class ForNode : public TemplateNode {
829852
std::function<void(Value&)> visit = [&](Value& iter) {
830853
auto filtered_items = Value::array();
831854
if (!iter.is_null()) {
832-
if (!iterable_value.is_array()) {
855+
if (!iterable_value.is_iterable()) {
833856
throw std::runtime_error("For loop iterable must be iterable: " + iterable_value.dump());
834857
}
835-
for (size_t i = 0, n = iter.size(); i < n; ++i) {
836-
auto item = iter.at(i);
858+
iterable_value.for_each([&](Value & item) {
837859
destructuring_assign(var_names, context, item);
838860
if (!condition || condition->evaluate(context).to_bool()) {
839861
filtered_items.push_back(item);
840862
}
841-
}
863+
});
842864
}
843865
if (filtered_items.empty()) {
844866
if (else_body) {
@@ -1115,7 +1137,7 @@ class BinaryOpExpr : public Expression {
11151137
if (name == "number") return l.is_number();
11161138
if (name == "string") return l.is_string();
11171139
if (name == "mapping") return l.is_object();
1118-
if (name == "iterable") return l.is_array();
1140+
if (name == "iterable") return l.is_iterable();
11191141
if (name == "sequence") return l.is_array();
11201142
if (name == "defined") return !l.is_null();
11211143
throw std::runtime_error("Unknown type for 'is' operator: " + name);

tests/test-minja.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ static void test_error_contains(const std::string & template_str, const json & b
119119
cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -t test-minja -j && ./build/bin/test-minja
120120
*/
121121
int main() {
122+
test_render(R"({{ {} is mapping }},{{ '' is mapping }})", {}, {}, "True,False");
123+
test_render(R"({{ {} is iterable }},{{ '' is iterable }})", {}, {}, "True,True");
124+
test_render(R"({% for x in ["a", "b"] %}{{ x }},{% endfor %})", {}, {}, "a,b,");
125+
test_render(R"({% for x in {"a": 1, "b": 2} %}{{ x }},{% endfor %})", {}, {}, "a,b,");
126+
test_render(R"({% for x in "ab" %}{{ x }},{% endfor %})", {}, {}, "a,b,");
122127
test_render(R"({{ 'foo bar'.title() }})", {}, {}, "Foo Bar");
123128
test_render(R"({{ 1 | safe }})", {}, {}, "1");
124129
test_render(R"({{ 'abc'.endswith('bc') }},{{ ''.endswith('a') }})", {}, {}, "True,False");
@@ -261,7 +266,7 @@ int main() {
261266
{{- x | tojson -}},
262267
{%- endfor -%}
263268
)", {}, {},
264-
R"(1,1.2,"a",True,True,False,False,null,[],[1],[1, 2],{},{"a": 1},{"1": "b"},)");
269+
R"(1,1.2,"a",true,true,false,false,null,[],[1],[1, 2],{},{"a": 1},{"1": "b"},)");
265270
test_render(
266271
R"(
267272
{%- set n = namespace(value=1, title='') -%}

0 commit comments

Comments
 (0)