mirror of
https://github.com/pantor/inja.git
synced 2026-02-24 04:16:25 +00:00
286 lines
9.9 KiB
C++
286 lines
9.9 KiB
C++
#ifndef PANTOR_INJA_RENDERER_HPP
|
|
#define PANTOR_INJA_RENDERER_HPP
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
|
|
namespace inja {
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
class Renderer {
|
|
public:
|
|
std::map<Parsed::CallbackSignature, std::function<json(const Parsed::Arguments&, const json&)>> map_callbacks;
|
|
|
|
template<bool>
|
|
bool eval_expression(const Parsed::ElementExpression& element, const json& data) {
|
|
const json var = eval_function(element, data);
|
|
if (var.empty()) { return false; }
|
|
else if (var.is_number()) { return (var != 0); }
|
|
else if (var.is_string()) { return not var.empty(); }
|
|
try {
|
|
return var.get<bool>();
|
|
} catch (json::type_error& e) {
|
|
inja_throw("json_error", e.what());
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template<typename T = json>
|
|
T eval_expression(const Parsed::ElementExpression& element, const json& data) {
|
|
const json var = eval_function(element, data);
|
|
if (var.empty()) return T();
|
|
try {
|
|
return var.get<T>();
|
|
} catch (json::type_error& e) {
|
|
inja_throw("json_error", e.what());
|
|
throw;
|
|
}
|
|
}
|
|
|
|
json eval_function(const Parsed::ElementExpression& element, const json& data) {
|
|
switch (element.function) {
|
|
case Parsed::Function::Upper: {
|
|
std::string str = eval_expression<std::string>(element.args[0], data);
|
|
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
|
return str;
|
|
}
|
|
case Parsed::Function::Lower: {
|
|
std::string str = eval_expression<std::string>(element.args[0], data);
|
|
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
|
return str;
|
|
}
|
|
case Parsed::Function::Range: {
|
|
const int number = eval_expression<int>(element.args[0], data);
|
|
std::vector<int> result(number);
|
|
std::iota(std::begin(result), std::end(result), 0);
|
|
return result;
|
|
}
|
|
case Parsed::Function::Length: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
return list.size();
|
|
}
|
|
case Parsed::Function::Sort: {
|
|
std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
std::sort(list.begin(), list.end());
|
|
return list;
|
|
}
|
|
case Parsed::Function::First: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
return list.front();
|
|
}
|
|
case Parsed::Function::Last: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
return list.back();
|
|
}
|
|
case Parsed::Function::Round: {
|
|
const double number = eval_expression<double>(element.args[0], data);
|
|
const int precision = eval_expression<int>(element.args[1], data);
|
|
return std::round(number * std::pow(10.0, precision)) / std::pow(10.0, precision);
|
|
}
|
|
case Parsed::Function::DivisibleBy: {
|
|
const int number = eval_expression<int>(element.args[0], data);
|
|
const int divisor = eval_expression<int>(element.args[1], data);
|
|
return (divisor != 0) && (number % divisor == 0);
|
|
}
|
|
case Parsed::Function::Odd: {
|
|
const int number = eval_expression<int>(element.args[0], data);
|
|
return (number % 2 != 0);
|
|
}
|
|
case Parsed::Function::Even: {
|
|
const int number = eval_expression<int>(element.args[0], data);
|
|
return (number % 2 == 0);
|
|
}
|
|
case Parsed::Function::Max: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
return *std::max_element(list.begin(), list.end());
|
|
}
|
|
case Parsed::Function::Min: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
|
return *std::min_element(list.begin(), list.end());
|
|
}
|
|
case Parsed::Function::Not: {
|
|
return not eval_expression<bool>(element.args[0], data);
|
|
}
|
|
case Parsed::Function::And: {
|
|
return (eval_expression<bool>(element.args[0], data) and eval_expression<bool>(element.args[1], data));
|
|
}
|
|
case Parsed::Function::Or: {
|
|
return (eval_expression<bool>(element.args[0], data) or eval_expression<bool>(element.args[1], data));
|
|
}
|
|
case Parsed::Function::In: {
|
|
const json value = eval_expression(element.args[0], data);
|
|
const json list = eval_expression(element.args[1], data);
|
|
return (std::find(list.begin(), list.end(), value) != list.end());
|
|
}
|
|
case Parsed::Function::Equal: {
|
|
return eval_expression(element.args[0], data) == eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::Greater: {
|
|
return eval_expression(element.args[0], data) > eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::Less: {
|
|
return eval_expression(element.args[0], data) < eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::GreaterEqual: {
|
|
return eval_expression(element.args[0], data) >= eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::LessEqual: {
|
|
return eval_expression(element.args[0], data) <= eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::Different: {
|
|
return eval_expression(element.args[0], data) != eval_expression(element.args[1], data);
|
|
}
|
|
case Parsed::Function::Float: {
|
|
return std::stod(eval_expression<std::string>(element.args[0], data));
|
|
}
|
|
case Parsed::Function::Int: {
|
|
return std::stoi(eval_expression<std::string>(element.args[0], data));
|
|
}
|
|
case Parsed::Function::ReadJson: {
|
|
try {
|
|
return data.at(json::json_pointer(element.command));
|
|
} catch (std::exception&) {
|
|
inja_throw("render_error", "variable '" + element.command + "' not found");
|
|
}
|
|
}
|
|
case Parsed::Function::Result: {
|
|
return element.result;
|
|
}
|
|
case Parsed::Function::Default: {
|
|
try {
|
|
return eval_expression(element.args[0], data);
|
|
} catch (std::exception&) {
|
|
return eval_expression(element.args[1], data);
|
|
}
|
|
}
|
|
case Parsed::Function::Callback: {
|
|
Parsed::CallbackSignature signature = std::make_pair(element.command, element.args.size());
|
|
return map_callbacks.at(signature)(element.args, data);
|
|
}
|
|
case Parsed::Function::Exists: {
|
|
const std::string name = eval_expression<std::string>(element.args[0], data);
|
|
return data.find(name) != data.end();
|
|
}
|
|
case Parsed::Function::ExistsInObject: {
|
|
const std::string name = eval_expression<std::string>(element.args[1], data);
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.find(name) != d.end();
|
|
}
|
|
case Parsed::Function::IsBoolean: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_boolean();
|
|
}
|
|
case Parsed::Function::IsNumber: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_number();
|
|
}
|
|
case Parsed::Function::IsInteger: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_number_integer();
|
|
}
|
|
case Parsed::Function::IsFloat: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_number_float();
|
|
}
|
|
case Parsed::Function::IsObject: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_object();
|
|
}
|
|
case Parsed::Function::IsArray: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_array();
|
|
}
|
|
case Parsed::Function::IsString: {
|
|
const json d = eval_expression(element.args[0], data);
|
|
return d.is_string();
|
|
}
|
|
}
|
|
|
|
inja_throw("render_error", "unknown function in renderer: " + element.command);
|
|
return json();
|
|
}
|
|
|
|
std::string render(Template temp, const json& data) {
|
|
std::string result {""};
|
|
for (const auto& element: temp.parsed_template().children) {
|
|
switch (element->type) {
|
|
case Parsed::Type::String: {
|
|
auto element_string = std::static_pointer_cast<Parsed::ElementString>(element);
|
|
result.append(element_string->text);
|
|
break;
|
|
}
|
|
case Parsed::Type::Expression: {
|
|
auto element_expression = std::static_pointer_cast<Parsed::ElementExpression>(element);
|
|
const json variable = eval_expression(*element_expression, data);
|
|
|
|
if (variable.is_string()) {
|
|
result.append( variable.get<std::string>() );
|
|
} else {
|
|
std::stringstream ss;
|
|
ss << variable;
|
|
result.append( ss.str() );
|
|
}
|
|
break;
|
|
}
|
|
case Parsed::Type::Loop: {
|
|
auto element_loop = std::static_pointer_cast<Parsed::ElementLoop>(element);
|
|
switch (element_loop->loop) {
|
|
case Parsed::Loop::ForListIn: {
|
|
const std::vector<json> list = eval_expression<std::vector<json>>(element_loop->list, data);
|
|
for (unsigned int i = 0; i < list.size(); i++) {
|
|
json data_loop = data;
|
|
/* For nested loops, use parent/index */
|
|
if (data_loop.count("loop") == 1) {
|
|
data_loop["loop"]["parent"] = data_loop["loop"];
|
|
}
|
|
data_loop[element_loop->value] = list[i];
|
|
data_loop["loop"]["index"] = i;
|
|
data_loop["loop"]["index1"] = i + 1;
|
|
data_loop["loop"]["is_first"] = (i == 0);
|
|
data_loop["loop"]["is_last"] = (i == list.size() - 1);
|
|
result.append( render(Template(*element_loop), data_loop) );
|
|
}
|
|
break;
|
|
}
|
|
case Parsed::Loop::ForMapIn: {
|
|
const std::map<std::string, json> map = eval_expression<std::map<std::string, json>>(element_loop->list, data);
|
|
for (const auto& item: map) {
|
|
json data_loop = data;
|
|
data_loop[element_loop->key] = item.first;
|
|
data_loop[element_loop->value] = item.second;
|
|
result.append( render(Template(*element_loop), data_loop) );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Parsed::Type::Condition: {
|
|
auto element_condition = std::static_pointer_cast<Parsed::ElementConditionContainer>(element);
|
|
for (const auto& branch: element_condition->children) {
|
|
auto element_branch = std::static_pointer_cast<Parsed::ElementConditionBranch>(branch);
|
|
if (element_branch->condition_type == Parsed::Condition::Else || eval_expression<bool>(element_branch->condition, data)) {
|
|
result.append( render(Template(*element_branch), data) );
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif // PANTOR_INJA_RENDERER_HPP
|