| 973 | : TemplateNode(loc), var_names(var_names), iterable(std::move(iterable)), condition(std::move(condition)), body(std::move(body)), recursive(recursive), else_body(std::move(else_body)) {} |
| 974 | |
| 975 | void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override { |
| 976 | // https://jinja.palletsprojects.com/en/3.0.x/templates/#for |
| 977 | if (!iterable) throw std::runtime_error("ForNode.iterable is null"); |
| 978 | if (!body) throw std::runtime_error("ForNode.body is null"); |
| 979 | |
| 980 | auto iterable_value = iterable->evaluate(context); |
| 981 | Value::CallableType loop_function; |
| 982 | |
| 983 | std::function<void(Value&)> visit = [&](Value& iter) { |
| 984 | auto filtered_items = Value::array(); |
| 985 | if (!iter.is_null()) { |
| 986 | if (!iterable_value.is_iterable()) { |
| 987 | throw std::runtime_error("For loop iterable must be iterable: " + iterable_value.dump()); |
| 988 | } |
| 989 | iterable_value.for_each([&](Value & item) { |
| 990 | destructuring_assign(var_names, context, item); |
| 991 | if (!condition || condition->evaluate(context).to_bool()) { |
| 992 | filtered_items.push_back(item); |
| 993 | } |
| 994 | }); |
| 995 | } |
| 996 | if (filtered_items.empty()) { |
| 997 | if (else_body) { |
| 998 | else_body->render(out, context); |
| 999 | } |
| 1000 | } else { |
| 1001 | auto loop = recursive ? Value::callable(loop_function) : Value::object(); |
| 1002 | loop.set("length", (int64_t) filtered_items.size()); |
| 1003 | |
| 1004 | size_t cycle_index = 0; |
| 1005 | loop.set("cycle", Value::callable([&](const std::shared_ptr<Context> &, ArgumentsValue & args) { |
| 1006 | if (args.args.empty() || !args.kwargs.empty()) { |
| 1007 | throw std::runtime_error("cycle() expects at least 1 positional argument and no named arg"); |
| 1008 | } |
| 1009 | auto item = args.args[cycle_index]; |
| 1010 | cycle_index = (cycle_index + 1) % args.args.size(); |
| 1011 | return item; |
| 1012 | })); |
| 1013 | auto loop_context = Context::make(Value::object(), context); |
| 1014 | loop_context->set("loop", loop); |
| 1015 | for (size_t i = 0, n = filtered_items.size(); i < n; ++i) { |
| 1016 | auto & item = filtered_items.at(i); |
| 1017 | destructuring_assign(var_names, loop_context, item); |
| 1018 | loop.set("index", (int64_t) i + 1); |
| 1019 | loop.set("index0", (int64_t) i); |
| 1020 | loop.set("revindex", (int64_t) (n - i)); |
| 1021 | loop.set("revindex0", (int64_t) (n - i - 1)); |
| 1022 | loop.set("length", (int64_t) n); |
| 1023 | loop.set("first", i == 0); |
| 1024 | loop.set("last", i == (n - 1)); |
| 1025 | loop.set("previtem", i > 0 ? filtered_items.at(i - 1) : Value()); |
| 1026 | loop.set("nextitem", i < n - 1 ? filtered_items.at(i + 1) : Value()); |
| 1027 | try { |
| 1028 | body->render(out, loop_context); |
| 1029 | } catch (const LoopControlException & e) { |
| 1030 | if (e.control_type == LoopControlType::Break) break; |
| 1031 | if (e.control_type == LoopControlType::Continue) continue; |
| 1032 | } |
nothing calls this directly
no test coverage detected