Skip to content

Commit

Permalink
add set statements
Browse files Browse the repository at this point in the history
  • Loading branch information
pantor committed Aug 3, 2020
1 parent 6d46bf6 commit 889da4d
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 17 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5)


project(inja LANGUAGES CXX VERSION 3.0.0)
project(inja LANGUAGES CXX VERSION 3.1.0)


option(INJA_USE_EMBEDDED_JSON "Use the shipped json header if not available on the system" ON)
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ inja::render("Hello {{ name }}!", data); // Returns "Hello world!"
## Integration
Inja is a headers only library, which can be downloaded from the [releases](https://github.com/pantor/inja/releases) or directly from the `include/` or `single_include/` folder. Inja uses `nlohmann/json.hpp` as its single dependency, so make sure it can be included from `inja.hpp`. json can be downloaded [here](https://github.com/nlohmann/json/releases). Then integration is as easy as:
Inja is a headers only library, which can be downloaded from the [releases](https://github.com/pantor/inja/releases) or directly from the `include/` or `single_include/` folder. Inja uses `nlohmann/json.hpp` (>= v3.8.0) as its single dependency, so make sure it can be included from `inja.hpp`. json can be downloaded [here](https://github.com/nlohmann/json/releases). Then integration is as easy as:
```.cpp
#include <inja.hpp>
Expand Down Expand Up @@ -308,6 +308,13 @@ render("Hello{# Todo #}!", data); // "Hello!"
```
### Set Statements
Variables can also be defined within the template using the set statment.
```.cpp
render("{% set new_hour=23 %}{{ new_hour }}pm", data); // "23pm"
```

## Supported compilers

Inja uses `string_view` from C++17, but includes the [polyfill](https://github.com/martinmoene/string-view-lite) from martinmoene. This way, the minimum version is C++11. Currently, the following compilers are tested:
Expand Down
2 changes: 1 addition & 1 deletion include/inja/inja.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 Pantor. All rights reserved.
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_INJA_HPP_
#define INCLUDE_INJA_INJA_HPP_
Expand Down
14 changes: 14 additions & 0 deletions include/inja/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ForArrayStatementNode;
class ForObjectStatementNode;
class IfStatementNode;
class IncludeStatementNode;
class SetStatementNode;


class NodeVisitor {
Expand All @@ -45,6 +46,7 @@ class NodeVisitor {
virtual void visit(const ForObjectStatementNode& node) = 0;
virtual void visit(const IfStatementNode& node) = 0;
virtual void visit(const IncludeStatementNode& node) = 0;
virtual void visit(const SetStatementNode& node) = 0;
};

/*!
Expand Down Expand Up @@ -311,6 +313,18 @@ class IncludeStatementNode : public StatementNode {
};
};

class SetStatementNode : public StatementNode {
public:
std::string key;
ExpressionListNode expression;

explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { }

void accept(NodeVisitor& v) const {
v.visit(*this);
};
};

} // namespace inja

#endif // INCLUDE_INJA_NODE_HPP_
23 changes: 23 additions & 0 deletions include/inja/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,29 @@ class Parser {

get_next_token();

} else if (tok.text == static_cast<decltype(tok.text)>("set")) {
get_next_token();

if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected variable name, got '" + tok.describe() + "'");
}

std::string key = static_cast<std::string>(tok.text);
get_next_token();

auto set_statement_node = std::make_shared<SetStatementNode>(key, tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(set_statement_node);
current_expression_list = &set_statement_node->expression;

if (tok.text != static_cast<decltype(tok.text)>("=")) {
throw_parser_error("expected '=', got '" + tok.describe() + "'");
}
get_next_token();

if (!parse_expression(tmpl, closing)) {
return false;
}

} else {
return false;
}
Expand Down
32 changes: 18 additions & 14 deletions include/inja/renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class Renderer : public NodeVisitor {
const json *json_input;
std::ostream *output_stream;

json json_loop_data;
json* current_loop_data = &json_loop_data["loop"];
json json_additional_data;
json* current_loop_data = &json_additional_data["loop"];

std::vector<std::shared_ptr<json>> json_tmp_stack;
std::stack<const json*> json_eval_stack;
Expand Down Expand Up @@ -161,8 +161,8 @@ class Renderer : public NodeVisitor {

try {
// First try to evaluate as a loop variable
if (json_loop_data.contains(ptr)) {
json_eval_stack.push(&json_loop_data.at(ptr));
if (json_additional_data.contains(ptr)) {
json_eval_stack.push(&json_additional_data.at(ptr));
} else {
json_eval_stack.push(&json_input->at(ptr));
}
Expand Down Expand Up @@ -502,7 +502,7 @@ class Renderer : public NodeVisitor {

size_t index = 0;
for (auto it = result->begin(); it != result->end(); ++it) {
json_loop_data[static_cast<std::string>(node.value)] = *it;
json_additional_data[static_cast<std::string>(node.value)] = *it;

(*current_loop_data)["index"] = index;
(*current_loop_data)["index1"] = index + 1;
Expand All @@ -513,12 +513,12 @@ class Renderer : public NodeVisitor {
++index;
}

json_loop_data[static_cast<std::string>(node.value)].clear();
json_additional_data[static_cast<std::string>(node.value)].clear();
if (!(*current_loop_data)["parent"].empty()) {
auto tmp = (*current_loop_data)["parent"];
*current_loop_data = std::move(tmp);
} else {
current_loop_data = &json_loop_data["loop"];
current_loop_data = &json_additional_data["loop"];
}
}

Expand All @@ -534,8 +534,8 @@ class Renderer : public NodeVisitor {

size_t index = 0;
for (auto it = result->begin(); it != result->end(); ++it) {
json_loop_data[static_cast<std::string>(node.key)] = it.key();
json_loop_data[static_cast<std::string>(node.value)] = it.value();
json_additional_data[static_cast<std::string>(node.key)] = it.key();
json_additional_data[static_cast<std::string>(node.value)] = it.value();

(*current_loop_data)["index"] = index;
(*current_loop_data)["index1"] = index + 1;
Expand All @@ -546,12 +546,12 @@ class Renderer : public NodeVisitor {
++index;
}

json_loop_data[static_cast<std::string>(node.key)].clear();
json_loop_data[static_cast<std::string>(node.value)].clear();
json_additional_data[static_cast<std::string>(node.key)].clear();
json_additional_data[static_cast<std::string>(node.value)].clear();
if (!(*current_loop_data)["parent"].empty()) {
*current_loop_data = std::move((*current_loop_data)["parent"]);
} else {
current_loop_data = &json_loop_data["loop"];
current_loop_data = &json_additional_data["loop"];
}
}

Expand All @@ -569,12 +569,16 @@ class Renderer : public NodeVisitor {
auto included_template_it = template_storage.find(node.file);

if (included_template_it != template_storage.end()) {
sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_loop_data);
sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data);
} else if (config.throw_at_missing_includes) {
throw_renderer_error("include '" + node.file + "' not found", node);
}
}

void visit(const SetStatementNode& node) {
json_additional_data[node.key] = *eval_expression_list(node.expression);
}

public:
Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage)
: config(config), template_storage(template_storage), function_storage(function_storage) { }
Expand All @@ -584,7 +588,7 @@ class Renderer : public NodeVisitor {
current_template = &tmpl;
json_input = &data;
if (loop_data) {
json_loop_data = *loop_data;
json_additional_data = *loop_data;
}

current_template->root.accept(*this);
Expand Down
2 changes: 2 additions & 0 deletions include/inja/statistics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class StatisticsVisitor : public NodeVisitor {

void visit(const IncludeStatementNode&) { }

void visit(const SetStatementNode&) { }

public:
unsigned int variable_counter;

Expand Down
7 changes: 7 additions & 0 deletions test/test-renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ TEST_CASE("types") {
"[inja.exception.parser_error] (at 1:43) expected statement, got 'end'");
}

SUBCASE("set statements") {
CHECK(env.render("{% set predefined=true %}{% if predefined %}a{% endif %}", data) == "a");
CHECK(env.render("{% set predefined=false %}{% if predefined %}a{% endif %}", data) == "");
CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data),
"[inja.exception.render_error] (at 1:7) variable 'predefined' not found");
}

SUBCASE("line statements") {
CHECK(env.render(R""""(## if is_happy
Yeah!
Expand Down

0 comments on commit 889da4d

Please sign in to comment.