From d5532ac26aa2a339793835b96e3ef88d5b720c23 Mon Sep 17 00:00:00 2001 From: pantor Date: Sun, 7 Apr 2019 16:15:12 +0200 Subject: [PATCH] add at function --- README.md | 1 + include/inja/bytecode.hpp | 1 + include/inja/parser.hpp | 5 +++-- include/inja/renderer.hpp | 15 +++++++++++---- single_include/inja/inja.hpp | 21 +++++++++++++++------ test/unit-renderer.cpp | 9 +++++++-- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b3a2392..4a1c0ec 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ render("Hello {{ lower(neighbour) }}!", data); // "Hello peter!" // Range function, useful for loops render("{% for i in range(4) %}{{ loop.index1 }}{% endfor %}", data); // "1234" +render("{% for i in range(3) %}{{ at(guests, i) }} {% endfor %}", data); // "Jeff Tom Patrick " // Length function (please don't combine with range, use list directly...) render("I count {{ length(guests) }} guests.", data); // "I count 3 guests." diff --git a/include/inja/bytecode.hpp b/include/inja/bytecode.hpp index f473691..8a3bce3 100644 --- a/include/inja/bytecode.hpp +++ b/include/inja/bytecode.hpp @@ -38,6 +38,7 @@ struct Bytecode { GreaterEqual, Less, LessEqual, + At, Different, DivisibleBy, Even, diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index ca63b16..c2364ba 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -18,6 +18,7 @@ namespace inja { class ParserStatic { ParserStatic() { + functions.add_builtin("at", 2, Bytecode::Op::At); functions.add_builtin("default", 2, Bytecode::Op::Default); functions.add_builtin("divisibleBy", 2, Bytecode::Op::DivisibleBy); functions.add_builtin("even", 1, Bytecode::Op::Even); @@ -183,8 +184,8 @@ class Parser { append_callback(tmpl, func_token.text, num_args); return true; } - } else if (m_tok.text == static_cast("true") || - m_tok.text == static_cast("false") || + } else if (m_tok.text == static_cast("true") || + m_tok.text == static_cast("false") || m_tok.text == static_cast("null")) { // true, false, null are json literals if (brace_level == 0 && bracket_level == 0) { diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index a877bf9..2266c9a 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -132,8 +132,8 @@ class Renderer { enum class Type { Map, Array }; Type loop_type; - nonstd::string_view key_name; // variable name for keys - nonstd::string_view value_name; // variable name for values + nonstd::string_view key_name; // variable name for keys + nonstd::string_view value_name; // variable name for values json data; // data with loop info added json values; // values to iterate over @@ -145,8 +145,8 @@ class Renderer { // loop over map using KeyValue = std::pair; using MapValues = std::vector; - MapValues map_values; // values to iterate over - MapValues::iterator map_it; // iterator over values + MapValues map_values; // values to iterate over + MapValues::iterator map_it; // iterator over values }; @@ -235,6 +235,13 @@ class Renderer { m_stack.emplace_back(std::move(result)); break; } + case Bytecode::Op::At: { + auto args = get_args(bc); + auto result = args[0]->at(args[1]->get()); + pop_args(bc); + m_stack.emplace_back(result); + break; + } case Bytecode::Op::First: { auto result = get_args(bc)[0]->front(); pop_args(bc); diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index fa327b8..679b420 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -1441,6 +1441,7 @@ struct Bytecode { GreaterEqual, Less, LessEqual, + At, Different, DivisibleBy, Even, @@ -2017,6 +2018,7 @@ namespace inja { class ParserStatic { ParserStatic() { + functions.add_builtin("at", 2, Bytecode::Op::At); functions.add_builtin("default", 2, Bytecode::Op::Default); functions.add_builtin("divisibleBy", 2, Bytecode::Op::DivisibleBy); functions.add_builtin("even", 1, Bytecode::Op::Even); @@ -2182,8 +2184,8 @@ class Parser { append_callback(tmpl, func_token.text, num_args); return true; } - } else if (m_tok.text == static_cast("true") || - m_tok.text == static_cast("false") || + } else if (m_tok.text == static_cast("true") || + m_tok.text == static_cast("false") || m_tok.text == static_cast("null")) { // true, false, null are json literals if (brace_level == 0 && bracket_level == 0) { @@ -2738,8 +2740,8 @@ class Renderer { enum class Type { Map, Array }; Type loop_type; - nonstd::string_view key_name; // variable name for keys - nonstd::string_view value_name; // variable name for values + nonstd::string_view key_name; // variable name for keys + nonstd::string_view value_name; // variable name for values json data; // data with loop info added json values; // values to iterate over @@ -2751,8 +2753,8 @@ class Renderer { // loop over map using KeyValue = std::pair; using MapValues = std::vector; - MapValues map_values; // values to iterate over - MapValues::iterator map_it; // iterator over values + MapValues map_values; // values to iterate over + MapValues::iterator map_it; // iterator over values }; @@ -2841,6 +2843,13 @@ class Renderer { m_stack.emplace_back(std::move(result)); break; } + case Bytecode::Op::At: { + auto args = get_args(bc); + auto result = args[0]->at(args[1]->get()); + pop_args(bc); + m_stack.emplace_back(result); + break; + } case Bytecode::Op::First: { auto result = get_args(bc)[0]->front(); pop_args(bc); diff --git a/test/unit-renderer.cpp b/test/unit-renderer.cpp index bcd56dd..6380948 100644 --- a/test/unit-renderer.cpp +++ b/test/unit-renderer.cpp @@ -62,11 +62,10 @@ TEST_CASE("types") { CHECK( env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if not loop.is_last %}, {% endif %}{% endfor %}!", data) == "0: Jeff, 1: Seb!" ); CHECK( env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if loop.is_last == false %}, {% endif %}{% endfor %}!", data) == "0: Jeff, 1: Seb!" ); - data["empty_loop"] = {}; - CHECK( env.render("{% for name in empty_loop %}a{% endfor %}", data) == "" ); CHECK( env.render("{% for name in {} %}a{% endfor %}", data) == "" ); CHECK_THROWS_WITH( env.render("{% for name ins names %}a{% endfor %}", data), "[inja.exception.parser_error] expected 'in', got 'ins'" ); + CHECK_THROWS_WITH( env.render("{% for name in empty_loop %}a{% endfor %}", data), "[inja.exception.render_error] variable 'empty_loop' not found" ); // CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is object" ); } @@ -144,6 +143,7 @@ TEST_CASE("functions") { data["brother"]["daughters"] = {"Maria", "Helen"}; data["property"] = "name"; data["age"] = 29; + data["i"] = 1; data["is_happy"] = true; data["is_sad"] = false; data["vars"] = {2, 3, 4, 0, -1, -2, -3}; @@ -182,6 +182,11 @@ TEST_CASE("functions") { // CHECK_THROWS_WITH( env.render("{{ sort(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" ); } + SECTION("at") { + CHECK( env.render("{{ at(names, 0) }}", data) == "Jeff" ); + CHECK( env.render("{{ at(names, i) }}", data) == "Seb" ); + } + SECTION("first") { CHECK( env.render("{{ first(names) }}", data) == "Jeff" ); // CHECK_THROWS_WITH( env.render("{{ first(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );