update inja

This commit is contained in:
garak
2019-09-30 14:30:42 -04:00
committed by huderlem
parent e47b3efdfe
commit 74477471a6
+204 -122
View File
@@ -1,54 +1,3 @@
// MIT License
// Copyright (c) 2018 lbersch
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// Copyright (c) 2009-2018 FIRST
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the FIRST nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef PANTOR_INJA_HPP #ifndef PANTOR_INJA_HPP
#define PANTOR_INJA_HPP #define PANTOR_INJA_HPP
@@ -517,7 +466,7 @@ public:
typedef const_pointer iterator; typedef const_pointer iterator;
typedef const_pointer const_iterator; typedef const_pointer const_iterator;
typedef std::reverse_iterator< const_iterator > reverse_iterator; typedef std::reverse_iterator< const_iterator > reverse_iterator;
typedef std::reverse_iterator< const_iterator > const_reverse_iterator; typedef std::reverse_iterator< const_iterator > const_reverse_iterator;
typedef std::size_t size_type; typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type; typedef std::ptrdiff_t difference_type;
@@ -1411,6 +1360,9 @@ enum class ElementNotation {
Pointer Pointer
}; };
/*!
* \brief Class for lexer configuration.
*/
struct LexerConfig { struct LexerConfig {
std::string statement_open {"{%"}; std::string statement_open {"{%"};
std::string statement_close {"%}"}; std::string statement_close {"%}"};
@@ -1421,6 +1373,9 @@ struct LexerConfig {
std::string comment_close {"#}"}; std::string comment_close {"#}"};
std::string open_chars {"#{"}; std::string open_chars {"#{"};
bool trim_blocks {false};
bool lstrip_blocks {false};
void update_open_chars() { void update_open_chars() {
open_chars = ""; open_chars = "";
if (open_chars.find(line_statement[0]) == std::string::npos) { if (open_chars.find(line_statement[0]) == std::string::npos) {
@@ -1438,6 +1393,9 @@ struct LexerConfig {
} }
}; };
/*!
* \brief Class for parser configuration.
*/
struct ParserConfig { struct ParserConfig {
ElementNotation notation {ElementNotation::Dot}; ElementNotation notation {ElementNotation::Dot};
}; };
@@ -1450,10 +1408,13 @@ struct ParserConfig {
#ifndef PANTOR_INJA_FUNCTION_STORAGE_HPP #ifndef PANTOR_INJA_FUNCTION_STORAGE_HPP
#define PANTOR_INJA_FUNCTION_STORAGE_HPP #define PANTOR_INJA_FUNCTION_STORAGE_HPP
#include <vector>
// #include "bytecode.hpp" // #include "bytecode.hpp"
#ifndef PANTOR_INJA_BYTECODE_HPP #ifndef PANTOR_INJA_BYTECODE_HPP
#define PANTOR_INJA_BYTECODE_HPP #define PANTOR_INJA_BYTECODE_HPP
#include <string>
#include <utility> #include <utility>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -1464,7 +1425,7 @@ struct ParserConfig {
namespace inja { namespace inja {
using namespace nlohmann; using json = nlohmann::json;
struct Bytecode { struct Bytecode {
@@ -1492,6 +1453,7 @@ struct Bytecode {
GreaterEqual, GreaterEqual,
Less, Less,
LessEqual, LessEqual,
At,
Different, Different,
DivisibleBy, DivisibleBy,
Even, Even,
@@ -1594,6 +1556,9 @@ using namespace nlohmann;
using Arguments = std::vector<const json*>; using Arguments = std::vector<const json*>;
using CallbackFunction = std::function<json(Arguments& args)>; using CallbackFunction = std::function<json(Arguments& args)>;
/*!
* \brief Class for builtin functions and user-defined callbacks.
*/
class FunctionStorage { class FunctionStorage {
public: public:
void add_builtin(nonstd::string_view name, unsigned int num_args, Bytecode::Op op) { void add_builtin(nonstd::string_view name, unsigned int num_args, Bytecode::Op op) {
@@ -1658,6 +1623,9 @@ class FunctionStorage {
#define PANTOR_INJA_PARSER_HPP #define PANTOR_INJA_PARSER_HPP
#include <limits> #include <limits>
#include <string>
#include <utility>
#include <vector>
// #include "bytecode.hpp" // #include "bytecode.hpp"
@@ -1678,12 +1646,17 @@ class FunctionStorage {
#ifndef PANTOR_INJA_TOKEN_HPP #ifndef PANTOR_INJA_TOKEN_HPP
#define PANTOR_INJA_TOKEN_HPP #define PANTOR_INJA_TOKEN_HPP
#include <string>
// #include "string_view.hpp" // #include "string_view.hpp"
namespace inja { namespace inja {
/*!
* \brief Helper-class for the inja Parser.
*/
struct Token { struct Token {
enum class Kind { enum class Kind {
Text, Text,
@@ -1737,13 +1710,17 @@ struct Token {
} }
#endif // PANTOR_INJA_TOKEN_HPP #endif // PANTOR_INJA_TOKEN_HPP
// #include "utils.hpp" // #include "utils.hpp"
#ifndef PANTOR_INJA_UTILS_HPP #ifndef PANTOR_INJA_UTILS_HPP
#define PANTOR_INJA_UTILS_HPP #define PANTOR_INJA_UTILS_HPP
#include <algorithm>
#include <fstream>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <utility>
// #include "string_view.hpp" // #include "string_view.hpp"
@@ -1755,11 +1732,22 @@ inline void inja_throw(const std::string& type, const std::string& message) {
throw std::runtime_error("[inja.exception." + type + "] " + message); throw std::runtime_error("[inja.exception." + type + "] " + message);
} }
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) {
inja_throw("file_error", "failed accessing file at '" + path + "'");
}
return file;
}
namespace string_view { namespace string_view {
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) { inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
start = std::min(start, view.size()); start = std::min(start, view.size());
end = std::min(std::max(start, end), view.size()); end = std::min(std::max(start, end), view.size());
return view.substr(start, end - start); // StringRef(Data + Start, End - Start); 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) { inline std::pair<nonstd::string_view, nonstd::string_view> split(nonstd::string_view view, char Separator) {
@@ -1783,6 +1771,9 @@ namespace string_view {
namespace inja { namespace inja {
/*!
* \brief Class for lexing an inja Template.
*/
class Lexer { class Lexer {
enum class State { enum class State {
Text, Text,
@@ -1831,12 +1822,15 @@ class Lexer {
// try to match one of the opening sequences, and get the close // try to match one of the opening sequences, and get the close
nonstd::string_view open_str = m_in.substr(m_pos); 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)) { if (inja::string_view::starts_with(open_str, m_config.expression_open)) {
m_state = State::ExpressionStart; m_state = State::ExpressionStart;
} else if (inja::string_view::starts_with(open_str, m_config.statement_open)) { } else if (inja::string_view::starts_with(open_str, m_config.statement_open)) {
m_state = State::StatementStart; m_state = State::StatementStart;
must_lstrip = m_config.lstrip_blocks;
} else if (inja::string_view::starts_with(open_str, m_config.comment_open)) { } else if (inja::string_view::starts_with(open_str, m_config.comment_open)) {
m_state = State::CommentStart; m_state = State::CommentStart;
must_lstrip = m_config.lstrip_blocks;
} else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') && } else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') &&
inja::string_view::starts_with(open_str, m_config.line_statement)) { inja::string_view::starts_with(open_str, m_config.line_statement)) {
m_state = State::LineStart; m_state = State::LineStart;
@@ -1844,8 +1838,13 @@ class Lexer {
m_pos += 1; // wasn't actually an opening sequence m_pos += 1; // wasn't actually an opening sequence
goto again; goto again;
} }
if (m_pos == m_tok_start) goto again; // don't generate empty token
return make_token(Token::Kind::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: { case State::ExpressionStart: {
m_state = State::ExpressionBody; m_state = State::ExpressionBody;
@@ -1872,7 +1871,7 @@ class Lexer {
case State::LineBody: case State::LineBody:
return scan_body("\n", Token::Kind::LineStatementClose); return scan_body("\n", Token::Kind::LineStatementClose);
case State::StatementBody: case State::StatementBody:
return scan_body(m_config.statement_close, Token::Kind::StatementClose); return scan_body(m_config.statement_close, Token::Kind::StatementClose, m_config.trim_blocks);
case State::CommentBody: { case State::CommentBody: {
// fast-scan to comment close // fast-scan to comment close
size_t end = m_in.substr(m_pos).find(m_config.comment_close); size_t end = m_in.substr(m_pos).find(m_config.comment_close);
@@ -1883,7 +1882,10 @@ class Lexer {
// return the entire comment in the close token // return the entire comment in the close token
m_state = State::Text; m_state = State::Text;
m_pos += end + m_config.comment_close.size(); m_pos += end + m_config.comment_close.size();
return make_token(Token::Kind::CommentClose); Token tok = make_token(Token::Kind::CommentClose);
if (m_config.trim_blocks)
skip_newline();
return tok;
} }
} }
} }
@@ -1891,7 +1893,7 @@ class Lexer {
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) { Token scan_body(nonstd::string_view close, Token::Kind closeKind, bool trim = false) {
again: again:
// skip whitespace (except for \n as it might be a close) // 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);
@@ -1905,7 +1907,10 @@ class Lexer {
if (inja::string_view::starts_with(m_in.substr(m_tok_start), close)) { if (inja::string_view::starts_with(m_in.substr(m_tok_start), close)) {
m_state = State::Text; m_state = State::Text;
m_pos = m_tok_start + close.size(); m_pos = m_tok_start + close.size();
return make_token(closeKind); Token tok = make_token(closeKind);
if (trim)
skip_newline();
return tok;
} }
// skip \n // skip \n
@@ -2026,6 +2031,34 @@ class Lexer {
Token make_token(Token::Kind kind) const { Token make_token(Token::Kind kind) const {
return Token(kind, string_view::slice(m_in, m_tok_start, m_pos)); return Token(kind, string_view::slice(m_in, m_tok_start, m_pos));
} }
void skip_newline() {
if (m_pos < m_in.size()) {
char ch = m_in[m_pos];
if (ch == '\n')
m_pos += 1;
else if (ch == '\r') {
m_pos += 1;
if (m_pos < m_in.size() && m_in[m_pos] == '\n')
m_pos += 1;
}
}
}
static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text)
{
nonstd::string_view result = text;
while (!result.empty()) {
char ch = result.back();
if (ch == ' ' || ch == '\t')
result.remove_suffix(1);
else if (ch == '\n' || ch == '\r')
break;
else
return text;
}
return result;
}
}; };
} }
@@ -2036,6 +2069,7 @@ class Lexer {
#ifndef PANTOR_INJA_TEMPLATE_HPP #ifndef PANTOR_INJA_TEMPLATE_HPP
#define PANTOR_INJA_TEMPLATE_HPP #define PANTOR_INJA_TEMPLATE_HPP
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -2045,6 +2079,9 @@ class Lexer {
namespace inja { namespace inja {
/*!
* \brief The main inja Template.
*/
struct Template { struct Template {
std::vector<Bytecode> bytecodes; std::vector<Bytecode> bytecodes;
std::string content; std::string content;
@@ -2054,7 +2091,7 @@ using TemplateStorage = std::map<std::string, Template>;
} }
#endif // PANTOR_INJA_TEMPLATE_HPP #endif // PANTOR_INJA_TEMPLATE_HPP
// #include "token.hpp" // #include "token.hpp"
@@ -2068,6 +2105,7 @@ namespace inja {
class ParserStatic { class ParserStatic {
ParserStatic() { ParserStatic() {
functions.add_builtin("at", 2, Bytecode::Op::At);
functions.add_builtin("default", 2, Bytecode::Op::Default); functions.add_builtin("default", 2, Bytecode::Op::Default);
functions.add_builtin("divisibleBy", 2, Bytecode::Op::DivisibleBy); functions.add_builtin("divisibleBy", 2, Bytecode::Op::DivisibleBy);
functions.add_builtin("even", 1, Bytecode::Op::Even); functions.add_builtin("even", 1, Bytecode::Op::Even);
@@ -2107,13 +2145,16 @@ class ParserStatic {
FunctionStorage functions; FunctionStorage functions;
}; };
/*!
* \brief Class for parsing an inja Template.
*/
class Parser { class Parser {
public: 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()) { } 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) { bool parse_expression(Template& tmpl) {
if (!parse_expression_and(tmpl)) return false; if (!parse_expression_and(tmpl)) return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != "or") return true; if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("or")) return true;
get_next_token(); get_next_token();
if (!parse_expression_and(tmpl)) return false; if (!parse_expression_and(tmpl)) return false;
append_function(tmpl, Bytecode::Op::Or, 2); append_function(tmpl, Bytecode::Op::Or, 2);
@@ -2122,7 +2163,7 @@ class Parser {
bool parse_expression_and(Template& tmpl) { bool parse_expression_and(Template& tmpl) {
if (!parse_expression_not(tmpl)) return false; if (!parse_expression_not(tmpl)) return false;
if (m_tok.kind != Token::Kind::Id || m_tok.text != "and") return true; if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("and")) return true;
get_next_token(); get_next_token();
if (!parse_expression_not(tmpl)) return false; if (!parse_expression_not(tmpl)) return false;
append_function(tmpl, Bytecode::Op::And, 2); append_function(tmpl, Bytecode::Op::And, 2);
@@ -2130,7 +2171,7 @@ class Parser {
} }
bool parse_expression_not(Template& tmpl) { bool parse_expression_not(Template& tmpl) {
if (m_tok.kind == Token::Kind::Id && m_tok.text == "not") { if (m_tok.kind == Token::Kind::Id && m_tok.text == static_cast<decltype(m_tok.text)>("not")) {
get_next_token(); get_next_token();
if (!parse_expression_not(tmpl)) return false; if (!parse_expression_not(tmpl)) return false;
append_function(tmpl, Bytecode::Op::Not, 1); append_function(tmpl, Bytecode::Op::Not, 1);
@@ -2145,7 +2186,7 @@ class Parser {
Bytecode::Op op; Bytecode::Op op;
switch (m_tok.kind) { switch (m_tok.kind) {
case Token::Kind::Id: case Token::Kind::Id:
if (m_tok.text == "in") if (m_tok.text == static_cast<decltype(m_tok.text)>("in"))
op = Bytecode::Op::In; op = Bytecode::Op::In;
else else
return true; return true;
@@ -2233,7 +2274,9 @@ class Parser {
append_callback(tmpl, func_token.text, num_args); append_callback(tmpl, func_token.text, num_args);
return true; return true;
} }
} else if (m_tok.text == "true" || m_tok.text == "false" || m_tok.text == "null") { } 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 // true, false, null are json literals
if (brace_level == 0 && bracket_level == 0) { if (brace_level == 0 && bracket_level == 0) {
json_first = m_tok.text; json_first = m_tok.text;
@@ -2312,7 +2355,7 @@ class Parser {
bool parse_statement(Template& tmpl, nonstd::string_view path) { bool parse_statement(Template& tmpl, nonstd::string_view path) {
if (m_tok.kind != Token::Kind::Id) return false; if (m_tok.kind != Token::Kind::Id) return false;
if (m_tok.text == "if") { if (m_tok.text == static_cast<decltype(m_tok.text)>("if")) {
get_next_token(); get_next_token();
// evaluate expression // evaluate expression
@@ -2323,7 +2366,7 @@ class Parser {
// conditional jump; destination will be filled in by else or endif // conditional jump; destination will be filled in by else or endif
tmpl.bytecodes.emplace_back(Bytecode::Op::ConditionalJump); tmpl.bytecodes.emplace_back(Bytecode::Op::ConditionalJump);
} else if (m_tok.text == "endif") { } else if (m_tok.text == static_cast<decltype(m_tok.text)>("endif")) {
if (m_if_stack.empty()) { if (m_if_stack.empty()) {
inja_throw("parser_error", "endif without matching if"); inja_throw("parser_error", "endif without matching if");
} }
@@ -2342,7 +2385,7 @@ class Parser {
// pop if stack // pop if stack
m_if_stack.pop_back(); m_if_stack.pop_back();
} else if (m_tok.text == "else") { } else if (m_tok.text == static_cast<decltype(m_tok.text)>("else")) {
if (m_if_stack.empty()) if (m_if_stack.empty())
inja_throw("parser_error", "else without matching if"); inja_throw("parser_error", "else without matching if");
auto& if_data = m_if_stack.back(); auto& if_data = m_if_stack.back();
@@ -2358,7 +2401,7 @@ class Parser {
if_data.prev_cond_jump = std::numeric_limits<unsigned int>::max(); if_data.prev_cond_jump = std::numeric_limits<unsigned int>::max();
// chained else if // chained else if
if (m_tok.kind == Token::Kind::Id && m_tok.text == "if") { if (m_tok.kind == Token::Kind::Id && m_tok.text == static_cast<decltype(m_tok.text)>("if")) {
get_next_token(); get_next_token();
// evaluate expression // evaluate expression
@@ -2370,7 +2413,7 @@ class Parser {
// conditional jump; destination will be filled in by else or endif // conditional jump; destination will be filled in by else or endif
tmpl.bytecodes.emplace_back(Bytecode::Op::ConditionalJump); tmpl.bytecodes.emplace_back(Bytecode::Op::ConditionalJump);
} }
} else if (m_tok.text == "for") { } else if (m_tok.text == static_cast<decltype(m_tok.text)>("for")) {
get_next_token(); get_next_token();
// options: for a in arr; for a, b in obj // options: for a in arr; for a, b in obj
@@ -2389,7 +2432,7 @@ class Parser {
get_next_token(); get_next_token();
} }
if (m_tok.kind != Token::Kind::Id || m_tok.text != "in") if (m_tok.kind != Token::Kind::Id || m_tok.text != static_cast<decltype(m_tok.text)>("in"))
inja_throw("parser_error", inja_throw("parser_error",
"expected 'in', got '" + m_tok.describe() + "'"); "expected 'in', got '" + m_tok.describe() + "'");
get_next_token(); get_next_token();
@@ -2403,7 +2446,7 @@ class Parser {
tmpl.bytecodes.back().value = key_token.text; tmpl.bytecodes.back().value = key_token.text;
} }
tmpl.bytecodes.back().str = static_cast<std::string>(value_token.text); tmpl.bytecodes.back().str = static_cast<std::string>(value_token.text);
} else if (m_tok.text == "endfor") { } else if (m_tok.text == static_cast<decltype(m_tok.text)>("endfor")) {
get_next_token(); get_next_token();
if (m_loop_stack.empty()) { if (m_loop_stack.empty()) {
inja_throw("parser_error", "endfor without matching for"); inja_throw("parser_error", "endfor without matching for");
@@ -2415,7 +2458,7 @@ class Parser {
tmpl.bytecodes.emplace_back(Bytecode::Op::EndLoop); 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(); m_loop_stack.pop_back();
} else if (m_tok.text == "include") { } else if (m_tok.text == static_cast<decltype(m_tok.text)>("include")) {
get_next_token(); get_next_token();
if (m_tok.kind != Token::Kind::String) { if (m_tok.kind != Token::Kind::String) {
@@ -2431,8 +2474,10 @@ class Parser {
} }
// sys::path::remove_dots(pathname, true, sys::path::Style::posix); // sys::path::remove_dots(pathname, true, sys::path::Style::posix);
Template include_template = parse_template(pathname); if (m_included_templates.find(pathname) == m_included_templates.end()) {
m_included_templates.emplace(pathname, include_template); Template include_template = parse_template(pathname);
m_included_templates.emplace(pathname, include_template);
}
// generate a reference bytecode // generate a reference bytecode
tmpl.bytecodes.emplace_back(Bytecode::Op::Include, json(pathname), Bytecode::Flag::ValueImmediate); tmpl.bytecodes.emplace_back(Bytecode::Op::Include, json(pathname), Bytecode::Flag::ValueImmediate);
@@ -2552,10 +2597,10 @@ class Parser {
} }
std::string load_file(nonstd::string_view filename) { std::string load_file(nonstd::string_view filename) {
std::ifstream file(static_cast<std::string>(filename)); std::ifstream file = open_file_or_throw(static_cast<std::string>(filename));
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return text; return text;
} }
private: private:
const ParserConfig& m_config; const ParserConfig& m_config;
@@ -2605,6 +2650,7 @@ class Parser {
#if __cplusplus < 201402L #if __cplusplus < 201402L
#include <cstddef> #include <cstddef>
#include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@@ -2655,6 +2701,9 @@ namespace stdinja = std;
#include <algorithm> #include <algorithm>
#include <numeric> #include <numeric>
#include <string>
#include <utility>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -2679,6 +2728,9 @@ inline nonstd::string_view convert_dot_to_json_pointer(nonstd::string_view dot,
return nonstd::string_view(out.data(), out.size()); return nonstd::string_view(out.data(), out.size());
} }
/*!
* \brief Class for rendering a Template with data.
*/
class Renderer { class Renderer {
std::vector<const json*>& get_args(const Bytecode& bc) { std::vector<const json*>& get_args(const Bytecode& bc) {
m_tmp_args.clear(); m_tmp_args.clear();
@@ -2765,7 +2817,7 @@ class Renderer {
LoopLevel& level = m_loop_stack.back(); LoopLevel& level = m_loop_stack.back();
if (level.loop_type == LoopLevel::Type::Array) { 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;
auto& loopData = level.data["loop"]; auto& loopData = level.data["loop"];
loopData["index"] = level.index; loopData["index"] = level.index;
loopData["index1"] = level.index + 1; loopData["index1"] = level.index + 1;
@@ -2787,8 +2839,8 @@ class Renderer {
enum class Type { Map, Array }; enum class Type { Map, Array };
Type loop_type; Type loop_type;
nonstd::string_view key_name; // variable name for keys nonstd::string_view key_name; // variable name for keys
nonstd::string_view value_name; // variable name for values nonstd::string_view value_name; // variable name for values
json data; // data with loop info added json data; // data with loop info added
json values; // values to iterate over json values; // values to iterate over
@@ -2800,8 +2852,8 @@ class Renderer {
// loop over map // loop over map
using KeyValue = std::pair<nonstd::string_view, json*>; using KeyValue = std::pair<nonstd::string_view, json*>;
using MapValues = std::vector<KeyValue>; using MapValues = std::vector<KeyValue>;
MapValues map_values; // values to iterate over MapValues map_values; // values to iterate over
MapValues::iterator map_it; // iterator over values MapValues::iterator map_it; // iterator over values
}; };
@@ -2835,11 +2887,11 @@ class Renderer {
} }
case Bytecode::Op::PrintValue: { case Bytecode::Op::PrintValue: {
const json& val = *get_args(bc)[0]; const json& val = *get_args(bc)[0];
if (val.is_string()) if (val.is_string()) {
os << val.get_ref<const std::string&>(); os << val.get_ref<const std::string&>();
else } else {
os << val.dump(); os << val.dump();
// val.dump(os); }
pop_args(bc); pop_args(bc);
break; break;
} }
@@ -2870,7 +2922,15 @@ class Renderer {
break; break;
} }
case Bytecode::Op::Length: { case Bytecode::Op::Length: {
auto result = get_args(bc)[0]->size(); const json& val = *get_args(bc)[0];
int result;
if (val.is_string()) {
result = val.get_ref<const std::string&>().length();
} else {
result = val.size();
}
pop_args(bc); pop_args(bc);
m_stack.emplace_back(result); m_stack.emplace_back(result);
break; break;
@@ -2882,6 +2942,13 @@ class Renderer {
m_stack.emplace_back(std::move(result)); m_stack.emplace_back(std::move(result));
break; 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: { case Bytecode::Op::First: {
auto result = get_args(bc)[0]->front(); auto result = get_args(bc)[0]->front();
pop_args(bc); pop_args(bc);
@@ -3091,7 +3158,7 @@ class Renderer {
break; break;
} }
case Bytecode::Op::Include: 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, data); Renderer(m_included_templates, m_callbacks).render_to(os, m_included_templates.find(get_imm(bc)->get_ref<const std::string&>())->second, *m_data);
break; break;
case Bytecode::Op::Callback: { case Bytecode::Op::Callback: {
auto callback = m_callbacks.find_callback(bc.str, bc.args); auto callback = m_callbacks.find_callback(bc.str, bc.args);
@@ -3216,12 +3283,17 @@ class Renderer {
// #include "template.hpp" // #include "template.hpp"
// #include "utils.hpp"
namespace inja { namespace inja {
using namespace nlohmann; using namespace nlohmann;
/*!
* \brief Class for changing the configuration.
*/
class Environment { class Environment {
class Impl { class Impl {
public: public:
@@ -3238,7 +3310,7 @@ class Environment {
std::unique_ptr<Impl> m_impl; std::unique_ptr<Impl> m_impl;
public: public:
Environment(): Environment("./") { } Environment(): Environment("") { }
explicit Environment(const std::string& global_path): m_impl(stdinja::make_unique<Impl>()) { explicit Environment(const std::string& global_path): m_impl(stdinja::make_unique<Impl>()) {
m_impl->input_path = global_path; m_impl->input_path = global_path;
@@ -3277,6 +3349,16 @@ class Environment {
m_impl->lexer_config.update_open_chars(); m_impl->lexer_config.update_open_chars();
} }
/// Sets whether to remove the first newline after a block
void set_trim_blocks(bool trim_blocks) {
m_impl->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_impl->lexer_config.lstrip_blocks = lstrip_blocks;
}
/// Sets the element notation syntax /// Sets the element notation syntax
void set_element_notation(ElementNotation notation) { void set_element_notation(ElementNotation notation) {
m_impl->parser_config.notation = notation; m_impl->parser_config.notation = notation;
@@ -3290,8 +3372,8 @@ class Environment {
Template parse_template(const std::string& filename) { Template parse_template(const std::string& filename) {
Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates);
return parser.parse_template(m_impl->input_path + static_cast<std::string>(filename)); return parser.parse_template(m_impl->input_path + static_cast<std::string>(filename));
} }
std::string render(nonstd::string_view input, const json& data) { std::string render(nonstd::string_view input, const json& data) {
return render(parse(input), data); return render(parse(input), data);
@@ -3304,35 +3386,35 @@ class Environment {
} }
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); 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); const json data = load_json(filename_data);
return render_file(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_impl->output_path + filename_out); std::ofstream file(m_impl->output_path + filename_out);
file << render_file(filename, data); file << render_file(filename, data);
file.close(); 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_impl->output_path + filename_out); std::ofstream file(m_impl->output_path + filename_out);
file << render(temp, data); file << render(temp, data);
file.close(); 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); const json data = load_json(filename_data);
write(filename, data, filename_out); 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); const json data = load_json(filename_data);
write(temp, data, filename_out); 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_impl->included_templates, m_impl->callbacks).render_to(os, tmpl, data); Renderer(m_impl->included_templates, m_impl->callbacks).render_to(os, tmpl, data);
@@ -3341,15 +3423,15 @@ class Environment {
std::string load_file(const std::string& filename) { std::string load_file(const std::string& filename) {
Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates);
return parser.load_file(m_impl->input_path + filename); return parser.load_file(m_impl->input_path + filename);
} }
json load_json(const std::string& filename) { json load_json(const std::string& filename) {
std::ifstream file(m_impl->input_path + filename); std::ifstream file = open_file_or_throw(m_impl->input_path + filename);
json j; json j;
file >> j; file >> j;
return 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_impl->callbacks.add_callback(name, numArgs, callback); m_impl->callbacks.add_callback(name, numArgs, callback);