From 92b0342b9c3abf1c4f97bde3b096c1a9efda7756 Mon Sep 17 00:00:00 2001 From: Piotr Wilkin Date: Wed, 11 Feb 2026 23:34:54 +0100 Subject: [PATCH 1/3] Add partial Jinja support for "indent" string filter --- common/jinja/value.cpp | 22 ++++++++++++++++++++-- tests/test-jinja.cpp | 6 ++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/common/jinja/value.cpp b/common/jinja/value.cpp index 2aa156b177..38da1df6d3 100644 --- a/common/jinja/value.cpp +++ b/common/jinja/value.cpp @@ -715,8 +715,26 @@ const func_builtins & value_string_t::get_builtins() const { return args.get_pos(0); }}, {"tojson", tojson}, - {"indent", [](const func_args &) -> value { - throw not_implemented_exception("String indent builtin not implemented"); + {"indent", [](const func_args &args) -> value { + // no support for "first" as that would require us to somehow access generation context + args.ensure_count(2, 4); + args.ensure_vals(true, true, false, false); + + auto input = args.get_pos(0); + auto arg0 = args.get_pos(1); + + int count = arg0->as_int(); + if (count <= 0) { + throw raised_exception("indent must be a positive number"); + } + std::string indented; + for (int i = 0; i < count; i++) { + indented.append(" "); + } + indented.append(input->as_string().str()); + auto res = mk_val(indented); + res->val_str.mark_input_based_on(input->as_string()); + return res; }}, {"join", [](const func_args &) -> value { throw not_implemented_exception("String join builtin not implemented"); diff --git a/tests/test-jinja.cpp b/tests/test-jinja.cpp index 1f25c6ae71..db0bc39294 100644 --- a/tests/test-jinja.cpp +++ b/tests/test-jinja.cpp @@ -691,6 +691,12 @@ static void test_filters(testing & t) { "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}" ); + test_template(t, "indent", + "{{ data|indent(4)}}", + { "data", "foo" }, + " foo" + ); + test_template(t, "chained filters", "{{ ' HELLO '|trim|lower }}", json::object(), From 121556f2ed49fd7c6ad50dd71a4784f3bf329cd4 Mon Sep 17 00:00:00 2001 From: Piotr Wilkin Date: Thu, 12 Feb 2026 01:14:37 +0100 Subject: [PATCH 2/3] Fully implement indent --- common/jinja/value.cpp | 47 +++++++++++++++++++++++++++++------------- tests/test-jinja.cpp | 18 +++++++++++++--- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/common/jinja/value.cpp b/common/jinja/value.cpp index 38da1df6d3..bffa39e420 100644 --- a/common/jinja/value.cpp +++ b/common/jinja/value.cpp @@ -4,6 +4,7 @@ // for converting from JSON to jinja values #include +#include #include #include #include @@ -716,24 +717,42 @@ const func_builtins & value_string_t::get_builtins() const { }}, {"tojson", tojson}, {"indent", [](const func_args &args) -> value { - // no support for "first" as that would require us to somehow access generation context - args.ensure_count(2, 4); - args.ensure_vals(true, true, false, false); - - auto input = args.get_pos(0); - auto arg0 = args.get_pos(1); - - int count = arg0->as_int(); - if (count <= 0) { - throw raised_exception("indent must be a positive number"); + args.ensure_count(1, 4); + value val_input = args.get_pos(0); + value val_width = args.get_kwarg_or_pos("width", 1); + const bool first = args.get_kwarg_or_pos("first", 2)->as_bool(); // undefined == false + const bool blank = args.get_kwarg_or_pos("blank", 3)->as_bool(); // undefined == false + if (!is_val(val_input)) { + throw raised_exception("indent() first argument must be a string"); + } + std::string indent; + if (is_val(val_width)) { + indent.assign(val_width->as_int(), ' '); + } else if (is_val(val_width)) { + indent = val_width->as_string().str(); + } else { + indent = " "; } std::string indented; - for (int i = 0; i < count; i++) { - indented.append(" "); + std::istringstream iss = std::istringstream(val_input->as_string().str()); + std::string line; + bool first_line = true; + while (std::getline(iss, line)) { + bool do_indent = true; + if (first_line) { + first_line = false; + do_indent &= first; + } else { + indented.append("\n"); + } + do_indent &= blank || !line.empty(); + if (do_indent) { + indented.append(indent); + } + indented.append(line); } - indented.append(input->as_string().str()); auto res = mk_val(indented); - res->val_str.mark_input_based_on(input->as_string()); + res->val_str.mark_input_based_on(val_input->as_string()); return res; }}, {"join", [](const func_args &) -> value { diff --git a/tests/test-jinja.cpp b/tests/test-jinja.cpp index db0bc39294..0f6c7edca0 100644 --- a/tests/test-jinja.cpp +++ b/tests/test-jinja.cpp @@ -692,9 +692,21 @@ static void test_filters(testing & t) { ); test_template(t, "indent", - "{{ data|indent(4)}}", - { "data", "foo" }, - " foo" + "{{ data|indent(4) }}", + {{ "data", "foo\nbar" }}, + "foo\n bar" + ); + + test_template(t, "indent first only", + "{{ data|indent(width=4,first=true) }}", + {{ "data", "foo\nbar" }}, + " foo\n bar" + ); + + test_template(t, "indent blank lines and first line", + "{{ data|indent(width=4,blank=true,first=true) }}", + {{ "data", "foo\n\nbar" }}, + " foo\n \n bar" ); test_template(t, "chained filters", From 32d33c7abee2f5cad06f62f40315a54429065fdd Mon Sep 17 00:00:00 2001 From: Piotr Wilkin Date: Thu, 12 Feb 2026 01:19:22 +0100 Subject: [PATCH 3/3] Add tests for all width variants. --- tests/test-jinja.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/test-jinja.cpp b/tests/test-jinja.cpp index 0f6c7edca0..c48c396eeb 100644 --- a/tests/test-jinja.cpp +++ b/tests/test-jinja.cpp @@ -692,21 +692,33 @@ static void test_filters(testing & t) { ); test_template(t, "indent", - "{{ data|indent(4) }}", + "{{ data|indent(2) }}", + {{ "data", "foo\nbar" }}, + "foo\n bar" + ); + + test_template(t, "indent first only", + "{{ data|indent(width=3,first=true) }}", + {{ "data", "foo\nbar" }}, + " foo\n bar" + ); + + test_template(t, "indent blank lines and first line", + "{{ data|indent(width=5,blank=true,first=true) }}", + {{ "data", "foo\n\nbar" }}, + " foo\n \n bar" + ); + + test_template(t, "indent with default width", + "{{ data|indent() }}", {{ "data", "foo\nbar" }}, "foo\n bar" ); - test_template(t, "indent first only", - "{{ data|indent(width=4,first=true) }}", + test_template(t, "indent with string", + "{{ data|indent(width='>>>>') }}", {{ "data", "foo\nbar" }}, - " foo\n bar" - ); - - test_template(t, "indent blank lines and first line", - "{{ data|indent(width=4,blank=true,first=true) }}", - {{ "data", "foo\n\nbar" }}, - " foo\n \n bar" + "foo\n>>>>bar" ); test_template(t, "chained filters",