diff --git a/common/jinja/value.cpp b/common/jinja/value.cpp index 2aa156b177..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 @@ -715,8 +716,44 @@ 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 { + 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; + 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); + } + auto res = mk_val(indented); + res->val_str.mark_input_based_on(val_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..c48c396eeb 100644 --- a/tests/test-jinja.cpp +++ b/tests/test-jinja.cpp @@ -691,6 +691,36 @@ static void test_filters(testing & t) { "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}" ); + test_template(t, "indent", + "{{ 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 with string", + "{{ data|indent(width='>>>>') }}", + {{ "data", "foo\nbar" }}, + "foo\n>>>>bar" + ); + test_template(t, "chained filters", "{{ ' HELLO '|trim|lower }}", json::object(),