initial codebase

This commit is contained in:
pantor
2017-08-12 13:57:34 +02:00
parent d5c99dcb16
commit d0eeb111ea
10 changed files with 26322 additions and 2 deletions

View File

@@ -1,2 +1,3 @@
# inja
A Template Engine for Modern C++
# Inja
A Template Engine for Modern C++

180
src/inja.hpp Normal file
View File

@@ -0,0 +1,180 @@
#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#include "json/json.hpp"
namespace inja {
using json = nlohmann::json;
using string = std::string;
template<class BidirIt, class Traits, class CharT, class UnaryFunction>
std::basic_string<CharT> regex_replace(BidirIt first, BidirIt last,
const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
std::basic_string<CharT> s;
typename std::match_results<BidirIt>::difference_type positionOfLastMatch = 0;
auto endOfLastMatch = first;
auto callback = [&](const std::match_results<BidirIt>& match)
{
auto positionOfThisMatch = match.position(0);
auto diff = positionOfThisMatch - positionOfLastMatch;
auto startOfThisMatch = endOfLastMatch;
std::advance(startOfThisMatch, diff);
s.append(endOfLastMatch, startOfThisMatch);
s.append(f(match));
auto lengthOfMatch = match.length(0);
positionOfLastMatch = positionOfThisMatch + lengthOfMatch;
endOfLastMatch = startOfThisMatch;
std::advance(endOfLastMatch, lengthOfMatch);
};
std::sregex_iterator begin(first, last, re), end;
std::for_each(begin, end, callback);
s.append(endOfLastMatch, last);
return s;
}
template<class Traits, class CharT, class UnaryFunction>
std::string regex_replace(const std::string& s,
const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
return regex_replace(s.cbegin(), s.cend(), re, f);
}
class Environment {
public:
Environment() {
}
json get_variable_data(string variable_name, json data) {
// Json Raw Data
if ( json::accept(variable_name) ) {
return json::parse(variable_name);
}
// Implement range function
std::regex range_regex("^range\\((\\d+)\\)$");
std::smatch range_match;
if (std::regex_match(variable_name, range_match, range_regex)) {
int counter = std::stoi(range_match[1].str());
std::vector<int> range(counter);
std::iota(range.begin(), range.end(), 0);
return range;
}
if (variable_name[0] != '/') {
variable_name = "/" + variable_name;
}
json::json_pointer ptr(variable_name);
json result = data[ptr];
if (result.is_null()) {
throw std::runtime_error("JSON pointer found no element.");
}
return result;
}
string render(string template_input, json data) {
return render(template_input, data, "./");
}
string render(string template_input, json data, string template_path) {
string result = template_input;
const std::regex include_regex("\\(\\% include \"(.*)\" \\%\\)");
result = inja::regex_replace(result, include_regex,
[this, template_path](const std::smatch& match) {
string filename = template_path + match[1].str();
return load_template(filename);
}
);
const std::regex loop_regex("\\(\\% for (\\w+) in (.+) \\%\\)(.*)\\(\\% endfor \\%\\)");
result = inja::regex_replace(result, loop_regex,
[this, data](const std::smatch& match) {
string result = "";
string entry_name = match[1].str();
string list_name = match[2].str();
string inner_string = match[3].str();
json list = get_variable_data(list_name, data);
if (!list.is_array()) throw std::runtime_error("JSON variable is not a list.");
for (int i = 0; i < list.size(); i++) {
const std::regex entry_regex("\\{\\{.*" + entry_name + ".*\\}\\}");
result += inja::regex_replace(inner_string, entry_regex,
[list_name, i](const std::smatch& match) {
return "{{ " + list_name + "/" + std::to_string(i) + " }}";
}
);
}
return result;
}
);
const std::regex condition_regex("\\(\\% if (\\w+) \\%\\)(.*)\\(\\% endif \\%\\)");
result = inja::regex_replace(result, condition_regex,
[this, data](const std::smatch& match) {
string condition_variable_name = match[1].str();
string inner_string = match[2].str();
if (get_variable_data(condition_variable_name, data)) {
return inner_string;
}
return string("");
}
);
const std::regex variable_regex("\\{\\{\\s*([^\\}]*[^\\s])\\s*\\}\\}");
result = inja::regex_replace(result, variable_regex,
[this, data](const std::smatch& match) {
string variable_name = match[1].str();
return get_variable_data(variable_name, data);
}
);
return result;
}
string render_template(string filename, json data) {
string text = load_template(filename);
string path = filename.substr(0, filename.find_last_of("/\\") + 1); // Include / itself
return render(text, data, path);
}
string load_template(string filename) {
std::ifstream file(filename);
string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return text;
}
json load_json(string filename) {
std::ifstream file(filename);
json j;
file >> j;
return j;
}
};
} // namespace inja

