From 1771369c04555c9d8d389e5dc95ac4ced952cbd6 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 14 May 2019 01:17:49 +0300 Subject: [PATCH 01/12] Add render-time error handling Add parser for 'include' statement --- include/jinja2cpp/error_info.h | 3 +- include/jinja2cpp/template.h | 36 ++++++++------ src/error_info.cpp | 3 ++ src/lexer.h | 16 +++++- src/renderer.h | 8 +-- src/statements.cpp | 5 ++ src/statements.h | 25 ++++++++++ src/template.cpp | 65 ++++++++++++------------- src/template_impl.h | 73 ++++++++++++++++++++-------- src/template_parser.cpp | 89 +++++++++++++++++++++++++++++++++- src/template_parser.h | 19 +++++++- test/basic_tests.cpp | 50 +++++++++---------- test/expressions_test.cpp | 6 +-- test/extends_test.cpp | 42 ++++++++-------- test/filters_test.cpp | 8 +-- test/forloop_test.cpp | 28 +++++------ test/if_test.cpp | 6 +-- test/macro_test.cpp | 18 +++---- test/perf_test.cpp | 36 +++++++------- test/set_test.cpp | 10 ++-- test/test_tools.h | 2 +- test/testers_test.cpp | 2 +- test/user_callable_test.cpp | 12 ++--- 23 files changed, 373 insertions(+), 189 deletions(-) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 5b169191..bc2765b6 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -30,7 +30,8 @@ enum class ErrorCode UnexpectedExprBegin, UnexpectedExprEnd, UnexpectedStmtBegin, - UnexpectedStmtEnd + UnexpectedStmtEnd, + TemplateNotParsed, }; struct SourceLocation diff --git a/include/jinja2cpp/template.h b/include/jinja2cpp/template.h index 8ce99791..467f6ed6 100644 --- a/include/jinja2cpp/template.h +++ b/include/jinja2cpp/template.h @@ -15,22 +15,25 @@ namespace jinja2 class ITemplateImpl; class TemplateEnv; template class TemplateImpl; -using ParseResult = nonstd::expected; -using ParseResultW = nonstd::expected; +template +using Result = nonstd::expected; +template +using ResultW = nonstd::expected; class Template { public: - Template(TemplateEnv* env = nullptr); + Template() : Template(nullptr) {} + explicit Template(TemplateEnv* env); ~Template(); - ParseResult Load(const char* tpl, std::string tplName = std::string()); - ParseResult Load(const std::string& str, std::string tplName = std::string()); - ParseResult Load(std::istream& stream, std::string tplName = std::string()); - ParseResult LoadFromFile(const std::string& fileName); + Result Load(const char* tpl, std::string tplName = std::string()); + Result Load(const std::string& str, std::string tplName = std::string()); + Result Load(std::istream& stream, std::string tplName = std::string()); + Result LoadFromFile(const std::string& fileName); - void Render(std::ostream& os, const ValuesMap& params); - std::string RenderAsString(const ValuesMap& params); + Result Render(std::ostream& os, const ValuesMap& params); + Result RenderAsString(const ValuesMap& params); private: std::shared_ptr m_impl; @@ -41,16 +44,17 @@ class Template class TemplateW { public: - TemplateW(TemplateEnv* env = nullptr); + TemplateW() : TemplateW(nullptr) {} + explicit TemplateW(TemplateEnv* env); ~TemplateW(); - ParseResultW Load(const wchar_t* tpl, std::string tplName = std::string()); - ParseResultW Load(const std::wstring& str, std::string tplName = std::string()); - ParseResultW Load(std::wistream& stream, std::string tplName = std::string()); - ParseResultW LoadFromFile(const std::string& fileName); + ResultW Load(const wchar_t* tpl, std::string tplName = std::string()); + ResultW Load(const std::wstring& str, std::string tplName = std::string()); + ResultW Load(std::wistream& stream, std::string tplName = std::string()); + ResultW LoadFromFile(const std::string& fileName); - void Render(std::wostream& os, const ValuesMap& params); - std::wstring RenderAsString(const ValuesMap& params); + ResultW Render(std::wostream& os, const ValuesMap& params); + ResultW RenderAsString(const ValuesMap& params); private: std::shared_ptr m_impl; diff --git a/src/error_info.cpp b/src/error_info.cpp index 72eed03a..78f8faa5 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -191,6 +191,9 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e case ErrorCode::UnexpectedStmtEnd: os << UNIVERSAL_STR("Unexpected statement block end"); break; + case ErrorCode::TemplateNotParsed: + os << UNIVERSAL_STR("Template not parsed"); + break; } os << std::endl << errInfo.GetLocationDescr(); } diff --git a/src/lexer.h b/src/lexer.h index 2ec1045b..8cd6dfea 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -85,6 +85,13 @@ struct Token Import, Recursive, Scoped, + With, + Without, + Ignore, + Missing, + Context, + From, + As, // Template control CommentBegin, @@ -154,7 +161,14 @@ enum class Keyword Include, Import, Recursive, - Scoped + Scoped, + With, + Without, + Ignore, + Missing, + Context, + From, + As, }; struct LexerHelper diff --git a/src/renderer.h b/src/renderer.h index e3b55779..e967de83 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -17,7 +17,7 @@ namespace jinja2 class RendererBase { public: - virtual ~RendererBase() {} + virtual ~RendererBase() = default; virtual void Render(OutStream& os, RenderContext& values) = 0; }; @@ -28,7 +28,7 @@ class ComposedRenderer : public RendererBase public: void AddRenderer(RendererPtr r) { - m_renderers.push_back(r); + m_renderers.push_back(std::move(r)); } void Render(OutStream& os, RenderContext& values) override { @@ -61,8 +61,8 @@ class RawTextRenderer : public RendererBase class ExpressionRenderer : public RendererBase { public: - ExpressionRenderer(ExpressionEvaluatorPtr<> expr) - : m_expression(expr) + explicit ExpressionRenderer(ExpressionEvaluatorPtr<> expr) + : m_expression(std::move(expr)) { } diff --git a/src/statements.cpp b/src/statements.cpp index c7406290..8c7ef663 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -307,6 +307,11 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) renderer->Render(os, values); } +void IncludeStatement::Render(OutStream& os, RenderContext& values) +{ + +} + void MacroStatement::PrepareMacroParams(RenderContext& values) { for (auto& p : m_params) diff --git a/src/statements.h b/src/statements.h index d3440366..ef90746a 100644 --- a/src/statements.h +++ b/src/statements.h @@ -195,6 +195,31 @@ class ExtendsStatement : public Statement void DoRender(OutStream &os, RenderContext &values); }; +class IncludeStatement : public Statement +{ +public: + IncludeStatement(bool ignoreMissing, bool withContext) + : m_ignoreMissing(ignoreMissing) + , m_withContext(withContext) + {} + + void SetIncludeNamesExpr(ExpressionEvaluatorPtr<> expr) + { + m_expr = expr; + } + + void Render(OutStream& os, RenderContext& values) override; +private: + bool m_ignoreMissing; + bool m_withContext; + ExpressionEvaluatorPtr<> m_expr; +}; + +class ImportStatement : public Statement +{ +public: +}; + class MacroStatement : public Statement { public: diff --git a/src/template.cpp b/src/template.cpp index b55ad5ee..c2ea0f36 100644 --- a/src/template.cpp +++ b/src/template.cpp @@ -19,25 +19,22 @@ Template::Template(TemplateEnv* env) } -Template::~Template() -{ - -} +Template::~Template() = default; -ParseResult Template::Load(const char* tpl, std::string tplName) +Result Template::Load(const char* tpl, std::string tplName) { std::string t(tpl); auto result = GetImpl(m_impl)->Load(std::move(t), std::move(tplName)); - return !result ? ParseResult() : nonstd::make_unexpected(std::move(result.get())); + return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); } -ParseResult Template::Load(const std::string& str, std::string tplName) +Result Template::Load(const std::string& str, std::string tplName) { auto result = GetImpl(m_impl)->Load(str, std::move(tplName)); - return !result ? ParseResult() : nonstd::make_unexpected(std::move(result.get())); + return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); } -ParseResult Template::Load(std::istream& stream, std::string tplName) +Result Template::Load(std::istream& stream, std::string tplName) { std::string t; @@ -51,31 +48,33 @@ ParseResult Template::Load(std::istream& stream, std::string tplName) } auto result = GetImpl(m_impl)->Load(std::move(t), std::move(tplName)); - return !result ? ParseResult() : nonstd::make_unexpected(std::move(result.get())); + return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); } -ParseResult Template::LoadFromFile(const std::string& fileName) +Result Template::LoadFromFile(const std::string& fileName) { std::ifstream file(fileName); if (!file.good()) - return ParseResult(); + return Result(); return Load(file, fileName); } -void Template::Render(std::ostream& os, const jinja2::ValuesMap& params) +Result Template::Render(std::ostream& os, const jinja2::ValuesMap& params) { - GetImpl(m_impl)->Render(os, params); + auto result = GetImpl(m_impl)->Render(os, params); + + return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); } -std::string Template::RenderAsString(const jinja2::ValuesMap& params) +Result Template::RenderAsString(const jinja2::ValuesMap& params) { std::string outStr; outStr.reserve(10000); std::ostringstream os(outStr); - Render(os, params); - return os.str(); + auto result = Render(os, params); + return result ? os.str() : Result(result.get_unexpected()); } TemplateW::TemplateW(TemplateEnv* env) @@ -84,25 +83,22 @@ TemplateW::TemplateW(TemplateEnv* env) } -TemplateW::~TemplateW() -{ - -} +TemplateW::~TemplateW() = default; -ParseResultW TemplateW::Load(const wchar_t* tpl, std::string tplName) +ResultW TemplateW::Load(const wchar_t* tpl, std::string tplName) { std::wstring t(tpl); auto result = GetImpl(m_impl)->Load(t, std::move(tplName)); - return !result ? ParseResultW() : nonstd::make_unexpected(std::move(result.get())); + return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); } -ParseResultW TemplateW::Load(const std::wstring& str, std::string tplName) +ResultW TemplateW::Load(const std::wstring& str, std::string tplName) { auto result = GetImpl(m_impl)->Load(str, std::move(tplName)); - return !result ? ParseResultW() : nonstd::make_unexpected(std::move(result.get())); + return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); } -ParseResultW TemplateW::Load(std::wistream& stream, std::string tplName) +ResultW TemplateW::Load(std::wistream& stream, std::string tplName) { std::wstring t; @@ -116,29 +112,30 @@ ParseResultW TemplateW::Load(std::wistream& stream, std::string tplName) } auto result = GetImpl(m_impl)->Load(t, std::move(tplName)); - return !result ? ParseResultW() : nonstd::make_unexpected(std::move(result.get())); + return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); } -ParseResultW TemplateW::LoadFromFile(const std::string& fileName) +ResultW TemplateW::LoadFromFile(const std::string& fileName) { std::wifstream file(fileName); if (!file.good()) - return ParseResultW(); + return ResultW(); return Load(file, fileName); } -void TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params) +ResultW TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params) { - GetImpl(m_impl)->Render(os, params); + auto result = GetImpl(m_impl)->Render(os, params); + return !result ? ResultW() : ResultW(nonstd::make_unexpected(std::move(result.get()))); } -std::wstring TemplateW::RenderAsString(const jinja2::ValuesMap& params) +ResultW TemplateW::RenderAsString(const jinja2::ValuesMap& params) { std::wostringstream os; - GetImpl(m_impl)->Render(os, params); + auto result = GetImpl(m_impl)->Render(os, params); - return os.str(); + return !result ? os.str() : ResultW(nonstd::make_unexpected(std::move(result.get()))); } } // jinga2 diff --git a/src/template_impl.h b/src/template_impl.h index e711f34e..eb2e1a85 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -19,7 +19,7 @@ namespace jinja2 class ITemplateImpl { public: - virtual ~ITemplateImpl() {} + virtual ~ITemplateImpl() = default; }; @@ -48,7 +48,7 @@ template class GenericStreamWriter : public OutStream::StreamWriter { public: - GenericStreamWriter(std::basic_ostream& os) + explicit GenericStreamWriter(std::basic_ostream& os) : m_os(os) {} @@ -70,7 +70,7 @@ template class StringStreamWriter : public OutStream::StreamWriter { public: - StringStreamWriter(std::basic_string* targetStr) + explicit StringStreamWriter(std::basic_string* targetStr) : m_targetStr(targetStr) {} @@ -97,7 +97,7 @@ class TemplateImpl : public ITemplateImpl public: using ThisType = TemplateImpl; - TemplateImpl(TemplateEnv* env) + explicit TemplateImpl(TemplateEnv* env) : m_env(env) { if (env) @@ -105,11 +105,13 @@ class TemplateImpl : public ITemplateImpl } auto GetRenderer() const {return m_renderer;} + auto GetTemplateName() const {}; boost::optional> Load(std::basic_string tpl, std::string tplName) { m_template = std::move(tpl); - TemplateParser parser(&m_template, m_settings, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName)); + m_templateName = tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName); + TemplateParser parser(&m_template, m_settings, m_templateName); auto parseResult = parser.Parse(); if (!parseResult) @@ -119,26 +121,56 @@ class TemplateImpl : public ITemplateImpl return boost::optional>(); } - void Render(std::basic_ostream& os, const ValuesMap& params) + boost::optional> Render(std::basic_ostream& os, const ValuesMap& params) { + boost::optional> normalResult; + if (!m_renderer) - return; + { + typename ErrorInfoTpl::Data errorData; + errorData.code = ErrorCode::TemplateNotParsed; + errorData.srcLoc.col = 1; + errorData.srcLoc.line = 1; + errorData.srcLoc.fileName = ""; - InternalValueMap intParams; - for (auto& ip : params) + return ErrorInfoTpl(errorData); + } + + try { - auto valRef = &ip.second.data(); - auto newParam = visit(visitors::InputValueConvertor(), *valRef); - if (!newParam) - intParams[ip.first] = ValueRef(static_cast(*valRef)); - else - intParams[ip.first] = newParam.get(); + InternalValueMap intParams; + for (auto& ip : params) + { + auto valRef = &ip.second.data(); + auto newParam = visit(visitors::InputValueConvertor(), *valRef); + if (!newParam) + intParams[ip.first] = ValueRef(static_cast(*valRef)); + else + intParams[ip.first] = newParam.get(); + } + RendererCallback callback(this); + RenderContext context(intParams, &callback); + InitRenderContext(context); + OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); + m_renderer->Render(outStream, context); } - RendererCallback callback(this); - RenderContext context(intParams, &callback); - InitRenderContext(context); - OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); - m_renderer->Render(outStream, context); + catch (const ErrorInfoTpl& error) + { + return error; + } + catch (const std::exception& ex) + { + typename ErrorInfoTpl::Data errorData; + errorData.code = ErrorCode::UnexpectedException; + errorData.srcLoc.col = 1; + errorData.srcLoc.line = 1; + errorData.srcLoc.fileName = m_templateName; + errorData.extraParams.push_back(Value(std::string(ex.what()))); + + return ErrorInfoTpl(errorData); + } + + return normalResult; } InternalValueMap& InitRenderContext(RenderContext& context) @@ -201,6 +233,7 @@ class TemplateImpl : public ITemplateImpl TemplateEnv* m_env; Settings m_settings; std::basic_string m_template; + std::string m_templateName; RendererPtr m_renderer; }; diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 565799ab..22564151 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -53,10 +53,12 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme case Keyword::EndCall: result = ParseEndCall(lexer, statementsInfo, tok); break; + case Keyword::Include: + result = ParseInclude(lexer, statementsInfo, tok); + break; case Keyword::Filter: case Keyword::EndFilter: case Keyword::EndSet: - case Keyword::Include: case Keyword::Import: return MakeParseError(ErrorCode::YetUnsupported, tok); default: @@ -581,4 +583,89 @@ StatementsParser::ParseResult StatementsParser::ParseEndCall(LexScanner&, Statem return ParseResult(); } +StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + if (statementsInfo.empty()) + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + + // auto operTok = lexer.NextToken(); + ExpressionEvaluatorPtr<> valueExpr; + ExpressionParser exprParser; + auto expr = exprParser.ParseFullExpression(lexer); + if (!expr) + return expr.get_unexpected(); + valueExpr = *expr; + + Token nextTok; + bool isIgnoreMissing = false; + bool isWithContext = true; + bool hasIgnoreMissing = false; + if (lexer.EatIfEqual(Keyword::Ignore, &nextTok)) + { + if (lexer.EatIfEqual(Keyword::Missing, &nextTok)) + isIgnoreMissing = true; + else + { + auto tok2 = nextTok; + tok2.type = Token::Missing; + return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); + } + hasIgnoreMissing = true; + } + + nextTok = lexer.NextToken(); + auto kw = lexer.GetAsKeyword(nextTok); + bool hasContextControl = false; + if (kw == Keyword::With || kw == Keyword::Without) + { + isWithContext = kw == Keyword::With; + nextTok = lexer.PeekNextToken(); + if (!lexer.EatIfEqual(Keyword::Missing, &nextTok)) + { + auto tok2 = nextTok; + tok2.type = Token::Context; + return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); + } + hasContextControl = true; + } + + if (nextTok != Token::Eof) + { + if (hasContextControl) + { + auto tok2 = nextTok; + tok2.type = Token::Eof; + return MakeParseError(ErrorCode::ExpectedEndOfStatement, nextTok); + } + + if (hasIgnoreMissing) + { + auto tok2 = nextTok; + tok2.type = Token::Eof; + auto tok3 = nextTok; + tok3.type = Token::With; + auto tok4 = nextTok; + tok4.type = Token::Without; + return MakeParseError(ErrorCode::UnexpectedToken, nextTok, {tok2, tok3, tok4}); + } + + auto tok2 = nextTok; + tok2.type = Token::Eof; + auto tok3 = nextTok; + tok3.type = Token::Ignore; + auto tok4 = nextTok; + tok4.type = Token::With; + auto tok5 = nextTok; + tok5.type = Token::Without; + return MakeParseError(ErrorCode::UnexpectedToken, nextTok, {tok2, tok3, tok4, tok5}); + } + + + auto renderer = std::make_shared(isIgnoreMissing, isWithContext); + renderer->SetIncludeNamesExpr(valueExpr); + statementsInfo.back().currentComposition->AddRenderer(renderer); + + return ParseResult(); +} + } diff --git a/src/template_parser.h b/src/template_parser.h index dac59eec..ee5b340a 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -49,7 +49,7 @@ template struct ParserTraitsBase { static Token::Type s_keywords[]; - static KeywordsInfo s_keywordsInfo[32]; + static KeywordsInfo s_keywordsInfo[39]; static std::unordered_map s_tokens; }; @@ -213,6 +213,7 @@ class StatementsParser ParseResult ParseEndMacro(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseEndCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseInclude(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); }; template @@ -770,7 +771,7 @@ class TemplateParser : public LexerHelper }; template -KeywordsInfo ParserTraitsBase::s_keywordsInfo[32] = { +KeywordsInfo ParserTraitsBase::s_keywordsInfo[39] = { {UNIVERSAL_STR("for"), Keyword::For}, {UNIVERSAL_STR("endfor"), Keyword::Endfor}, {UNIVERSAL_STR("in"), Keyword::In}, @@ -803,6 +804,13 @@ KeywordsInfo ParserTraitsBase::s_keywordsInfo[32] = { {UNIVERSAL_STR("None"), Keyword::None}, {UNIVERSAL_STR("recursive"), Keyword::Recursive}, {UNIVERSAL_STR("scoped"), Keyword::Scoped}, + {UNIVERSAL_STR("with"), Keyword::With}, + {UNIVERSAL_STR("without"), Keyword::Without}, + {UNIVERSAL_STR("ignore"), Keyword::Ignore}, + {UNIVERSAL_STR("missing"), Keyword::Missing}, + {UNIVERSAL_STR("context"), Keyword::Context}, + {UNIVERSAL_STR("from"), Keyword::From}, + {UNIVERSAL_STR("as"), Keyword::As}, }; template @@ -861,6 +869,13 @@ std::unordered_map ParserTraitsBase::s_tokens = { {Token::Import, UNIVERSAL_STR("import")}, {Token::Recursive, UNIVERSAL_STR("recursive")}, {Token::Scoped, UNIVERSAL_STR("scoped")}, + {Token::With, UNIVERSAL_STR("with")}, + {Token::Without, UNIVERSAL_STR("without")}, + {Token::Ignore, UNIVERSAL_STR("ignore")}, + {Token::Missing, UNIVERSAL_STR("missing")}, + {Token::Context, UNIVERSAL_STR("context")}, + {Token::From, UNIVERSAL_STR("form")}, + {Token::As, UNIVERSAL_STR("as")}, {Token::CommentBegin, UNIVERSAL_STR("{#")}, {Token::CommentEnd, UNIVERSAL_STR("#}")}, {Token::StmtBegin, UNIVERSAL_STR("{%")}, diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index facf0c3c..f25eb753 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -14,7 +14,7 @@ TEST(BasicTests, PlainSingleLineTemplateProcessing) Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = "Hello World from Parser!"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -27,7 +27,7 @@ TEST(BasicTests, PlainSingleLineTemplateProcessing_Wide) TemplateW tpl; ASSERT_TRUE(tpl.Load(source)); - std::wstring result = tpl.RenderAsString(ValuesMap{}); + std::wstring result = tpl.RenderAsString(ValuesMap{}).value(); std::wcout << result << std::endl; std::wstring expectedResult = L"Hello World from Parser!"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -41,7 +41,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World from Parser!)"; @@ -55,7 +55,7 @@ TEST(BasicTests, InlineCommentsSkip) Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = "Hello World from Parser!"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -70,7 +70,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World from Parser!)"; @@ -87,7 +87,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World from Parser!)"; @@ -109,7 +109,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World from Parser!)"; @@ -125,7 +125,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- from Parser!)"; @@ -141,7 +141,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- @@ -158,7 +158,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World --< from Parser!)"; @@ -174,7 +174,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World --< from Parser!)"; @@ -189,7 +189,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World --< from Parser!)"; @@ -205,7 +205,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World > --< @@ -222,7 +222,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- from Parser!)"; @@ -238,7 +238,7 @@ from Parser!)"; Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- from Parser!)"; @@ -259,7 +259,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World --from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -279,7 +279,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- from Parser!)"; @@ -299,7 +299,7 @@ TEST(BasicTests, TrimSpaces_3) return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World --)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -322,7 +322,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World str1 str2 str3 str4 from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -342,7 +342,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World > -- < from Parser!)"; @@ -363,7 +363,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World > -- < from Parser!)"; @@ -385,7 +385,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World > -- < from Parser!)"; @@ -405,7 +405,7 @@ TEST(BasicTests, TrimSpaces_8) return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World> -- < from Parser!)"; @@ -427,7 +427,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -> -- < from Parser!)"; @@ -449,7 +449,7 @@ from Parser!)"; return; } - std::string result = tpl.RenderAsString(ValuesMap{}); + std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; std::string expectedResult = R"(Hello World -- > -- < diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index f8180040..31a58cd9 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -37,7 +37,7 @@ TEST(ExpressionsTest, BinaryMathOperations) {"boolTrueValue", true}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( 11 @@ -76,7 +76,7 @@ TEST(ExpressionsTest, IfExpression) {"boolTrueValue", true}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( 3 @@ -103,7 +103,7 @@ TEST_P(LogicalExprTest, Test) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); diff --git a/test/extends_test.cpp b/test/extends_test.cpp index 4cb8fe7a..912611d6 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -28,11 +28,11 @@ TEST_F(ExtendsTest, BasicExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World!"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; EXPECT_STREQ(baseResult.c_str(), result.c_str()); } @@ -45,11 +45,11 @@ TEST_F(ExtendsTest, SimpleBlockExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World! -><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = "Hello World! ->Extended block!<-"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -65,15 +65,15 @@ TEST_F(ExtendsTest, TwoLevelBlockExtends) auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); auto tpl2 = m_env.LoadTemplate("derived2.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World! -><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = "Hello World! ->Extended block!<-"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); - std::string result2 = tpl2.RenderAsString(jinja2::ValuesMap{}); + std::string result2 = tpl2.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result2 << std::endl; expectedResult = "Hello World! ->Extended block!derived2 block=>innerB1 content<=<-"; EXPECT_STREQ(expectedResult.c_str(), result2.c_str()); @@ -87,11 +87,11 @@ TEST_F(ExtendsTest, DoubleBlocksExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World! -><- -><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = "Hello World! ->Extended block b1!<- ->Extended block b2!<-"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -105,11 +105,11 @@ TEST_F(ExtendsTest, SuperBlocksExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World! -><- -><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = "Hello World! ->Extended block b1!=>block b1<=<- ->Extended block b2!<-"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -128,14 +128,14 @@ TEST_F(ExtendsTest, SuperAndSelfBlocksExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = R"(Hello World!-><- --><----><---><- --><----><-- )"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(Hello World!->Extended block b1!=>block b1 - first entry<=<- -->Extended block b1!=>block b1 - first entry<=<----><--->Extended block b2!<- @@ -161,14 +161,14 @@ TEST_F(ExtendsTest, InnerBlocksExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = R"(Hello World!-><- --><----><---><- --><----><-- )"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(Hello World!->Extended block b1!=>block b1 - first entry<=###Extended innerB1 block first entry!###<- -->Extended block b1!=>block b1 - first entry<=###Extended innerB1 block first entry!###<----><--->Extended block b2!<- @@ -187,11 +187,11 @@ TEST_F(ExtendsTest, ScopedBlocksExtends) auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World!\n-><-\n-><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(Hello World! -><- @@ -216,11 +216,11 @@ Some Stuff auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = "Hello World!\n"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(Hello World! -><-->SCOPEDMACROTEXT<-)"; @@ -239,11 +239,11 @@ R"({% extends "base.j2tpl" %}{% block body %}->{{ testMacro('RegularMacroText') auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); - std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; std::string expectedResult = ""; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); - std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(->#REGULARMACROTEXT#<-)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 9c71496b..f5c21817 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -22,7 +22,7 @@ TEST_P(ListIteratorTest, Test) Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); @@ -64,7 +64,7 @@ TEST_P(FilterGroupByTest, Test) Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); @@ -87,7 +87,7 @@ TEST(FilterGenericTestSingle, ApplyMacroTest) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( HELLO WORLD! @@ -113,7 +113,7 @@ TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( STR1->STR2->STR3 diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index 2a6f0cf6..dee021b6 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -23,7 +23,7 @@ a[{{i}}] = image[{{i}}]; {"its", ValuesList{0, 1, 2} } }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[0]; @@ -47,7 +47,7 @@ a[{{i}}] = image[{{i}}]; ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[0]; @@ -72,7 +72,7 @@ a[{{i}}] = image[{{i}}]; {"ints", ValuesList()} }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( )"; @@ -94,7 +94,7 @@ a[{{i}}] = image[{{i}}]; {"its", Reflect(std::vector{0, 1, 2} ) } }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[0]; @@ -118,7 +118,7 @@ TEST_P(RangeForLoopTest, IntegersRangeLoop) ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); @@ -156,7 +156,7 @@ a[{{i}}] = image[{{loop.cycle(2, 4, 6)}}]; ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[2]; @@ -182,7 +182,7 @@ a[{{i}}] = image[{{loop.cycle("a", "b", "c")}}]; ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[a]; @@ -208,7 +208,7 @@ a[{{i}}] = image[{{i}}]; ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[0] = image[0]; @@ -247,7 +247,7 @@ No indexes given ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( No indexes given @@ -271,7 +271,7 @@ TEST(ForLoopTest, LoopVariableWithIf) {"its", ValuesList{0, 1, 2, 3, 4} } }; - std::string result = mytemplate.RenderAsString(params); + std::string result = mytemplate.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( 0 length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=2; @@ -296,7 +296,7 @@ length={{loop.length}}, index={{loop.index}}, index0={{loop.index0}}, first={{lo {"its", ValuesList{0, 1, 2} } }; - std::string result = mytemplate.RenderAsString(params); + std::string result = mytemplate.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=1; @@ -327,7 +327,7 @@ a[{{i}}] = "{{name}}_{{loop.index0}}"; } }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( a[1] = "image1_0"; @@ -355,7 +355,7 @@ TEST(ForLoopTest, SimpleNestedLoop) {"inners", ValuesList{0, 1}} }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << "[" << result << "]" << std::endl; std::string expectedResult = R"DELIM( a[0] = image[0]; @@ -401,7 +401,7 @@ TEST(ForLoopTest, RecursiveLoop) ValuesMap params = {}; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << "[" << result << "]" << std::endl; std::string expectedResult = R"DELIM( root1 -> child1_1 -> child1_2 -> child1_3 -> root2 -> child2_1 -> child2_2 -> child2_3 -> root3 -> child3_1 -> child3_2 -> child3_3 -> )DELIM"; diff --git a/test/if_test.cpp b/test/if_test.cpp index 64e12c45..26e7be89 100644 --- a/test/if_test.cpp +++ b/test/if_test.cpp @@ -23,7 +23,7 @@ Hello from Jinja template! {"FalseVal", true}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello from Jinja template! @@ -49,7 +49,7 @@ Else branch triggered! {"FalseVal", false}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( Else branch triggered! @@ -79,7 +79,7 @@ ElseIf branch triggered! {"FalseVal", false}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( ElseIf 2 branch triggered! diff --git a/test/macro_test.cpp b/test/macro_test.cpp index 123b2437..02e10468 100644 --- a/test/macro_test.cpp +++ b/test/macro_test.cpp @@ -26,7 +26,7 @@ Hello World! return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World! @@ -54,7 +54,7 @@ TEST(MacroTest, OneParamMacro) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( -->Hello<-- @@ -82,7 +82,7 @@ TEST(MacroTest, OneDefaultParamMacro) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( -->Hello<-- @@ -115,7 +115,7 @@ TEST(MacroTest, ClosureMacro) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( -->-->Some Value -> Hello World<--<-- @@ -149,7 +149,7 @@ kwargs: {{ kwargs | pprint }} return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( name: test @@ -180,7 +180,7 @@ Hello World! -> {{ caller() }} <- return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World! -> Message from caller <- @@ -206,7 +206,7 @@ TEST(MacroTest, CallWithParamsAndSimpleMacro) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( -> HELLO WORLD <- @@ -232,7 +232,7 @@ TEST(MacroTest, CallWithParamsAndMacro) return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World >>> -> hello world <--> HELLO WORLD <- @@ -262,7 +262,7 @@ kwargs: {{ kwargs | pprint }} return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = R"( name: $call$ diff --git a/test/perf_test.cpp b/test/perf_test.cpp index 96b5ef50..843906ee 100644 --- a/test/perf_test.cpp +++ b/test/perf_test.cpp @@ -20,10 +20,10 @@ TEST(PerfTests, PlainText) jinja2::ValuesMap params; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -37,10 +37,10 @@ TEST(PerfTests, SimpleSubstituteText) jinja2::ValuesMap params = {{"message", "Hello World!"}}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -54,10 +54,10 @@ TEST(PerfTests, ValueSubstituteText) jinja2::ValuesMap params = {{"message", 100500}}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -71,10 +71,10 @@ TEST(PerfTests, SimpleSubstituteFilterText) jinja2::ValuesMap params = {{"message", "Hello World!"}}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -89,10 +89,10 @@ TEST(PerfTests, DoubleSubstituteText) jinja2::ValuesMap params = {{"message", "Hello World!"}, {"number", 10}}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -106,10 +106,10 @@ TEST(PerfTests, ForLoopText) jinja2::ValuesMap params = {}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 20; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -123,10 +123,10 @@ TEST(PerfTests, ForLoopParamText) jinja2::ValuesMap params = {{"num", 20}}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 20; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -140,10 +140,10 @@ TEST(PerfTests, ForLoopIndexText) jinja2::ValuesMap params = {}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 20; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } @@ -157,10 +157,10 @@ TEST(PerfTests, ForLoopIfText) jinja2::ValuesMap params = {}; - std::cout << tpl.RenderAsString(params) << std::endl; + std::cout << tpl.RenderAsString(params).value() << std::endl; std::string result; for (int n = 0; n < Iterations * 20; ++ n) - result = tpl.RenderAsString(params); + result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; } diff --git a/test/set_test.cpp b/test/set_test.cpp index e406dc39..1384cd61 100644 --- a/test/set_test.cpp +++ b/test/set_test.cpp @@ -26,7 +26,7 @@ paramsVal: {{intValue}} {"boolTrueValue", true}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( localVal: 3 @@ -53,7 +53,7 @@ lastName: {{lastName}} }}, }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( firtsName: John @@ -76,7 +76,7 @@ world: {{tuple[1]}} ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( hello: Hello @@ -99,7 +99,7 @@ world: {{tuple[1]}} ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( hello: Hello @@ -123,7 +123,7 @@ world: {{dict.world}} ValuesMap params = { }; - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( hello: Hello diff --git a/test/test_tools.h b/test/test_tools.h index c2c9a420..3ebfa8bd 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -123,7 +123,7 @@ class SubstitutionTestBase : public ::testing::TestWithParam return; } - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); diff --git a/test/testers_test.cpp b/test/testers_test.cpp index d7718e38..19dc6758 100644 --- a/test/testers_test.cpp +++ b/test/testers_test.cpp @@ -18,7 +18,7 @@ TEST_P(TestersGenericTest, Test) Template tpl; ASSERT_TRUE(tpl.Load(source)); - std::string result = tpl.RenderAsString(PrepareTestData()); + std::string result = tpl.RenderAsString(PrepareTestData()).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index a904bc2d..e6e7d369 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -30,7 +30,7 @@ TEST(UserCallableTest, SimpleUserCallable) jinja2::ValuesMap params; params["test"] = std::move(uc); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World! @@ -63,7 +63,7 @@ TEST(UserCallableTest, SimpleUserCallableWithParams1) jinja2::ValuesMap params; params["test"] = std::move(uc); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World! @@ -98,7 +98,7 @@ TEST(UserCallableTest, SimpleUserCallableWithParams2) ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} ); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( Hello World! @@ -139,7 +139,7 @@ TEST(UserCallableTest, ReflectedCallable) params["reflected"] = jinja2::Reflect(reflected); params["innerReflected"] = jinja2::Reflect(innerReflected); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( 100500 @@ -188,7 +188,7 @@ TEST_P(UserCallableParamConvertTest, Test) return val; }, ArgInfo{"**kwargs"}); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); @@ -237,7 +237,7 @@ TEST_P(UserCallableFilterTest, Test) return testValue == pattern; }, ArgInfo{"testVal"}, ArgInfo{"pattern"}); - std::string result = tpl.RenderAsString(params); + std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); From 4e49f33a41306c05f2751930fed2aeaa467b94f9 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 14 May 2019 19:49:08 +0000 Subject: [PATCH 02/12] Small refactor of error reporting --- src/template_parser.cpp | 107 +++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 22564151..969a840b 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -75,6 +75,38 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme return result; } +struct ErrorTokenConverter +{ + const Token& baseTok; + + ErrorTokenConverter(const Token& t) + : baseTok(t) + {} + + Token operator()(const Token& tok) const + { + return tok; + } + + template + Token operator()(T tokType) const + { + auto newTok = baseTok; + newTok.type = static_cast(tokType); + if (newTok.type == Token::Identifier || newTok.type == Token::String) + newTok.range.endOffset = newTok.range.startOffset; + return newTok; + } +}; + +template +auto MakeParseErrorTL(ErrorCode code, const Token& baseTok, Args ... expectedTokens) +{ + ErrorTokenConverter tokCvt(baseTok); + + return MakeParseError(code, baseTok, {tokCvt(expectedTokens)...}); +} + StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, StatementInfoList &statementsInfo, const Token &stmtTok) { @@ -101,11 +133,7 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat tok2.type = Token::Identifier; tok2.range.endOffset = tok2.range.startOffset; tok2.value = InternalValue(); - Token tok3 = tok2; - tok3.type = Token::In; - Token tok4 = tok2; - tok4.type = static_cast(','); - return MakeParseError(ErrorCode::ExpectedToken, tok1, {tok2, tok3, tok4}); + return MakeParseErrorTL(ErrorCode::ExpectedToken, tok1, tok2, Token::In, ','); } auto pivotToken = lexer.PeekNextToken(); @@ -133,13 +161,7 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat else if (lexer.PeekNextToken() != Token::Eof) { auto tok1 = lexer.PeekNextToken(); - auto tok2 = tok1; - tok2.type = Token::If; - auto tok3 = tok1; - tok3.type = Token::Recursive; - auto tok4 = tok1; - tok4.type = Token::Eof; - return MakeParseError(ErrorCode::ExpectedToken, tok1, {tok2, tok3, tok4}); + return MakeParseErrorTL(ErrorCode::ExpectedToken, tok1, Token::If, Token::Recursive, Token::Eof); } auto renderer = std::make_shared(vars, *valueExpr, ifExpr, isRecursive); @@ -333,11 +355,7 @@ StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, St { nextTok = lexer.PeekNextToken(); if (nextTok != Token::Eof) - { - auto tok2 = nextTok; - tok2.type = Token::Scoped; - return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); - } + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Scoped); } blockRenderer = std::make_shared(blockName, isScoped); @@ -349,8 +367,7 @@ StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, St return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, StatementInfoList& statementsInfo - , const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); @@ -364,7 +381,7 @@ StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, tok3.type = Token::Eof; return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2, tok3}); } - + if (nextTok == Token::Identifier) lexer.EatToken(); @@ -401,9 +418,7 @@ StatementsParser::ParseResult StatementsParser::ParseExtends(LexScanner& lexer, tok2.type = Token::Identifier; tok2.range.endOffset = tok2.range.startOffset; tok2.value = EmptyValue{}; - auto tok3 = tok2; - tok3.type = Token::String; - return MakeParseError(ErrorCode::ExpectedToken, tok, {tok2, tok3}); + return MakeParseErrorTL(ErrorCode::ExpectedToken, tok, tok2, Token::String); } auto renderer = std::make_shared(AsString(tok.value), tok == Token::String); @@ -439,12 +454,8 @@ StatementsParser::ParseResult StatementsParser::ParseMacro(LexScanner& lexer, St else if (lexer.PeekNextToken() != Token::Eof) { Token tok = lexer.PeekNextToken(); - Token tok1; - tok1.type = Token::RBracket; - Token tok2; - tok2.type = Token::Eof; - return MakeParseError(ErrorCode::UnexpectedToken, tok, {tok1, tok2}); + return MakeParseErrorTL(ErrorCode::UnexpectedToken, tok, Token::RBracket, Token::Eof); } auto renderer = std::make_shared(std::move(macroName), std::move(macroParams)); @@ -605,11 +616,8 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, if (lexer.EatIfEqual(Keyword::Missing, &nextTok)) isIgnoreMissing = true; else - { - auto tok2 = nextTok; - tok2.type = Token::Missing; - return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); - } + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Missing); + hasIgnoreMissing = true; } @@ -621,43 +629,20 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, isWithContext = kw == Keyword::With; nextTok = lexer.PeekNextToken(); if (!lexer.EatIfEqual(Keyword::Missing, &nextTok)) - { - auto tok2 = nextTok; - tok2.type = Token::Context; - return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); - } + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Context); + hasContextControl = true; } if (nextTok != Token::Eof) { if (hasContextControl) - { - auto tok2 = nextTok; - tok2.type = Token::Eof; - return MakeParseError(ErrorCode::ExpectedEndOfStatement, nextTok); - } + return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof); if (hasIgnoreMissing) - { - auto tok2 = nextTok; - tok2.type = Token::Eof; - auto tok3 = nextTok; - tok3.type = Token::With; - auto tok4 = nextTok; - tok4.type = Token::Without; - return MakeParseError(ErrorCode::UnexpectedToken, nextTok, {tok2, tok3, tok4}); - } + return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::With, Token::Without); - auto tok2 = nextTok; - tok2.type = Token::Eof; - auto tok3 = nextTok; - tok3.type = Token::Ignore; - auto tok4 = nextTok; - tok4.type = Token::With; - auto tok5 = nextTok; - tok5.type = Token::Without; - return MakeParseError(ErrorCode::UnexpectedToken, nextTok, {tok2, tok3, tok4, tok5}); + return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Ignore, Token::With, Token::Without); } From 57666c41ec683dc4bf3a9409814346fec77e6fb8 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 15 May 2019 10:24:18 +0000 Subject: [PATCH 03/12] Fix parser, add tests for 'include' statement --- src/template_parser.cpp | 17 ++++----- test/extends_test.cpp | 14 +------- test/includes_test.cpp.cpp | 74 ++++++++++++++++++++++++++++++++++++++ test/test_tools.h | 43 ++++++++++++++++++++++ 4 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 test/includes_test.cpp.cpp diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 969a840b..3d347873 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -607,30 +607,31 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, return expr.get_unexpected(); valueExpr = *expr; - Token nextTok; + Token nextTok = lexer.PeekNextToken(); bool isIgnoreMissing = false; bool isWithContext = true; bool hasIgnoreMissing = false; - if (lexer.EatIfEqual(Keyword::Ignore, &nextTok)) + if (lexer.EatIfEqual(Keyword::Ignore)) { - if (lexer.EatIfEqual(Keyword::Missing, &nextTok)) + if (lexer.EatIfEqual(Keyword::Missing)) isIgnoreMissing = true; else - return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Missing); + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Missing); hasIgnoreMissing = true; + nextTok = lexer.PeekNextToken(); } - nextTok = lexer.NextToken(); auto kw = lexer.GetAsKeyword(nextTok); bool hasContextControl = false; if (kw == Keyword::With || kw == Keyword::Without) { + lexer.EatToken(); isWithContext = kw == Keyword::With; - nextTok = lexer.PeekNextToken(); - if (!lexer.EatIfEqual(Keyword::Missing, &nextTok)) - return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Context); + if (!lexer.EatIfEqual(Keyword::Context)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context); + nextTok = lexer.PeekNextToken(); hasContextControl = true; } diff --git a/test/extends_test.cpp b/test/extends_test.cpp index 912611d6..1ec421c9 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -6,19 +6,7 @@ #include "jinja2cpp/filesystem_handler.h" #include "jinja2cpp/template_env.h" -class ExtendsTest : public testing::Test -{ -public: - void SetUp() override - { - m_templateFs = std::make_shared(); - m_env.AddFilesystemHandler(std::string(), m_templateFs); - } - -protected: - std::shared_ptr m_templateFs; - jinja2::TemplateEnv m_env; -}; +using ExtendsTest = TemplateEnvFixture; TEST_F(ExtendsTest, BasicExtends) { diff --git a/test/includes_test.cpp.cpp b/test/includes_test.cpp.cpp new file mode 100644 index 00000000..2a0484d4 --- /dev/null +++ b/test/includes_test.cpp.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include "test_tools.h" + +// Test cases are taken from the pandor/Jinja2 tests + +class IncludeTest : public TemplateEnvFixture +{ +protected: + void SetUp() override + { + TemplateEnvFixture::SetUp(); + + AddFile("header", "[{{ foo }}|{{ 23 }}]"); + AddFile("o_printer", "({{ o }})"); + } +}; + +TEST_F(IncludeTest, TestContextInclude) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + auto result = Render(R"({% include "header" %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" with context %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" without context %})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% include "header" ignore missing with context %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" ignore missing without context %})", params); + EXPECT_EQ("[|23]", result); +} + +TEST_F(IncludeTest, TestChoiceIncludes) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + auto result = Render(R"({% include ["missing", "header"] %})", params); + EXPECT_EQ("[42|23]", result); + + result = Render(R"({% include ["missing", "missing2"] ignore missing %})", params); + EXPECT_EQ("", result); + +#if 0 + t = test_env.from_string('{% include ["missing", "missing2"] %}') + pytest.raises(TemplateNotFound, t.render) + try: + t.render() + except TemplatesNotFound as e: + assert e.templates == ['missing', 'missing2'] + assert e.name == 'missing2' + else: + assert False, 'thou shalt raise' + + def test_includes(t, **ctx): + ctx['foo'] = 42 + assert t.render(ctx) == '[42|23]' + + t = test_env.from_string('{% include ["missing", "header"] %}') + test_includes(t) + t = test_env.from_string('{% include x %}') + test_includes(t, x=['missing', 'header']) + t = test_env.from_string('{% include [x, "header"] %}') + test_includes(t, x='missing') + t = test_env.from_string('{% include x %}') + test_includes(t, x='header') + t = test_env.from_string('{% include x %}') + test_includes(t, x='header') + t = test_env.from_string('{% include [x] %}') + test_includes(t, x='header') +#endif +} diff --git a/test/test_tools.h b/test/test_tools.h index 3ebfa8bd..aaaf9ecd 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include struct InputOutputPair { @@ -130,6 +132,47 @@ class SubstitutionTestBase : public ::testing::TestWithParam } }; +class TemplateEnvFixture : public ::testing::Test +{ +protected: + void SetUp() override + { + m_templateFs = std::make_shared(); + m_env.AddFilesystemHandler(std::string(), m_templateFs); + } + + void AddFile(std::string fileName, std::string content) + { + m_templateFs->AddFile(std::move(fileName), std::move(content)); + } + + std::string Render(std::string tplBody, const jinja2::ValuesMap& params = {}) + { + jinja2::Template tpl(&m_env); + auto loadResult = tpl.Load(std::move(tplBody)); + EXPECT_TRUE(!!loadResult); + if (!loadResult) + { + std::cout << "Template loading error: " << loadResult.error() << std::endl; + return ""; + } + + auto renderResult = tpl.RenderAsString(params); + EXPECT_TRUE(!!renderResult); + if (!renderResult) + { + std::cout << "Template rendering error: " << renderResult.error() << std::endl; + return ""; + } + + return renderResult.value(); + } + +protected: + std::shared_ptr m_templateFs; + jinja2::TemplateEnv m_env; +}; + struct SubstitutionGenericTestTag; using SubstitutionGenericTest = InputOutputPairTest; From d26df2c85909b0667b7f01bf13c644edb92c03ed Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 16 May 2019 02:10:59 +0300 Subject: [PATCH 04/12] Fully implement 'include' statement --- include/jinja2cpp/error_info.h | 2 + src/render_context.h | 11 ++- src/statements.cpp | 104 +++++++++++++++++++++++++--- src/string_converter_filter.cpp | 10 +-- src/template_env.cpp | 12 +++- src/template_impl.h | 57 ++++++++++++--- src/template_parser.h | 2 +- src/value_visitors.h | 16 +++-- test/includes_test.cpp | 119 ++++++++++++++++++++++++++++++++ test/includes_test.cpp.cpp | 74 -------------------- 10 files changed, 302 insertions(+), 105 deletions(-) create mode 100644 test/includes_test.cpp delete mode 100644 test/includes_test.cpp.cpp diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index bc2765b6..64d82576 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -31,7 +31,9 @@ enum class ErrorCode UnexpectedExprEnd, UnexpectedStmtBegin, UnexpectedStmtEnd, + TemplateNotFound, TemplateNotParsed, + InvalidValueType, }; struct SourceLocation diff --git a/src/render_context.h b/src/render_context.h index c87e09c1..62241851 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -18,8 +18,12 @@ struct IRendererCallback virtual TargetString GetAsTargetString(const InternalValue& val) = 0; virtual OutStream GetStreamOnString(TargetString& str) = 0; virtual nonstd::variant>, ErrorInfo>, - nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0; + nonstd::expected>, ErrorInfo>, + nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0; + virtual nonstd::variant>, ErrorInfo>, + nonstd::expected>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const = 0; + virtual void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) = 0; }; class RenderContext @@ -92,13 +96,14 @@ class RenderContext RenderContext Clone(bool includeCurrentContext) const { if (!includeCurrentContext) - return RenderContext(*m_externalScope, m_rendererCallback); + return RenderContext(m_emptyScope, m_rendererCallback); return RenderContext(*this); } private: InternalValueMap* m_currentScope; const InternalValueMap* m_externalScope; + InternalValueMap m_emptyScope; std::list m_scopes; IRendererCallback* m_rendererCallback; diff --git a/src/statements.cpp b/src/statements.cpp index 8c7ef663..780f73d3 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -272,28 +272,42 @@ class ParentTemplateRenderer : public BlocksRenderer ExtendsStatement::BlocksCollection* m_blocks; }; +template struct TemplateImplVisitor { - ExtendsStatement::BlocksCollection* m_blocks; + // ExtendsStatement::BlocksCollection* m_blocks; + const Fn& m_fn; - TemplateImplVisitor(ExtendsStatement::BlocksCollection* blocks) - : m_blocks(blocks) + explicit TemplateImplVisitor(const Fn& fn) + : m_fn(fn) {} template - RendererPtr operator()(nonstd::expected>, ErrorInfoTpl> tpl) const + Result operator()(nonstd::expected>, ErrorInfoTpl> tpl) const { if (!tpl) - return RendererPtr(); - return std::make_shared>(tpl.value(), m_blocks); + return Result{}; + return m_fn(tpl.value()); } - RendererPtr operator()(EmptyValue) const + Result operator()(EmptyValue) const { - return RendererPtr(); + return Result(); } }; +template +Result VisitTemplateImpl(Arg&& tpl, Fn&& fn) +{ + return visit(TemplateImplVisitor(fn), tpl); +} + +template class RendererTpl, typename CharT, typename ... Args> +auto CreateTemplateRenderer(std::shared_ptr> tpl, Args&& ... args) +{ + return std::make_shared>(tpl, std::forward(args)...); +} + void ExtendsStatement::Render(OutStream& os, RenderContext& values) { if (!m_isPath) @@ -302,14 +316,86 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) return; } auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName); - auto renderer = visit(TemplateImplVisitor(&m_blocks), tpl); + auto renderer = VisitTemplateImpl(tpl, [this](auto tplPtr) { + return CreateTemplateRenderer(tplPtr, &m_blocks); + }); if (renderer) renderer->Render(os, values); } +template +class IncludedTemplateRenderer : public RendererBase +{ +public: + IncludedTemplateRenderer(std::shared_ptr> tpl, bool withContext) + : m_template(tpl) + , m_withContext(withContext) + { + } + + void Render(OutStream& os, RenderContext& values) override + { + RenderContext innerContext = values.Clone(m_withContext); + m_template->GetRenderer()->Render(os, innerContext); + } + +private: + std::shared_ptr> m_template; + bool m_withContext; +}; + void IncludeStatement::Render(OutStream& os, RenderContext& values) { + auto templateNames = m_expr->Evaluate(values); + bool isConverted = false; + ListAdapter list = ConvertToList(templateNames, isConverted); + + auto doRender = [this, &values, &os](auto&& name) -> bool + { + auto tpl = values.GetRendererCallback()->LoadTemplate(name); + auto renderer = VisitTemplateImpl(tpl, [this](auto tplPtr) { + return CreateTemplateRenderer(tplPtr, m_withContext); + }); + if (renderer) + { + renderer->Render(os, values); + return true; + } + + return false; + }; + + bool rendered = false; + if (isConverted) + { + for (auto& name : list) + { + rendered = doRender(name); + if (rendered) + break; + } + } + else + { + rendered = doRender(templateNames); + } + if (!rendered && !m_ignoreMissing) + { + InternalValueList files; + ValuesList extraParams; + if (isConverted) + { + extraParams.push_back(IntValue2Value(templateNames)); + } + else + { + files.push_back(templateNames); + extraParams.push_back(IntValue2Value(ListAdapter::CreateAdapter(std::move(files)))); + } + + values.GetRendererCallback()->ThrowRuntimeError(ErrorCode::TemplateNotFound, std::move(extraParams)); + } } void MacroStatement::PrepareMacroParams(RenderContext& values) diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index bedaded8..da3852f1 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -227,8 +227,9 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex break; case ReplaceMode: result = ApplyStringConverter(baseVal, [this, &context](auto str) -> InternalValue { - auto oldStr = GetAsSameString(str, this->GetArgumentValue("old", context)); - auto newStr = GetAsSameString(str, this->GetArgumentValue("new", context)); + std::decay_t emptyStr; + auto oldStr = GetAsSameString(str, this->GetArgumentValue("old", context)).value_or(emptyStr); + auto newStr = GetAsSameString(str, this->GetArgumentValue("new", context)).value_or(emptyStr); auto count = ConvertToInt(this->GetArgumentValue("count", context)); if (count == 0) ba::replace_all(str, oldStr, newStr); @@ -242,6 +243,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex break; case TruncateMode: result = ApplyStringConverter(baseVal, [this, &context, &isAlNum](auto str) -> InternalValue { + std::decay_t emptyStr; auto length = ConvertToInt(this->GetArgumentValue("length", context)); auto killWords = ConvertToBool(this->GetArgumentValue("killwords", context)); auto end = GetAsSameString(str, this->GetArgumentValue("end", context)); @@ -254,7 +256,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex if (static_cast(str.size()) > (length + leeway)) { str.erase(str.begin() + length, str.end()); - str += end; + str += end.value_or(emptyStr); } return InternalValue(str); } @@ -273,7 +275,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex } str.erase(p, str.end()); ba::trim_right(str); - str += end; + str += end.value_or(emptyStr); return InternalValue(str); }); diff --git a/src/template_env.cpp b/src/template_env.cpp index fca86c04..134cd3aa 100644 --- a/src/template_env.cpp +++ b/src/template_env.cpp @@ -40,6 +40,7 @@ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesyste { using Functions = TemplateFunctions; using ResultType = typename Functions::ResultType; + using ErrorType = typename ResultType::error_type; auto tpl = Functions::CreateTemplate(env); for (auto& fh : filesystemHandlers) @@ -54,10 +55,19 @@ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesyste auto res = tpl.Load(*stream); if (!res) return ResultType(res.get_unexpected()); + + return ResultType(tpl); } } - return ResultType(tpl); + typename ErrorType::Data errorData; + errorData.code = ErrorCode::FileNotFound; + errorData.srcLoc.col = 1; + errorData.srcLoc.line = 1; + errorData.srcLoc.fileName = ""; + errorData.extraParams.push_back(Value(fileName)); + + return ResultType(nonstd::make_unexpected(ErrorType(errorData))); } nonstd::expected TemplateEnv::LoadTemplate(std::string fileName) diff --git a/src/template_impl.h b/src/template_impl.h index eb2e1a85..e61568bb 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -179,28 +179,57 @@ class TemplateImpl : public ITemplateImpl return curScope; } - auto LoadTemplate(const std::string& fileName) - { - using ResultType = nonstd::variant>, ErrorInfo>, nonstd::expected>, ErrorInfoW>>; - using TplOrError = nonstd::expected>, ErrorInfoTpl>; + using TplOrError = nonstd::expected>, ErrorInfoTpl>; + TplLoadResultType LoadTemplate(const std::string& fileName) + { if (!m_env) - return ResultType(EmptyValue()); + return TplLoadResultType(EmptyValue()); auto tplWrapper = TemplateLoader::Load(fileName, m_env); if (!tplWrapper) - return ResultType(TplOrError(tplWrapper.get_unexpected())); + return TplLoadResultType(TplOrError(tplWrapper.get_unexpected())); + + return TplLoadResultType(TplOrError(std::static_pointer_cast(tplWrapper.value().m_impl))); + } + + TplLoadResultType LoadTemplate(const InternalValue& fileName) + { + auto name = GetAsSameString(std::string(), fileName); + if (!name) + { + typename ErrorInfoTpl::Data errorData; + errorData.code = ErrorCode::UnexpectedException; + errorData.srcLoc.col = 1; + errorData.srcLoc.line = 1; + errorData.srcLoc.fileName = m_templateName; + errorData.extraParams.push_back(IntValue2Value(fileName)); + return TplOrError(nonstd::make_unexpected(ErrorInfoTpl(errorData))); + } + + return LoadTemplate(name.value()); + } - return ResultType(TplOrError(std::static_pointer_cast(tplWrapper.value().m_impl))); + void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) + { + typename ErrorInfoTpl::Data errorData; + errorData.code = code; + errorData.srcLoc.col = 1; + errorData.srcLoc.line = 1; + errorData.srcLoc.fileName = m_templateName; + errorData.extraParams = std::move(extraParams); + + throw ErrorInfoTpl(std::move(errorData)); } class RendererCallback : public IRendererCallback { public: - RendererCallback(ThisType* host) + explicit RendererCallback(ThisType* host) : m_host(host) {} @@ -225,6 +254,18 @@ class TemplateImpl : public ITemplateImpl return m_host->LoadTemplate(fileName); } + nonstd::variant>, ErrorInfo>, + nonstd::expected>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const override + { + return m_host->LoadTemplate(fileName); + } + + void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) override + { + m_host->ThrowRuntimeError(code, std::move(extraParams)); + } + private: ThisType* m_host; }; diff --git a/src/template_parser.h b/src/template_parser.h index ee5b340a..e05633a4 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -592,7 +592,7 @@ class TemplateParser : public LexerHelper if (!tok.value.IsEmpty()) { std::basic_string tpl; - return GetAsSameString(tpl, tok.value); + return GetAsSameString(tpl, tok.value).value_or(std::basic_string()); } return UNIVERSAL_STR("<>").template GetValue(); diff --git a/src/value_visitors.h b/src/value_visitors.h index 154a0d92..da1f4095 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -817,14 +817,15 @@ struct StringConverterImpl : public BaseVisitor()(std: }; template -struct SameStringGetter : public visitors::BaseVisitor> +struct SameStringGetter : public visitors::BaseVisitor>> { using ResultString = std::basic_string; - using BaseVisitor::operator (); + using Result = nonstd::expected; + using BaseVisitor::operator (); - ResultString operator()(const ResultString& str) const + Result operator()(const ResultString& str) const { - return str; + return nonstd::make_unexpected(str); } }; @@ -854,7 +855,12 @@ auto ApplyStringConverter(const InternalValue& str, Fn&& fn) template auto GetAsSameString(const std::basic_string&, const InternalValue& val) { - return Apply>(val); + using Result = nonstd::optional>; + auto result = Apply>(val); + if (!result) + return Result(result.error()); + + return Result(); } } // jinja2 diff --git a/test/includes_test.cpp b/test/includes_test.cpp new file mode 100644 index 00000000..8903f67c --- /dev/null +++ b/test/includes_test.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "test_tools.h" + +// Test cases are taken from the pandor/Jinja2 tests + +class IncludeTest : public TemplateEnvFixture +{ +protected: + void SetUp() override + { + TemplateEnvFixture::SetUp(); + + AddFile("header", "[{{ foo }}|{{ 23 }}]"); + AddFile("o_printer", "({{ o }})"); + } +}; + +TEST_F(IncludeTest, TestContextInclude) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + auto result = Render(R"({% include "header" %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" with context %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" without context %})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% include "header" ignore missing with context %})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% include "header" ignore missing without context %})", params); + EXPECT_EQ("[|23]", result); +} + +TEST_F(IncludeTest, TestChoiceIncludes) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + auto result = Render(R"({% include ["missing", "header"] %})", params); + EXPECT_EQ("[42|23]", result); + + result = Render(R"({% include ["missing", "missing2"] ignore missing %})", params); + EXPECT_EQ("", result); + + auto testInclude = [&, this](std::string tpl, jinja2::ValuesMap params) + { + params["foo"] = 42; + return Render(std::move(tpl), params); + }; + + EXPECT_EQ("[42|23]", testInclude(R"({% include ["missing", "header"] %})", {})); + EXPECT_EQ("[42|23]", testInclude(R"({% include x %})", {{"x", jinja2::ValuesList{jinja2::Value("missing"), jinja2::Value("header")}}})); + EXPECT_EQ("[42|23]", testInclude(R"({% include [x, "header"] %})", {{"x", jinja2::Value("header")}})); + EXPECT_EQ("[42|23]", testInclude(R"({% include x %})", {{"x", jinja2::Value("header")}})); + EXPECT_EQ("[42|23]", testInclude(R"({% include [x] %})", {{"x", jinja2::Value("header")}})); + EXPECT_EQ("[42|23]", testInclude(R"({% include "head" ~ x %})", {{"x", jinja2::Value("er")}})); +} + +TEST_F(IncludeTest, TestMissingIncludesError1) +{ + jinja2::ValuesMap params{}; + + jinja2::Template tpl(&m_env); + auto loadResult = tpl.Load(R"({% include "missing" %})"); + EXPECT_FALSE(!loadResult); + + auto renderResult = tpl.RenderAsString(params); + EXPECT_TRUE(!renderResult); + auto error = renderResult.error(); + EXPECT_EQ(jinja2::ErrorCode::TemplateNotFound, error.GetCode()); + auto& extraParams = error.GetExtraParams(); + ASSERT_EQ(1ull, extraParams.size()); + auto filesList = nonstd::get_if(&extraParams[0].data()); + EXPECT_NE(nullptr, filesList); + EXPECT_EQ(1ull, filesList->GetSize()); + EXPECT_EQ("missing", filesList->GetValueByIndex(0).asString()); +} + +TEST_F(IncludeTest, TestMissingIncludesError2) +{ + jinja2::ValuesMap params{}; + + jinja2::Template tpl(&m_env); + auto loadResult = tpl.Load(R"({% include ["missing", "missing2"] %})"); + EXPECT_FALSE(!loadResult); + + auto renderResult = tpl.RenderAsString(params); + EXPECT_TRUE(!renderResult); + auto error = renderResult.error(); + EXPECT_EQ(jinja2::ErrorCode::TemplateNotFound, error.GetCode()); + auto& extraParams = error.GetExtraParams(); + ASSERT_EQ(1ull, extraParams.size()); + auto filesList = nonstd::get_if(&extraParams[0].data()); + EXPECT_NE(nullptr, filesList); + EXPECT_EQ(2ull, filesList->GetSize()); + EXPECT_EQ("missing", filesList->GetValueByIndex(0).asString()); + EXPECT_EQ("missing2", filesList->GetValueByIndex(1).asString()); +} + +TEST_F(IncludeTest, TestContextIncludeWithOverrides) +{ + AddFile("item", "{{ item }}"); + EXPECT_EQ("123", Render(R"({% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %})")); +} + +TEST_F(IncludeTest, TestUnoptimizedScopes) +{ + auto result = Render( +R"({% macro outer(o) %} +{% macro inner() %} +{% include "o_printer" %} +{% endmacro %} +{{ inner() }} +{%- endmacro %} +{{ outer("FOO") }})"); + + EXPECT_EQ("(FOO)", result); +} diff --git a/test/includes_test.cpp.cpp b/test/includes_test.cpp.cpp deleted file mode 100644 index 2a0484d4..00000000 --- a/test/includes_test.cpp.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -#include "test_tools.h" - -// Test cases are taken from the pandor/Jinja2 tests - -class IncludeTest : public TemplateEnvFixture -{ -protected: - void SetUp() override - { - TemplateEnvFixture::SetUp(); - - AddFile("header", "[{{ foo }}|{{ 23 }}]"); - AddFile("o_printer", "({{ o }})"); - } -}; - -TEST_F(IncludeTest, TestContextInclude) -{ - jinja2::ValuesMap params{{"foo", 42}}; - - auto result = Render(R"({% include "header" %})", params); - EXPECT_EQ("[42|23]", result); - result = Render(R"({% include "header" with context %})", params); - EXPECT_EQ("[42|23]", result); - result = Render(R"({% include "header" without context %})", params); - EXPECT_EQ("[|23]", result); - result = Render(R"({% include "header" ignore missing with context %})", params); - EXPECT_EQ("[42|23]", result); - result = Render(R"({% include "header" ignore missing without context %})", params); - EXPECT_EQ("[|23]", result); -} - -TEST_F(IncludeTest, TestChoiceIncludes) -{ - jinja2::ValuesMap params{{"foo", 42}}; - - auto result = Render(R"({% include ["missing", "header"] %})", params); - EXPECT_EQ("[42|23]", result); - - result = Render(R"({% include ["missing", "missing2"] ignore missing %})", params); - EXPECT_EQ("", result); - -#if 0 - t = test_env.from_string('{% include ["missing", "missing2"] %}') - pytest.raises(TemplateNotFound, t.render) - try: - t.render() - except TemplatesNotFound as e: - assert e.templates == ['missing', 'missing2'] - assert e.name == 'missing2' - else: - assert False, 'thou shalt raise' - - def test_includes(t, **ctx): - ctx['foo'] = 42 - assert t.render(ctx) == '[42|23]' - - t = test_env.from_string('{% include ["missing", "header"] %}') - test_includes(t) - t = test_env.from_string('{% include x %}') - test_includes(t, x=['missing', 'header']) - t = test_env.from_string('{% include [x, "header"] %}') - test_includes(t, x='missing') - t = test_env.from_string('{% include x %}') - test_includes(t, x='header') - t = test_env.from_string('{% include x %}') - test_includes(t, x='header') - t = test_env.from_string('{% include [x] %}') - test_includes(t, x='header') -#endif -} From 8d44e5447b725eb3723d745960646fec685e1b23 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 16 May 2019 02:25:49 +0300 Subject: [PATCH 05/12] Fix gcc/clang build --- src/error_info.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/error_info.cpp b/src/error_info.cpp index 78f8faa5..31f19198 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -194,6 +194,12 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e case ErrorCode::TemplateNotParsed: os << UNIVERSAL_STR("Template not parsed"); break; + case ErrorCode::TemplateNotFound: + os << UNIVERSAL_STR("Template(s) not found: ") << errInfo.GetExtraParams()[0]; + break; + case ErrorCode::InvalidValueType: + os << UNIVERSAL_STR("Invalid value type"); + break; } os << std::endl << errInfo.GetLocationDescr(); } From 094f95071d2d41161c562614d99f4e7fa25aa74e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 16 May 2019 13:19:40 +0000 Subject: [PATCH 06/12] Add statement AST visitor --- CMakeLists.txt | 2 +- src/ast_visitor.h | 114 ++++++++++++++++++++++++++++++++++++++++++++++ src/renderer.h | 17 +++++-- src/statements.h | 24 +++++++++- 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/ast_visitor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e029e278..fc4a6684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0.1) -project(Jinja2Cpp VERSION 0.9.1) +project(Jinja2Cpp VERSION 0.9.2) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) diff --git a/src/ast_visitor.h b/src/ast_visitor.h new file mode 100644 index 00000000..33201770 --- /dev/null +++ b/src/ast_visitor.h @@ -0,0 +1,114 @@ +#ifndef AST_VISITOR_H +#define AST_VISITOR_H + +namespace jinja2 +{ +class RendererBase; +class ExpressionEvaluatorBase; +class Statement; +class ForStatement; +class IfStatement; +class ElseBranchStatement; +class SetStatement; +class ParentBlockStatement; +class BlockStatement; +class ExtendsStatement; +class IncludeStatement; +class ImportStatement; +class MacroStatement; +class MacroCallStatement; +class ComposedRenderer; +class RawTextRenderer; +class ExpressionRenderer; + +class StatementVisitor; + +class VisitableStatement +{ +public: + virtual void ApplyVisitor(StatementVisitor* visitor) = 0; + virtual void ApplyVisitor(StatementVisitor* visitor) const = 0; +}; + +#define VISITABLE_STATEMENT() \ + void ApplyVisitor(StatementVisitor* visitor) override {visitor->DoVisit(this);} \ + void ApplyVisitor(StatementVisitor* visitor) const override {visitor->DoVisit(this);} \ + +namespace detail +{ +template +class VisitorIfaceImpl : public Base +{ +public: + using Base::DoVisit; + + virtual void DoVisit(Type*) {} + virtual void DoVisit(const Type*) {} +}; + +template +class VisitorIfaceImpl +{ +public: + virtual void DoVisit(Type*) {} + virtual void DoVisit(const Type*) {} +}; + +template +struct VisitorBaseImpl; + +template +struct VisitorBaseImpl +{ + using current_base = VisitorIfaceImpl; + using base_type = typename VisitorBaseImpl::base_type; +}; + +template +struct VisitorBaseImpl +{ + using base_type = VisitorIfaceImpl; +}; + + +template +struct VisitorBase +{ + using type = typename VisitorBaseImpl::base_type; +}; +} + +template +using VisitorBase = typename detail::VisitorBase::type; + +class StatementVisitor : public VisitorBase< + RendererBase, + Statement, + ForStatement, + IfStatement, + ElseBranchStatement, + SetStatement, + ParentBlockStatement, + BlockStatement, + ExtendsStatement, + IncludeStatement, + ImportStatement, + MacroStatement, + MacroCallStatement, + ComposedRenderer, + RawTextRenderer, + ExpressionRenderer> +{ +public: + void Visit(VisitableStatement* stmt) + { + stmt->ApplyVisitor(this); + } + void Visit(const VisitableStatement* stmt) + { + stmt->ApplyVisitor(this); + } +}; +} + +#endif \ No newline at end of file diff --git a/src/renderer.h b/src/renderer.h index e967de83..231ecfa6 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -6,6 +6,7 @@ #include "lexertk.h" #include "expression_evaluator.h" #include "render_context.h" +#include "ast_visitor.h" #include #include @@ -21,11 +22,17 @@ class RendererBase virtual void Render(OutStream& os, RenderContext& values) = 0; }; +class VisitableRendererBase : public RendererBase, public VisitableStatement +{ +}; + using RendererPtr = std::shared_ptr; -class ComposedRenderer : public RendererBase +class ComposedRenderer : public VisitableRendererBase { public: + VISITABLE_STATEMENT(); + void AddRenderer(RendererPtr r) { m_renderers.push_back(std::move(r)); @@ -40,9 +47,11 @@ class ComposedRenderer : public RendererBase std::vector m_renderers; }; -class RawTextRenderer : public RendererBase +class RawTextRenderer : public VisitableRendererBase { public: + VISITABLE_STATEMENT(); + RawTextRenderer(const void* ptr, size_t len) : m_ptr(ptr) , m_length(len) @@ -58,9 +67,11 @@ class RawTextRenderer : public RendererBase size_t m_length; }; -class ExpressionRenderer : public RendererBase +class ExpressionRenderer : public VisitableRendererBase { public: + VISITABLE_STATEMENT(); + explicit ExpressionRenderer(ExpressionEvaluatorPtr<> expr) : m_expression(std::move(expr)) { diff --git a/src/statements.h b/src/statements.h index ef90746a..5acd3914 100644 --- a/src/statements.h +++ b/src/statements.h @@ -9,8 +9,9 @@ namespace jinja2 { -struct Statement : public RendererBase +struct Statement : public VisitableRendererBase { + VISITABLE_STATEMENT(); }; template @@ -30,6 +31,8 @@ using MacroParams = std::vector; class ForStatement : public Statement { public: + VISITABLE_STATEMENT(); + ForStatement(std::vector vars, ExpressionEvaluatorPtr<> expr, ExpressionEvaluatorPtr<> ifExpr, bool isRecursive) : m_vars(std::move(vars)) , m_value(expr) @@ -67,6 +70,8 @@ class ElseBranchStatement; class IfStatement : public Statement { public: + VISITABLE_STATEMENT(); + IfStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) { @@ -94,6 +99,8 @@ class IfStatement : public Statement class ElseBranchStatement : public Statement { public: + VISITABLE_STATEMENT(); + ElseBranchStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) { @@ -114,6 +121,8 @@ class ElseBranchStatement : public Statement class SetStatement : public Statement { public: + VISITABLE_STATEMENT(); + SetStatement(std::vector fields) : m_fields(std::move(fields)) { @@ -133,6 +142,8 @@ class SetStatement : public Statement class ParentBlockStatement : public Statement { public: + VISITABLE_STATEMENT(); + ParentBlockStatement(std::string name, bool isScoped) : m_name(std::move(name)) , m_isScoped(isScoped) @@ -154,6 +165,8 @@ class ParentBlockStatement : public Statement class BlockStatement : public Statement { public: + VISITABLE_STATEMENT(); + BlockStatement(std::string name) : m_name(std::move(name)) { @@ -175,6 +188,8 @@ class BlockStatement : public Statement class ExtendsStatement : public Statement { public: + VISITABLE_STATEMENT(); + using BlocksCollection = std::unordered_map>; ExtendsStatement(std::string name, bool isPath) @@ -198,6 +213,8 @@ class ExtendsStatement : public Statement class IncludeStatement : public Statement { public: + VISITABLE_STATEMENT(); + IncludeStatement(bool ignoreMissing, bool withContext) : m_ignoreMissing(ignoreMissing) , m_withContext(withContext) @@ -218,11 +235,14 @@ class IncludeStatement : public Statement class ImportStatement : public Statement { public: + VISITABLE_STATEMENT(); }; class MacroStatement : public Statement { public: + VISITABLE_STATEMENT(); + MacroStatement(std::string name, MacroParams params) : m_name(std::move(name)) , m_params(std::move(params)) @@ -251,6 +271,8 @@ class MacroStatement : public Statement class MacroCallStatement : public MacroStatement { public: + VISITABLE_STATEMENT(); + MacroCallStatement(std::string macroName, CallParams callParams, MacroParams callbackParams) : MacroStatement("$call$", std::move(callbackParams)) , m_macroName(std::move(macroName)) From 6fbd7d47ecc83021588665c09241891a482215a8 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 18 May 2019 12:43:54 +0300 Subject: [PATCH 07/12] Add parser and tests for 'import'/'from' statements --- src/ast_visitor.h | 1 + src/statements.cpp | 7 +- src/statements.h | 28 ++++++++ src/template_parser.cpp | 140 +++++++++++++++++++++++++++++++++++++++- src/template_parser.h | 2 + test/import_test.cpp | 37 +++++++++++ 6 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 test/import_test.cpp diff --git a/src/ast_visitor.h b/src/ast_visitor.h index 33201770..b23bdbad 100644 --- a/src/ast_visitor.h +++ b/src/ast_visitor.h @@ -111,4 +111,5 @@ class StatementVisitor : public VisitorBase< }; } + #endif \ No newline at end of file diff --git a/src/statements.cpp b/src/statements.cpp index 780f73d3..8a455cd9 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -398,6 +398,12 @@ void IncludeStatement::Render(OutStream& os, RenderContext& values) } } + +void ImportStatement::Render(OutStream& os, RenderContext& values) +{ + +} + void MacroStatement::PrepareMacroParams(RenderContext& values) { for (auto& p : m_params) @@ -506,5 +512,4 @@ void MacroCallStatement::SetupMacroScope(InternalValueMap&) } - } // jinja2 diff --git a/src/statements.h b/src/statements.h index 5acd3914..7c155ca8 100644 --- a/src/statements.h +++ b/src/statements.h @@ -236,6 +236,32 @@ class ImportStatement : public Statement { public: VISITABLE_STATEMENT(); + + explicit ImportStatement(bool withContext) + : m_withContext(withContext) + {} + + void SetImportNameExpr(ExpressionEvaluatorPtr<> expr) + { + m_nameExpr = expr; + } + + void SetNamespace(std::string name) + { + m_namespace = std::move(name); + } + + void AddNameToImport(std::string name, std::string alias) + { + m_namesToImport[std::move(name)] = std::move(alias); + } + + void Render(OutStream& os, RenderContext& values) override; +private: + bool m_withContext; + ExpressionEvaluatorPtr<> m_nameExpr; + nonstd::optional m_namespace; + std::unordered_map m_namesToImport; }; class MacroStatement : public Statement @@ -253,6 +279,7 @@ class MacroStatement : public Statement { m_mainBody = renderer; } + void Render(OutStream &os, RenderContext &values) override; protected: @@ -291,4 +318,5 @@ class MacroCallStatement : public MacroStatement }; } // jinja2 + #endif // STATEMENTS_H diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 3d347873..782c2759 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -56,10 +56,15 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme case Keyword::Include: result = ParseInclude(lexer, statementsInfo, tok); break; + case Keyword::Import: + result = ParseImport(lexer, statementsInfo, tok); + break; + case Keyword::From: + result = ParseFrom(lexer, statementsInfo, tok); + break; case Keyword::Filter: case Keyword::EndFilter: case Keyword::EndSet: - case Keyword::Import: return MakeParseError(ErrorCode::YetUnsupported, tok); default: return MakeParseError(ErrorCode::UnexpectedToken, tok); @@ -79,7 +84,7 @@ struct ErrorTokenConverter { const Token& baseTok; - ErrorTokenConverter(const Token& t) + explicit ErrorTokenConverter(const Token& t) : baseTok(t) {} @@ -654,4 +659,135 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, return ParseResult(); } +StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + ExpressionEvaluatorPtr<> valueExpr; + ExpressionParser exprParser; + auto expr = exprParser.ParseFullExpression(lexer); + if (!expr) + return expr.get_unexpected(); + valueExpr = *expr; + + if (!lexer.EatIfEqual(Keyword::As)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::As); + + Token name; + if (!lexer.EatIfEqual(Token::Identifier, &name)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Identifier); + + Token nextTok = lexer.PeekNextToken(); + auto kw = lexer.GetAsKeyword(nextTok); + bool hasContextControl = false; + bool isWithContext = false; + if (kw == Keyword::With || kw == Keyword::Without) + { + lexer.EatToken(); + isWithContext = kw == Keyword::With; + if (!lexer.EatIfEqual(Keyword::Context)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context); + + nextTok = lexer.PeekNextToken(); + hasContextControl = true; + } + + if (nextTok != Token::Eof) + { + if (hasContextControl) + return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof); + + return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::With, Token::Without); + } + + auto renderer = std::make_shared(isWithContext); + renderer->SetImportNameExpr(valueExpr); + renderer->SetNamespace(AsString(name.value)); + statementsInfo.back().currentComposition->AddRenderer(renderer); + + return ParseResult(); +} + +StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + ExpressionEvaluatorPtr<> valueExpr; + ExpressionParser exprParser; + auto expr = exprParser.ParseFullExpression(lexer); + if (!expr) + return expr.get_unexpected(); + valueExpr = *expr; + + if (!lexer.EatIfEqual(Keyword::Import)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Identifier); + + std::vector> mappedNames; + + Token nextTok; + + for (;;) + { + if (!mappedNames.empty()) + { + if (!lexer.EatIfEqual(Token::Comma)) + break; + } + + nextTok = lexer.PeekNextToken(); + if (lexer.GetAsKeyword(nextTok) != Keyword::Unknown) + break; + + std::pair macroMap; + if (!lexer.EatIfEqual(Token::Identifier, &nextTok)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Identifier); + + macroMap.first = AsString(nextTok.value); + + if (lexer.EatIfEqual(Keyword::As)) + { + if (!lexer.EatIfEqual(Token::Identifier, &nextTok)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Identifier); + macroMap.second = AsString(nextTok.value); + } + else + { + macroMap.second = macroMap.first; + } + mappedNames.push_back(std::move(macroMap)); + } + + nextTok = lexer.PeekNextToken(); + auto kw = lexer.GetAsKeyword(nextTok); + bool hasContextControl = false; + bool isWithContext = false; + if (kw == Keyword::With || kw == Keyword::Without) + { + lexer.EatToken(); + isWithContext = kw == Keyword::With; + if (!lexer.EatIfEqual(Keyword::Context)) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context); + + nextTok = lexer.PeekNextToken(); + hasContextControl = true; + } + + if (nextTok != Token::Eof) + { + if (hasContextControl) + return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof); + + if (mappedNames.empty()) + MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Identifier); + else + MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Comma, Token::With, Token::Without); + } + + auto renderer = std::make_shared(isWithContext); + renderer->SetImportNameExpr(valueExpr); + + for (auto& nameInfo : mappedNames) + renderer->AddNameToImport(std::move(nameInfo.first), std::move(nameInfo.second)); + + statementsInfo.back().currentComposition->AddRenderer(renderer); + + return ParseResult(); +} + } diff --git a/src/template_parser.h b/src/template_parser.h index e05633a4..e2974efa 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -214,6 +214,8 @@ class StatementsParser ParseResult ParseCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseEndCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseInclude(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); }; template diff --git a/test/import_test.cpp b/test/import_test.cpp new file mode 100644 index 00000000..41ee43ba --- /dev/null +++ b/test/import_test.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "test_tools.h" + +// Test cases are taken from the pandor/Jinja2 tests + +class ImportTest : public TemplateEnvFixture +{ +protected: + void SetUp() override + { + TemplateEnvFixture::SetUp(); + + AddFile("module", "{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}"); + AddFile("header", "[{{ foo }}|{{ 23 }}]"); + AddFile("o_printer", "({{ o }})"); + } +}; + +TEST_F(ImportTest, TestContextImports) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + auto result = Render(R"({% import "module" as m %}{{ m.test() }})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% import "module" as m without context %}{{ m.test() }})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% import "module" as m with context %}{{ m.test() }})", params); + EXPECT_EQ("[42|23]", result); + result = Render(R"({% from "module" import test %}{{ test() }})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% from "module" import test without context %}{{ test() }})", params); + EXPECT_EQ("[|23]", result); + result = Render(R"({% from "module" import test with context %}{{ test() }})", params); + EXPECT_EQ("[42|23]", result); +} \ No newline at end of file From d1d2b04117062212b07b20cec45e1a81f0b18286 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 22 May 2019 00:35:50 +0300 Subject: [PATCH 08/12] Implement 'import' statement --- src/internal_value.cpp | 2 +- src/internal_value.h | 2 +- src/lexertk.h | 2 +- src/render_context.h | 37 +++++++---- src/statements.cpp | 144 ++++++++++++++++++++++++++++++++++++++--- src/statements.h | 5 ++ test/import_test.cpp | 28 ++++++-- 7 files changed, 186 insertions(+), 34 deletions(-) diff --git a/src/internal_value.cpp b/src/internal_value.cpp index a64f94be..ede575f7 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -507,7 +507,7 @@ struct OutputValueConvertor } result_t operator()(const Callable&) const {return result_t();} result_t operator()(const UserCallable&) const {return result_t();} - result_t operator()(const RendererBase*) const {return result_t();} + result_t operator()(const std::shared_ptr&) const {return result_t();} template result_t operator()(const RecWrapper& val) const diff --git a/src/internal_value.h b/src/internal_value.h index 91f51655..fda704e8 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -95,7 +95,7 @@ struct KeyValuePair; class RendererBase; class InternalValue; -using InternalValueData = nonstd::variant, RecursiveWrapper, RendererBase*>; +using InternalValueData = nonstd::variant, RecursiveWrapper, std::shared_ptr>; using InternalValueRef = ReferenceWrapper; using InternalValueMap = std::unordered_map; diff --git a/src/lexertk.h b/src/lexertk.h index 432472ee..87091d63 100644 --- a/src/lexertk.h +++ b/src/lexertk.h @@ -634,7 +634,7 @@ namespace lexertk scan_operator(); return; } - else if (traits::is_letter(*s_itr_)) + else if (traits::is_letter(*s_itr_) || ('_' == (*s_itr_))) { scan_symbol(); return; diff --git a/src/render_context.h b/src/render_context.h index 62241851..1307fb55 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -55,25 +55,29 @@ class RenderContext auto FindValue(const std::string& val, bool& found) const { - for (auto p = m_scopes.rbegin(); p != m_scopes.rend(); ++ p) + auto finder = [&val, &found](auto& map) mutable { - auto& map = *p; - auto valP = map.find(val); - if (valP != map.end()) - { + auto p = map.find(val); + if (p != map.end()) found = true; + + return p; + }; + + if (m_boundScope) + { + auto valP = finder(*m_boundScope); + if (found) return valP; - } } - auto valP = m_externalScope->find(val); - if (valP != m_externalScope->end()) + + for (auto p = m_scopes.rbegin(); p != m_scopes.rend(); ++ p) { - found = true; - return valP; + auto valP = finder(*p); + if (found) + return valP; } - - found = false; - return m_externalScope->end(); + return finder(*m_externalScope); } auto& GetCurrentScope() const @@ -100,13 +104,18 @@ class RenderContext return RenderContext(*this); } + + void BindScope(InternalValueMap* scope) + { + m_boundScope = scope; + } private: InternalValueMap* m_currentScope; const InternalValueMap* m_externalScope; InternalValueMap m_emptyScope; std::list m_scopes; IRendererCallback* m_rendererCallback; - + const InternalValueMap* m_boundScope = nullptr; }; } // jinja2 diff --git a/src/statements.cpp b/src/statements.cpp index 8a455cd9..43ebde0f 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -3,6 +3,8 @@ #include "template_impl.h" #include "value_visitors.h" +#include + #include @@ -181,11 +183,11 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) BlocksRenderer* blockRenderer = nullptr; // static_cast(*parentTplPtr); for (auto& tplVal : parentTplsList) { - auto ptr = GetIf(&tplVal); + auto ptr = GetIf(&tplVal); if (!ptr) continue; - auto parentTplPtr = static_cast(*ptr); + auto parentTplPtr = static_cast(ptr->get()); if (parentTplPtr->HasBlock(m_name)) { @@ -199,7 +201,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) auto& scope = innerContext.EnterScope(); - scope["$$__super_block"] = static_cast(this); + scope["$$__super_block"] = RendererPtr(this, boost::null_deleter()); scope["super"] = Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { m_mainBody->Render(stream, context); }); @@ -236,7 +238,7 @@ class ParentTemplateRenderer : public BlocksRenderer { auto& scope = values.GetCurrentScope(); InternalValueList parentTemplates; - parentTemplates.push_back(InternalValue(static_cast(this))); + parentTemplates.push_back(InternalValue(RendererPtr(this, boost::null_deleter()))); bool isFound = false; auto p = values.FindValue("$$__parent_template", isFound); if (isFound) @@ -277,16 +279,24 @@ struct TemplateImplVisitor { // ExtendsStatement::BlocksCollection* m_blocks; const Fn& m_fn; + bool m_throwError; - explicit TemplateImplVisitor(const Fn& fn) + explicit TemplateImplVisitor(const Fn& fn, bool throwError) : m_fn(fn) + , m_throwError(throwError) {} template Result operator()(nonstd::expected>, ErrorInfoTpl> tpl) const { - if (!tpl) + if (!m_throwError && !tpl) + { return Result{}; + } + else if (!tpl) + { + throw tpl.error(); + } return m_fn(tpl.value()); } @@ -297,9 +307,9 @@ struct TemplateImplVisitor }; template -Result VisitTemplateImpl(Arg&& tpl, Fn&& fn) +Result VisitTemplateImpl(Arg&& tpl, bool throwError, Fn&& fn) { - return visit(TemplateImplVisitor(fn), tpl); + return visit(TemplateImplVisitor(fn, throwError), tpl); } template class RendererTpl, typename CharT, typename ... Args> @@ -316,7 +326,7 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) return; } auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName); - auto renderer = VisitTemplateImpl(tpl, [this](auto tplPtr) { + auto renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer(tplPtr, &m_blocks); }); if (renderer) @@ -353,7 +363,7 @@ void IncludeStatement::Render(OutStream& os, RenderContext& values) auto doRender = [this, &values, &os](auto&& name) -> bool { auto tpl = values.GetRendererCallback()->LoadTemplate(name); - auto renderer = VisitTemplateImpl(tpl, [this](auto tplPtr) { + auto renderer = VisitTemplateImpl(tpl, false, [this](auto tplPtr) { return CreateTemplateRenderer(tplPtr, m_withContext); }); if (renderer) @@ -398,10 +408,124 @@ void IncludeStatement::Render(OutStream& os, RenderContext& values) } } +class ImportedMacroRenderer : public RendererBase +{ +public: + explicit ImportedMacroRenderer(InternalValueMap&& map, bool withContext) + : m_importedContext(std::move(map)) + , m_withContext(withContext) + {} + + void Render(OutStream& os, RenderContext& values) override + { + } + + void InvokeMacro(const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context) + { + auto ctx = context.Clone(m_withContext); + ctx.BindScope(&m_importedContext); + callable.GetStatementCallable()(params, stream, ctx); + } + + static void InvokeMacro(const std::string& contextName, const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context) + { + bool contextValFound = false; + auto contextVal = context.FindValue(contextName, contextValFound); + if (!contextValFound) + return; + + auto rendererPtr = GetIf(&contextVal->second); + if (!rendererPtr) + return; + + auto renderer = static_cast(rendererPtr->get()); + renderer->InvokeMacro(callable, params, stream, context); + } + +private: + InternalValueMap m_importedContext; + bool m_withContext; +}; void ImportStatement::Render(OutStream& os, RenderContext& values) { + auto name = m_nameExpr->Evaluate(values); + + if (!m_renderer) + { + auto tpl = values.GetRendererCallback()->LoadTemplate(name); + m_renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { + return CreateTemplateRenderer(tplPtr, true); + }); + } + + if (!m_renderer) + return; + + std::string scopeName; + { + TargetString tsScopeName = values.GetRendererCallback()->GetAsTargetString(name); + scopeName = "$$_imported_" + GetAsSameString(scopeName, tsScopeName).value(); + } + + TargetString str; + auto tmpStream = values.GetRendererCallback()->GetStreamOnString(str); + + RenderContext newContext = values.Clone(m_withContext); + InternalValueMap importedScope; + { + auto& intImportedScope = newContext.EnterScope(); + m_renderer->Render(tmpStream, newContext); + importedScope = std::move(intImportedScope); + } + + ImportNames(values, importedScope, scopeName); + values.GetCurrentScope()[scopeName] = std::static_pointer_cast(std::make_shared(std::move(importedScope), m_withContext)); +} + +void +ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const +{ + InternalValueMap importedNs; + + for (auto& var : importedScope) + { + if (var.first.empty()) + continue; + + if (var.first[0] == '_') + continue; + + auto& val = var.second; + auto mappedP = m_namesToImport.find(var.first); + if (!m_namespace && mappedP == m_namesToImport.end()) + continue; + + InternalValue imported; + auto callable = GetIf(&var.second); + if (!callable) + { + imported = std::move(var.second); + } + else if (callable->GetKind() == Callable::Macro) + { + imported = Callable(Callable::Macro, [this, fn = std::move(*callable), scopeName](const CallParams& params, OutStream& stream, RenderContext& context) { + ImportedMacroRenderer::InvokeMacro(scopeName, fn, params, stream, context); + }); + } + else + { + continue; + } + + if (m_namespace) + importedNs[var.first] = std::move(imported); + else + values.GetCurrentScope()[mappedP->second] = std::move(imported); + } + if (m_namespace) + values.GetCurrentScope()[m_namespace.value()] = MapAdapter::CreateAdapter(std::move(importedNs)); } void MacroStatement::PrepareMacroParams(RenderContext& values) diff --git a/src/statements.h b/src/statements.h index 7c155ca8..9e1e0bf6 100644 --- a/src/statements.h +++ b/src/statements.h @@ -257,8 +257,13 @@ class ImportStatement : public Statement } void Render(OutStream& os, RenderContext& values) override; + +private: + void ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const; + private: bool m_withContext; + RendererPtr m_renderer; ExpressionEvaluatorPtr<> m_nameExpr; nonstd::optional m_namespace; std::unordered_map m_namesToImport; diff --git a/test/import_test.cpp b/test/import_test.cpp index 41ee43ba..8d46891e 100644 --- a/test/import_test.cpp +++ b/test/import_test.cpp @@ -12,7 +12,13 @@ class ImportTest : public TemplateEnvFixture { TemplateEnvFixture::SetUp(); - AddFile("module", "{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}"); + AddFile("module", R"( +{% macro test() %}[{{ foo }}|{{ 23 }}]{% endmacro %} +{% set sbar=56 %} +{% macro __inner() %}77{% endmacro %} +{% macro test_set() %}[{{ foo }}|{{ sbar }}]{% endmacro %} +{% macro test_inner() %}[{{ foo }}|{{ __inner() }}]{% endmacro %} +)"); AddFile("header", "[{{ foo }}|{{ 23 }}]"); AddFile("o_printer", "({{ o }})"); } @@ -22,12 +28,20 @@ TEST_F(ImportTest, TestContextImports) { jinja2::ValuesMap params{{"foo", 42}}; - auto result = Render(R"({% import "module" as m %}{{ m.test() }})", params); - EXPECT_EQ("[|23]", result); - result = Render(R"({% import "module" as m without context %}{{ m.test() }})", params); - EXPECT_EQ("[|23]", result); - result = Render(R"({% import "module" as m with context %}{{ m.test() }})", params); - EXPECT_EQ("[42|23]", result); + auto result = Render(R"({% import "module" as m %}{{ m.test() }}{{ m.test_set() }})", params); + EXPECT_EQ("[|23][|56]", result); + result = Render(R"({% import "module" as m without context %}{{ m.test() }}{{ m.test_set() }})", params); + EXPECT_EQ("[|23][|56]", result); + result = Render(R"({% import "module" as m with context %}{{ m.test() }}{{ m.test_set() }})", params); + EXPECT_EQ("[42|23][42|56]", result); + result = Render(R"({% import "module" as m without context %}{% set sbar=88 %}{{ m.test() }}{{ m.test_set() }})", params); + EXPECT_EQ("[|23][|56]", result); + result = Render(R"({% import "module" as m with context %}{% set sbar=88 %}{{ m.test() }}{{ m.test_set() }})", params); + EXPECT_EQ("[42|23][42|56]", result); + result = Render(R"({% import "module" as m without context %}{{ m.test() }}{{ m.test_inner() }})", params); + EXPECT_EQ("[|23][|77]", result); + result = Render(R"({% import "module" as m with context %}{{ m.test() }}{{ m.test_inner() }})", params); + EXPECT_EQ("[42|23][42|77]", result); result = Render(R"({% from "module" import test %}{{ test() }})", params); EXPECT_EQ("[|23]", result); result = Render(R"({% from "module" import test without context %}{{ test() }})", params); From 8e483cc0f6d6c8289c4d5cbbf04f4bed002b9582 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 22 May 2019 00:50:59 +0300 Subject: [PATCH 09/12] Fix build --- src/statements.cpp | 3 +-- src/statements.h | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/statements.cpp b/src/statements.cpp index 43ebde0f..b3e3af38 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -454,7 +454,7 @@ void ImportStatement::Render(OutStream& os, RenderContext& values) if (!m_renderer) { auto tpl = values.GetRendererCallback()->LoadTemplate(name); - m_renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { + m_renderer = VisitTemplateImpl(tpl, true, [](auto tplPtr) { return CreateTemplateRenderer(tplPtr, true); }); } @@ -496,7 +496,6 @@ ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedSc if (var.first[0] == '_') continue; - auto& val = var.second; auto mappedP = m_namesToImport.find(var.first); if (!m_namespace && mappedP == m_namesToImport.end()) continue; diff --git a/src/statements.h b/src/statements.h index 9e1e0bf6..b341fb83 100644 --- a/src/statements.h +++ b/src/statements.h @@ -9,8 +9,9 @@ namespace jinja2 { -struct Statement : public VisitableRendererBase +class Statement : public VisitableRendererBase { +public: VISITABLE_STATEMENT(); }; From 3c23be09d888894b06bcca38b62da07223e5fb87 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 22 May 2019 01:18:19 +0300 Subject: [PATCH 10/12] Fix build --- src/statements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/statements.cpp b/src/statements.cpp index b3e3af38..809f2970 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -508,7 +508,7 @@ ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedSc } else if (callable->GetKind() == Callable::Macro) { - imported = Callable(Callable::Macro, [this, fn = std::move(*callable), scopeName](const CallParams& params, OutStream& stream, RenderContext& context) { + imported = Callable(Callable::Macro, [fn = std::move(*callable), scopeName](const CallParams& params, OutStream& stream, RenderContext& context) { ImportedMacroRenderer::InvokeMacro(scopeName, fn, params, stream, context); }); } From e8d20802d77d77d8edbc4a6567533ee4dbcc1570 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 23 May 2019 00:51:27 +0300 Subject: [PATCH 11/12] Add extra tests for 'import' syntax --- src/template_parser.cpp | 43 +++++++++++++++++++++++------------------ test/import_test.cpp | 14 +++++++++++++- test/test_tools.h | 15 ++++++++++---- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 782c2759..0b473a6e 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -721,18 +721,38 @@ StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, Sta std::vector> mappedNames; Token nextTok; + bool hasContextControl = false; + bool isWithContext = false; for (;;) { + bool hasComma = false; if (!mappedNames.empty()) { - if (!lexer.EatIfEqual(Token::Comma)) - break; + if (!lexer.EatIfEqual(Token::Comma)) + hasComma = true;; } nextTok = lexer.PeekNextToken(); - if (lexer.GetAsKeyword(nextTok) != Keyword::Unknown) - break; + auto kw = lexer.GetAsKeyword(nextTok); + if (kw == Keyword::With || kw == Keyword::Without) + { + lexer.NextToken(); + if (lexer.EatIfEqual(Keyword::Context)) + { + hasContextControl = true; + isWithContext = kw == Keyword::With; + nextTok = lexer.PeekNextToken(); + break; + } + else + { + lexer.ReturnToken(); + } + } + + if (hasComma) + break; std::pair macroMap; if (!lexer.EatIfEqual(Token::Identifier, &nextTok)) @@ -753,21 +773,6 @@ StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, Sta mappedNames.push_back(std::move(macroMap)); } - nextTok = lexer.PeekNextToken(); - auto kw = lexer.GetAsKeyword(nextTok); - bool hasContextControl = false; - bool isWithContext = false; - if (kw == Keyword::With || kw == Keyword::Without) - { - lexer.EatToken(); - isWithContext = kw == Keyword::With; - if (!lexer.EatIfEqual(Keyword::Context)) - return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context); - - nextTok = lexer.PeekNextToken(); - hasContextControl = true; - } - if (nextTok != Token::Eof) { if (hasContextControl) diff --git a/test/import_test.cpp b/test/import_test.cpp index 8d46891e..4aa2e40c 100644 --- a/test/import_test.cpp +++ b/test/import_test.cpp @@ -48,4 +48,16 @@ TEST_F(ImportTest, TestContextImports) EXPECT_EQ("[|23]", result); result = Render(R"({% from "module" import test with context %}{{ test() }})", params); EXPECT_EQ("[42|23]", result); -} \ No newline at end of file +} + +TEST_F(ImportTest, TestImportSyntax) +{ + Load(R"({% from "foo" import bar %})"); + Load(R"({% from "foo" import bar, baz %})"); + Load(R"({% from "foo" import bar, baz with context %})"); + Load(R"({% from "foo" import bar, baz, with context %})"); + Load(R"({% from "foo" import bar, with context %})"); + Load(R"({% from "foo" import bar, with, context %})"); + Load(R"({% from "foo" import bar, with with context %})"); +} + diff --git a/test/test_tools.h b/test/test_tools.h index aaaf9ecd..4154fd9a 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -145,8 +145,8 @@ class TemplateEnvFixture : public ::testing::Test { m_templateFs->AddFile(std::move(fileName), std::move(content)); } - - std::string Render(std::string tplBody, const jinja2::ValuesMap& params = {}) + + jinja2::Template Load(std::string tplBody) { jinja2::Template tpl(&m_env); auto loadResult = tpl.Load(std::move(tplBody)); @@ -154,9 +154,16 @@ class TemplateEnvFixture : public ::testing::Test if (!loadResult) { std::cout << "Template loading error: " << loadResult.error() << std::endl; - return ""; + return jinja2::Template{}; } - + + return tpl; + } + + std::string Render(std::string tplBody, const jinja2::ValuesMap& params = {}) + { + auto tpl = Load(std::move(tplBody)); + auto renderResult = tpl.RenderAsString(params); EXPECT_TRUE(!!renderResult); if (!renderResult) From 3cd0596ec95323c1987eed07da9864e55b3dc9a1 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 23 May 2019 09:20:43 +0000 Subject: [PATCH 12/12] Add tests for the parsing errors --- test/errors_test.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/errors_test.cpp b/test/errors_test.cpp index dd0e83b3..a71d3f73 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -119,7 +119,7 @@ INSTANTIATE_TEST_CASE_P(BasicExpressionsTest, ErrorsGenericTest, ::testing::Valu "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"} )); -INSTANTIATE_TEST_CASE_P(StatementsTest, ErrorsGenericTest, ::testing::Values( +INSTANTIATE_TEST_CASE_P(StatementsTest_1, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{% if %}", "noname.j2tpl:1:7: error: Expected expression, got: '<>'\n{% if %}\n ---^-------"}, InputOutputPair{"{% endif %}", @@ -168,6 +168,30 @@ INSTANTIATE_TEST_CASE_P(StatementsTest, ErrorsGenericTest, ::testing::Values( "noname.j2tpl:1:12: error: Unexpected token '<>'. Expected: '<>', '<>'\n{% extends %}\n ---^-------"}, InputOutputPair{"{% extends 10 %}", "noname.j2tpl:1:12: error: Unexpected token '10'. Expected: '<>', '<>'\n{% extends 10 %}\n ---^-------"}, + InputOutputPair{"{% import %}", + "noname.j2tpl:1:11: error: Unexpected token: '<>'\n{% import %}\n ---^-------"}, + InputOutputPair{"{% import 'foo' %}", + "noname.j2tpl:1:17: error: Unexpected token '<>'. Expected: 'as'\n{% import 'foo' %}\n ---^-------"}, + InputOutputPair{"{% import 'foo' as %}", + "noname.j2tpl:1:20: error: Unexpected token '<>'. Expected: '<>'\n{% import 'foo' as %}\n ---^-------"}, + InputOutputPair{"{% import 'foo', as %}", + "noname.j2tpl:1:16: error: Unexpected token ','. Expected: 'as'\n{% import 'foo', as %}\n ---^-------"}, + InputOutputPair{"{% import 'foo', %}", + "noname.j2tpl:1:16: error: Unexpected token ','. Expected: 'as'\n{% import 'foo', %}\n ---^-------"}, + InputOutputPair{"{% import 'foo', bar %}", + "noname.j2tpl:1:16: error: Unexpected token ','. Expected: 'as'\n{% import 'foo', bar %}\n ---^-------"}, + InputOutputPair{"{% from 'foo' import, %}", + "noname.j2tpl:1:21: error: Unexpected token ','. Expected: '<>'\n{% from 'foo' import, %}\n ---^-------"}, + InputOutputPair{"{% from 'foo' import %}", + "noname.j2tpl:1:22: error: Unexpected token '<>'. Expected: '<>'\n{% from 'foo' import %}\n ---^-------"}, + InputOutputPair{"{% from 'foo' import bar, %}", + "noname.j2tpl:1:27: error: Unexpected token '<>'. Expected: '<>'\n{% from 'foo' import bar, %}\n ---^-------"}, + InputOutputPair{"{% from 'foo' import bar,, with context %}", + "noname.j2tpl:1:26: error: Unexpected token ','. Expected: '<>'\n{% from 'foo' import bar,, with context %}\n ---^-------"}, + InputOutputPair{"{% from 'foo' import bar with context, %}", + "noname.j2tpl:1:38: error: Expected end of statement, got: ','\n{% from 'foo' import bar with context, %}\n ---^-------"} + )); +INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{% block %}", "noname.j2tpl:1:10: error: Identifier expected\n{% block %}\n ---^-------"}, InputOutputPair{"{% block 10 %}",