diff --git a/README.md b/README.md index fce74a78..a45cc563 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,10 @@ Assignments only set the value within the rendering context; they do not modify A few functions are implemented within the inja template syntax. They can be called with ```.cpp -// Upper and lower function, for string cases +// Upper, lower and capitalize function, for string cases render("Hello {{ upper(neighbour) }}!", data); // "Hello PETER!" render("Hello {{ lower(neighbour) }}!", data); // "Hello peter!" +render("Hello {{ capitalize(neighbour) }}!", data); // "Hello Peter!" // Range function, useful for loops render("{% for i in range(4) %}{{ loop.index1 }}{% endfor %}", data); // "1234" diff --git a/include/inja/function_storage.hpp b/include/inja/function_storage.hpp index 6150ffff..9542d7a2 100644 --- a/include/inja/function_storage.hpp +++ b/include/inja/function_storage.hpp @@ -34,6 +34,7 @@ class FunctionStorage { Modulo, AtId, At, + Capitalize, Default, DivisibleBy, Even, @@ -76,6 +77,7 @@ class FunctionStorage { std::map, FunctionData> function_storage = { {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("capitalize", 1), FunctionData {Operation::Capitalize}}, {std::make_pair("default", 2), FunctionData {Operation::Default}}, {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, {std::make_pair("even", 1), FunctionData {Operation::Even}}, diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index ba784c08..efd936ed 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -309,6 +309,12 @@ class Renderer : public NodeVisitor { data_eval_stack.push(&args[0]->at(args[1]->get())); } } break; + case Op::Capitalize: { + auto result = get_arguments<1>(node)[0]->get(); + result[0] = static_cast(::toupper(result[0])); + std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast(::tolower(c)); }); + make_result(std::move(result)); + } break; case Op::Default: { const auto test_arg = get_arguments<1, 0, false>(node)[0]; data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 3535da0b..6c62e8da 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -123,6 +123,7 @@ class FunctionStorage { Modulo, AtId, At, + Capitalize, Default, DivisibleBy, Even, @@ -165,6 +166,7 @@ class FunctionStorage { std::map, FunctionData> function_storage = { {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("capitalize", 1), FunctionData {Operation::Capitalize}}, {std::make_pair("default", 2), FunctionData {Operation::Default}}, {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, {std::make_pair("even", 1), FunctionData {Operation::Even}}, @@ -2378,6 +2380,12 @@ class Renderer : public NodeVisitor { data_eval_stack.push(&args[0]->at(args[1]->get())); } } break; + case Op::Capitalize: { + auto result = get_arguments<1>(node)[0]->get(); + result[0] = std::toupper(result[0]); + std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast(::tolower(c)); }); + make_result(std::move(result)); + } break; case Op::Default: { const auto test_arg = get_arguments<1, 0, false>(node)[0]; data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); diff --git a/test/test-functions.cpp b/test/test-functions.cpp index 6f1c0a99..7a680899 100644 --- a/test/test-functions.cpp +++ b/test/test-functions.cpp @@ -58,6 +58,11 @@ TEST_CASE("functions") { // [json.exception.type_error.302] type must be string, but is number" ); } + SUBCASE("capitalize") { + CHECK(env.render("{{ capitalize(name) }}", data) == "Peter"); + CHECK(env.render("{{ capitalize(city) }}", data) == "New york"); + } + SUBCASE("range") { CHECK(env.render("{{ range(2) }}", data) == "[0,1]"); CHECK(env.render("{{ range(4) }}", data) == "[0,1,2,3]");