mirror of
https://github.com/pantor/inja.git
synced 2026-02-22 11:26:24 +00:00
initial codebase
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
# inja
|
||||
A Template Engine for Modern C++
|
||||
# Inja
|
||||
|
||||
A Template Engine for Modern C++
|
||||
180
src/inja.hpp
Normal file
180
src/inja.hpp
Normal 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
21
src/json/LICENSE.MIT.txt
Normal 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
14396
src/json/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1
test/data/include.txt
Normal file
1
test/data/include.txt
Normal file
@@ -0,0 +1 @@
|
||||
Answer: (% include "simple.txt" %)
|
||||
11
test/data/nested.txt
Normal file
11
test/data/nested.txt
Normal 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
1
test/data/simple.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello {{ name }}.
|
||||
BIN
test/src/test
Executable file
BIN
test/src/test
Executable file
Binary file not shown.
91
test/src/test.cpp
Normal file
91
test/src/test.cpp
Normal 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
11618
test/thirdparty/catch.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user