add clang-format

This commit is contained in:
pantor
2020-06-16 18:53:41 +02:00
parent c85f9a3837
commit 5cc3e30b66
20 changed files with 4007 additions and 4195 deletions

7
.clang-format Normal file
View File

@@ -0,0 +1,7 @@
---
Language: Cpp
BasedOnStyle: LLVM
ColumnLimit: 120
SpaceBeforeCpp11BracedList: true
...

View File

@@ -10,12 +10,10 @@
#include "string_view.hpp"
namespace inja {
using json = nlohmann::json;
struct Bytecode {
enum class Op : uint8_t {
Nop,
@@ -117,18 +115,18 @@ struct Bytecode {
};
Op op {Op::Nop};
uint32_t args: 30;
uint32_t flags: 2;
uint32_t args : 30;
uint32_t flags : 2;
json value;
std::string str;
Bytecode(): args(0), flags(0) {}
explicit Bytecode(Op op, unsigned int args = 0): op(op), args(args), flags(0) {}
explicit Bytecode(Op op, nonstd::string_view str, unsigned int flags): op(op), args(0), flags(flags), str(str) {}
explicit Bytecode(Op op, json&& value, unsigned int flags): op(op), args(0), flags(flags), value(std::move(value)) {}
Bytecode() : args(0), flags(0) {}
explicit Bytecode(Op op, unsigned int args = 0) : op(op), args(args), flags(0) {}
explicit Bytecode(Op op, nonstd::string_view str, unsigned int flags) : op(op), args(0), flags(flags), str(str) {}
explicit Bytecode(Op op, json &&value, unsigned int flags) : op(op), args(0), flags(flags), value(std::move(value)) {}
};
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_BYTECODE_HPP_
#endif // INCLUDE_INJA_BYTECODE_HPP_

View File

@@ -8,13 +8,9 @@
#include "string_view.hpp"
namespace inja {
enum class ElementNotation {
Dot,
Pointer
};
enum class ElementNotation { Dot, Pointer };
/*!
* \brief Class for lexer configuration.
@@ -56,6 +52,6 @@ struct ParserConfig {
ElementNotation notation {ElementNotation::Dot};
};
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_CONFIG_HPP_
#endif // INCLUDE_INJA_CONFIG_HPP_

View File

@@ -3,8 +3,8 @@
#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_
#define INCLUDE_INJA_ENVIRONMENT_HPP_
#include <memory>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
@@ -19,7 +19,6 @@
#include "template.hpp"
#include "utils.hpp"
namespace inja {
using json = nlohmann::json;
@@ -28,125 +27,118 @@ using json = nlohmann::json;
* \brief Class for changing the configuration.
*/
class Environment {
public:
Environment(): Environment("") { }
public:
Environment() : Environment("") {}
explicit Environment(const std::string& global_path): m_input_path(global_path), m_output_path(global_path) {}
explicit Environment(const std::string &global_path) : m_input_path(global_path), m_output_path(global_path) {}
Environment(const std::string& input_path, const std::string& output_path): m_input_path(input_path), m_output_path(output_path) {}
Environment(const std::string &input_path, const std::string &output_path)
: m_input_path(input_path), m_output_path(output_path) {}
/// Sets the opener and closer for template statements
void set_statement(const std::string& open, const std::string& close) {
void set_statement(const std::string &open, const std::string &close) {
m_lexer_config.statement_open = open;
m_lexer_config.statement_close = close;
m_lexer_config.update_open_chars();
}
/// Sets the opener for template line statements
void set_line_statement(const std::string& open) {
void set_line_statement(const std::string &open) {
m_lexer_config.line_statement = open;
m_lexer_config.update_open_chars();
}
/// Sets the opener and closer for template expressions
void set_expression(const std::string& open, const std::string& close) {
void set_expression(const std::string &open, const std::string &close) {
m_lexer_config.expression_open = open;
m_lexer_config.expression_close = close;
m_lexer_config.update_open_chars();
}
/// Sets the opener and closer for template comments
void set_comment(const std::string& open, const std::string& close) {
void set_comment(const std::string &open, const std::string &close) {
m_lexer_config.comment_open = open;
m_lexer_config.comment_close = close;
m_lexer_config.update_open_chars();
}
/// Sets whether to remove the first newline after a block
void set_trim_blocks(bool trim_blocks) {
m_lexer_config.trim_blocks = trim_blocks;
}
void set_trim_blocks(bool trim_blocks) { m_lexer_config.trim_blocks = trim_blocks; }
/// Sets whether to strip the spaces and tabs from the start of a line to a block
void set_lstrip_blocks(bool lstrip_blocks) {
m_lexer_config.lstrip_blocks = lstrip_blocks;
}
void set_lstrip_blocks(bool lstrip_blocks) { m_lexer_config.lstrip_blocks = lstrip_blocks; }
/// Sets the element notation syntax
void set_element_notation(ElementNotation notation) {
m_parser_config.notation = notation;
}
void set_element_notation(ElementNotation notation) { m_parser_config.notation = notation; }
Template parse(nonstd::string_view input) {
Parser parser(m_parser_config, m_lexer_config, m_included_templates);
return parser.parse(input);
}
Template parse_template(const std::string& filename) {
Template parse_template(const std::string &filename) {
Parser parser(m_parser_config, m_lexer_config, m_included_templates);
return parser.parse_template(m_input_path + static_cast<std::string>(filename));
}
std::string render(nonstd::string_view input, const json& data) {
return render(parse(input), data);
}
std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); }
std::string render(const Template& tmpl, const json& data) {
std::string render(const Template &tmpl, const json &data) {
std::stringstream os;
render_to(os, tmpl, data);
return os.str();
}
std::string render_file(const std::string& filename, const json& data) {
std::string render_file(const std::string &filename, const json &data) {
return render(parse_template(filename), data);
}
std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) {
std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) {
const json data = load_json(filename_data);
return render_file(filename, data);
}
void write(const std::string& filename, const json& data, const std::string& filename_out) {
void write(const std::string &filename, const json &data, const std::string &filename_out) {
std::ofstream file(m_output_path + filename_out);
file << render_file(filename, data);
file.close();
}
void write(const Template& temp, const json& data, const std::string& filename_out) {
void write(const Template &temp, const json &data, const std::string &filename_out) {
std::ofstream file(m_output_path + filename_out);
file << render(temp, data);
file.close();
}
void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) {
void write_with_json_file(const std::string &filename, const std::string &filename_data,
const std::string &filename_out) {
const json data = load_json(filename_data);
write(filename, data, filename_out);
}
void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) {
void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) {
const json data = load_json(filename_data);
write(temp, data, filename_out);
}
std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) {
std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) {
Renderer(m_included_templates, m_callbacks).render_to(os, tmpl, data);
return os;
}
std::string load_file(const std::string& filename) {
std::string load_file(const std::string &filename) {
Parser parser(m_parser_config, m_lexer_config, m_included_templates);
return parser.load_file(m_input_path + filename);
}
json load_json(const std::string& filename) {
json load_json(const std::string &filename) {
std::ifstream file = open_file_or_throw(m_input_path + filename);
json j;
file >> j;
return j;
}
void add_callback(const std::string& name, unsigned int numArgs, const CallbackFunction& callback) {
void add_callback(const std::string &name, unsigned int numArgs, const CallbackFunction &callback) {
m_callbacks.add_callback(name, numArgs, callback);
}
@@ -154,11 +146,9 @@ class Environment {
* Then, a template can be rendered in another template using the
* include "<name>" syntax.
*/
void include_template(const std::string& name, const Template& tmpl) {
m_included_templates[name] = tmpl;
}
void include_template(const std::string &name, const Template &tmpl) { m_included_templates[name] = tmpl; }
private:
private:
std::string m_input_path;
std::string m_output_path;
@@ -172,18 +162,16 @@ class Environment {
/*!
@brief render with default settings to a string
*/
inline std::string render(nonstd::string_view input, const json& data) {
return Environment().render(input, data);
}
inline std::string render(nonstd::string_view input, const json &data) { return Environment().render(input, data); }
/*!
@brief render with default settings to the given output stream
*/
inline void render_to(std::ostream& os, nonstd::string_view input, const json& data) {
inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) {
Environment env;
env.render_to(os, env.parse(input), data);
}
}
} // namespace inja
#endif // INCLUDE_INJA_ENVIRONMENT_HPP_
#endif // INCLUDE_INJA_ENVIRONMENT_HPP_

View File

@@ -6,7 +6,6 @@
#include <stdexcept>
#include <string>
namespace inja {
struct SourceLocation {
@@ -21,39 +20,35 @@ struct InjaError : public std::runtime_error {
bool has_location {false};
SourceLocation location;
InjaError(const std::string& type, const std::string& message)
: std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message) { }
InjaError(const std::string& type, const std::string& message, SourceLocation location)
: std::runtime_error(
"[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message
), type(type), message(message), has_location(true), location(location) { }
InjaError(const std::string &type, const std::string &message)
: std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message) {}
InjaError(const std::string &type, const std::string &message, SourceLocation location)
: std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" +
std::to_string(location.column) + ") " + message),
type(type), message(message), has_location(true), location(location) {}
};
struct ParserError : public InjaError {
ParserError(const std::string& message) : InjaError("parser_error", message) { }
ParserError(const std::string& message, SourceLocation location)
: InjaError("parser_error", message, location) { }
ParserError(const std::string &message) : InjaError("parser_error", message) {}
ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {}
};
struct RenderError : public InjaError {
RenderError(const std::string& message) : InjaError("render_error", message) { }
RenderError(const std::string& message, SourceLocation location)
: InjaError("render_error", message, location) { }
RenderError(const std::string &message) : InjaError("render_error", message) {}
RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {}
};
struct FileError : public InjaError {
FileError(const std::string& message) : InjaError("file_error", message) { }
FileError(const std::string& message, SourceLocation location)
: InjaError("file_error", message, location) { }
FileError(const std::string &message) : InjaError("file_error", message) {}
FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {}
};
struct JsonError : public InjaError {
JsonError(const std::string& message) : InjaError("json_error", message) { }
JsonError(const std::string& message, SourceLocation location)
: InjaError("json_error", message, location) { }
JsonError(const std::string &message) : InjaError("json_error", message) {}
JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {}
};
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_EXCEPTIONS_HPP_
#endif // INCLUDE_INJA_EXCEPTIONS_HPP_

View File

@@ -8,26 +8,25 @@
#include "bytecode.hpp"
#include "string_view.hpp"
namespace inja {
using json = nlohmann::json;
using Arguments = std::vector<const json*>;
using CallbackFunction = std::function<json(Arguments& args)>;
using Arguments = std::vector<const json *>;
using CallbackFunction = std::function<json(Arguments &args)>;
/*!
* \brief Class for builtin functions and user-defined callbacks.
*/
class FunctionStorage {
public:
public:
void add_builtin(nonstd::string_view name, unsigned int num_args, Bytecode::Op op) {
auto& data = get_or_new(name, num_args);
auto &data = get_or_new(name, num_args);
data.op = op;
}
void add_callback(nonstd::string_view name, unsigned int num_args, const CallbackFunction& function) {
auto& data = get_or_new(name, num_args);
void add_callback(nonstd::string_view name, unsigned int num_args, const CallbackFunction &function) {
auto &data = get_or_new(name, num_args);
data.function = function;
}
@@ -45,16 +44,16 @@ class FunctionStorage {
return nullptr;
}
private:
private:
struct FunctionData {
unsigned int num_args {0};
Bytecode::Op op {Bytecode::Op::Nop}; // for builtins
CallbackFunction function; // for callbacks
CallbackFunction function; // for callbacks
};
FunctionData& get_or_new(nonstd::string_view name, unsigned int num_args) {
FunctionData &get_or_new(nonstd::string_view name, unsigned int num_args) {
auto &vec = m_map[static_cast<std::string>(name)];
for (auto &i: vec) {
for (auto &i : vec) {
if (i.num_args == num_args) {
return i;
}
@@ -64,13 +63,13 @@ class FunctionStorage {
return vec.back();
}
const FunctionData* get(nonstd::string_view name, unsigned int num_args) const {
const FunctionData *get(nonstd::string_view name, unsigned int num_args) const {
auto it = m_map.find(static_cast<std::string>(name));
if (it == m_map.end()) {
return nullptr;
}
for (auto &&i: it->second) {
for (auto &&i : it->second) {
if (i.num_args == num_args) {
return &i;
}
@@ -81,6 +80,6 @@ class FunctionStorage {
std::map<std::string, std::vector<FunctionData>> m_map;
};
}
} // namespace inja
#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_
#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_

View File

@@ -13,12 +13,11 @@
#include <nlohmann/json.hpp>
#include "exceptions.hpp"
#include "environment.hpp"
#include "string_view.hpp"
#include "template.hpp"
#include "exceptions.hpp"
#include "parser.hpp"
#include "renderer.hpp"
#include "string_view.hpp"
#include "template.hpp"
#endif // INCLUDE_INJA_INJA_HPP_
#endif // INCLUDE_INJA_INJA_HPP_

View File

@@ -10,7 +10,6 @@
#include "token.hpp"
#include "utils.hpp"
namespace inja {
/*!
@@ -29,13 +28,13 @@ class Lexer {
CommentBody
} m_state;
const LexerConfig& m_config;
const LexerConfig &m_config;
nonstd::string_view m_in;
size_t m_tok_start;
size_t m_pos;
public:
explicit Lexer(const LexerConfig& config) : m_config(config) {}
public:
explicit Lexer(const LexerConfig &config) : m_config(config) {}
SourceLocation current_position() const {
// Get line and offset position (starts at 1:1)
@@ -53,7 +52,7 @@ class Lexer {
search_start = sliced.find("\n", search_start + 1);
count_lines += 1;
}
return {count_lines + 1, sliced.length() - last_newline + 1};
}
@@ -68,97 +67,100 @@ class Lexer {
m_tok_start = m_pos;
again:
if (m_tok_start >= m_in.size()) return make_token(Token::Kind::Eof);
if (m_tok_start >= m_in.size())
return make_token(Token::Kind::Eof);
switch (m_state) {
default:
case State::Text: {
// fast-scan to first open character
size_t open_start = m_in.substr(m_pos).find_first_of(m_config.open_chars);
if (open_start == nonstd::string_view::npos) {
// didn't find open, return remaining text as text token
m_pos = m_in.size();
return make_token(Token::Kind::Text);
}
m_pos += open_start;
default:
case State::Text: {
// fast-scan to first open character
size_t open_start = m_in.substr(m_pos).find_first_of(m_config.open_chars);
if (open_start == nonstd::string_view::npos) {
// didn't find open, return remaining text as text token
m_pos = m_in.size();
return make_token(Token::Kind::Text);
}
m_pos += open_start;
// try to match one of the opening sequences, and get the close
nonstd::string_view open_str = m_in.substr(m_pos);
bool must_lstrip = false;
if (inja::string_view::starts_with(open_str, m_config.expression_open)) {
m_state = State::ExpressionStart;
} else if (inja::string_view::starts_with(open_str, m_config.statement_open)) {
m_state = State::StatementStart;
must_lstrip = m_config.lstrip_blocks;
} else if (inja::string_view::starts_with(open_str, m_config.comment_open)) {
m_state = State::CommentStart;
must_lstrip = m_config.lstrip_blocks;
} else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') &&
inja::string_view::starts_with(open_str, m_config.line_statement)) {
m_state = State::LineStart;
} else {
m_pos += 1; // wasn't actually an opening sequence
goto again;
}
// try to match one of the opening sequences, and get the close
nonstd::string_view open_str = m_in.substr(m_pos);
bool must_lstrip = false;
if (inja::string_view::starts_with(open_str, m_config.expression_open)) {
m_state = State::ExpressionStart;
} else if (inja::string_view::starts_with(open_str, m_config.statement_open)) {
m_state = State::StatementStart;
must_lstrip = m_config.lstrip_blocks;
} else if (inja::string_view::starts_with(open_str, m_config.comment_open)) {
m_state = State::CommentStart;
must_lstrip = m_config.lstrip_blocks;
} else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') &&
inja::string_view::starts_with(open_str, m_config.line_statement)) {
m_state = State::LineStart;
} else {
m_pos += 1; // wasn't actually an opening sequence
goto again;
}
nonstd::string_view text = string_view::slice(m_in, m_tok_start, m_pos);
if (must_lstrip)
text = clear_final_line_if_whitespace(text);
nonstd::string_view text = string_view::slice(m_in, m_tok_start, m_pos);
if (must_lstrip)
text = clear_final_line_if_whitespace(text);
if (text.empty()) goto again; // don't generate empty token
return Token(Token::Kind::Text, text);
}
case State::ExpressionStart: {
m_state = State::ExpressionBody;
m_pos += m_config.expression_open.size();
return make_token(Token::Kind::ExpressionOpen);
}
case State::LineStart: {
m_state = State::LineBody;
m_pos += m_config.line_statement.size();
return make_token(Token::Kind::LineStatementOpen);
}
case State::StatementStart: {
m_state = State::StatementBody;
m_pos += m_config.statement_open.size();
return make_token(Token::Kind::StatementOpen);
}
case State::CommentStart: {
m_state = State::CommentBody;
m_pos += m_config.comment_open.size();
return make_token(Token::Kind::CommentOpen);
}
case State::ExpressionBody:
return scan_body(m_config.expression_close, Token::Kind::ExpressionClose);
case State::LineBody:
return scan_body("\n", Token::Kind::LineStatementClose);
case State::StatementBody:
return scan_body(m_config.statement_close, Token::Kind::StatementClose, m_config.trim_blocks);
case State::CommentBody: {
// fast-scan to comment close
size_t end = m_in.substr(m_pos).find(m_config.comment_close);
if (end == nonstd::string_view::npos) {
m_pos = m_in.size();
return make_token(Token::Kind::Eof);
}
// return the entire comment in the close token
m_state = State::Text;
m_pos += end + m_config.comment_close.size();
Token tok = make_token(Token::Kind::CommentClose);
if (m_config.trim_blocks)
skip_newline();
return tok;
if (text.empty())
goto again; // don't generate empty token
return Token(Token::Kind::Text, text);
}
case State::ExpressionStart: {
m_state = State::ExpressionBody;
m_pos += m_config.expression_open.size();
return make_token(Token::Kind::ExpressionOpen);
}
case State::LineStart: {
m_state = State::LineBody;
m_pos += m_config.line_statement.size();
return make_token(Token::Kind::LineStatementOpen);
}
case State::StatementStart: {
m_state = State::StatementBody;
m_pos += m_config.statement_open.size();
return make_token(Token::Kind::StatementOpen);
}
case State::CommentStart: {
m_state = State::CommentBody;
m_pos += m_config.comment_open.size();
return make_token(Token::Kind::CommentOpen);
}
case State::ExpressionBody:
return scan_body(m_config.expression_close, Token::Kind::ExpressionClose);
case State::LineBody:
return scan_body("\n", Token::Kind::LineStatementClose);
case State::StatementBody:
return scan_body(m_config.statement_close, Token::Kind::StatementClose, m_config.trim_blocks);
case State::CommentBody: {
// fast-scan to comment close
size_t end = m_in.substr(m_pos).find(m_config.comment_close);
if (end == nonstd::string_view::npos) {
m_pos = m_in.size();
return make_token(Token::Kind::Eof);
}
// return the entire comment in the close token
m_state = State::Text;
m_pos += end + m_config.comment_close.size();
Token tok = make_token(Token::Kind::CommentClose);
if (m_config.trim_blocks)
skip_newline();
return tok;
}
}
}
const LexerConfig& get_config() const { return m_config; }
const LexerConfig &get_config() const { return m_config; }
private:
private:
Token scan_body(nonstd::string_view close, Token::Kind closeKind, bool trim = false) {
again:
// skip whitespace (except for \n as it might be a close)
if (m_tok_start >= m_in.size()) return make_token(Token::Kind::Eof);
if (m_tok_start >= m_in.size())
return make_token(Token::Kind::Eof);
char ch = m_in[m_tok_start];
if (ch == ' ' || ch == '\t' || ch == '\r') {
m_tok_start += 1;
@@ -185,66 +187,66 @@ class Lexer {
if (std::isalpha(ch)) {
return scan_id();
}
switch (ch) {
case ',':
return make_token(Token::Kind::Comma);
case ':':
return make_token(Token::Kind::Colon);
case '(':
return make_token(Token::Kind::LeftParen);
case ')':
return make_token(Token::Kind::RightParen);
case '[':
return make_token(Token::Kind::LeftBracket);
case ']':
return make_token(Token::Kind::RightBracket);
case '{':
return make_token(Token::Kind::LeftBrace);
case '}':
return make_token(Token::Kind::RightBrace);
case '>':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::GreaterEqual);
}
return make_token(Token::Kind::GreaterThan);
case '<':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::LessEqual);
}
return make_token(Token::Kind::LessThan);
case '=':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::Equal);
}
return make_token(Token::Kind::Unknown);
case '!':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::NotEqual);
}
return make_token(Token::Kind::Unknown);
case '\"':
return scan_string();
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return scan_number();
case '_':
return scan_id();
default:
return make_token(Token::Kind::Unknown);
case ',':
return make_token(Token::Kind::Comma);
case ':':
return make_token(Token::Kind::Colon);
case '(':
return make_token(Token::Kind::LeftParen);
case ')':
return make_token(Token::Kind::RightParen);
case '[':
return make_token(Token::Kind::LeftBracket);
case ']':
return make_token(Token::Kind::RightBracket);
case '{':
return make_token(Token::Kind::LeftBrace);
case '}':
return make_token(Token::Kind::RightBrace);
case '>':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::GreaterEqual);
}
return make_token(Token::Kind::GreaterThan);
case '<':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::LessEqual);
}
return make_token(Token::Kind::LessThan);
case '=':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::Equal);
}
return make_token(Token::Kind::Unknown);
case '!':
if (m_pos < m_in.size() && m_in[m_pos] == '=') {
m_pos += 1;
return make_token(Token::Kind::NotEqual);
}
return make_token(Token::Kind::Unknown);
case '\"':
return scan_string();
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return scan_number();
case '_':
return scan_id();
default:
return make_token(Token::Kind::Unknown);
}
}
@@ -280,7 +282,8 @@ class Lexer {
Token scan_string() {
bool escape {false};
for (;;) {
if (m_pos >= m_in.size()) break;
if (m_pos >= m_in.size())
break;
char ch = m_in[m_pos++];
if (ch == '\\') {
escape = true;
@@ -293,9 +296,7 @@ class Lexer {
return make_token(Token::Kind::String);
}
Token make_token(Token::Kind kind) const {
return Token(kind, string_view::slice(m_in, m_tok_start, m_pos));
}
Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, m_tok_start, m_pos)); }
void skip_newline() {
if (m_pos < m_in.size()) {
@@ -315,7 +316,7 @@ class Lexer {
while (!result.empty()) {
char ch = result.back();
if (ch == ' ' || ch == '\t')
result.remove_suffix(1);
result.remove_suffix(1);
else if (ch == '\n' || ch == '\r')
break;
else
@@ -325,6 +326,6 @@ class Lexer {
}
};
}
} // namespace inja
#endif // INCLUDE_INJA_LEXER_HPP_
#endif // INCLUDE_INJA_LEXER_HPP_

View File

@@ -5,7 +5,7 @@
#include <limits>
#include <string>
#include <utility>
#include <utility>
#include <vector>
#include "bytecode.hpp"
@@ -19,7 +19,6 @@
#include <nlohmann/json.hpp>
namespace inja {
class ParserStatic {
@@ -52,11 +51,11 @@ class ParserStatic {
functions.add_builtin("isString", 1, Bytecode::Op::IsString);
}
public:
ParserStatic(const ParserStatic&) = delete;
ParserStatic& operator=(const ParserStatic&) = delete;
public:
ParserStatic(const ParserStatic &) = delete;
ParserStatic &operator=(const ParserStatic &) = delete;
static const ParserStatic& get_instance() {
static const ParserStatic &get_instance() {
static ParserStatic inst;
return inst;
}
@@ -68,31 +67,41 @@ class ParserStatic {
* \brief Class for parsing an inja Template.
*/
class Parser {
public:
explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& included_templates): m_config(parser_config), m_lexer(lexer_config), m_included_templates(included_templates), m_static(ParserStatic::get_instance()) { }
public:
explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config,
TemplateStorage &included_templates)
: m_config(parser_config), m_lexer(lexer_config), m_included_templates(included_templates),
m_static(ParserStatic::get_instance()) {}
bool parse_expression(Template& tmpl) {
if (!parse_expression_and(tmpl)) return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("or")) return true;
bool parse_expression(Template &tmpl) {
if (!parse_expression_and(tmpl))
return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("or"))
return true;
get_next_token();
if (!parse_expression_and(tmpl)) return false;
if (!parse_expression_and(tmpl))
return false;
append_function(tmpl, Bytecode::Op::Or, 2);
return true;
}
bool parse_expression_and(Template& tmpl) {
if (!parse_expression_not(tmpl)) return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("and")) return true;
bool parse_expression_and(Template &tmpl) {
if (!parse_expression_not(tmpl))
return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("and"))
return true;
get_next_token();
if (!parse_expression_not(tmpl)) return false;
if (!parse_expression_not(tmpl))
return false;
append_function(tmpl, Bytecode::Op::And, 2);
return true;
}
bool parse_expression_not(Template& tmpl) {
bool parse_expression_not(Template &tmpl) {
if (m_tok.kind == Token::Kind::Id && m_tok.text == static_cast<decltype(m_tok.text)>("not")) {
get_next_token();
if (!parse_expression_not(tmpl)) return false;
if (!parse_expression_not(tmpl))
return false;
append_function(tmpl, Bytecode::Op::Not, 1);
return true;
} else {
@@ -100,164 +109,169 @@ class Parser {
}
}
bool parse_expression_comparison(Template& tmpl) {
if (!parse_expression_datum(tmpl)) return false;
bool parse_expression_comparison(Template &tmpl) {
if (!parse_expression_datum(tmpl))
return false;
Bytecode::Op op;
switch (m_tok.kind) {
case Token::Kind::Id:
if (m_tok.text == static_cast<decltype(m_tok.text)>("in"))
op = Bytecode::Op::In;
else
return true;
break;
case Token::Kind::Equal:
op = Bytecode::Op::Equal;
break;
case Token::Kind::GreaterThan:
op = Bytecode::Op::Greater;
break;
case Token::Kind::LessThan:
op = Bytecode::Op::Less;
break;
case Token::Kind::LessEqual:
op = Bytecode::Op::LessEqual;
break;
case Token::Kind::GreaterEqual:
op = Bytecode::Op::GreaterEqual;
break;
case Token::Kind::NotEqual:
op = Bytecode::Op::Different;
break;
default:
case Token::Kind::Id:
if (m_tok.text == static_cast<decltype(m_tok.text)>("in"))
op = Bytecode::Op::In;
else
return true;
break;
case Token::Kind::Equal:
op = Bytecode::Op::Equal;
break;
case Token::Kind::GreaterThan:
op = Bytecode::Op::Greater;
break;
case Token::Kind::LessThan:
op = Bytecode::Op::Less;
break;
case Token::Kind::LessEqual:
op = Bytecode::Op::LessEqual;
break;
case Token::Kind::GreaterEqual:
op = Bytecode::Op::GreaterEqual;
break;
case Token::Kind::NotEqual:
op = Bytecode::Op::Different;
break;
default:
return true;
}
get_next_token();
if (!parse_expression_datum(tmpl)) return false;
if (!parse_expression_datum(tmpl))
return false;
append_function(tmpl, op, 2);
return true;
}
bool parse_expression_datum(Template& tmpl) {
bool parse_expression_datum(Template &tmpl) {
nonstd::string_view json_first;
size_t bracket_level = 0;
size_t brace_level = 0;
for (;;) {
switch (m_tok.kind) {
case Token::Kind::LeftParen: {
get_next_token();
if (!parse_expression(tmpl)) return false;
if (m_tok.kind != Token::Kind::RightParen) {
throw_parser_error("unmatched '('");
}
get_next_token();
return true;
case Token::Kind::LeftParen: {
get_next_token();
if (!parse_expression(tmpl))
return false;
if (m_tok.kind != Token::Kind::RightParen) {
throw_parser_error("unmatched '('");
}
case Token::Kind::Id:
get_peek_token();
if (m_peek_tok.kind == Token::Kind::LeftParen) {
// function call, parse arguments
Token func_token = m_tok;
get_next_token(); // id
get_next_token(); // leftParen
unsigned int num_args = 0;
if (m_tok.kind == Token::Kind::RightParen) {
// no args
get_next_token();
} else {
for (;;) {
if (!parse_expression(tmpl)) {
throw_parser_error("expected expression, got '" + m_tok.describe() + "'");
}
num_args += 1;
if (m_tok.kind == Token::Kind::RightParen) {
get_next_token();
break;
}
if (m_tok.kind != Token::Kind::Comma) {
throw_parser_error("expected ')' or ',', got '" + m_tok.describe() + "'");
}
get_next_token();
}
}
auto op = m_static.functions.find_builtin(func_token.text, num_args);
if (op != Bytecode::Op::Nop) {
// swap arguments for default(); see comment in RenderTo()
if (op == Bytecode::Op::Default)
std::swap(tmpl.bytecodes.back(), *(tmpl.bytecodes.rbegin() + 1));
append_function(tmpl, op, num_args);
return true;
} else {
append_callback(tmpl, func_token.text, num_args);
return true;
}
} else if (m_tok.text == static_cast<decltype(m_tok.text)>("true") ||
m_tok.text == static_cast<decltype(m_tok.text)>("false") ||
m_tok.text == static_cast<decltype(m_tok.text)>("null")) {
// true, false, null are json literals
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
goto returnJson;
}
break;
} else {
// normal literal (json read)
tmpl.bytecodes.emplace_back(
Bytecode::Op::Push, m_tok.text,
m_config.notation == ElementNotation::Pointer ? Bytecode::Flag::ValueLookupPointer : Bytecode::Flag::ValueLookupDot);
get_next_token();
return true;
}
case Token::Kind::Id:
get_peek_token();
if (m_peek_tok.kind == Token::Kind::LeftParen) {
// function call, parse arguments
Token func_token = m_tok;
get_next_token(); // id
get_next_token(); // leftParen
unsigned int num_args = 0;
if (m_tok.kind == Token::Kind::RightParen) {
// no args
get_next_token();
} else {
for (;;) {
if (!parse_expression(tmpl)) {
throw_parser_error("expected expression, got '" + m_tok.describe() + "'");
}
num_args += 1;
if (m_tok.kind == Token::Kind::RightParen) {
get_next_token();
break;
}
if (m_tok.kind != Token::Kind::Comma) {
throw_parser_error("expected ')' or ',', got '" + m_tok.describe() + "'");
}
get_next_token();
}
}
auto op = m_static.functions.find_builtin(func_token.text, num_args);
if (op != Bytecode::Op::Nop) {
// swap arguments for default(); see comment in RenderTo()
if (op == Bytecode::Op::Default)
std::swap(tmpl.bytecodes.back(), *(tmpl.bytecodes.rbegin() + 1));
append_function(tmpl, op, num_args);
return true;
} else {
append_callback(tmpl, func_token.text, num_args);
return true;
}
// json passthrough
case Token::Kind::Number:
case Token::Kind::String:
} else if (m_tok.text == static_cast<decltype(m_tok.text)>("true") ||
m_tok.text == static_cast<decltype(m_tok.text)>("false") ||
m_tok.text == static_cast<decltype(m_tok.text)>("null")) {
// true, false, null are json literals
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
goto returnJson;
}
break;
case Token::Kind::Comma:
case Token::Kind::Colon:
if (brace_level == 0 && bracket_level == 0) {
throw_parser_error("unexpected token '" + m_tok.describe() + "'");
}
break;
case Token::Kind::LeftBracket:
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
}
bracket_level += 1;
break;
case Token::Kind::LeftBrace:
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
}
brace_level += 1;
break;
case Token::Kind::RightBracket:
if (bracket_level == 0) {
throw_parser_error("unexpected ']'");
}
--bracket_level;
if (brace_level == 0 && bracket_level == 0) goto returnJson;
break;
case Token::Kind::RightBrace:
if (brace_level == 0) {
throw_parser_error("unexpected '}'");
}
--brace_level;
if (brace_level == 0 && bracket_level == 0) goto returnJson;
break;
default:
if (brace_level != 0) {
throw_parser_error("unmatched '{'");
}
if (bracket_level != 0) {
throw_parser_error("unmatched '['");
}
return false;
} else {
// normal literal (json read)
tmpl.bytecodes.emplace_back(Bytecode::Op::Push, m_tok.text,
m_config.notation == ElementNotation::Pointer ? Bytecode::Flag::ValueLookupPointer
: Bytecode::Flag::ValueLookupDot);
get_next_token();
return true;
}
// json passthrough
case Token::Kind::Number:
case Token::Kind::String:
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
goto returnJson;
}
break;
case Token::Kind::Comma:
case Token::Kind::Colon:
if (brace_level == 0 && bracket_level == 0) {
throw_parser_error("unexpected token '" + m_tok.describe() + "'");
}
break;
case Token::Kind::LeftBracket:
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
}
bracket_level += 1;
break;
case Token::Kind::LeftBrace:
if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text;
}
brace_level += 1;
break;
case Token::Kind::RightBracket:
if (bracket_level == 0) {
throw_parser_error("unexpected ']'");
}
--bracket_level;
if (brace_level == 0 && bracket_level == 0)
goto returnJson;
break;
case Token::Kind::RightBrace:
if (brace_level == 0) {
throw_parser_error("unexpected '}'");
}
--brace_level;
if (brace_level == 0 && bracket_level == 0)
goto returnJson;
break;
default:
if (brace_level != 0) {
throw_parser_error("unmatched '{'");
}
if (bracket_level != 0) {
throw_parser_error("unmatched '['");
}
return false;
}
get_next_token();
@@ -271,14 +285,16 @@ class Parser {
return true;
}
bool parse_statement(Template& tmpl, nonstd::string_view path) {
if (m_tok.kind != Token::Kind::Id) return false;
bool parse_statement(Template &tmpl, nonstd::string_view path) {
if (m_tok.kind != Token::Kind::Id)
return false;
if (m_tok.text == static_cast<decltype(m_tok.text)>("if")) {
get_next_token();
// evaluate expression
if (!parse_expression(tmpl)) return false;
if (!parse_expression(tmpl))
return false;
// start a new if block on if stack
m_if_stack.emplace_back(static_cast<decltype(m_if_stack)::value_type::jump_t>(tmpl.bytecodes.size()));
@@ -289,7 +305,7 @@ class Parser {
if (m_if_stack.empty()) {
throw_parser_error("endif without matching if");
}
auto& if_data = m_if_stack.back();
auto &if_data = m_if_stack.back();
get_next_token();
// previous conditional jump jumps here
@@ -298,7 +314,7 @@ class Parser {
}
// update all previous unconditional jumps to here
for (size_t i: if_data.uncond_jumps) {
for (size_t i : if_data.uncond_jumps) {
tmpl.bytecodes[i].args = tmpl.bytecodes.size();
}
@@ -307,7 +323,7 @@ class Parser {
} else if (m_tok.text == static_cast<decltype(m_tok.text)>("else")) {
if (m_if_stack.empty())
throw_parser_error("else without matching if");
auto& if_data = m_if_stack.back();
auto &if_data = m_if_stack.back();
get_next_token();
// end previous block with unconditional jump to endif; destination will be
@@ -324,7 +340,8 @@ class Parser {
get_next_token();
// evaluate expression
if (!parse_expression(tmpl)) return false;
if (!parse_expression(tmpl))
return false;
// update "previous jump"
if_data.prev_cond_jump = tmpl.bytecodes.size();
@@ -352,11 +369,11 @@ class Parser {
}
if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("in"))
throw_parser_error(
"expected 'in', got '" + m_tok.describe() + "'");
throw_parser_error("expected 'in', got '" + m_tok.describe() + "'");
get_next_token();
if (!parse_expression(tmpl)) return false;
if (!parse_expression(tmpl))
return false;
m_loop_stack.push_back(tmpl.bytecodes.size());
@@ -375,7 +392,7 @@ class Parser {
tmpl.bytecodes[m_loop_stack.back()].args = tmpl.bytecodes.size();
tmpl.bytecodes.emplace_back(Bytecode::Op::EndLoop);
tmpl.bytecodes.back().args = m_loop_stack.back() + 1; // loop body
tmpl.bytecodes.back().args = m_loop_stack.back() + 1; // loop body
m_loop_stack.pop_back();
} else if (m_tok.text == static_cast<decltype(m_tok.text)>("include")) {
get_next_token();
@@ -387,7 +404,7 @@ class Parser {
// build the relative path
json json_name = json::parse(m_tok.text);
std::string pathname = static_cast<std::string>(path);
pathname += json_name.get_ref<const std::string&>();
pathname += json_name.get_ref<const std::string &>();
if (pathname.compare(0, 2, "./") == 0) {
pathname.erase(0, 2);
}
@@ -408,10 +425,10 @@ class Parser {
return true;
}
void append_function(Template& tmpl, Bytecode::Op op, unsigned int num_args) {
void append_function(Template &tmpl, Bytecode::Op op, unsigned int num_args) {
// we can merge with back-to-back push
if (!tmpl.bytecodes.empty()) {
Bytecode& last = tmpl.bytecodes.back();
Bytecode &last = tmpl.bytecodes.back();
if (last.op == Bytecode::Op::Push) {
last.op = op;
last.args = num_args;
@@ -423,12 +440,11 @@ class Parser {
tmpl.bytecodes.emplace_back(op, num_args);
}
void append_callback(Template& tmpl, nonstd::string_view name, unsigned int num_args) {
void append_callback(Template &tmpl, nonstd::string_view name, unsigned int num_args) {
// we can merge with back-to-back push value (not lookup)
if (!tmpl.bytecodes.empty()) {
Bytecode& last = tmpl.bytecodes.back();
if (last.op == Bytecode::Op::Push &&
(last.flags & Bytecode::Flag::ValueMask) == Bytecode::Flag::ValueImmediate) {
Bytecode &last = tmpl.bytecodes.back();
if (last.op == Bytecode::Op::Push && (last.flags & Bytecode::Flag::ValueMask) == Bytecode::Flag::ValueImmediate) {
last.op = Bytecode::Op::Callback;
last.args = num_args;
last.str = static_cast<std::string>(name);
@@ -441,55 +457,56 @@ class Parser {
tmpl.bytecodes.back().str = static_cast<std::string>(name);
}
void parse_into(Template& tmpl, nonstd::string_view path) {
void parse_into(Template &tmpl, nonstd::string_view path) {
m_lexer.start(tmpl.content);
for (;;) {
get_next_token();
switch (m_tok.kind) {
case Token::Kind::Eof:
if (!m_if_stack.empty()) throw_parser_error("unmatched if");
if (!m_loop_stack.empty()) throw_parser_error("unmatched for");
return;
case Token::Kind::Text:
tmpl.bytecodes.emplace_back(Bytecode::Op::PrintText, m_tok.text, 0u);
break;
case Token::Kind::StatementOpen:
get_next_token();
if (!parse_statement(tmpl, path)) {
throw_parser_error("expected statement, got '" + m_tok.describe() + "'");
}
if (m_tok.kind != Token::Kind::StatementClose) {
throw_parser_error("expected statement close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::LineStatementOpen:
get_next_token();
parse_statement(tmpl, path);
if (m_tok.kind != Token::Kind::LineStatementClose &&
m_tok.kind != Token::Kind::Eof) {
throw_parser_error("expected line statement close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::ExpressionOpen:
get_next_token();
if (!parse_expression(tmpl)) {
throw_parser_error("expected expression, got '" + m_tok.describe() + "'");
}
append_function(tmpl, Bytecode::Op::PrintValue, 1);
if (m_tok.kind != Token::Kind::ExpressionClose) {
throw_parser_error("expected expression close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::CommentOpen:
get_next_token();
if (m_tok.kind != Token::Kind::CommentClose) {
throw_parser_error("expected comment close, got '" + m_tok.describe() + "'");
}
break;
default:
throw_parser_error("unexpected token '" + m_tok.describe() + "'");
break;
case Token::Kind::Eof:
if (!m_if_stack.empty())
throw_parser_error("unmatched if");
if (!m_loop_stack.empty())
throw_parser_error("unmatched for");
return;
case Token::Kind::Text:
tmpl.bytecodes.emplace_back(Bytecode::Op::PrintText, m_tok.text, 0u);
break;
case Token::Kind::StatementOpen:
get_next_token();
if (!parse_statement(tmpl, path)) {
throw_parser_error("expected statement, got '" + m_tok.describe() + "'");
}
if (m_tok.kind != Token::Kind::StatementClose) {
throw_parser_error("expected statement close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::LineStatementOpen:
get_next_token();
parse_statement(tmpl, path);
if (m_tok.kind != Token::Kind::LineStatementClose && m_tok.kind != Token::Kind::Eof) {
throw_parser_error("expected line statement close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::ExpressionOpen:
get_next_token();
if (!parse_expression(tmpl)) {
throw_parser_error("expected expression, got '" + m_tok.describe() + "'");
}
append_function(tmpl, Bytecode::Op::PrintValue, 1);
if (m_tok.kind != Token::Kind::ExpressionClose) {
throw_parser_error("expected expression close, got '" + m_tok.describe() + "'");
}
break;
case Token::Kind::CommentOpen:
get_next_token();
if (m_tok.kind != Token::Kind::CommentClose) {
throw_parser_error("expected comment close, got '" + m_tok.describe() + "'");
}
break;
default:
throw_parser_error("unexpected token '" + m_tok.describe() + "'");
break;
}
}
}
@@ -501,16 +518,14 @@ class Parser {
return result;
}
Template parse(nonstd::string_view input) {
return parse(input, "./");
}
Template parse(nonstd::string_view input) { return parse(input, "./"); }
Template parse_template(nonstd::string_view filename) {
Template result;
result.content = load_file(filename);
nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1);
// StringRef path = sys::path::parent_path(filename);
// StringRef path = sys::path::parent_path(filename);
Parser(m_config, m_lexer.get_config(), m_included_templates).parse_into(result, path);
return result;
}
@@ -521,32 +536,27 @@ class Parser {
return text;
}
private:
const ParserConfig& m_config;
private:
const ParserConfig &m_config;
Lexer m_lexer;
Token m_tok;
Token m_peek_tok;
bool m_have_peek_tok {false};
TemplateStorage& m_included_templates;
const ParserStatic& m_static;
TemplateStorage &m_included_templates;
const ParserStatic &m_static;
struct IfData {
using jump_t = size_t;
jump_t prev_cond_jump;
std::vector<jump_t> uncond_jumps;
explicit IfData(jump_t condJump)
: prev_cond_jump(condJump)
{
}
explicit IfData(jump_t condJump) : prev_cond_jump(condJump) {}
};
std::vector<IfData> m_if_stack;
std::vector<size_t> m_loop_stack;
void throw_parser_error(const std::string& message) {
throw ParserError(message, m_lexer.current_position());
}
void throw_parser_error(const std::string &message) { throw ParserError(message, m_lexer.current_position()); }
void get_next_token() {
if (m_have_peek_tok) {
@@ -565,6 +575,6 @@ class Parser {
}
};
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_PARSER_HPP_
#endif // INCLUDE_INJA_PARSER_HPP_

View File

@@ -3,7 +3,6 @@
#ifndef INCLUDE_INJA_POLYFILL_HPP_
#define INCLUDE_INJA_POLYFILL_HPP_
#if __cplusplus < 201402L
#include <cstddef>
@@ -11,44 +10,31 @@
#include <type_traits>
#include <utility>
namespace stdinja {
template<class T> struct _Unique_if {
typedef std::unique_ptr<T> _Single_object;
};
template <class T> struct _Unique_if { typedef std::unique_ptr<T> _Single_object; };
template<class T> struct _Unique_if<T[]> {
typedef std::unique_ptr<T[]> _Unknown_bound;
};
template <class T> struct _Unique_if<T[]> { typedef std::unique_ptr<T[]> _Unknown_bound; };
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template <class T, size_t N> struct _Unique_if<T[N]> { typedef void _Known_bound; };
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
template <class T, class... Args> typename _Unique_if<T>::_Single_object make_unique(Args &&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
template <class T> typename _Unique_if<T>::_Unknown_bound make_unique(size_t n) {
typedef typename std::remove_extent<T>::type U;
return std::unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
template <class T, class... Args> typename _Unique_if<T>::_Known_bound make_unique(Args &&...) = delete;
} // namespace stdinja
} // namespace stdinja
#else
namespace stdinja = std;
#endif // memory */
#endif // memory */
#endif // INCLUDE_INJA_POLYFILL_HPP_
#endif // INCLUDE_INJA_POLYFILL_HPP_

View File

@@ -16,10 +16,9 @@
#include "template.hpp"
#include "utils.hpp"
namespace inja {
inline nonstd::string_view convert_dot_to_json_pointer(nonstd::string_view dot, std::string& out) {
inline nonstd::string_view convert_dot_to_json_pointer(nonstd::string_view dot, std::string &out) {
out.clear();
do {
nonstd::string_view part;
@@ -34,7 +33,7 @@ inline nonstd::string_view convert_dot_to_json_pointer(nonstd::string_view dot,
* \brief Class for rendering a Template with data.
*/
class Renderer {
std::vector<const json*>& get_args(const Bytecode& bc) {
std::vector<const json *> &get_args(const Bytecode &bc) {
m_tmp_args.clear();
bool has_imm = ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop);
@@ -57,7 +56,7 @@ class Renderer {
return m_tmp_args;
}
void pop_args(const Bytecode& bc) {
void pop_args(const Bytecode &bc) {
unsigned int popArgs = bc.args;
if ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop) {
popArgs -= 1;
@@ -67,22 +66,22 @@ class Renderer {
}
}
const json* get_imm(const Bytecode& bc) {
const json *get_imm(const Bytecode &bc) {
std::string ptr_buffer;
nonstd::string_view ptr;
switch (bc.flags & Bytecode::Flag::ValueMask) {
case Bytecode::Flag::ValuePop:
return nullptr;
case Bytecode::Flag::ValueImmediate:
return &bc.value;
case Bytecode::Flag::ValueLookupDot:
ptr = convert_dot_to_json_pointer(bc.str, ptr_buffer);
break;
case Bytecode::Flag::ValueLookupPointer:
ptr_buffer += '/';
ptr_buffer += bc.str;
ptr = ptr_buffer;
break;
case Bytecode::Flag::ValuePop:
return nullptr;
case Bytecode::Flag::ValueImmediate:
return &bc.value;
case Bytecode::Flag::ValueLookupDot:
ptr = convert_dot_to_json_pointer(bc.str, ptr_buffer);
break;
case Bytecode::Flag::ValueLookupPointer:
ptr_buffer += '/';
ptr_buffer += bc.str;
ptr = ptr_buffer;
break;
}
json::json_pointer json_ptr(ptr.data());
try {
@@ -92,10 +91,10 @@ class Renderer {
return &m_loop_data->at(json_ptr);
}
return &m_data->at(json_ptr);
} catch (std::exception&) {
} catch (std::exception &) {
// try to evaluate as a no-argument callback
if (auto callback = m_callbacks.find_callback(bc.str, 0)) {
std::vector<const json*> arguments {};
std::vector<const json *> arguments {};
m_tmp_val = callback(arguments);
return &m_tmp_val;
}
@@ -104,7 +103,7 @@ class Renderer {
}
}
bool truthy(const json& var) const {
bool truthy(const json &var) const {
if (var.empty()) {
return false;
} else if (var.is_number()) {
@@ -115,16 +114,16 @@ class Renderer {
try {
return var.get<bool>();
} catch (json::type_error& e) {
} catch (json::type_error &e) {
throw JsonError(e.what());
}
}
void update_loop_data() {
LoopLevel& level = m_loop_stack.back();
void update_loop_data() {
LoopLevel &level = m_loop_stack.back();
if (level.loop_type == LoopLevel::Type::Array) {
level.data[static_cast<std::string>(level.value_name)] = level.values.at(level.index); // *level.it;
level.data[static_cast<std::string>(level.value_name)] = level.values.at(level.index); // *level.it;
} else {
level.data[static_cast<std::string>(level.key_name)] = level.map_it->first;
level.data[static_cast<std::string>(level.value_name)] = *level.map_it->second;
@@ -136,457 +135,459 @@ class Renderer {
loopData["is_last"] = (level.index == level.size - 1);
}
const TemplateStorage& m_included_templates;
const FunctionStorage& m_callbacks;
const TemplateStorage &m_included_templates;
const FunctionStorage &m_callbacks;
std::vector<json> m_stack;
struct LoopLevel {
enum class Type { Map, Array };
Type loop_type;
nonstd::string_view key_name; // variable name for keys
nonstd::string_view value_name; // variable name for values
json data; // data with loop info added
nonstd::string_view key_name; // variable name for keys
nonstd::string_view value_name; // variable name for values
json data; // data with loop info added
json values; // values to iterate over
json values; // values to iterate over
// loop over list
size_t index; // current list index
size_t size; // length of list
size_t index; // current list index
size_t size; // length of list
// loop over map
using KeyValue = std::pair<nonstd::string_view, json*>;
using KeyValue = std::pair<nonstd::string_view, json *>;
using MapValues = std::vector<KeyValue>;
MapValues map_values; // values to iterate over
MapValues::iterator map_it; // iterator over values
MapValues map_values; // values to iterate over
MapValues::iterator map_it; // iterator over values
};
std::vector<LoopLevel> m_loop_stack;
json* m_loop_data;
const json* m_data;
json *m_loop_data;
const json *m_data;
std::vector<const json*> m_tmp_args;
std::vector<const json *> m_tmp_args;
json m_tmp_val;
public:
Renderer(const TemplateStorage& included_templates, const FunctionStorage& callbacks): m_included_templates(included_templates), m_callbacks(callbacks) {
public:
Renderer(const TemplateStorage &included_templates, const FunctionStorage &callbacks)
: m_included_templates(included_templates), m_callbacks(callbacks) {
m_stack.reserve(16);
m_tmp_args.reserve(4);
m_loop_stack.reserve(16);
}
void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) {
void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) {
m_data = &data;
m_loop_data = loop_data;
for (size_t i = 0; i < tmpl.bytecodes.size(); ++i) {
const auto& bc = tmpl.bytecodes[i];
const auto &bc = tmpl.bytecodes[i];
switch (bc.op) {
case Bytecode::Op::Nop: {
break;
case Bytecode::Op::Nop: {
break;
}
case Bytecode::Op::PrintText: {
os << bc.str;
break;
}
case Bytecode::Op::PrintValue: {
const json &val = *get_args(bc)[0];
if (val.is_string()) {
os << val.get_ref<const std::string &>();
} else {
os << val.dump();
}
case Bytecode::Op::PrintText: {
os << bc.str;
break;
}
case Bytecode::Op::PrintValue: {
const json& val = *get_args(bc)[0];
if (val.is_string()) {
os << val.get_ref<const std::string&>();
} else {
os << val.dump();
}
pop_args(bc);
break;
}
case Bytecode::Op::Push: {
m_stack.emplace_back(*get_imm(bc));
break;
}
case Bytecode::Op::Upper: {
auto result = get_args(bc)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Lower: {
auto result = get_args(bc)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Range: {
int number = get_args(bc)[0]->get<int>();
std::vector<int> result(number);
std::iota(std::begin(result), std::end(result), 0);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Length: {
const json& val = *get_args(bc)[0];
pop_args(bc);
break;
}
case Bytecode::Op::Push: {
m_stack.emplace_back(*get_imm(bc));
break;
}
case Bytecode::Op::Upper: {
auto result = get_args(bc)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Lower: {
auto result = get_args(bc)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Range: {
int number = get_args(bc)[0]->get<int>();
std::vector<int> result(number);
std::iota(std::begin(result), std::end(result), 0);
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Length: {
const json &val = *get_args(bc)[0];
size_t result;
if (val.is_string()) {
result = val.get_ref<const std::string&>().length();
} else {
result = val.size();
}
size_t result;
if (val.is_string()) {
result = val.get_ref<const std::string &>().length();
} else {
result = val.size();
}
pop_args(bc);
m_stack.emplace_back(result);
break;
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Sort: {
auto result = get_args(bc)[0]->get<std::vector<json>>();
std::sort(result.begin(), result.end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::At: {
auto args = get_args(bc);
auto result = args[0]->at(args[1]->get<int>());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::First: {
auto result = get_args(bc)[0]->front();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Last: {
auto result = get_args(bc)[0]->back();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Round: {
auto args = get_args(bc);
double number = args[0]->get<double>();
int precision = args[1]->get<int>();
pop_args(bc);
m_stack.emplace_back(std::round(number * std::pow(10.0, precision)) / std::pow(10.0, precision));
break;
}
case Bytecode::Op::DivisibleBy: {
auto args = get_args(bc);
int number = args[0]->get<int>();
int divisor = args[1]->get<int>();
pop_args(bc);
m_stack.emplace_back((divisor != 0) && (number % divisor == 0));
break;
}
case Bytecode::Op::Odd: {
int number = get_args(bc)[0]->get<int>();
pop_args(bc);
m_stack.emplace_back(number % 2 != 0);
break;
}
case Bytecode::Op::Even: {
int number = get_args(bc)[0]->get<int>();
pop_args(bc);
m_stack.emplace_back(number % 2 == 0);
break;
}
case Bytecode::Op::Max: {
auto args = get_args(bc);
auto result = *std::max_element(args[0]->begin(), args[0]->end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Min: {
auto args = get_args(bc);
auto result = *std::min_element(args[0]->begin(), args[0]->end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Not: {
bool result = !truthy(*get_args(bc)[0]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::And: {
auto args = get_args(bc);
bool result = truthy(*args[0]) && truthy(*args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Or: {
auto args = get_args(bc);
bool result = truthy(*args[0]) || truthy(*args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::In: {
auto args = get_args(bc);
bool result = std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Equal: {
auto args = get_args(bc);
bool result = (*args[0] == *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Greater: {
auto args = get_args(bc);
bool result = (*args[0] > *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Less: {
auto args = get_args(bc);
bool result = (*args[0] < *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::GreaterEqual: {
auto args = get_args(bc);
bool result = (*args[0] >= *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::LessEqual: {
auto args = get_args(bc);
bool result = (*args[0] <= *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Different: {
auto args = get_args(bc);
bool result = (*args[0] != *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Float: {
double result = std::stod(get_args(bc)[0]->get_ref<const std::string &>());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Int: {
int result = std::stoi(get_args(bc)[0]->get_ref<const std::string &>());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Exists: {
auto &&name = get_args(bc)[0]->get_ref<const std::string &>();
bool result = (data.find(name) != data.end());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::ExistsInObject: {
auto args = get_args(bc);
auto &&name = args[1]->get_ref<const std::string &>();
bool result = (args[0]->find(name) != args[0]->end());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsBoolean: {
bool result = get_args(bc)[0]->is_boolean();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsNumber: {
bool result = get_args(bc)[0]->is_number();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsInteger: {
bool result = get_args(bc)[0]->is_number_integer();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsFloat: {
bool result = get_args(bc)[0]->is_number_float();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsObject: {
bool result = get_args(bc)[0]->is_object();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsArray: {
bool result = get_args(bc)[0]->is_array();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsString: {
bool result = get_args(bc)[0]->is_string();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Default: {
// default needs to be a bit "magic"; we can't evaluate the first
// argument during the push operation, so we swap the arguments during
// the parse phase so the second argument is pushed on the stack and
// the first argument is in the immediate
try {
const json *imm = get_imm(bc);
// if no exception was raised, replace the stack value with it
m_stack.back() = *imm;
} catch (std::exception &) {
// couldn't read immediate, just leave the stack as is
}
case Bytecode::Op::Sort: {
auto result = get_args(bc)[0]->get<std::vector<json>>();
std::sort(result.begin(), result.end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
break;
}
case Bytecode::Op::Include:
Renderer(m_included_templates, m_callbacks)
.render_to(os, m_included_templates.find(get_imm(bc)->get_ref<const std::string &>())->second, *m_data,
m_loop_data);
break;
case Bytecode::Op::Callback: {
auto callback = m_callbacks.find_callback(bc.str, bc.args);
if (!callback) {
throw RenderError("function '" + static_cast<std::string>(bc.str) + "' (" +
std::to_string(static_cast<unsigned int>(bc.args)) + ") not found");
}
case Bytecode::Op::At: {
auto args = get_args(bc);
auto result = args[0]->at(args[1]->get<int>());
pop_args(bc);
m_stack.emplace_back(result);
break;
json result = callback(get_args(bc));
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Jump: {
i = bc.args - 1; // -1 due to ++i in loop
break;
}
case Bytecode::Op::ConditionalJump: {
if (!truthy(m_stack.back())) {
i = bc.args - 1; // -1 due to ++i in loop
}
case Bytecode::Op::First: {
auto result = get_args(bc)[0]->front();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Last: {
auto result = get_args(bc)[0]->back();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Round: {
auto args = get_args(bc);
double number = args[0]->get<double>();
int precision = args[1]->get<int>();
pop_args(bc);
m_stack.emplace_back(std::round(number * std::pow(10.0, precision)) / std::pow(10.0, precision));
break;
}
case Bytecode::Op::DivisibleBy: {
auto args = get_args(bc);
int number = args[0]->get<int>();
int divisor = args[1]->get<int>();
pop_args(bc);
m_stack.emplace_back((divisor != 0) && (number % divisor == 0));
break;
}
case Bytecode::Op::Odd: {
int number = get_args(bc)[0]->get<int>();
pop_args(bc);
m_stack.emplace_back(number % 2 != 0);
break;
}
case Bytecode::Op::Even: {
int number = get_args(bc)[0]->get<int>();
pop_args(bc);
m_stack.emplace_back(number % 2 == 0);
break;
}
case Bytecode::Op::Max: {
auto args = get_args(bc);
auto result = *std::max_element(args[0]->begin(), args[0]->end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Min: {
auto args = get_args(bc);
auto result = *std::min_element(args[0]->begin(), args[0]->end());
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Not: {
bool result = !truthy(*get_args(bc)[0]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::And: {
auto args = get_args(bc);
bool result = truthy(*args[0]) && truthy(*args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Or: {
auto args = get_args(bc);
bool result = truthy(*args[0]) || truthy(*args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::In: {
auto args = get_args(bc);
bool result = std::find(args[1]->begin(), args[1]->end(), *args[0]) !=
args[1]->end();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Equal: {
auto args = get_args(bc);
bool result = (*args[0] == *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Greater: {
auto args = get_args(bc);
bool result = (*args[0] > *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Less: {
auto args = get_args(bc);
bool result = (*args[0] < *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::GreaterEqual: {
auto args = get_args(bc);
bool result = (*args[0] >= *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::LessEqual: {
auto args = get_args(bc);
bool result = (*args[0] <= *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Different: {
auto args = get_args(bc);
bool result = (*args[0] != *args[1]);
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Float: {
double result =
std::stod(get_args(bc)[0]->get_ref<const std::string&>());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Int: {
int result = std::stoi(get_args(bc)[0]->get_ref<const std::string&>());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Exists: {
auto&& name = get_args(bc)[0]->get_ref<const std::string&>();
bool result = (data.find(name) != data.end());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::ExistsInObject: {
auto args = get_args(bc);
auto&& name = args[1]->get_ref<const std::string&>();
bool result = (args[0]->find(name) != args[0]->end());
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsBoolean: {
bool result = get_args(bc)[0]->is_boolean();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsNumber: {
bool result = get_args(bc)[0]->is_number();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsInteger: {
bool result = get_args(bc)[0]->is_number_integer();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsFloat: {
bool result = get_args(bc)[0]->is_number_float();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsObject: {
bool result = get_args(bc)[0]->is_object();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsArray: {
bool result = get_args(bc)[0]->is_array();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::IsString: {
bool result = get_args(bc)[0]->is_string();
pop_args(bc);
m_stack.emplace_back(result);
break;
}
case Bytecode::Op::Default: {
// default needs to be a bit "magic"; we can't evaluate the first
// argument during the push operation, so we swap the arguments during
// the parse phase so the second argument is pushed on the stack and
// the first argument is in the immediate
try {
const json* imm = get_imm(bc);
// if no exception was raised, replace the stack value with it
m_stack.back() = *imm;
} catch (std::exception&) {
// couldn't read immediate, just leave the stack as is
}
break;
}
case Bytecode::Op::Include:
Renderer(m_included_templates, m_callbacks).render_to(os, m_included_templates.find(get_imm(bc)->get_ref<const std::string&>())->second, *m_data, m_loop_data);
break;
case Bytecode::Op::Callback: {
auto callback = m_callbacks.find_callback(bc.str, bc.args);
if (!callback) {
throw RenderError("function '" + static_cast<std::string>(bc.str) + "' (" + std::to_string(static_cast<unsigned int>(bc.args)) + ") not found");
}
json result = callback(get_args(bc));
pop_args(bc);
m_stack.emplace_back(std::move(result));
break;
}
case Bytecode::Op::Jump: {
i = bc.args - 1; // -1 due to ++i in loop
break;
}
case Bytecode::Op::ConditionalJump: {
if (!truthy(m_stack.back())) {
i = bc.args - 1; // -1 due to ++i in loop
}
m_stack.pop_back();
break;
}
case Bytecode::Op::StartLoop: {
// jump past loop body if empty
if (m_stack.back().empty()) {
m_stack.pop_back();
i = bc.args; // ++i in loop will take it past EndLoop
break;
}
case Bytecode::Op::StartLoop: {
// jump past loop body if empty
if (m_stack.back().empty()) {
m_stack.pop_back();
i = bc.args; // ++i in loop will take it past EndLoop
break;
}
m_loop_stack.emplace_back();
LoopLevel& level = m_loop_stack.back();
level.value_name = bc.str;
level.values = std::move(m_stack.back());
if (m_loop_data) {
level.data = *m_loop_data;
}
level.index = 0;
m_stack.pop_back();
if (bc.value.is_string()) {
// map iterator
if (!level.values.is_object()) {
m_loop_stack.pop_back();
throw RenderError("for key, value requires object");
}
level.loop_type = LoopLevel::Type::Map;
level.key_name = bc.value.get_ref<const std::string&>();
// sort by key
for (auto it = level.values.begin(), end = level.values.end(); it != end; ++it) {
level.map_values.emplace_back(it.key(), &it.value());
}
auto sort_lambda = [](const LoopLevel::KeyValue& a, const LoopLevel::KeyValue& b) { return a.first < b.first; };
std::sort(level.map_values.begin(), level.map_values.end(), sort_lambda);
level.map_it = level.map_values.begin();
level.size = level.map_values.size();
} else {
if (!level.values.is_array()) {
m_loop_stack.pop_back();
throw RenderError("type must be array");
}
// list iterator
level.loop_type = LoopLevel::Type::Array;
level.size = level.values.size();
}
// provide parent access in nested loop
auto parent_loop_it = level.data.find("loop");
if (parent_loop_it != level.data.end()) {
json loop_copy = *parent_loop_it;
(*parent_loop_it)["parent"] = std::move(loop_copy);
}
// set "current" loop data to this level
m_loop_data = &level.data;
update_loop_data();
break;
m_loop_stack.emplace_back();
LoopLevel &level = m_loop_stack.back();
level.value_name = bc.str;
level.values = std::move(m_stack.back());
if (m_loop_data) {
level.data = *m_loop_data;
}
case Bytecode::Op::EndLoop: {
if (m_loop_stack.empty()) {
throw RenderError("unexpected state in renderer");
}
LoopLevel& level = m_loop_stack.back();
level.index = 0;
m_stack.pop_back();
bool done;
level.index += 1;
if (level.loop_type == LoopLevel::Type::Array) {
done = (level.index == level.values.size());
} else {
level.map_it += 1;
done = (level.map_it == level.map_values.end());
}
if (done) {
if (bc.value.is_string()) {
// map iterator
if (!level.values.is_object()) {
m_loop_stack.pop_back();
// set "current" data to outer loop data or main data as appropriate
if (!m_loop_stack.empty()) {
m_loop_data = &m_loop_stack.back().data;
} else {
m_loop_data = loop_data;
}
break;
throw RenderError("for key, value requires object");
}
level.loop_type = LoopLevel::Type::Map;
level.key_name = bc.value.get_ref<const std::string &>();
// sort by key
for (auto it = level.values.begin(), end = level.values.end(); it != end; ++it) {
level.map_values.emplace_back(it.key(), &it.value());
}
auto sort_lambda = [](const LoopLevel::KeyValue &a, const LoopLevel::KeyValue &b) {
return a.first < b.first;
};
std::sort(level.map_values.begin(), level.map_values.end(), sort_lambda);
level.map_it = level.map_values.begin();
level.size = level.map_values.size();
} else {
if (!level.values.is_array()) {
m_loop_stack.pop_back();
throw RenderError("type must be array");
}
update_loop_data();
// list iterator
level.loop_type = LoopLevel::Type::Array;
level.size = level.values.size();
}
// jump back to start of loop
i = bc.args - 1; // -1 due to ++i in loop
// provide parent access in nested loop
auto parent_loop_it = level.data.find("loop");
if (parent_loop_it != level.data.end()) {
json loop_copy = *parent_loop_it;
(*parent_loop_it)["parent"] = std::move(loop_copy);
}
// set "current" loop data to this level
m_loop_data = &level.data;
update_loop_data();
break;
}
case Bytecode::Op::EndLoop: {
if (m_loop_stack.empty()) {
throw RenderError("unexpected state in renderer");
}
LoopLevel &level = m_loop_stack.back();
bool done;
level.index += 1;
if (level.loop_type == LoopLevel::Type::Array) {
done = (level.index == level.values.size());
} else {
level.map_it += 1;
done = (level.map_it == level.map_values.end());
}
if (done) {
m_loop_stack.pop_back();
// set "current" data to outer loop data or main data as appropriate
if (!m_loop_stack.empty()) {
m_loop_data = &m_loop_stack.back().data;
} else {
m_loop_data = loop_data;
}
break;
}
default: {
throw RenderError("unknown op in renderer: " + std::to_string(static_cast<unsigned int>(bc.op)));
}
update_loop_data();
// jump back to start of loop
i = bc.args - 1; // -1 due to ++i in loop
break;
}
default: {
throw RenderError("unknown op in renderer: " + std::to_string(static_cast<unsigned int>(bc.op)));
}
}
}
}
};
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_RENDERER_HPP_
#endif // INCLUDE_INJA_RENDERER_HPP_

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@
#include "bytecode.hpp"
namespace inja {
/*!
@@ -22,6 +21,6 @@ struct Template {
using TemplateStorage = std::map<std::string, Template>;
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_TEMPLATE_HPP_
#endif // INCLUDE_INJA_TEMPLATE_HPP_

View File

@@ -7,7 +7,6 @@
#include "string_view.hpp"
namespace inja {
/*!
@@ -16,31 +15,31 @@ namespace inja {
struct Token {
enum class Kind {
Text,
ExpressionOpen, // {{
ExpressionClose, // }}
LineStatementOpen, // ##
LineStatementClose, // \n
StatementOpen, // {%
StatementClose, // %}
CommentOpen, // {#
CommentClose, // #}
Id, // this, this.foo
Number, // 1, 2, -1, 5.2, -5.3
String, // "this"
Comma, // ,
Colon, // :
LeftParen, // (
RightParen, // )
LeftBracket, // [
RightBracket, // ]
LeftBrace, // {
RightBrace, // }
Equal, // ==
GreaterThan, // >
GreaterEqual, // >=
LessThan, // <
LessEqual, // <=
NotEqual, // !=
ExpressionOpen, // {{
ExpressionClose, // }}
LineStatementOpen, // ##
LineStatementClose, // \n
StatementOpen, // {%
StatementClose, // %}
CommentOpen, // {#
CommentClose, // #}
Id, // this, this.foo
Number, // 1, 2, -1, 5.2, -5.3
String, // "this"
Comma, // ,
Colon, // :
LeftParen, // (
RightParen, // )
LeftBracket, // [
RightBracket, // ]
LeftBrace, // {
RightBrace, // }
Equal, // ==
GreaterThan, // >
GreaterEqual, // >=
LessThan, // <
LessEqual, // <=
NotEqual, // !=
Unknown,
Eof
} kind {Kind::Unknown};
@@ -48,22 +47,22 @@ struct Token {
nonstd::string_view text;
constexpr Token() = default;
constexpr Token(Kind kind, nonstd::string_view text): kind(kind), text(text) {}
constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {}
std::string describe() const {
switch (kind) {
case Kind::Text:
return "<text>";
case Kind::LineStatementClose:
return "<eol>";
case Kind::Eof:
return "<eof>";
default:
return static_cast<std::string>(text);
case Kind::Text:
return "<text>";
case Kind::LineStatementClose:
return "<eol>";
case Kind::Eof:
return "<eof>";
default:
return static_cast<std::string>(text);
}
}
};
}
} // namespace inja
#endif // INCLUDE_INJA_TOKEN_HPP_
#endif // INCLUDE_INJA_TOKEN_HPP_

View File

@@ -11,40 +11,39 @@
#include "exceptions.hpp"
#include "string_view.hpp"
namespace inja {
inline std::ifstream open_file_or_throw(const std::string& path) {
inline std::ifstream open_file_or_throw(const std::string &path) {
std::ifstream file;
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
file.open(path);
} catch(const std::ios_base::failure& /*e*/) {
} catch (const std::ios_base::failure & /*e*/) {
throw FileError("failed accessing file at '" + path + "'");
}
return file;
}
namespace string_view {
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
start = std::min(start, view.size());
end = std::min(std::max(start, end), view.size());
return view.substr(start, end - start); // StringRef(Data + Start, End - Start);
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
start = std::min(start, view.size());
end = std::min(std::max(start, end), view.size());
return view.substr(start, end - start); // StringRef(Data + Start, End - Start);
}
inline std::pair<nonstd::string_view, nonstd::string_view> split(nonstd::string_view view, char Separator) {
size_t idx = view.find(Separator);
if (idx == nonstd::string_view::npos) {
return std::make_pair(view, nonstd::string_view());
}
return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos));
}
inline std::pair<nonstd::string_view, nonstd::string_view> split(nonstd::string_view view, char Separator) {
size_t idx = view.find(Separator);
if (idx == nonstd::string_view::npos) {
return std::make_pair(view, nonstd::string_view());
}
return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos));
}
inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) {
return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0);
}
} // namespace string_view
inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) {
return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0);
}
} // namespace string_view
} // namespace inja
} // namespace inja
#endif // INCLUDE_INJA_UTILS_HPP_
#endif // INCLUDE_INJA_UTILS_HPP_

File diff suppressed because it is too large Load Diff

View File

@@ -3,231 +3,261 @@
#include "hayai/hayai.hpp"
#include <inja/inja.hpp>
using json = nlohmann::json;
inja::Environment env;
json smallData = {{"name","Peter"},
{"list001",{"lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit","aliquam","accumsan"}},
{"list002",{"donec","in","egestas","diam","aenean","suscipit","scelerisque","efficitur","integer","a"}},
{"list003",{"maecenas","metus","erat","vestibulum","quis","porta","in","consequat","sed","justo"}},
json smallData = {
{"name", "Peter"},
{"list001", {"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "aliquam", "accumsan"}},
{"list002", {"donec", "in", "egestas", "diam", "aenean", "suscipit", "scelerisque", "efficitur", "integer", "a"}},
{"list003", {"maecenas", "metus", "erat", "vestibulum", "quis", "porta", "in", "consequat", "sed", "justo"}},
};
json largeData = {{"name","Peter"},
{"list001",{"lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit","aliquam","accumsan"}},
{"list002",{"donec","in","egestas","diam","aenean","suscipit","scelerisque","efficitur","integer","a"}},
{"list003",{"maecenas","metus","erat","vestibulum","quis","porta","in","consequat","sed","justo"}},
{"list004",{"aliquam","erat","volutpat","integer","laoreet","tellus","ut","nibh","viverra","in"}},
{"list005",{"integer","eu","velit","ac","erat","fringilla","ullamcorper","id","in","tortor"}},
{"list006",{"quisque","at","dapibus","ante","donec","et","lectus","sit","amet","lorem"}},
{"list007",{"curabitur","imperdiet","nibh","sit","amet","pellentesque","tincidunt","quisque","ornare","enim"}},
{"list008",{"aliquam","vitae","condimentum","libero","non","sodales","metus","vestibulum","aliquet","ullamcorper"}},
{"list009",{"pellentesque","sed","diam","tellus","donec","sed","nulla","lorem","sed","imperdiet"}},
{"list010",{"nam","sagittis","maximus","congue","class","aptent","taciti","sociosqu","ad","litora"}},
{"list011",{"praesent","semper","elit","ut","consequat","fringilla","mauris","turpis","volutpat","risus"}},
{"list012",{"nullam","risus","urna","facilisis","sit","amet","nibh","id","blandit","mollis"}},
{"list013",{"donec","non","ante","nisi","praesent","suscipit","erat","quis","lorem","convallis"}},
{"list014",{"maecenas","tincidunt","suscipit","ipsum","non","tristique","proin","accumsan","cursus","justo"}},
{"list015",{"nulla","facilisi","nullam","et","enim","finibus","aliquet","mi","ut","mattis"}},
{"list016",{"fusce","rutrum","lobortis","ligula","vitae","condimentum","risus","ultrices","sed","sed"}},
{"list017",{"nullam","quis","tristique","ante","proin","neque","ante","sollicitudin","non","bibendum"}},
{"list018",{"nam","bibendum","quam","et","semper","eleifend","donec","placerat","scelerisque","mi"}},
{"list019",{"donec","vel","suscipit","turpis","cras","semper","arcu","in","velit","hendrerit"}},
{"list020",{"praesent","congue","gravida","mauris","integer","ornare","dictum","lorem","non","consequat"}},
{"list021",{"nam","tincidunt","tortor","sit","amet","gravida","dapibus","fusce","laoreet","nulla"}},
{"list022",{"in","id","enim","pellentesque","suscipit","velit","id","posuere","metus","quisque"}},
{"list023",{"curabitur","elit","ex","aliquam","eget","mi","sed","tristique","scelerisque","ligula"}},
{"list024",{"vivamus","dui","lacus","hendrerit","eu","sagittis","non","finibus","consequat","turpis"}},
{"list025",{"etiam","ultrices","vel","massa","non","vehicula","duis","aliquet","massa","id"}},
{"list026",{"duis","ultrices","sem","a","sapien","dignissim","hendrerit","eu","vel","sem"}},
{"list027",{"vivamus","eu","elit","ac","metus","pharetra","pretium","etiam","mattis","velit"}},
{"list028",{"ut","ac","commodo","risus","nam","eget","hendrerit","magna","vestibulum","elementum"}},
{"list029",{"mauris","in","ultricies","tellus","ut","in","velit","porttitor","vulputate","enim"}},
{"list030",{"donec","dapibus","urna","ac","scelerisque","mollis","urna","orci","congue","nulla"}},
{"list031",{"vivamus","eleifend","dapibus","maximus","donec","eu","accumsan","nulla","eu","iaculis"}},
{"list032",{"aliquam","erat","volutpat","orci","varius","natoque","penatibus","et","magnis","dis"}},
{"list033",{"maecenas","laoreet","sed","quam","eget","bibendum","etiam","et","sollicitudin","mi"}},
{"list034",{"aliquam","rhoncus","purus","vel","nibh","laoreet","pharetra","vestibulum","ante","ipsum"}},
{"list035",{"quisque","semper","urna","vel","mollis","gravida","fusce","nibh","mauris","porttitor"}},
{"list036",{"sed","luctus","risus","et","sodales","mollis","maecenas","a","tincidunt","risus"}},
{"list037",{"in","hac","habitasse","platea","dictumst","nullam","fermentum","commodo","tempor","aenean"}},
{"list038",{"nunc","rhoncus","diam","magna","ac","lacinia","felis","consequat","non","maecenas"}},
{"list039",{"sed","scelerisque","velit","est","sed","mollis","ligula","cursus","sed","aliquam"}},
{"list040",{"nulla","dictum","arcu","eu","mauris","malesuada","quis","cursus","nisi","faucibus"}},
{"list041",{"phasellus","posuere","lacus","nec","molestie","accumsan","metus","tortor","facilisis","nunc"}},
{"list042",{"curabitur","ex","neque","pretium","sit","amet","leo","in","tempus","vestibulum"}},
{"list043",{"curabitur","tortor","tortor","fermentum","eget","velit","ac","cursus","faucibus","odio"}},
{"list044",{"aenean","varius","malesuada","sapien","sed","hendrerit","purus","molestie","sed","sed"}},
{"list045",{"nam","hendrerit","pretium","purus","id","fermentum","nunc","in","lacus","metus"}},
{"list046",{"fusce","euismod","libero","sed","diam","pharetra","non","semper","elit","accumsan"}},
{"list047",{"phasellus","sed","ligula","sit","amet","justo","vestibulum","fermentum","congue","sed"}},
{"list048",{"sed","dignissim","felis","eget","elit","dictum","malesuada","vivamus","maximus","mi"}},
{"list049",{"proin","lobortis","tempus","molestie","sed","tortor","ipsum","finibus","non","aliquet"}},
{"list050",{"integer","ut","leo","dui","proin","condimentum","dolor","a","suscipit","maximus"}},
{"list051",{"vivamus","sapien","neque","dapibus","sed","volutpat","at","vestibulum","sit","amet"}},
{"list052",{"curabitur","enim","enim","faucibus","sit","amet","luctus","aliquam","placerat","nec"}},
{"list053",{"suspendisse","ligula","dolor","ultricies","sit","amet","iaculis","vel","luctus","sit"}},
{"list054",{"aenean","aliquam","elit","sit","amet","ultricies","lobortis","nullam","porttitor","libero"}},
{"list055",{"ut","tincidunt","convallis","placerat","suspendisse","potenti","cras","commodo","fringilla","lacus"}},
{"list056",{"nulla","quis","elit","est","etiam","sagittis","molestie","porta","lorem","ipsum"}},
{"list057",{"in","turpis","nibh","gravida","vitae","ex","eu","eleifend","tincidunt","neque"}},
{"list058",{"suspendisse","in","fermentum","orci","etiam","viverra","metus","a","condimentum","blandit"}},
{"list059",{"fusce","dapibus","velit","id","est","maximus","rhoncus","quisque","lobortis","tortor"}},
{"list060",{"ut","molestie","ornare","velit","id","consectetur","nunc","varius","eu","vivamus"}},
{"list061",{"aenean","egestas","nibh","at","tincidunt","varius","felis","metus","ullamcorper","purus"}},
{"list062",{"donec","a","eleifend","libero","sed","fermentum","sem","fusce","eget","lacinia"}},
{"list063",{"nullam","metus","mauris","ornare","in","sem","at","posuere","placerat","nisl"}},
{"list064",{"maecenas","vitae","enim","ipsum","sed","mattis","porta","mi","congue","suscipit"}},
{"list065",{"nulla","placerat","porttitor","metus","non","suscipit","turpis","rhoncus","non","etiam"}},
{"list066",{"morbi","id","fringilla","quam","eget","sollicitudin","felis","aliquam","quis","risus"}},
{"list067",{"pellentesque","maximus","leo","ut","quam","consequat","lacinia","suspendisse","facilisis","leo"}},
{"list068",{"phasellus","rhoncus","urna","felis","nullam","pharetra","porttitor","neque","sit","amet"}},
{"list069",{"in","posuere","mauris","sit","amet","nisl","tempor","eget","euismod","neque"}},
{"list070",{"mauris","id","lobortis","magna","suspendisse","nisl","orci","auctor","suscipit","malesuada"}},
{"list071",{"ut","facilisis","magna","vitae","ligula","laoreet","tempor","class","aptent","taciti"}},
{"list072",{"aenean","sagittis","lacus","vel","nulla","porta","quis","egestas","sem","ullamcorper"}},
{"list073",{"donec","eget","purus","scelerisque","rutrum","dolor","eget","maximus","dui","aliquam"}},
{"list074",{"donec","congue","a","massa","vel","posuere","donec","ullamcorper","elit","id"}},
{"list075",{"praesent","consectetur","est","eu","tellus","dictum","tempor","nullam","id","risus"}},
{"list076",{"etiam","gravida","gravida","tempus","fusce","lobortis","orci","ac","volutpat","mollis"}},
{"list077",{"quisque","quis","turpis","et","augue","dignissim","maximus","donec","vel","justo"}},
{"list078",{"aenean","consectetur","mi","non","nibh","bibendum","ac","tincidunt","dui","tristique"}},
{"list079",{"aliquam","tempus","elit","non","ipsum","finibus","ut","luctus","dolor","tincidunt"}},
{"list080",{"donec","eget","ultricies","tellus","et","imperdiet","tellus","vivamus","maximus","massa"}},
{"list081",{"pellentesque","eget","volutpat","velit","eu","venenatis","mi","suspendisse","fringilla","dignissim"}},
{"list082",{"maecenas","auctor","leo","sed","sapien","vehicula","nec","porttitor","ante","porta"}},
{"list083",{"nulla","risus","lectus","interdum","rutrum","aliquam","sed","consectetur","id","risus"}},
{"list084",{"vestibulum","ante","ipsum","primis","in","faucibus","orci","luctus","et","ultrices"}},
{"list085",{"vivamus","vestibulum","leo","vitae","lorem","tincidunt","nec","gravida","neque","vehicula"}},
{"list086",{"donec","mollis","urna","lectus","praesent","ornare","arcu","convallis","lacus","tempus"}},
{"list087",{"nam","laoreet","nibh","vel","pretium","vestibulum","cras","finibus","eget","eros"}},
{"list088",{"integer","pulvinar","nunc","nec","orci","accumsan","venenatis","maecenas","quam","est"}},
{"list089",{"ut","aliquet","risus","vel","dapibus","tristique","fusce","rutrum","risus","cursus"}},
{"list090",{"suspendisse","ac","ullamcorper","justo","morbi","vel","dolor","condimentum","viverra","ligula"}},
{"list091",{"phasellus","vestibulum","elit","et","dictum","rutrum","velit","odio","mattis","odio"}},
{"list092",{"curabitur","nunc","diam","pellentesque","sed","ultrices","et","varius","non","lacus"}},
{"list093",{"aliquam","consectetur","elementum","felis","sed","hendrerit","massa","consectetur","sagittis","nunc"}},
{"list094",{"in","hac","habitasse","platea","dictumst","integer","hendrerit","lorem","nec","auctor"}},
{"list095",{"pellentesque","dictum","mattis","mattis","donec","nec","commodo","nibh","in","imperdiet"}},
{"list096",{"mauris","eget","urna","vulputate","iaculis","tellus","pharetra","laoreet","justo","proin"}},
{"list097",{"nam","tincidunt","tortor","eu","scelerisque","convallis","erat","magna","consectetur","enim"}},
{"list098",{"aenean","posuere","ante","vitae","lobortis","fringilla","magna","lorem","volutpat","dui"}},
{"list099",{"suspendisse","mattis","arcu","ut","ipsum","cursus","vestibulum","interdum","et","malesuada"}},
{"list100",{"aenean","pharetra","leo","sem","non","pretium","nibh","faucibus","id","duis"}},
{"list101",{"lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit","aliquam","accumsan"}},
{"list102",{"donec","in","egestas","diam","aenean","suscipit","scelerisque","efficitur","integer","a"}},
{"list103",{"maecenas","metus","erat","vestibulum","quis","porta","in","consequat","sed","justo"}},
{"list104",{"aliquam","erat","volutpat","integer","laoreet","tellus","ut","nibh","viverra","in"}},
{"list105",{"integer","eu","velit","ac","erat","fringilla","ullamcorper","id","in","tortor"}},
{"list106",{"quisque","at","dapibus","ante","donec","et","lectus","sit","amet","lorem"}},
{"list107",{"curabitur","imperdiet","nibh","sit","amet","pellentesque","tincidunt","quisque","ornare","enim"}},
{"list108",{"aliquam","vitae","condimentum","libero","non","sodales","metus","vestibulum","aliquet","ullamcorper"}},
{"list109",{"pellentesque","sed","diam","tellus","donec","sed","nulla","lorem","sed","imperdiet"}},
{"list110",{"nam","sagittis","maximus","congue","class","aptent","taciti","sociosqu","ad","litora"}},
{"list111",{"praesent","semper","elit","ut","consequat","fringilla","mauris","turpis","volutpat","risus"}},
{"list112",{"nullam","risus","urna","facilisis","sit","amet","nibh","id","blandit","mollis"}},
{"list113",{"donec","non","ante","nisi","praesent","suscipit","erat","quis","lorem","convallis"}},
{"list114",{"maecenas","tincidunt","suscipit","ipsum","non","tristique","proin","accumsan","cursus","justo"}},
{"list115",{"nulla","facilisi","nullam","et","enim","finibus","aliquet","mi","ut","mattis"}},
{"list116",{"fusce","rutrum","lobortis","ligula","vitae","condimentum","risus","ultrices","sed","sed"}},
{"list117",{"nullam","quis","tristique","ante","proin","neque","ante","sollicitudin","non","bibendum"}},
{"list118",{"nam","bibendum","quam","et","semper","eleifend","donec","placerat","scelerisque","mi"}},
{"list119",{"donec","vel","suscipit","turpis","cras","semper","arcu","in","velit","hendrerit"}},
{"list120",{"praesent","congue","gravida","mauris","integer","ornare","dictum","lorem","non","consequat"}},
{"list121",{"nam","tincidunt","tortor","sit","amet","gravida","dapibus","fusce","laoreet","nulla"}},
{"list122",{"in","id","enim","pellentesque","suscipit","velit","id","posuere","metus","quisque"}},
{"list123",{"curabitur","elit","ex","aliquam","eget","mi","sed","tristique","scelerisque","ligula"}},
{"list124",{"vivamus","dui","lacus","hendrerit","eu","sagittis","non","finibus","consequat","turpis"}},
{"list125",{"etiam","ultrices","vel","massa","non","vehicula","duis","aliquet","massa","id"}},
{"list126",{"duis","ultrices","sem","a","sapien","dignissim","hendrerit","eu","vel","sem"}},
{"list127",{"vivamus","eu","elit","ac","metus","pharetra","pretium","etiam","mattis","velit"}},
{"list128",{"ut","ac","commodo","risus","nam","eget","hendrerit","magna","vestibulum","elementum"}},
{"list129",{"mauris","in","ultricies","tellus","ut","in","velit","porttitor","vulputate","enim"}},
{"list130",{"donec","dapibus","urna","ac","scelerisque","mollis","urna","orci","congue","nulla"}},
{"list131",{"vivamus","eleifend","dapibus","maximus","donec","eu","accumsan","nulla","eu","iaculis"}},
{"list132",{"aliquam","erat","volutpat","orci","varius","natoque","penatibus","et","magnis","dis"}},
{"list133",{"maecenas","laoreet","sed","quam","eget","bibendum","etiam","et","sollicitudin","mi"}},
{"list134",{"aliquam","rhoncus","purus","vel","nibh","laoreet","pharetra","vestibulum","ante","ipsum"}},
{"list135",{"quisque","semper","urna","vel","mollis","gravida","fusce","nibh","mauris","porttitor"}},
{"list136",{"sed","luctus","risus","et","sodales","mollis","maecenas","a","tincidunt","risus"}},
{"list137",{"in","hac","habitasse","platea","dictumst","nullam","fermentum","commodo","tempor","aenean"}},
{"list138",{"nunc","rhoncus","diam","magna","ac","lacinia","felis","consequat","non","maecenas"}},
{"list139",{"sed","scelerisque","velit","est","sed","mollis","ligula","cursus","sed","aliquam"}},
{"list140",{"nulla","dictum","arcu","eu","mauris","malesuada","quis","cursus","nisi","faucibus"}},
{"list141",{"phasellus","posuere","lacus","nec","molestie","accumsan","metus","tortor","facilisis","nunc"}},
{"list142",{"curabitur","ex","neque","pretium","sit","amet","leo","in","tempus","vestibulum"}},
{"list143",{"curabitur","tortor","tortor","fermentum","eget","velit","ac","cursus","faucibus","odio"}},
{"list144",{"aenean","varius","malesuada","sapien","sed","hendrerit","purus","molestie","sed","sed"}},
{"list145",{"nam","hendrerit","pretium","purus","id","fermentum","nunc","in","lacus","metus"}},
{"list146",{"fusce","euismod","libero","sed","diam","pharetra","non","semper","elit","accumsan"}},
{"list147",{"phasellus","sed","ligula","sit","amet","justo","vestibulum","fermentum","congue","sed"}},
{"list148",{"sed","dignissim","felis","eget","elit","dictum","malesuada","vivamus","maximus","mi"}},
{"list149",{"proin","lobortis","tempus","molestie","sed","tortor","ipsum","finibus","non","aliquet"}},
{"list150",{"integer","ut","leo","dui","proin","condimentum","dolor","a","suscipit","maximus"}},
{"list151",{"vivamus","sapien","neque","dapibus","sed","volutpat","at","vestibulum","sit","amet"}},
{"list152",{"curabitur","enim","enim","faucibus","sit","amet","luctus","aliquam","placerat","nec"}},
{"list153",{"suspendisse","ligula","dolor","ultricies","sit","amet","iaculis","vel","luctus","sit"}},
{"list154",{"aenean","aliquam","elit","sit","amet","ultricies","lobortis","nullam","porttitor","libero"}},
{"list155",{"ut","tincidunt","convallis","placerat","suspendisse","potenti","cras","commodo","fringilla","lacus"}},
{"list156",{"nulla","quis","elit","est","etiam","sagittis","molestie","porta","lorem","ipsum"}},
{"list157",{"in","turpis","nibh","gravida","vitae","ex","eu","eleifend","tincidunt","neque"}},
{"list158",{"suspendisse","in","fermentum","orci","etiam","viverra","metus","a","condimentum","blandit"}},
{"list159",{"fusce","dapibus","velit","id","est","maximus","rhoncus","quisque","lobortis","tortor"}},
{"list160",{"ut","molestie","ornare","velit","id","consectetur","nunc","varius","eu","vivamus"}},
{"list161",{"aenean","egestas","nibh","at","tincidunt","varius","felis","metus","ullamcorper","purus"}},
{"list162",{"donec","a","eleifend","libero","sed","fermentum","sem","fusce","eget","lacinia"}},
{"list163",{"nullam","metus","mauris","ornare","in","sem","at","posuere","placerat","nisl"}},
{"list164",{"maecenas","vitae","enim","ipsum","sed","mattis","porta","mi","congue","suscipit"}},
{"list165",{"nulla","placerat","porttitor","metus","non","suscipit","turpis","rhoncus","non","etiam"}},
{"list166",{"morbi","id","fringilla","quam","eget","sollicitudin","felis","aliquam","quis","risus"}},
{"list167",{"pellentesque","maximus","leo","ut","quam","consequat","lacinia","suspendisse","facilisis","leo"}},
{"list168",{"phasellus","rhoncus","urna","felis","nullam","pharetra","porttitor","neque","sit","amet"}},
{"list169",{"in","posuere","mauris","sit","amet","nisl","tempor","eget","euismod","neque"}},
{"list170",{"mauris","id","lobortis","magna","suspendisse","nisl","orci","auctor","suscipit","malesuada"}},
{"list171",{"ut","facilisis","magna","vitae","ligula","laoreet","tempor","class","aptent","taciti"}},
{"list172",{"aenean","sagittis","lacus","vel","nulla","porta","quis","egestas","sem","ullamcorper"}},
{"list173",{"donec","eget","purus","scelerisque","rutrum","dolor","eget","maximus","dui","aliquam"}},
{"list174",{"donec","congue","a","massa","vel","posuere","donec","ullamcorper","elit","id"}},
{"list175",{"praesent","consectetur","est","eu","tellus","dictum","tempor","nullam","id","risus"}},
{"list176",{"etiam","gravida","gravida","tempus","fusce","lobortis","orci","ac","volutpat","mollis"}},
{"list177",{"quisque","quis","turpis","et","augue","dignissim","maximus","donec","vel","justo"}},
{"list178",{"aenean","consectetur","mi","non","nibh","bibendum","ac","tincidunt","dui","tristique"}},
{"list179",{"aliquam","tempus","elit","non","ipsum","finibus","ut","luctus","dolor","tincidunt"}},
{"list180",{"donec","eget","ultricies","tellus","et","imperdiet","tellus","vivamus","maximus","massa"}},
{"list181",{"pellentesque","eget","volutpat","velit","eu","venenatis","mi","suspendisse","fringilla","dignissim"}},
{"list182",{"maecenas","auctor","leo","sed","sapien","vehicula","nec","porttitor","ante","porta"}},
{"list183",{"nulla","risus","lectus","interdum","rutrum","aliquam","sed","consectetur","id","risus"}},
{"list184",{"vestibulum","ante","ipsum","primis","in","faucibus","orci","luctus","et","ultrices"}},
{"list185",{"vivamus","vestibulum","leo","vitae","lorem","tincidunt","nec","gravida","neque","vehicula"}},
{"list186",{"donec","mollis","urna","lectus","praesent","ornare","arcu","convallis","lacus","tempus"}},
{"list187",{"nam","laoreet","nibh","vel","pretium","vestibulum","cras","finibus","eget","eros"}},
{"list188",{"integer","pulvinar","nunc","nec","orci","accumsan","venenatis","maecenas","quam","est"}},
{"list189",{"ut","aliquet","risus","vel","dapibus","tristique","fusce","rutrum","risus","cursus"}},
{"list190",{"suspendisse","ac","ullamcorper","justo","morbi","vel","dolor","condimentum","viverra","ligula"}},
{"list191",{"phasellus","vestibulum","elit","et","dictum","rutrum","velit","odio","mattis","odio"}},
{"list192",{"curabitur","nunc","diam","pellentesque","sed","ultrices","et","varius","non","lacus"}},
{"list193",{"aliquam","consectetur","elementum","felis","sed","hendrerit","massa","consectetur","sagittis","nunc"}},
{"list194",{"in","hac","habitasse","platea","dictumst","integer","hendrerit","lorem","nec","auctor"}},
{"list195",{"pellentesque","dictum","mattis","mattis","donec","nec","commodo","nibh","in","imperdiet"}},
{"list196",{"mauris","eget","urna","vulputate","iaculis","tellus","pharetra","laoreet","justo","proin"}},
{"list197",{"nam","tincidunt","tortor","eu","scelerisque","convallis","erat","magna","consectetur","enim"}},
{"list198",{"aenean","posuere","ante","vitae","lobortis","fringilla","magna","lorem","volutpat","dui"}},
{"list199",{"suspendisse","mattis","arcu","ut","ipsum","cursus","vestibulum","interdum","et","malesuada"}},
{"list200",{"aenean","pharetra","leo","sem","non","pretium","nibh","faucibus","id","duis"}}
};
json largeData = {
{"name", "Peter"},
{"list001", {"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "aliquam", "accumsan"}},
{"list002", {"donec", "in", "egestas", "diam", "aenean", "suscipit", "scelerisque", "efficitur", "integer", "a"}},
{"list003", {"maecenas", "metus", "erat", "vestibulum", "quis", "porta", "in", "consequat", "sed", "justo"}},
{"list004", {"aliquam", "erat", "volutpat", "integer", "laoreet", "tellus", "ut", "nibh", "viverra", "in"}},
{"list005", {"integer", "eu", "velit", "ac", "erat", "fringilla", "ullamcorper", "id", "in", "tortor"}},
{"list006", {"quisque", "at", "dapibus", "ante", "donec", "et", "lectus", "sit", "amet", "lorem"}},
{"list007",
{"curabitur", "imperdiet", "nibh", "sit", "amet", "pellentesque", "tincidunt", "quisque", "ornare", "enim"}},
{"list008",
{"aliquam", "vitae", "condimentum", "libero", "non", "sodales", "metus", "vestibulum", "aliquet", "ullamcorper"}},
{"list009", {"pellentesque", "sed", "diam", "tellus", "donec", "sed", "nulla", "lorem", "sed", "imperdiet"}},
{"list010", {"nam", "sagittis", "maximus", "congue", "class", "aptent", "taciti", "sociosqu", "ad", "litora"}},
{"list011",
{"praesent", "semper", "elit", "ut", "consequat", "fringilla", "mauris", "turpis", "volutpat", "risus"}},
{"list012", {"nullam", "risus", "urna", "facilisis", "sit", "amet", "nibh", "id", "blandit", "mollis"}},
{"list013", {"donec", "non", "ante", "nisi", "praesent", "suscipit", "erat", "quis", "lorem", "convallis"}},
{"list014",
{"maecenas", "tincidunt", "suscipit", "ipsum", "non", "tristique", "proin", "accumsan", "cursus", "justo"}},
{"list015", {"nulla", "facilisi", "nullam", "et", "enim", "finibus", "aliquet", "mi", "ut", "mattis"}},
{"list016", {"fusce", "rutrum", "lobortis", "ligula", "vitae", "condimentum", "risus", "ultrices", "sed", "sed"}},
{"list017", {"nullam", "quis", "tristique", "ante", "proin", "neque", "ante", "sollicitudin", "non", "bibendum"}},
{"list018", {"nam", "bibendum", "quam", "et", "semper", "eleifend", "donec", "placerat", "scelerisque", "mi"}},
{"list019", {"donec", "vel", "suscipit", "turpis", "cras", "semper", "arcu", "in", "velit", "hendrerit"}},
{"list020",
{"praesent", "congue", "gravida", "mauris", "integer", "ornare", "dictum", "lorem", "non", "consequat"}},
{"list021", {"nam", "tincidunt", "tortor", "sit", "amet", "gravida", "dapibus", "fusce", "laoreet", "nulla"}},
{"list022", {"in", "id", "enim", "pellentesque", "suscipit", "velit", "id", "posuere", "metus", "quisque"}},
{"list023", {"curabitur", "elit", "ex", "aliquam", "eget", "mi", "sed", "tristique", "scelerisque", "ligula"}},
{"list024", {"vivamus", "dui", "lacus", "hendrerit", "eu", "sagittis", "non", "finibus", "consequat", "turpis"}},
{"list025", {"etiam", "ultrices", "vel", "massa", "non", "vehicula", "duis", "aliquet", "massa", "id"}},
{"list026", {"duis", "ultrices", "sem", "a", "sapien", "dignissim", "hendrerit", "eu", "vel", "sem"}},
{"list027", {"vivamus", "eu", "elit", "ac", "metus", "pharetra", "pretium", "etiam", "mattis", "velit"}},
{"list028", {"ut", "ac", "commodo", "risus", "nam", "eget", "hendrerit", "magna", "vestibulum", "elementum"}},
{"list029", {"mauris", "in", "ultricies", "tellus", "ut", "in", "velit", "porttitor", "vulputate", "enim"}},
{"list030", {"donec", "dapibus", "urna", "ac", "scelerisque", "mollis", "urna", "orci", "congue", "nulla"}},
{"list031", {"vivamus", "eleifend", "dapibus", "maximus", "donec", "eu", "accumsan", "nulla", "eu", "iaculis"}},
{"list032", {"aliquam", "erat", "volutpat", "orci", "varius", "natoque", "penatibus", "et", "magnis", "dis"}},
{"list033", {"maecenas", "laoreet", "sed", "quam", "eget", "bibendum", "etiam", "et", "sollicitudin", "mi"}},
{"list034", {"aliquam", "rhoncus", "purus", "vel", "nibh", "laoreet", "pharetra", "vestibulum", "ante", "ipsum"}},
{"list035", {"quisque", "semper", "urna", "vel", "mollis", "gravida", "fusce", "nibh", "mauris", "porttitor"}},
{"list036", {"sed", "luctus", "risus", "et", "sodales", "mollis", "maecenas", "a", "tincidunt", "risus"}},
{"list037", {"in", "hac", "habitasse", "platea", "dictumst", "nullam", "fermentum", "commodo", "tempor", "aenean"}},
{"list038", {"nunc", "rhoncus", "diam", "magna", "ac", "lacinia", "felis", "consequat", "non", "maecenas"}},
{"list039", {"sed", "scelerisque", "velit", "est", "sed", "mollis", "ligula", "cursus", "sed", "aliquam"}},
{"list040", {"nulla", "dictum", "arcu", "eu", "mauris", "malesuada", "quis", "cursus", "nisi", "faucibus"}},
{"list041",
{"phasellus", "posuere", "lacus", "nec", "molestie", "accumsan", "metus", "tortor", "facilisis", "nunc"}},
{"list042", {"curabitur", "ex", "neque", "pretium", "sit", "amet", "leo", "in", "tempus", "vestibulum"}},
{"list043", {"curabitur", "tortor", "tortor", "fermentum", "eget", "velit", "ac", "cursus", "faucibus", "odio"}},
{"list044", {"aenean", "varius", "malesuada", "sapien", "sed", "hendrerit", "purus", "molestie", "sed", "sed"}},
{"list045", {"nam", "hendrerit", "pretium", "purus", "id", "fermentum", "nunc", "in", "lacus", "metus"}},
{"list046", {"fusce", "euismod", "libero", "sed", "diam", "pharetra", "non", "semper", "elit", "accumsan"}},
{"list047", {"phasellus", "sed", "ligula", "sit", "amet", "justo", "vestibulum", "fermentum", "congue", "sed"}},
{"list048", {"sed", "dignissim", "felis", "eget", "elit", "dictum", "malesuada", "vivamus", "maximus", "mi"}},
{"list049", {"proin", "lobortis", "tempus", "molestie", "sed", "tortor", "ipsum", "finibus", "non", "aliquet"}},
{"list050", {"integer", "ut", "leo", "dui", "proin", "condimentum", "dolor", "a", "suscipit", "maximus"}},
{"list051", {"vivamus", "sapien", "neque", "dapibus", "sed", "volutpat", "at", "vestibulum", "sit", "amet"}},
{"list052", {"curabitur", "enim", "enim", "faucibus", "sit", "amet", "luctus", "aliquam", "placerat", "nec"}},
{"list053", {"suspendisse", "ligula", "dolor", "ultricies", "sit", "amet", "iaculis", "vel", "luctus", "sit"}},
{"list054", {"aenean", "aliquam", "elit", "sit", "amet", "ultricies", "lobortis", "nullam", "porttitor", "libero"}},
{"list055",
{"ut", "tincidunt", "convallis", "placerat", "suspendisse", "potenti", "cras", "commodo", "fringilla", "lacus"}},
{"list056", {"nulla", "quis", "elit", "est", "etiam", "sagittis", "molestie", "porta", "lorem", "ipsum"}},
{"list057", {"in", "turpis", "nibh", "gravida", "vitae", "ex", "eu", "eleifend", "tincidunt", "neque"}},
{"list058", {"suspendisse", "in", "fermentum", "orci", "etiam", "viverra", "metus", "a", "condimentum", "blandit"}},
{"list059", {"fusce", "dapibus", "velit", "id", "est", "maximus", "rhoncus", "quisque", "lobortis", "tortor"}},
{"list060", {"ut", "molestie", "ornare", "velit", "id", "consectetur", "nunc", "varius", "eu", "vivamus"}},
{"list061", {"aenean", "egestas", "nibh", "at", "tincidunt", "varius", "felis", "metus", "ullamcorper", "purus"}},
{"list062", {"donec", "a", "eleifend", "libero", "sed", "fermentum", "sem", "fusce", "eget", "lacinia"}},
{"list063", {"nullam", "metus", "mauris", "ornare", "in", "sem", "at", "posuere", "placerat", "nisl"}},
{"list064", {"maecenas", "vitae", "enim", "ipsum", "sed", "mattis", "porta", "mi", "congue", "suscipit"}},
{"list065", {"nulla", "placerat", "porttitor", "metus", "non", "suscipit", "turpis", "rhoncus", "non", "etiam"}},
{"list066", {"morbi", "id", "fringilla", "quam", "eget", "sollicitudin", "felis", "aliquam", "quis", "risus"}},
{"list067",
{"pellentesque", "maximus", "leo", "ut", "quam", "consequat", "lacinia", "suspendisse", "facilisis", "leo"}},
{"list068", {"phasellus", "rhoncus", "urna", "felis", "nullam", "pharetra", "porttitor", "neque", "sit", "amet"}},
{"list069", {"in", "posuere", "mauris", "sit", "amet", "nisl", "tempor", "eget", "euismod", "neque"}},
{"list070",
{"mauris", "id", "lobortis", "magna", "suspendisse", "nisl", "orci", "auctor", "suscipit", "malesuada"}},
{"list071", {"ut", "facilisis", "magna", "vitae", "ligula", "laoreet", "tempor", "class", "aptent", "taciti"}},
{"list072", {"aenean", "sagittis", "lacus", "vel", "nulla", "porta", "quis", "egestas", "sem", "ullamcorper"}},
{"list073", {"donec", "eget", "purus", "scelerisque", "rutrum", "dolor", "eget", "maximus", "dui", "aliquam"}},
{"list074", {"donec", "congue", "a", "massa", "vel", "posuere", "donec", "ullamcorper", "elit", "id"}},
{"list075", {"praesent", "consectetur", "est", "eu", "tellus", "dictum", "tempor", "nullam", "id", "risus"}},
{"list076", {"etiam", "gravida", "gravida", "tempus", "fusce", "lobortis", "orci", "ac", "volutpat", "mollis"}},
{"list077", {"quisque", "quis", "turpis", "et", "augue", "dignissim", "maximus", "donec", "vel", "justo"}},
{"list078", {"aenean", "consectetur", "mi", "non", "nibh", "bibendum", "ac", "tincidunt", "dui", "tristique"}},
{"list079", {"aliquam", "tempus", "elit", "non", "ipsum", "finibus", "ut", "luctus", "dolor", "tincidunt"}},
{"list080", {"donec", "eget", "ultricies", "tellus", "et", "imperdiet", "tellus", "vivamus", "maximus", "massa"}},
{"list081",
{"pellentesque", "eget", "volutpat", "velit", "eu", "venenatis", "mi", "suspendisse", "fringilla", "dignissim"}},
{"list082", {"maecenas", "auctor", "leo", "sed", "sapien", "vehicula", "nec", "porttitor", "ante", "porta"}},
{"list083", {"nulla", "risus", "lectus", "interdum", "rutrum", "aliquam", "sed", "consectetur", "id", "risus"}},
{"list084", {"vestibulum", "ante", "ipsum", "primis", "in", "faucibus", "orci", "luctus", "et", "ultrices"}},
{"list085", {"vivamus", "vestibulum", "leo", "vitae", "lorem", "tincidunt", "nec", "gravida", "neque", "vehicula"}},
{"list086", {"donec", "mollis", "urna", "lectus", "praesent", "ornare", "arcu", "convallis", "lacus", "tempus"}},
{"list087", {"nam", "laoreet", "nibh", "vel", "pretium", "vestibulum", "cras", "finibus", "eget", "eros"}},
{"list088", {"integer", "pulvinar", "nunc", "nec", "orci", "accumsan", "venenatis", "maecenas", "quam", "est"}},
{"list089", {"ut", "aliquet", "risus", "vel", "dapibus", "tristique", "fusce", "rutrum", "risus", "cursus"}},
{"list090",
{"suspendisse", "ac", "ullamcorper", "justo", "morbi", "vel", "dolor", "condimentum", "viverra", "ligula"}},
{"list091", {"phasellus", "vestibulum", "elit", "et", "dictum", "rutrum", "velit", "odio", "mattis", "odio"}},
{"list092", {"curabitur", "nunc", "diam", "pellentesque", "sed", "ultrices", "et", "varius", "non", "lacus"}},
{"list093",
{"aliquam", "consectetur", "elementum", "felis", "sed", "hendrerit", "massa", "consectetur", "sagittis", "nunc"}},
{"list094", {"in", "hac", "habitasse", "platea", "dictumst", "integer", "hendrerit", "lorem", "nec", "auctor"}},
{"list095", {"pellentesque", "dictum", "mattis", "mattis", "donec", "nec", "commodo", "nibh", "in", "imperdiet"}},
{"list096", {"mauris", "eget", "urna", "vulputate", "iaculis", "tellus", "pharetra", "laoreet", "justo", "proin"}},
{"list097",
{"nam", "tincidunt", "tortor", "eu", "scelerisque", "convallis", "erat", "magna", "consectetur", "enim"}},
{"list098", {"aenean", "posuere", "ante", "vitae", "lobortis", "fringilla", "magna", "lorem", "volutpat", "dui"}},
{"list099",
{"suspendisse", "mattis", "arcu", "ut", "ipsum", "cursus", "vestibulum", "interdum", "et", "malesuada"}},
{"list100", {"aenean", "pharetra", "leo", "sem", "non", "pretium", "nibh", "faucibus", "id", "duis"}},
{"list101", {"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "aliquam", "accumsan"}},
{"list102", {"donec", "in", "egestas", "diam", "aenean", "suscipit", "scelerisque", "efficitur", "integer", "a"}},
{"list103", {"maecenas", "metus", "erat", "vestibulum", "quis", "porta", "in", "consequat", "sed", "justo"}},
{"list104", {"aliquam", "erat", "volutpat", "integer", "laoreet", "tellus", "ut", "nibh", "viverra", "in"}},
{"list105", {"integer", "eu", "velit", "ac", "erat", "fringilla", "ullamcorper", "id", "in", "tortor"}},
{"list106", {"quisque", "at", "dapibus", "ante", "donec", "et", "lectus", "sit", "amet", "lorem"}},
{"list107",
{"curabitur", "imperdiet", "nibh", "sit", "amet", "pellentesque", "tincidunt", "quisque", "ornare", "enim"}},
{"list108",
{"aliquam", "vitae", "condimentum", "libero", "non", "sodales", "metus", "vestibulum", "aliquet", "ullamcorper"}},
{"list109", {"pellentesque", "sed", "diam", "tellus", "donec", "sed", "nulla", "lorem", "sed", "imperdiet"}},
{"list110", {"nam", "sagittis", "maximus", "congue", "class", "aptent", "taciti", "sociosqu", "ad", "litora"}},
{"list111",
{"praesent", "semper", "elit", "ut", "consequat", "fringilla", "mauris", "turpis", "volutpat", "risus"}},
{"list112", {"nullam", "risus", "urna", "facilisis", "sit", "amet", "nibh", "id", "blandit", "mollis"}},
{"list113", {"donec", "non", "ante", "nisi", "praesent", "suscipit", "erat", "quis", "lorem", "convallis"}},
{"list114",
{"maecenas", "tincidunt", "suscipit", "ipsum", "non", "tristique", "proin", "accumsan", "cursus", "justo"}},
{"list115", {"nulla", "facilisi", "nullam", "et", "enim", "finibus", "aliquet", "mi", "ut", "mattis"}},
{"list116", {"fusce", "rutrum", "lobortis", "ligula", "vitae", "condimentum", "risus", "ultrices", "sed", "sed"}},
{"list117", {"nullam", "quis", "tristique", "ante", "proin", "neque", "ante", "sollicitudin", "non", "bibendum"}},
{"list118", {"nam", "bibendum", "quam", "et", "semper", "eleifend", "donec", "placerat", "scelerisque", "mi"}},
{"list119", {"donec", "vel", "suscipit", "turpis", "cras", "semper", "arcu", "in", "velit", "hendrerit"}},
{"list120",
{"praesent", "congue", "gravida", "mauris", "integer", "ornare", "dictum", "lorem", "non", "consequat"}},
{"list121", {"nam", "tincidunt", "tortor", "sit", "amet", "gravida", "dapibus", "fusce", "laoreet", "nulla"}},
{"list122", {"in", "id", "enim", "pellentesque", "suscipit", "velit", "id", "posuere", "metus", "quisque"}},
{"list123", {"curabitur", "elit", "ex", "aliquam", "eget", "mi", "sed", "tristique", "scelerisque", "ligula"}},
{"list124", {"vivamus", "dui", "lacus", "hendrerit", "eu", "sagittis", "non", "finibus", "consequat", "turpis"}},
{"list125", {"etiam", "ultrices", "vel", "massa", "non", "vehicula", "duis", "aliquet", "massa", "id"}},
{"list126", {"duis", "ultrices", "sem", "a", "sapien", "dignissim", "hendrerit", "eu", "vel", "sem"}},
{"list127", {"vivamus", "eu", "elit", "ac", "metus", "pharetra", "pretium", "etiam", "mattis", "velit"}},
{"list128", {"ut", "ac", "commodo", "risus", "nam", "eget", "hendrerit", "magna", "vestibulum", "elementum"}},
{"list129", {"mauris", "in", "ultricies", "tellus", "ut", "in", "velit", "porttitor", "vulputate", "enim"}},
{"list130", {"donec", "dapibus", "urna", "ac", "scelerisque", "mollis", "urna", "orci", "congue", "nulla"}},
{"list131", {"vivamus", "eleifend", "dapibus", "maximus", "donec", "eu", "accumsan", "nulla", "eu", "iaculis"}},
{"list132", {"aliquam", "erat", "volutpat", "orci", "varius", "natoque", "penatibus", "et", "magnis", "dis"}},
{"list133", {"maecenas", "laoreet", "sed", "quam", "eget", "bibendum", "etiam", "et", "sollicitudin", "mi"}},
{"list134", {"aliquam", "rhoncus", "purus", "vel", "nibh", "laoreet", "pharetra", "vestibulum", "ante", "ipsum"}},
{"list135", {"quisque", "semper", "urna", "vel", "mollis", "gravida", "fusce", "nibh", "mauris", "porttitor"}},
{"list136", {"sed", "luctus", "risus", "et", "sodales", "mollis", "maecenas", "a", "tincidunt", "risus"}},
{"list137", {"in", "hac", "habitasse", "platea", "dictumst", "nullam", "fermentum", "commodo", "tempor", "aenean"}},
{"list138", {"nunc", "rhoncus", "diam", "magna", "ac", "lacinia", "felis", "consequat", "non", "maecenas"}},
{"list139", {"sed", "scelerisque", "velit", "est", "sed", "mollis", "ligula", "cursus", "sed", "aliquam"}},
{"list140", {"nulla", "dictum", "arcu", "eu", "mauris", "malesuada", "quis", "cursus", "nisi", "faucibus"}},
{"list141",
{"phasellus", "posuere", "lacus", "nec", "molestie", "accumsan", "metus", "tortor", "facilisis", "nunc"}},
{"list142", {"curabitur", "ex", "neque", "pretium", "sit", "amet", "leo", "in", "tempus", "vestibulum"}},
{"list143", {"curabitur", "tortor", "tortor", "fermentum", "eget", "velit", "ac", "cursus", "faucibus", "odio"}},
{"list144", {"aenean", "varius", "malesuada", "sapien", "sed", "hendrerit", "purus", "molestie", "sed", "sed"}},
{"list145", {"nam", "hendrerit", "pretium", "purus", "id", "fermentum", "nunc", "in", "lacus", "metus"}},
{"list146", {"fusce", "euismod", "libero", "sed", "diam", "pharetra", "non", "semper", "elit", "accumsan"}},
{"list147", {"phasellus", "sed", "ligula", "sit", "amet", "justo", "vestibulum", "fermentum", "congue", "sed"}},
{"list148", {"sed", "dignissim", "felis", "eget", "elit", "dictum", "malesuada", "vivamus", "maximus", "mi"}},
{"list149", {"proin", "lobortis", "tempus", "molestie", "sed", "tortor", "ipsum", "finibus", "non", "aliquet"}},
{"list150", {"integer", "ut", "leo", "dui", "proin", "condimentum", "dolor", "a", "suscipit", "maximus"}},
{"list151", {"vivamus", "sapien", "neque", "dapibus", "sed", "volutpat", "at", "vestibulum", "sit", "amet"}},
{"list152", {"curabitur", "enim", "enim", "faucibus", "sit", "amet", "luctus", "aliquam", "placerat", "nec"}},
{"list153", {"suspendisse", "ligula", "dolor", "ultricies", "sit", "amet", "iaculis", "vel", "luctus", "sit"}},
{"list154", {"aenean", "aliquam", "elit", "sit", "amet", "ultricies", "lobortis", "nullam", "porttitor", "libero"}},
{"list155",
{"ut", "tincidunt", "convallis", "placerat", "suspendisse", "potenti", "cras", "commodo", "fringilla", "lacus"}},
{"list156", {"nulla", "quis", "elit", "est", "etiam", "sagittis", "molestie", "porta", "lorem", "ipsum"}},
{"list157", {"in", "turpis", "nibh", "gravida", "vitae", "ex", "eu", "eleifend", "tincidunt", "neque"}},
{"list158", {"suspendisse", "in", "fermentum", "orci", "etiam", "viverra", "metus", "a", "condimentum", "blandit"}},
{"list159", {"fusce", "dapibus", "velit", "id", "est", "maximus", "rhoncus", "quisque", "lobortis", "tortor"}},
{"list160", {"ut", "molestie", "ornare", "velit", "id", "consectetur", "nunc", "varius", "eu", "vivamus"}},
{"list161", {"aenean", "egestas", "nibh", "at", "tincidunt", "varius", "felis", "metus", "ullamcorper", "purus"}},
{"list162", {"donec", "a", "eleifend", "libero", "sed", "fermentum", "sem", "fusce", "eget", "lacinia"}},
{"list163", {"nullam", "metus", "mauris", "ornare", "in", "sem", "at", "posuere", "placerat", "nisl"}},
{"list164", {"maecenas", "vitae", "enim", "ipsum", "sed", "mattis", "porta", "mi", "congue", "suscipit"}},
{"list165", {"nulla", "placerat", "porttitor", "metus", "non", "suscipit", "turpis", "rhoncus", "non", "etiam"}},
{"list166", {"morbi", "id", "fringilla", "quam", "eget", "sollicitudin", "felis", "aliquam", "quis", "risus"}},
{"list167",
{"pellentesque", "maximus", "leo", "ut", "quam", "consequat", "lacinia", "suspendisse", "facilisis", "leo"}},
{"list168", {"phasellus", "rhoncus", "urna", "felis", "nullam", "pharetra", "porttitor", "neque", "sit", "amet"}},
{"list169", {"in", "posuere", "mauris", "sit", "amet", "nisl", "tempor", "eget", "euismod", "neque"}},
{"list170",
{"mauris", "id", "lobortis", "magna", "suspendisse", "nisl", "orci", "auctor", "suscipit", "malesuada"}},
{"list171", {"ut", "facilisis", "magna", "vitae", "ligula", "laoreet", "tempor", "class", "aptent", "taciti"}},
{"list172", {"aenean", "sagittis", "lacus", "vel", "nulla", "porta", "quis", "egestas", "sem", "ullamcorper"}},
{"list173", {"donec", "eget", "purus", "scelerisque", "rutrum", "dolor", "eget", "maximus", "dui", "aliquam"}},
{"list174", {"donec", "congue", "a", "massa", "vel", "posuere", "donec", "ullamcorper", "elit", "id"}},
{"list175", {"praesent", "consectetur", "est", "eu", "tellus", "dictum", "tempor", "nullam", "id", "risus"}},
{"list176", {"etiam", "gravida", "gravida", "tempus", "fusce", "lobortis", "orci", "ac", "volutpat", "mollis"}},
{"list177", {"quisque", "quis", "turpis", "et", "augue", "dignissim", "maximus", "donec", "vel", "justo"}},
{"list178", {"aenean", "consectetur", "mi", "non", "nibh", "bibendum", "ac", "tincidunt", "dui", "tristique"}},
{"list179", {"aliquam", "tempus", "elit", "non", "ipsum", "finibus", "ut", "luctus", "dolor", "tincidunt"}},
{"list180", {"donec", "eget", "ultricies", "tellus", "et", "imperdiet", "tellus", "vivamus", "maximus", "massa"}},
{"list181",
{"pellentesque", "eget", "volutpat", "velit", "eu", "venenatis", "mi", "suspendisse", "fringilla", "dignissim"}},
{"list182", {"maecenas", "auctor", "leo", "sed", "sapien", "vehicula", "nec", "porttitor", "ante", "porta"}},
{"list183", {"nulla", "risus", "lectus", "interdum", "rutrum", "aliquam", "sed", "consectetur", "id", "risus"}},
{"list184", {"vestibulum", "ante", "ipsum", "primis", "in", "faucibus", "orci", "luctus", "et", "ultrices"}},
{"list185", {"vivamus", "vestibulum", "leo", "vitae", "lorem", "tincidunt", "nec", "gravida", "neque", "vehicula"}},
{"list186", {"donec", "mollis", "urna", "lectus", "praesent", "ornare", "arcu", "convallis", "lacus", "tempus"}},
{"list187", {"nam", "laoreet", "nibh", "vel", "pretium", "vestibulum", "cras", "finibus", "eget", "eros"}},
{"list188", {"integer", "pulvinar", "nunc", "nec", "orci", "accumsan", "venenatis", "maecenas", "quam", "est"}},
{"list189", {"ut", "aliquet", "risus", "vel", "dapibus", "tristique", "fusce", "rutrum", "risus", "cursus"}},
{"list190",
{"suspendisse", "ac", "ullamcorper", "justo", "morbi", "vel", "dolor", "condimentum", "viverra", "ligula"}},
{"list191", {"phasellus", "vestibulum", "elit", "et", "dictum", "rutrum", "velit", "odio", "mattis", "odio"}},
{"list192", {"curabitur", "nunc", "diam", "pellentesque", "sed", "ultrices", "et", "varius", "non", "lacus"}},
{"list193",
{"aliquam", "consectetur", "elementum", "felis", "sed", "hendrerit", "massa", "consectetur", "sagittis", "nunc"}},
{"list194", {"in", "hac", "habitasse", "platea", "dictumst", "integer", "hendrerit", "lorem", "nec", "auctor"}},
{"list195", {"pellentesque", "dictum", "mattis", "mattis", "donec", "nec", "commodo", "nibh", "in", "imperdiet"}},
{"list196", {"mauris", "eget", "urna", "vulputate", "iaculis", "tellus", "pharetra", "laoreet", "justo", "proin"}},
{"list197",
{"nam", "tincidunt", "tortor", "eu", "scelerisque", "convallis", "erat", "magna", "consectetur", "enim"}},
{"list198", {"aenean", "posuere", "ante", "vitae", "lobortis", "fringilla", "magna", "lorem", "volutpat", "dui"}},
{"list199",
{"suspendisse", "mattis", "arcu", "ut", "ipsum", "cursus", "vestibulum", "interdum", "et", "malesuada"}},
{"list200", {"aenean", "pharetra", "leo", "sem", "non", "pretium", "nibh", "faucibus", "id", "duis"}}};
std::string string_template{"Lorem {{ name }}! {% for v1 in list001 %}{{ v1 }} {% for v2 in list002 %}{{ v2 }} {% for v3 in list003 %}{{ v1 }} {{ v3 }} {{name}}\n{% endfor %}{% endfor %}{% endfor %}Omnis in aut nobis libero enim. Porro optio ratione molestiae necessitatibus numquam architecto soluta. Magnam minus unde quas {{ name }} aspernatur occaecati et voluptas cupiditate. Assumenda ut alias quam voluptate aut saepe ullam dignissimos. \n Sequi aut autem nihil voluptatem tenetur incidunt. Autem commodi animi rerum. {{ lower(name) }} Mollitia eligendi aut sed rerum veniam. Eum et fugit velit sint ratione voluptatem aliquam. Minima sint consectetur natus modi quis. Animi est nesciunt cupiditate nostrum iure. Voluptatem accusamus vel corporis. \n Debitis {{ name }} sunt est debitis distinctio ut. Provident corrupti nihil velit aut tempora corporis corrupti exercitationem. Praesentium cumque ex est itaque."};
std::string string_template {
"Lorem {{ name }}! {% for v1 in list001 %}{{ v1 }} {% for v2 in list002 %}{{ v2 }} {% for v3 in list003 %}{{ v1 }} "
"{{ v3 }} {{name}}\n{% endfor %}{% endfor %}{% endfor %}Omnis in aut nobis libero enim. Porro optio ratione "
"molestiae necessitatibus numquam architecto soluta. Magnam minus unde quas {{ name }} aspernatur occaecati et "
"voluptas cupiditate. Assumenda ut alias quam voluptate aut saepe ullam dignissimos. \n Sequi aut autem nihil "
"voluptatem tenetur incidunt. Autem commodi animi rerum. {{ lower(name) }} Mollitia eligendi aut sed rerum veniam. "
"Eum et fugit velit sint ratione voluptatem aliquam. Minima sint consectetur natus modi quis. Animi est nesciunt "
"cupiditate nostrum iure. Voluptatem accusamus vel corporis. \n Debitis {{ name }} sunt est debitis distinctio ut. "
"Provident corrupti nihil velit aut tempora corporis corrupti exercitationem. Praesentium cumque ex est itaque."};
BENCHMARK(InjaBenchmarkerSmallData, render, 10, 100) { env.render(string_template, smallData); }
BENCHMARK(InjaBenchmarkerSmallData, render, 10, 100) {
env.render(string_template, smallData);
}
BENCHMARK(InjaBenchmarkerLargeData, render, 10, 100) {
env.render(string_template, largeData);
}
BENCHMARK(InjaBenchmarkerLargeData, render, 10, 100) { env.render(string_template, largeData); }
int main() {
hayai::ConsoleOutputter consoleOutputter;

View File

@@ -3,74 +3,73 @@
#include "doctest/doctest.h"
#include "inja/inja.hpp"
using json = nlohmann::json;
const std::string test_file_directory {"../test/data/"};
TEST_CASE("loading") {
inja::Environment env;
json data;
data["name"] = "Jeff";
inja::Environment env;
json data;
data["name"] = "Jeff";
SUBCASE("Files should be loaded") {
CHECK(env.load_file(test_file_directory + "simple.txt") == "Hello {{ name }}.");
}
SUBCASE("Files should be loaded") { CHECK(env.load_file(test_file_directory + "simple.txt") == "Hello {{ name }}."); }
SUBCASE("Files should be rendered") {
CHECK(env.render_file(test_file_directory + "simple.txt", data) == "Hello Jeff.");
}
SUBCASE("Files should be rendered") {
CHECK(env.render_file(test_file_directory + "simple.txt", data) == "Hello Jeff.");
}
SUBCASE("File includes should be rendered") {
CHECK(env.render_file(test_file_directory + "include.txt", data) == "Answer: Hello Jeff.");
}
SUBCASE("File includes should be rendered") {
CHECK(env.render_file(test_file_directory + "include.txt", data) == "Answer: Hello Jeff.");
}
SUBCASE("File error should throw") {
std::string path(test_file_directory + "does-not-exist");
SUBCASE("File error should throw") {
std::string path(test_file_directory + "does-not-exist");
std::string file_error_message = "[inja.exception.file_error] failed accessing file at '" + path + "'";
CHECK_THROWS_WITH(env.load_file(path), file_error_message.c_str());
CHECK_THROWS_WITH(env.load_json(path), file_error_message.c_str());
}
std::string file_error_message = "[inja.exception.file_error] failed accessing file at '" + path + "'";
CHECK_THROWS_WITH(env.load_file(path), file_error_message.c_str());
CHECK_THROWS_WITH(env.load_json(path), file_error_message.c_str());
}
}
TEST_CASE("complete-files") {
inja::Environment env {test_file_directory};
inja::Environment env {test_file_directory};
for (std::string test_name : {"simple-file", "nested", "nested-line", "html"}) {
SUBCASE(test_name.c_str()) {
CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt"));
}
for (std::string test_name : {"simple-file", "nested", "nested-line", "html"}) {
SUBCASE(test_name.c_str()) {
CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") ==
env.load_file(test_name + "/result.txt"));
}
}
for (std::string test_name : {"error-unknown"}) {
SUBCASE(test_name.c_str()) {
CHECK_THROWS_WITH(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json"), "[inja.exception.parser_error] (at 2:11) expected 'in', got 'ins'");
}
for (std::string test_name : {"error-unknown"}) {
SUBCASE(test_name.c_str()) {
CHECK_THROWS_WITH(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json"),
"[inja.exception.parser_error] (at 2:11) expected 'in', got 'ins'");
}
}
}
TEST_CASE("complete-files-whitespace-control") {
inja::Environment env {test_file_directory};
env.set_trim_blocks(true);
env.set_lstrip_blocks(true);
for (std::string test_name : {"nested-whitespace"}) {
SUBCASE(test_name.c_str()) {
CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt"));
}
inja::Environment env {test_file_directory};
env.set_trim_blocks(true);
env.set_lstrip_blocks(true);
for (std::string test_name : {"nested-whitespace"}) {
SUBCASE(test_name.c_str()) {
CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") ==
env.load_file(test_name + "/result.txt"));
}
}
}
TEST_CASE("global-path") {
inja::Environment env {test_file_directory, "./"};
inja::Environment env_result {"./"};
json data;
data["name"] = "Jeff";
inja::Environment env {test_file_directory, "./"};
inja::Environment env_result {"./"};
json data;
data["name"] = "Jeff";
SUBCASE("Files should be written") {
env.write("simple.txt", data, "global-path-result.txt");
CHECK(env_result.load_file("global-path-result.txt") == "Hello Jeff.");
}
SUBCASE("Files should be written") {
env.write("simple.txt", data, "global-path-result.txt");
CHECK(env_result.load_file("global-path-result.txt") == "Hello Jeff.");
}
}

View File

@@ -3,78 +3,85 @@
#include "doctest/doctest.h"
#include "inja/inja.hpp"
using json = nlohmann::json;
TEST_CASE("dot-to-pointer") {
std::string buffer;
CHECK(inja::convert_dot_to_json_pointer("test", buffer) == "/test");
CHECK(inja::convert_dot_to_json_pointer("guests.2", buffer) == "/guests/2");
CHECK(inja::convert_dot_to_json_pointer("person.names.surname", buffer) == "/person/names/surname");
std::string buffer;
CHECK(inja::convert_dot_to_json_pointer("test", buffer) == "/test");
CHECK(inja::convert_dot_to_json_pointer("guests.2", buffer) == "/guests/2");
CHECK(inja::convert_dot_to_json_pointer("person.names.surname", buffer) == "/person/names/surname");
}
TEST_CASE("types") {
inja::Environment env;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
data["is_happy"] = true;
data["is_sad"] = false;
data["relatives"]["mother"] = "Maria";
data["relatives"]["brother"] = "Chris";
data["relatives"]["sister"] = "Jenny";
data["vars"] = {2, 3, 4, 0, -1, -2, -3};
inja::Environment env;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = {{"name", "Maria"}};
data["is_happy"] = true;
data["is_sad"] = false;
data["relatives"]["mother"] = "Maria";
data["relatives"]["brother"] = "Chris";
data["relatives"]["sister"] = "Jenny";
data["vars"] = {2, 3, 4, 0, -1, -2, -3};
SUBCASE("basic") {
CHECK(env.render("", data) == "");
CHECK(env.render("Hello World!", data) == "Hello World!");
}
SUBCASE("basic") {
CHECK(env.render("", data) == "");
CHECK(env.render("Hello World!", data) == "Hello World!");
}
SUBCASE("variables") {
CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!");
CHECK(env.render("{{ name }}", data) == "Peter");
CHECK(env.render("{{name}}", data) == "Peter");
CHECK(env.render("{{ name }} is {{ age }} years old.", data) == "Peter is 29 years old.");
CHECK(env.render("Hello {{ name }}! I come from {{ city }}.", data) == "Hello Peter! I come from Brunswick.");
CHECK(env.render("Hello {{ names.1 }}!", data) == "Hello Seb!");
CHECK(env.render("Hello {{ brother.name }}!", data) == "Hello Chris!");
CHECK(env.render("Hello {{ brother.daughter0.name }}!", data) == "Hello Maria!");
CHECK(env.render("{{ \"{{ no_value }}\" }}", data) == "{{ no_value }}");
SUBCASE("variables") {
CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!");
CHECK(env.render("{{ name }}", data) == "Peter");
CHECK(env.render("{{name}}", data) == "Peter");
CHECK(env.render("{{ name }} is {{ age }} years old.", data) == "Peter is 29 years old.");
CHECK(env.render("Hello {{ name }}! I come from {{ city }}.", data) == "Hello Peter! I come from Brunswick.");
CHECK(env.render("Hello {{ names.1 }}!", data) == "Hello Seb!");
CHECK(env.render("Hello {{ brother.name }}!", data) == "Hello Chris!");
CHECK(env.render("Hello {{ brother.daughter0.name }}!", data) == "Hello Maria!");
CHECK(env.render("{{ \"{{ no_value }}\" }}", data) == "{{ no_value }}");
CHECK_THROWS_WITH(env.render("{{unknown}}", data), "[inja.exception.render_error] variable 'unknown' not found");
}
CHECK_THROWS_WITH(env.render("{{unknown}}", data), "[inja.exception.render_error] variable 'unknown' not found");
}
SUBCASE("comments") {
CHECK(env.render("Hello{# This is a comment #}!", data) == "Hello!");
CHECK(env.render("{# --- #Todo --- #}", data) == "");
}
SUBCASE("comments") {
CHECK(env.render("Hello{# This is a comment #}!", data) == "Hello!");
CHECK(env.render("{# --- #Todo --- #}", data) == "");
}
SUBCASE("loops") {
CHECK(env.render("{% for name in names %}a{% endfor %}", data) == "aa");
CHECK(env.render("Hello {% for name in names %}{{ name }} {% endfor %}!", data) == "Hello Jeff Seb !");
CHECK(env.render("Hello {% for name in names %}{{ loop.index }}: {{ name }}, {% endfor %}!", data) ==
"Hello 0: Jeff, 1: Seb, !");
CHECK(env.render("{% for type, name in relatives %}{{ loop.index1 }}: {{ type }}: {{ name }}{% if loop.is_last == "
"false %}, {% endif %}{% endfor %}",
data) == "1: brother: Chris, 2: mother: Maria, 3: sister: Jenny");
CHECK(env.render("{% for v in vars %}{% if v > 0 %}+{% endif %}{% endfor %}", data) == "+++");
CHECK(env.render(
"{% for name in names %}{{ loop.index }}: {{ name }}{% if not loop.is_last %}, {% endif %}{% endfor %}!",
data) == "0: Jeff, 1: Seb!");
CHECK(env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if loop.is_last == false %}, {% endif %}{% "
"endfor %}!",
data) == "0: Jeff, 1: Seb!");
SUBCASE("loops") {
CHECK(env.render("{% for name in names %}a{% endfor %}", data) == "aa");
CHECK(env.render("Hello {% for name in names %}{{ name }} {% endfor %}!", data) == "Hello Jeff Seb !");
CHECK(env.render("Hello {% for name in names %}{{ loop.index }}: {{ name }}, {% endfor %}!", data) == "Hello 0: Jeff, 1: Seb, !");
CHECK(env.render("{% for type, name in relatives %}{{ loop.index1 }}: {{ type }}: {{ name }}{% if loop.is_last == false %}, {% endif %}{% endfor %}", data) == "1: brother: Chris, 2: mother: Maria, 3: sister: Jenny");
CHECK(env.render("{% for v in vars %}{% if v > 0 %}+{% endif %}{% endfor %}", data) == "+++" );
CHECK(env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if not loop.is_last %}, {% endif %}{% endfor %}!", data) == "0: Jeff, 1: Seb!");
CHECK(env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if loop.is_last == false %}, {% endif %}{% endfor %}!", data) == "0: Jeff, 1: Seb!");
CHECK(env.render("{% for name in {} %}a{% endfor %}", data) == "");
CHECK( env.render("{% for name in {} %}a{% endfor %}", data) == "");
CHECK_THROWS_WITH(env.render("{% for name ins names %}a{% endfor %}", data),
"[inja.exception.parser_error] (at 1:13) expected 'in', got 'ins'");
CHECK_THROWS_WITH(env.render("{% for name in empty_loop %}a{% endfor %}", data),
"[inja.exception.render_error] variable 'empty_loop' not found");
// CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data),
// "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is object" );
}
CHECK_THROWS_WITH(env.render("{% for name ins names %}a{% endfor %}", data), "[inja.exception.parser_error] (at 1:13) expected 'in', got 'ins'");
CHECK_THROWS_WITH(env.render("{% for name in empty_loop %}a{% endfor %}", data), "[inja.exception.render_error] variable 'empty_loop' not found");
// CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is object" );
}
SUBCASE("nested loops") {
auto ldata = json::parse(
R"DELIM(
SUBCASE("nested loops") {
auto ldata = json::parse(
R"DELIM(
{ "outer" : [
{ "inner" : [
{ "in2" : [ 1, 2 ] },
@@ -90,364 +97,382 @@ R"DELIM(
}
]
}
)DELIM"
);
CHECK(env.render(R"DELIM(
)DELIM");
CHECK(env.render(R"DELIM(
{% for o in outer %}{% for i in o.inner %}{{loop.parent.index}}:{{loop.index}}::{{loop.parent.is_last}}
{% for ii in i.in2%}{{ii}},{%endfor%}
{%endfor%}{%endfor%}
)DELIM",
ldata) == "\n0:0::false\n1,2,\n0:1::false\n\n0:2::false\n\n2:0::true\n3,4,\n2:1::true\n5,6,\n\n");
}
ldata) == "\n0:0::false\n1,2,\n0:1::false\n\n0:2::false\n\n2:0::true\n3,4,\n2:1::true\n5,6,\n\n");
}
SUBCASE("conditionals") {
CHECK(env.render("{% if is_happy %}Yeah!{% endif %}", data) == "Yeah!");
CHECK(env.render("{% if is_sad %}Yeah!{% endif %}", data) == "");
CHECK(env.render("{% if is_sad %}Yeah!{% else %}Nooo...{% endif %}", data) == "Nooo...");
CHECK(env.render("{% if age == 29 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age > 29 %}Right{% else %}Wrong{% endif %}", data) == "Wrong");
CHECK(env.render("{% if age <= 29 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age != 28 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age >= 30 %}Right{% else %}Wrong{% endif %}", data) == "Wrong");
CHECK(env.render("{% if age in [28, 29, 30] %}True{% endif %}", data) == "True");
CHECK(env.render("{% if age == 28 %}28{% else if age == 29 %}29{% endif %}", data) == "29");
CHECK(env.render("{% if age == 26 %}26{% else if age == 27 %}27{% else if age == 28 %}28{% else %}29{% endif %}", data) == "29");
CHECK(env.render("{% if age == 25 %}+{% endif %}{% if age == 29 %}+{% else %}-{% endif %}", data) == "+");
SUBCASE("conditionals") {
CHECK(env.render("{% if is_happy %}Yeah!{% endif %}", data) == "Yeah!");
CHECK(env.render("{% if is_sad %}Yeah!{% endif %}", data) == "");
CHECK(env.render("{% if is_sad %}Yeah!{% else %}Nooo...{% endif %}", data) == "Nooo...");
CHECK(env.render("{% if age == 29 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age > 29 %}Right{% else %}Wrong{% endif %}", data) == "Wrong");
CHECK(env.render("{% if age <= 29 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age != 28 %}Right{% else %}Wrong{% endif %}", data) == "Right");
CHECK(env.render("{% if age >= 30 %}Right{% else %}Wrong{% endif %}", data) == "Wrong");
CHECK(env.render("{% if age in [28, 29, 30] %}True{% endif %}", data) == "True");
CHECK(env.render("{% if age == 28 %}28{% else if age == 29 %}29{% endif %}", data) == "29");
CHECK(env.render("{% if age == 26 %}26{% else if age == 27 %}27{% else if age == 28 %}28{% else %}29{% endif %}",
data) == "29");
CHECK(env.render("{% if age == 25 %}+{% endif %}{% if age == 29 %}+{% else %}-{% endif %}", data) == "+");
CHECK_THROWS_WITH(env.render("{% if is_happy %}{% if is_happy %}{% endif %}", data), "[inja.exception.parser_error] (at 1:46) unmatched if");
CHECK_THROWS_WITH(env.render("{% if is_happy %}{% else if is_happy %}{% end if %}", data), "[inja.exception.parser_error] (at 1:43) expected statement, got 'end'");
}
CHECK_THROWS_WITH(env.render("{% if is_happy %}{% if is_happy %}{% endif %}", data),
"[inja.exception.parser_error] (at 1:46) unmatched if");
CHECK_THROWS_WITH(env.render("{% if is_happy %}{% else if is_happy %}{% end if %}", data),
"[inja.exception.parser_error] (at 1:43) expected statement, got 'end'");
}
SUBCASE("line statements") {
CHECK(env.render(R"(## if is_happy
SUBCASE("line statements") {
CHECK(env.render(R"(## if is_happy
Yeah!
## endif)", data) == R"(Yeah!
## endif)",
data) == R"(Yeah!
)");
CHECK(env.render(R"(## if is_happy
CHECK(env.render(R"(## if is_happy
## if is_happy
Yeah!
## endif
## endif )", data) == R"(Yeah!
## endif )",
data) == R"(Yeah!
)");
}
}
}
TEST_CASE("functions") {
inja::Environment env;
inja::Environment env;
json data;
data["name"] = "Peter";
data["city"] = "New York";
data["names"] = {"Jeff", "Seb", "Peter", "Tom"};
data["temperature"] = 25.6789;
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["property"] = "name";
data["age"] = 29;
data["i"] = 1;
data["is_happy"] = true;
data["is_sad"] = false;
data["vars"] = {2, 3, 4, 0, -1, -2, -3};
json data;
data["name"] = "Peter";
data["city"] = "New York";
data["names"] = {"Jeff", "Seb", "Peter", "Tom"};
data["temperature"] = 25.6789;
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["property"] = "name";
data["age"] = 29;
data["i"] = 1;
data["is_happy"] = true;
data["is_sad"] = false;
data["vars"] = {2, 3, 4, 0, -1, -2, -3};
SUBCASE("upper") {
CHECK(env.render("{{ upper(name) }}", data) == "PETER");
CHECK(env.render("{{ upper( name ) }}", data) == "PETER");
CHECK(env.render("{{ upper(city) }}", data) == "NEW YORK");
CHECK(env.render("{{ upper(upper(name)) }}", data) == "PETER");
// CHECK_THROWS_WITH( env.render("{{ upper(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is number" );
// CHECK_THROWS_WITH( env.render("{{ upper(true) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is boolean" );
}
SUBCASE("upper") {
CHECK(env.render("{{ upper(name) }}", data) == "PETER");
CHECK(env.render("{{ upper( name ) }}", data) == "PETER");
CHECK(env.render("{{ upper(city) }}", data) == "NEW YORK");
CHECK(env.render("{{ upper(upper(name)) }}", data) == "PETER");
// CHECK_THROWS_WITH( env.render("{{ upper(5) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be string, but is number" ); CHECK_THROWS_WITH( env.render("{{
// upper(true) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is
// boolean" );
}
SUBCASE("lower") {
CHECK(env.render("{{ lower(name) }}", data) == "peter");
CHECK(env.render("{{ lower(city) }}", data) == "new york");
// CHECK_THROWS_WITH( env.render("{{ lower(5.45) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is number" );
}
SUBCASE("lower") {
CHECK(env.render("{{ lower(name) }}", data) == "peter");
CHECK(env.render("{{ lower(city) }}", data) == "new york");
// CHECK_THROWS_WITH( env.render("{{ lower(5.45) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be string, but is number" );
}
SUBCASE("range") {
CHECK(env.render("{{ range(2) }}", data) == "[0,1]");
CHECK(env.render("{{ range(4) }}", data) == "[0,1,2,3]");
// CHECK_THROWS_WITH( env.render("{{ range(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("range") {
CHECK(env.render("{{ range(2) }}", data) == "[0,1]");
CHECK(env.render("{{ range(4) }}", data) == "[0,1,2,3]");
// CHECK_THROWS_WITH( env.render("{{ range(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("length") {
CHECK(env.render("{{ length(names) }}", data) == "4"); // Length of array
CHECK(env.render("{{ length(name) }}", data) == "5"); // Length of string
// CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("length") {
CHECK(env.render("{{ length(names) }}", data) == "4"); // Length of array
CHECK(env.render("{{ length(name) }}", data) == "5"); // Length of string
// CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("sort") {
CHECK(env.render("{{ sort([3, 2, 1]) }}", data) == "[1,2,3]");
CHECK(env.render("{{ sort([\"bob\", \"charlie\", \"alice\"]) }}", data) == "[\"alice\",\"bob\",\"charlie\"]");
// CHECK_THROWS_WITH( env.render("{{ sort(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("sort") {
CHECK(env.render("{{ sort([3, 2, 1]) }}", data) == "[1,2,3]");
CHECK(env.render("{{ sort([\"bob\", \"charlie\", \"alice\"]) }}", data) == "[\"alice\",\"bob\",\"charlie\"]");
// CHECK_THROWS_WITH( env.render("{{ sort(5) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("at") {
CHECK(env.render("{{ at(names, 0) }}", data) == "Jeff");
CHECK(env.render("{{ at(names, i) }}", data) == "Seb");
}
SUBCASE("at") {
CHECK(env.render("{{ at(names, 0) }}", data) == "Jeff");
CHECK(env.render("{{ at(names, i) }}", data) == "Seb");
}
SUBCASE("first") {
CHECK(env.render("{{ first(names) }}", data) == "Jeff");
// CHECK_THROWS_WITH( env.render("{{ first(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("first") {
CHECK(env.render("{{ first(names) }}", data) == "Jeff");
// CHECK_THROWS_WITH( env.render("{{ first(5) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("last") {
CHECK(env.render("{{ last(names) }}", data) == "Tom");
// CHECK_THROWS_WITH( env.render("{{ last(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("last") {
CHECK(env.render("{{ last(names) }}", data) == "Tom");
// CHECK_THROWS_WITH( env.render("{{ last(5) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is number" );
}
SUBCASE("round") {
CHECK(env.render("{{ round(4, 0) }}", data) == "4.0");
CHECK(env.render("{{ round(temperature, 2) }}", data) == "25.68");
// CHECK_THROWS_WITH( env.render("{{ round(name, 2) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("round") {
CHECK(env.render("{{ round(4, 0) }}", data) == "4.0");
CHECK(env.render("{{ round(temperature, 2) }}", data) == "25.68");
// CHECK_THROWS_WITH( env.render("{{ round(name, 2) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("divisibleBy") {
CHECK(env.render("{{ divisibleBy(50, 5) }}", data) == "true");
CHECK(env.render("{{ divisibleBy(12, 3) }}", data) == "true");
CHECK(env.render("{{ divisibleBy(11, 3) }}", data) == "false");
// CHECK_THROWS_WITH( env.render("{{ divisibleBy(name, 2) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("divisibleBy") {
CHECK(env.render("{{ divisibleBy(50, 5) }}", data) == "true");
CHECK(env.render("{{ divisibleBy(12, 3) }}", data) == "true");
CHECK(env.render("{{ divisibleBy(11, 3) }}", data) == "false");
// CHECK_THROWS_WITH( env.render("{{ divisibleBy(name, 2) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("odd") {
CHECK(env.render("{{ odd(11) }}", data) == "true");
CHECK(env.render("{{ odd(12) }}", data) == "false");
// CHECK_THROWS_WITH( env.render("{{ odd(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("odd") {
CHECK(env.render("{{ odd(11) }}", data) == "true");
CHECK(env.render("{{ odd(12) }}", data) == "false");
// CHECK_THROWS_WITH( env.render("{{ odd(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("even") {
CHECK(env.render("{{ even(11) }}", data) == "false");
CHECK(env.render("{{ even(12) }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("even") {
CHECK(env.render("{{ even(11) }}", data) == "false");
CHECK(env.render("{{ even(12) }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be number, but is string" );
}
SUBCASE("max") {
CHECK(env.render("{{ max([1, 2, 3]) }}", data) == "3");
CHECK(env.render("{{ max([-5.2, 100.2, 2.4]) }}", data) == "100.2");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("max") {
CHECK(env.render("{{ max([1, 2, 3]) }}", data) == "3");
CHECK(env.render("{{ max([-5.2, 100.2, 2.4]) }}", data) == "100.2");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("min") {
CHECK(env.render("{{ min([1, 2, 3]) }}", data) == "1");
CHECK(env.render("{{ min([-5.2, 100.2, 2.4]) }}", data) == "-5.2");
// CHECK_THROWS_WITH( env.render("{{ min(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("min") {
CHECK(env.render("{{ min([1, 2, 3]) }}", data) == "1");
CHECK(env.render("{{ min([-5.2, 100.2, 2.4]) }}", data) == "-5.2");
// CHECK_THROWS_WITH( env.render("{{ min(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("float") {
CHECK(env.render("{{ float(\"2.2\") == 2.2 }}", data) == "true");
CHECK(env.render("{{ float(\"-1.25\") == -1.25 }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("float") {
CHECK(env.render("{{ float(\"2.2\") == 2.2 }}", data) == "true");
CHECK(env.render("{{ float(\"-1.25\") == -1.25 }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("int") {
CHECK(env.render("{{ int(\"2\") == 2 }}", data) == "true");
CHECK(env.render("{{ int(\"-1.25\") == -1 }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("int") {
CHECK(env.render("{{ int(\"2\") == 2 }}", data) == "true");
CHECK(env.render("{{ int(\"-1.25\") == -1 }}", data) == "true");
// CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error]
// [json.exception.type_error.302] type must be array, but is string" );
}
SUBCASE("default") {
CHECK(env.render("{{ default(11, 0) }}", data) == "11");
CHECK(env.render("{{ default(nothing, 0) }}", data) == "0");
CHECK(env.render("{{ default(name, \"nobody\") }}", data) == "Peter");
CHECK(env.render("{{ default(surname, \"nobody\") }}", data) == "nobody");
CHECK(env.render("{{ default(surname, \"{{ surname }}\") }}", data) == "{{ surname }}");
CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] variable 'lastname' not found");
}
SUBCASE("default") {
CHECK(env.render("{{ default(11, 0) }}", data) == "11");
CHECK(env.render("{{ default(nothing, 0) }}", data) == "0");
CHECK(env.render("{{ default(name, \"nobody\") }}", data) == "Peter");
CHECK(env.render("{{ default(surname, \"nobody\") }}", data) == "nobody");
CHECK(env.render("{{ default(surname, \"{{ surname }}\") }}", data) == "{{ surname }}");
CHECK_THROWS_WITH(env.render("{{ default(surname, lastname) }}", data),
"[inja.exception.render_error] variable 'lastname' not found");
}
SUBCASE("exists") {
CHECK(env.render("{{ exists(\"name\") }}", data) == "true");
CHECK(env.render("{{ exists(\"zipcode\") }}", data) == "false");
CHECK(env.render("{{ exists(name) }}", data) == "false");
CHECK(env.render("{{ exists(property) }}", data) == "true");
}
SUBCASE("exists") {
CHECK(env.render("{{ exists(\"name\") }}", data) == "true");
CHECK(env.render("{{ exists(\"zipcode\") }}", data) == "false");
CHECK(env.render("{{ exists(name) }}", data) == "false");
CHECK(env.render("{{ exists(property) }}", data) == "true");
}
SUBCASE("existsIn") {
CHECK(env.render("{{ existsIn(brother, \"name\") }}", data) == "true");
CHECK(env.render("{{ existsIn(brother, \"parents\") }}", data) == "false");
CHECK(env.render("{{ existsIn(brother, property) }}", data) == "true");
CHECK(env.render("{{ existsIn(brother, name) }}", data) == "false");
CHECK_THROWS_WITH(env.render("{{ existsIn(sister, \"lastname\") }}", data), "[inja.exception.render_error] variable 'sister' not found");
CHECK_THROWS_WITH(env.render("{{ existsIn(brother, sister) }}", data), "[inja.exception.render_error] variable 'sister' not found");
}
SUBCASE("existsIn") {
CHECK(env.render("{{ existsIn(brother, \"name\") }}", data) == "true");
CHECK(env.render("{{ existsIn(brother, \"parents\") }}", data) == "false");
CHECK(env.render("{{ existsIn(brother, property) }}", data) == "true");
CHECK(env.render("{{ existsIn(brother, name) }}", data) == "false");
CHECK_THROWS_WITH(env.render("{{ existsIn(sister, \"lastname\") }}", data),
"[inja.exception.render_error] variable 'sister' not found");
CHECK_THROWS_WITH(env.render("{{ existsIn(brother, sister) }}", data),
"[inja.exception.render_error] variable 'sister' not found");
}
SUBCASE("isType") {
CHECK(env.render("{{ isBoolean(is_happy) }}", data) == "true");
CHECK(env.render("{{ isBoolean(vars) }}", data) == "false");
CHECK(env.render("{{ isNumber(age) }}", data) == "true");
CHECK(env.render("{{ isNumber(name) }}", data) == "false");
CHECK(env.render("{{ isInteger(age) }}", data) == "true");
CHECK(env.render("{{ isInteger(is_happy) }}", data) == "false");
CHECK(env.render("{{ isFloat(temperature) }}", data) == "true");
CHECK(env.render("{{ isFloat(age) }}", data) == "false");
CHECK(env.render("{{ isObject(brother) }}", data) == "true");
CHECK(env.render("{{ isObject(vars) }}", data) == "false");
CHECK(env.render("{{ isArray(vars) }}", data) == "true");
CHECK(env.render("{{ isArray(name) }}", data) == "false");
CHECK(env.render("{{ isString(name) }}", data) == "true");
CHECK(env.render("{{ isString(names) }}", data) == "false");
}
SUBCASE("isType") {
CHECK(env.render("{{ isBoolean(is_happy) }}", data) == "true");
CHECK(env.render("{{ isBoolean(vars) }}", data) == "false");
CHECK(env.render("{{ isNumber(age) }}", data) == "true");
CHECK(env.render("{{ isNumber(name) }}", data) == "false");
CHECK(env.render("{{ isInteger(age) }}", data) == "true");
CHECK(env.render("{{ isInteger(is_happy) }}", data) == "false");
CHECK(env.render("{{ isFloat(temperature) }}", data) == "true");
CHECK(env.render("{{ isFloat(age) }}", data) == "false");
CHECK(env.render("{{ isObject(brother) }}", data) == "true");
CHECK(env.render("{{ isObject(vars) }}", data) == "false");
CHECK(env.render("{{ isArray(vars) }}", data) == "true");
CHECK(env.render("{{ isArray(name) }}", data) == "false");
CHECK(env.render("{{ isString(name) }}", data) == "true");
CHECK(env.render("{{ isString(names) }}", data) == "false");
}
}
TEST_CASE("callbacks") {
inja::Environment env;
json data;
data["age"] = 28;
inja::Environment env;
json data;
data["age"] = 28;
env.add_callback("double", 1, [](inja::Arguments& args) {
int number = args.at(0)->get<int>();
return 2 * number;
});
env.add_callback("double", 1, [](inja::Arguments &args) {
int number = args.at(0)->get<int>();
return 2 * number;
});
env.add_callback("half", 1, [](inja::Arguments args) {
int number = args.at(0)->get<int>();
return number / 2;
});
env.add_callback("half", 1, [](inja::Arguments args) {
int number = args.at(0)->get<int>();
return number / 2;
});
std::string greet = "Hello";
env.add_callback("double-greetings", 0, [greet](inja::Arguments args) {
return greet + " " + greet + "!";
});
std::string greet = "Hello";
env.add_callback("double-greetings", 0, [greet](inja::Arguments args) { return greet + " " + greet + "!"; });
env.add_callback("multiply", 2, [](inja::Arguments args) {
double number1 = args.at(0)->get<double>();
auto number2 = args.at(1)->get<double>();
return number1 * number2;
});
env.add_callback("multiply", 2, [](inja::Arguments args) {
double number1 = args.at(0)->get<double>();
auto number2 = args.at(1)->get<double>();
return number1 * number2;
});
env.add_callback("multiply", 3, [](inja::Arguments args) {
double number1 = args.at(0)->get<double>();
double number2 = args.at(1)->get<double>();
double number3 = args.at(2)->get<double>();
return number1 * number2 * number3;
});
env.add_callback("multiply", 3, [](inja::Arguments args) {
double number1 = args.at(0)->get<double>();
double number2 = args.at(1)->get<double>();
double number3 = args.at(2)->get<double>();
return number1 * number2 * number3;
});
env.add_callback("multiply", 0, [](inja::Arguments args) {
return 1.0;
});
env.add_callback("multiply", 0, [](inja::Arguments args) { return 1.0; });
CHECK(env.render("{{ double(age) }}", data) == "56");
CHECK(env.render("{{ half(age) }}", data) == "14");
CHECK(env.render("{{ double-greetings }}", data) == "Hello Hello!");
CHECK(env.render("{{ double-greetings() }}", data) == "Hello Hello!");
CHECK(env.render("{{ multiply(4, 5) }}", data) == "20.0");
CHECK(env.render("{{ multiply(3, 4, 5) }}", data) == "60.0");
CHECK(env.render("{{ multiply }}", data) == "1.0");
CHECK(env.render("{{ double(age) }}", data) == "56");
CHECK(env.render("{{ half(age) }}", data) == "14");
CHECK(env.render("{{ double-greetings }}", data) == "Hello Hello!");
CHECK(env.render("{{ double-greetings() }}", data) == "Hello Hello!");
CHECK(env.render("{{ multiply(4, 5) }}", data) == "20.0");
CHECK(env.render("{{ multiply(3, 4, 5) }}", data) == "60.0");
CHECK(env.render("{{ multiply }}", data) == "1.0");
}
TEST_CASE("combinations") {
inja::Environment env;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
data["is_happy"] = true;
inja::Environment env;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = {{"name", "Maria"}};
data["is_happy"] = true;
CHECK(env.render("{% if upper(\"Peter\") == \"PETER\" %}TRUE{% endif %}", data) == "TRUE");
CHECK(env.render("{% if lower(upper(name)) == \"peter\" %}TRUE{% endif %}", data) == "TRUE");
CHECK(env.render("{% for i in range(4) %}{{ loop.index1 }}{% endfor %}", data) == "1234");
CHECK(env.render("{% if upper(\"Peter\") == \"PETER\" %}TRUE{% endif %}", data) == "TRUE");
CHECK(env.render("{% if lower(upper(name)) == \"peter\" %}TRUE{% endif %}", data) == "TRUE");
CHECK(env.render("{% for i in range(4) %}{{ loop.index1 }}{% endfor %}", data) == "1234");
}
TEST_CASE("templates") {
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["is_happy"] = true;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["is_happy"] = true;
SUBCASE("reuse") {
inja::Environment env;
inja::Template temp = env.parse("{% if is_happy %}{{ name }}{% else %}{{ city }}{% endif %}");
SUBCASE("reuse") {
inja::Environment env;
inja::Template temp = env.parse("{% if is_happy %}{{ name }}{% else %}{{ city }}{% endif %}");
CHECK(env.render(temp, data) == "Peter");
CHECK(env.render(temp, data) == "Peter");
data["is_happy"] = false;
data["is_happy"] = false;
CHECK(env.render(temp, data) == "Brunswick");
}
CHECK(env.render(temp, data) == "Brunswick");
}
SUBCASE("include") {
inja::Environment env;
inja::Template t1 = env.parse("Hello {{ name }}");
env.include_template("greeting", t1);
SUBCASE("include") {
inja::Environment env;
inja::Template t1 = env.parse("Hello {{ name }}");
env.include_template("greeting", t1);
inja::Template t2 = env.parse("{% include \"greeting\" %}!");
CHECK(env.render(t2, data) == "Hello Peter!");
CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"), "[inja.exception.file_error] failed accessing file at 'does-not-exist'");
}
inja::Template t2 = env.parse("{% include \"greeting\" %}!");
CHECK(env.render(t2, data) == "Hello Peter!");
CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"),
"[inja.exception.file_error] failed accessing file at 'does-not-exist'");
}
SUBCASE("include-in-loop") {
json loop_data;
loop_data["cities"] = json::array({{{"name", "Munich"}}, {{"name", "New York"}}});
SUBCASE("include-in-loop") {
json loop_data;
loop_data["cities"] = json::array({{{"name", "Munich"}}, {{"name", "New York"}}});
inja::Environment env;
env.include_template("city.tpl", env.parse("{{ loop.index }}:{{ city.name }};"));
inja::Environment env;
env.include_template("city.tpl", env.parse("{{ loop.index }}:{{ city.name }};"));
CHECK(env.render("{% for city in cities %}{% include \"city.tpl\" %}{% endfor %}", loop_data) == "0:Munich;1:New York;");
}
CHECK(env.render("{% for city in cities %}{% include \"city.tpl\" %}{% endfor %}", loop_data) ==
"0:Munich;1:New York;");
}
}
TEST_CASE("other-syntax") {
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
data["is_happy"] = true;
json data;
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = {{"name", "Maria"}};
data["is_happy"] = true;
SUBCASE("variables") {
inja::Environment env;
env.set_element_notation(inja::ElementNotation::Pointer);
SUBCASE("variables") {
inja::Environment env;
env.set_element_notation(inja::ElementNotation::Pointer);
CHECK(env.render("{{ name }}", data) == "Peter");
CHECK(env.render("Hello {{ names/1 }}!", data) == "Hello Seb!");
CHECK(env.render("Hello {{ brother/name }}!", data) == "Hello Chris!");
CHECK(env.render("Hello {{ brother/daughter0/name }}!", data) == "Hello Maria!");
CHECK(env.render("{{ name }}", data) == "Peter");
CHECK(env.render("Hello {{ names/1 }}!", data) == "Hello Seb!");
CHECK(env.render("Hello {{ brother/name }}!", data) == "Hello Chris!");
CHECK(env.render("Hello {{ brother/daughter0/name }}!", data) == "Hello Maria!");
CHECK_THROWS_WITH(env.render("{{unknown/name}}", data), "[inja.exception.render_error] variable 'unknown/name' not found");
}
CHECK_THROWS_WITH(env.render("{{unknown/name}}", data),
"[inja.exception.render_error] variable 'unknown/name' not found");
}
SUBCASE("other expression syntax") {
inja::Environment env;
SUBCASE("other expression syntax") {
inja::Environment env;
CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!");
CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!");
env.set_expression("(&", "&)");
env.set_expression("(&", "&)");
CHECK(env.render("Hello {{ name }}!", data) == "Hello {{ name }}!");
CHECK(env.render("Hello (& name &)!", data) == "Hello Peter!");
}
CHECK(env.render("Hello {{ name }}!", data) == "Hello {{ name }}!");
CHECK(env.render("Hello (& name &)!", data) == "Hello Peter!");
}
SUBCASE("other comment syntax") {
inja::Environment env;
env.set_comment("(&", "&)");
SUBCASE("other comment syntax") {
inja::Environment env;
env.set_comment("(&", "&)");
CHECK(env.render("Hello {# Test #}", data) == "Hello {# Test #}");
CHECK(env.render("Hello (& Test &)", data) == "Hello ");
}
CHECK(env.render("Hello {# Test #}", data) == "Hello {# Test #}");
CHECK(env.render("Hello (& Test &)", data) == "Hello ");
}
SUBCASE("multiple changes") {
inja::Environment env;
env.set_line_statement("$$");
env.set_expression("<%", "%>");
SUBCASE("multiple changes") {
inja::Environment env;
env.set_line_statement("$$");
env.set_expression("<%", "%>");
std::string string_template = R"DELIM(Hello <%name%>
std::string string_template = R"DELIM(Hello <%name%>
$$ if name == "Peter"
You really are <%name%>
$$ endif
)DELIM";
CHECK(env.render(string_template, data) == "Hello Peter\n You really are Peter\n");
}
CHECK(env.render(string_template, data) == "Hello Peter\n You really are Peter\n");
}
}

View File

@@ -1,34 +1,33 @@
// Copyright (c) 2019 Pantor. All rights reserved.
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"
#include "inja/inja.hpp"
using json = nlohmann::json;
TEST_CASE("copy-environment") {
inja::Environment env;
env.add_callback("double", 1, [](inja::Arguments& args) {
int number = args.at(0)->get<int>();
return 2 * number;
});
inja::Environment env;
env.add_callback("double", 1, [](inja::Arguments &args) {
int number = args.at(0)->get<int>();
return 2 * number;
});
inja::Template t1 = env.parse("{{ double(2) }}");
env.include_template("tpl", t1);
std::string test_tpl = "{% include \"tpl\" %}";
inja::Template t1 = env.parse("{{ double(2) }}");
env.include_template("tpl", t1);
std::string test_tpl = "{% include \"tpl\" %}";
REQUIRE(env.render(test_tpl, json()) == "4");
REQUIRE(env.render(test_tpl, json()) == "4");
inja::Environment copy(env);
CHECK(copy.render(test_tpl, json()) == "4");
inja::Environment copy(env);
CHECK(copy.render(test_tpl, json()) == "4");
// overwrite template in source env
inja::Template t2 = env.parse("{{ double(4) }}");
env.include_template("tpl", t2);
REQUIRE(env.render(test_tpl, json()) == "8");
// overwrite template in source env
inja::Template t2 = env.parse("{{ double(4) }}");
env.include_template("tpl", t2);
REQUIRE(env.render(test_tpl, json()) == "8");
// template is unchanged in copy
CHECK(copy.render(test_tpl, json()) == "4");
// template is unchanged in copy
CHECK(copy.render(test_tpl, json()) == "4");
}