21
src/json/LICENSE.MIT.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2017 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14396
src/json/json.hpp Normal file

File diff suppressed because it is too large Load Diff

1
test/data/include.txt Normal file
View File

@@ -0,0 +1 @@
Answer: (% include "simple.txt" %)

11
test/data/nested.txt Normal file
View File

@@ -0,0 +1,11 @@
^{% for x in x_array %}
{% for y in y_array %}
{{x}}-{{y}}
{% endfor %}
{% endfor %}
json a;
a["type"] = loop|condition|include
a["command"] = x in x_array
a["innerString"] = "..."
a["children"] = []

1
test/data/simple.txt Normal file
View File

@@ -0,0 +1 @@
Hello {{ name }}.

BIN
test/src/test Executable file

Binary file not shown.

91
test/src/test.cpp Normal file
View File

@@ -0,0 +1,91 @@
#define CATCH_CONFIG_MAIN
#include "../thirdparty/catch.hpp"
#include "../../src/inja.hpp"
using Environment = inja::Environment;
using json = nlohmann::json;
TEST_CASE("Variables") {
Environment env = Environment();
json data;
data["name"] = "Peter";
data["city"] = "Washington D.C.";
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
SECTION("Variables from values") {
REQUIRE( env.get_variable_data("42", data) == 42 );
REQUIRE( env.get_variable_data("3.1415", data) == 3.1415 );
REQUIRE( env.get_variable_data("\"hello\"", data) == "hello" );
}
SECTION("Variables from functions") {
REQUIRE( env.get_variable_data("range(3)", data) == std::vector<int>({0, 1, 2}) );
}
SECTION("Variables from JSON data") {
REQUIRE( env.get_variable_data("name", data) == "Peter" );
REQUIRE( env.get_variable_data("names/1", data) == "Seb" );
REQUIRE( env.get_variable_data("brother/name", data) == "Chris" );
REQUIRE( env.get_variable_data("brother/daughters/0", data) == "Maria" );
REQUIRE_THROWS_WITH( env.get_variable_data("noelement", data), "JSON pointer found no element." );
}
SECTION("Variables should be rendered") {
REQUIRE( env.render("My name is...", data) == "My name is..." );
REQUIRE( env.render("My name is {{ name }}.", data) == "My name is Peter." );
REQUIRE( env.render("My name is {{name}}.", data) == "My name is Peter." );
REQUIRE( env.render("My name is {{ name }}. I come from {{ city }}", data) == "My name is Peter. I come from Washington D.C." );
REQUIRE( env.render("My name is {{ names/1 }}.", data) == "My name is Seb." );
REQUIRE( env.render("My name is {{ brother/name }}.", data) == "My name is Chris." );
REQUIRE( env.render("My name is {{ brother/daughter0/name }}.", data) == "My name is Maria." );
}
}
TEST_CASE("Loops should be rendered") {
Environment env = Environment();
json data;
data["list"] = {"v1", "v2", "v3", "v4"};
REQUIRE( env.render("List: (% for entry in list %)a(% endfor %)", data) == "List: aaaa" );
REQUIRE( env.render("List: (% for entry in list %){{ entry }}, (% endfor %)", data) == "List: v1, v2, v3, v4, " );
REQUIRE( env.render("List: (% for i in range(4) %)a(% endfor %)", data) == "List: aaaa" );
}
TEST_CASE("Conditionals should be rendered") {
Environment env = Environment();
json data;
data["good"] = true;
data["bad"] = false;
data["a"] = 2;
REQUIRE( env.render("(% if good %)a(% endif %)", data) == "a" );
// REQUIRE( env.render("(% if good %)a(% endif %) (% if bad %)b(% endif %)", data) == "a " );
// REQUIRE( env.render("(% if good %)one(% else %)two(% endif %)", data) == "one" );
// REQUIRE( env.render("(% if bad %)one(% else %)two(% endif %)", data) == "two" );
// REQUIRE( env.render("(% if a == 2 %)one(% else %)two(% endif %)", data) == "one" );
// REQUIRE( env.render("(% if "b" in {"a", "b", "c"} %)one(% endif %)", data) == "one" );
}
TEST_CASE("Files should handled") {
Environment env = Environment();
json data;
data["name"] = "Jeff";
SECTION("Files should be loaded") {
REQUIRE( env.load_template("../data/simple.txt") == "Hello {{ name }}." );
}
SECTION("Files should be rendered") {
REQUIRE( env.render_template("../data/simple.txt", data) == "Hello Jeff." );
}
SECTION("File includes should be rendered") {
REQUIRE( env.render_template("../data/include.txt", data) == "Answer: Hello Jeff." );
}
}

11618
test/thirdparty/catch.hpp vendored Normal file

File diff suppressed because it is too large Load Diff