add round function, updated readme

This commit is contained in:
pantor
2017-08-21 17:16:29 +02:00
parent dd65af4457
commit a665f24ef7
2 changed files with 57 additions and 33 deletions

View File

@@ -1,7 +1,6 @@
[<div align="center"><img width="500" src="https://raw.githubusercontent.com/pantor/inja/master/doc/logo.jpg"></div>](https://github.com/pantor/inja/releases)
[![Build Status](https://travis-ci.org/pantor/inja.svg?branch=master)](https://travis-ci.org/pantor/inja)
[![Build status](https://ci.appveyor.com/api/projects/status/qtgniyyg6fn8ich8?svg=true)](https://ci.appveyor.com/project/pantor/inja)
[![Coverage Status](https://img.shields.io/coveralls/pantor/inja.svg)](https://coveralls.io/r/pantor/inja)
@@ -10,7 +9,7 @@
[![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/pantor/inja/master/LICENSE)
Inja is a template engine for modern C++, loosely inspired by [jinja](http://jinja.pocoo.org) for python. It has an easy and yet powerful template syntax with all variables, loops, conditions, includes, blocks, comments you need, nested and combined as you like. The rendering syntax is works like magic and uses the wonderful [json](https://github.com/nlohmann/json) library by nlohmann for data input. Most importantly, *inja* needs only two header files, which is (nearly) as trivial as integration in C++ can get. Of course, everything is tested on all relevant compilers. Have a look what it looks like:
Inja is a template engine for modern C++, loosely inspired by [jinja](http://jinja.pocoo.org) for python. It has an easy and yet powerful template syntax with all variables, loops, conditions, includes, blocks, comments you need, nested and combined as you like. Inja uses the wonderful [json](https://github.com/nlohmann/json) library by nlohmann for data input and handling. Most importantly, *inja* needs only two header files, which is (nearly) as trivial as integration in C++ can get. Of course, everything is tested on all relevant compilers. Have a look what it looks like:
```c++
json data;
@@ -36,7 +35,12 @@ using json = nlohmann::json;
## Tutorial
This tutorial will give you an idea how to use inja. It will explain the most important concepts and give practical advices using examples and exectuable code. Beside this tutorial, you can check the [documentation]() for further information.
### Template Rendering
The basic template rendering takes a template as a `std::string` and a `json` object for all data. It returns the rendered template as an `std::string`.
```c++
json data;
data["name"] = "world";
@@ -56,11 +60,11 @@ result = env.render_template("./template.txt", data);
result = env.render_template("./template.txt", "./data.json");
// Or write a rendered template file
env.write("./template.txt", "./result.txt")
env.write("./template.txt", data, "./result.txt")
env.write("./template.txt", "./data.json", "./result.txt")
```
The environment class can be configured.
The environment class can be configured to your needs.
```c++
// With default settings
Environment env_default = Environment();
@@ -80,7 +84,7 @@ env.setLineStatements("##"); // Line statement (just an opener)
### Variables
Variables can be rendered within the `{{ ... }}` expressions.
Variables are rendered within the `{{ ... }}` expressions.
```c++
json data;
data["neighbour"] = "Peter";
@@ -115,11 +119,11 @@ render(R"(Guest List:
2: Pierre
3: Tom */
```
In a loop, the special variables `number index`, `number index1`, `bool is_first` and `bool is_last` are available.
In a loop, the special variables `index (number)`, `index1 (number)`, `is_first (boolean)` and `is_last (boolean)` are available.
#### Conditions
Conditions support if, else if and else statements. Following conditions for example:
Conditions support the typical if, else if and else statements. Following conditions are for example possible:
```c++
// Standard comparisons with variable
render("{% if time/hour >= 18 %}…{% endif %}", data); // True
@@ -136,7 +140,7 @@ render("{% if not guest_count %}…{% endif %}", data); // True
#### Includes
Include other files, relative from the current file location.
This include other files, relative from the current file location.
```
{% include "footer.html" %}
```
@@ -145,7 +149,7 @@ Include other files, relative from the current file location.
A few functions are implemented within the inja template syntax. They can be called with
```c++
// upper(<string>)
// Upper and lower function, for string cases
render("Hello {{ upper(neighbour) }}!", data); // "Hello PETER!"
render("Hello {{ lower(neighbour) }}!", data); // "Hello peter!"
@@ -154,6 +158,10 @@ render("{% for i in range(4) %}{{ index1 }}{% endfor %}", data); // "1234"
// Length function (but please don't combine with range, use list directly...)
render("I count {{ length(guests) }} guests.", data); // "I count 3 guests."
// Round numbers to a given precision
render({{ round(3.1415, 0) }}, data) // 3
render({{ round(3.1415, 3) }}, data) // 3.142
```
### Comments

View File

@@ -102,8 +102,6 @@ inline Match search(const std::string& input, std::vector<Regex> regexes, size_t
if (not search_match.found()) { return Match(); }
// Vector of id vs groups
// 0: 1, 1: 2, 2: 3, 3: 2
// 0 1 1 2 2 2 3 3
std::vector<int> regex_mark_counts;
for (int i = 0; i < regexes.size(); i++) {
for (int j = 0; j < regexes[i].mark_count() + 1; j++) {
@@ -240,7 +238,8 @@ public:
Upper,
Lower,
Range,
Length
Length,
Round
};
const std::map<Function, Regex> regex_map_functions = {
@@ -248,6 +247,7 @@ public:
{Function::Lower, Regex{"lower\\(\\s*(.*?)\\s*\\)"}},
{Function::Range, Regex{"range\\(\\s*(.*?)\\s*\\)"}},
{Function::Length, Regex{"length\\(\\s*(.*?)\\s*\\)"}},
{Function::Round, Regex{"length\\(\\s*(.*?)\\s*,\\s*(.*?)\\s*\\)"}},
};
Parser() { }
@@ -373,10 +373,24 @@ public:
Environment(): Environment("./") { }
Environment(std::string global_path): global_path(global_path), parser() { }
void setExpression(std::string open, std::string close) {
parser.regex_map_delimiters[Parser::Delimiter::Expression] = Regex{open + close};
void setStatement(std::string open, std::string close) {
parser.regex_map_delimiters[Parser::Delimiter::Statement] = Regex{open + "\\s*(.+?)\\s*" + close};
}
void setLineStatement(std::string open) {
parser.regex_map_delimiters[Parser::Delimiter::LineStatement] = Regex{"(?:^|\\n)" + open + "\\s*(.+)\\s*"};
}
void setExpression(std::string open, std::string close) {
parser.regex_map_delimiters[Parser::Delimiter::Expression] = Regex{open + "\\s*(.+?)\\s*" + close};
}
void setComment(std::string open, std::string close) {
parser.regex_map_delimiters[Parser::Delimiter::Comment] = Regex{open + "\\s*(.+?)\\s*" + close};
}
json eval_variable(std::string input, json data) {
return eval_variable(input, data, true);
}
@@ -413,6 +427,13 @@ public:
if (not list.is_array()) { throw std::runtime_error("Argument in length function is not a list."); }
return list.size();
}
case Parser::Function::Round: {
json number = eval_variable(match_function.str(1), data);
json precision = eval_variable(match_function.str(2), data);
if (not number.is_number()) { throw std::runtime_error("Argument in length function is not a number."); }
if (not precision.is_number()) { throw std::runtime_error("Argument in length function is not a number."); }
return std::round(number.get<double>() * std::pow(10.0, precision.get<int>())) / std::pow(10.0, precision.get<int>());
}
}
if (input[0] != '/') { input.insert(0, "/"); }
@@ -441,34 +462,22 @@ public:
return (std::find(list.begin(), list.end(), item) != list.end());
}
case Parser::ConditionOperators::Equal: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 == comp2;
return eval_variable(match_condition.str(1), data) == eval_variable(match_condition.str(2), data);
}
case Parser::ConditionOperators::Greater: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 > comp2;
return eval_variable(match_condition.str(1), data) > eval_variable(match_condition.str(2), data);
}
case Parser::ConditionOperators::Less: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 < comp2;
return eval_variable(match_condition.str(1), data) < eval_variable(match_condition.str(2), data);
}
case Parser::ConditionOperators::GreaterEqual: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 >= comp2;
return eval_variable(match_condition.str(1), data) >= eval_variable(match_condition.str(2), data);
}
case Parser::ConditionOperators::LessEqual: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 <= comp2;
return eval_variable(match_condition.str(1), data) <= eval_variable(match_condition.str(2), data);
}
case Parser::ConditionOperators::Different: {
json comp1 = eval_variable(match_condition.str(1), data);
json comp2 = eval_variable(match_condition.str(2), data);
return comp1 != comp2;
return eval_variable(match_condition.str(1), data) != eval_variable(match_condition.str(2), data);
}
}
@@ -557,7 +566,6 @@ public:
return render_tree(parsed, data, path);
}
std::string render(std::string input, json data) {
return render(input, data, "./");
}
@@ -572,6 +580,14 @@ public:
json data = load_json(filename_data);
return render_template(filename, data);
}
void write(std::string filename, json data, std::string filename_out) {
}
void write(std::string filename, std::string filename_data, std::string filename_out) {
}
std::string load_file(std::string filename) {
std::ifstream file(global_path + filename);