From 9550971f4847ca2c2f481ec3b8478d80e90ba9d0 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 31 Aug 2018 23:30:21 +0300 Subject: [PATCH] Add space trimming control and global environment settings * Add space trimming control and global environment settings * [skip ci] Update ReadMe.md * [skip ci] Update CMakeLists.txt install script --- CMakeLists.txt | 1 + README.md | 66 +++++- include/jinja2cpp/template_env.h | 13 ++ src/template_impl.h | 51 ++--- src/template_parser.h | 110 ++++++++-- test/basic_tests.cpp | 349 ++++++++++++++++++++++++++++++- 6 files changed, 540 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d136d0..96df6f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ install(TARGETS ${LIB_TARGET_NAME} ARCHIVE DESTINATION lib/static) install (DIRECTORY include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) diff --git a/README.md b/README.md index 651fd31..0368c16 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar - [Reflection](#reflection) - ['set' statement](#set-statement) - ['extends' statement](#extends-statement) + - [Macros](#macros) - [Error reporting](#error-reporting) - [Other features](#other-features) - [Current Jinja2 support](#current-jinja2-support) @@ -45,8 +46,9 @@ Main features of Jinja2Cpp: - Partial support for both narrow- and wide-character strings both for templates and parameters. - Built-in reflection for C++ types. - Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions. -- Basic control statements (set, for, if). +- Control statements (set, for, if). - Templates extention. +- Macros - Rich error reporting. For instance, this simple code: @@ -381,6 +383,54 @@ private: '**extends**' statement here defines the template to extend. Set of '**block**' statements after defines actual filling of the corresponding blocks from the extended template. If block from the extended template contains something (like ```namespaced_decls``` from the example above), this content can be rendered with help of '**super()**' function. In other case the whole content of the block will be replaced. More detailed description of template inheritance feature can be found in [Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/#template-inheritance). +## Macros +Ths sample above violates 'DRY' rule. It contains the code which could be generalized. And Jinja2 supports features for such kind generalization. This feature called '**macros**'. The sample can be rewritten the following way: +```c++ +{% macro MethodsDecl(class, access) %} +{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', access) %} + {{ method.fullPrototype }}; +{% endfor %} +{% endmacro %} + +class {{ class.name }} +{ +public: + {{ MethodsDecl(class, ['Public']) }} +protected: + {{ MethodsDecl(class, ['Protected']) }} +private: + {{ MethodsDecl(class, ['Private', 'Undefined']) }} +}; + +{% endfor %} +``` + +`MethodsDecl` statement here is a **macro** which takes two arguments. First one is a class with method definitions. The second is a tuple of access specifiers. Macro takes non-implicit methods from the methods collection (`rejectattr('isImplicit')` filter) then select such methods which have right access specifier (`selectattr('accessType', 'in', access)`), then just prints the method full prototype. Finally, the macro is invoked as a regular function call: `MethodsDecl(class, ['Public'])` and replaced with rendered macro body. + +There is another way to invoke macro: the **call** statement. Simply put, this is a way to call macro with *callback*. Let's take another sample: + +```c++ +{% macro InlineMethod(resultType='void', methodName, methodParams=[]) %} +inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} ) +{ + {{ caller() }} +} +{% endmacro %} + +{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' enum.enumName ~ ' e']) %} + switch (e) + { +{% for item in enum.items %} + case {{enum.nsScope}}::{{item}}: + return "{{item}}"; +{% endfor %} + } + return "Unknown Item"; +{% endcall %} +``` + +Here is an `InlineMacro` which just describe the inline method definition skeleton. This macro doesn't contain the actual method body. Instead of this it calls `caller` special function. This function invokes the special **callback** macro which is a body of `call` statement. And this macro can have parameters as well. More detailed this mechanics described in the [Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/#macros). + ## Error reporting It's difficult to write complex template completely without errors. Missed braces, wrong characters, incorrect names... Everything is possible. So, it's crucial to be able to get informative error report from the template engine. Jinja2Cpp provides such kind of report. ```Template::Load``` method (and TemplateEnv::LoadTemplate respectively) return instance of ```ErrorInfo``` class which contains details about the error. These details include: - Error code @@ -463,8 +513,10 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - 'for' statement (with 'else' branch and 'if' part support) - 'extends' statement - 'set' statement -- 'extends' statement +- 'extends'/'block' statements +- 'macro'/'call' statements - recursive loops +- space control # Supported compilers Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled feature): @@ -476,7 +528,7 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f - Microsoft Visual Studio 2017 x86 # Build and install -Jinja2Cpp has got only one external dependency: boost library (at least version 1.55). Because of types from boost are used inside library, you should compile both your projects and Jinja2Cpp library with similar compiler settings. Otherwise ABI could be broken. +Jinja2Cpp has got only two external dependency: boost library (at least version 1.55) and expected-lite. Because of types from boost are used inside library, you should compile both your projects and Jinja2Cpp library with similar compiler settings. Otherwise ABI could be broken. In order to compile Jinja2Cpp you need: @@ -562,6 +614,14 @@ target_link_libraries(YourTarget ``` # Changelog +## Version 0.9 +* Support of 'extents'/'block' statements +* Support of 'macro'/'call' statements +* Rich error reporting +* Support for recursive loops +* Support for space control before and after control blocks +* Improve reflection + ## Version 0.6 * A lot of filters has been implemented. Full set of supported filters listed here: https://github.com/flexferrum/Jinja2Cpp/issues/7 * A lot of testers has been implemented. Full set of supported testers listed here: https://github.com/flexferrum/Jinja2Cpp/issues/8 diff --git a/include/jinja2cpp/template_env.h b/include/jinja2cpp/template_env.h index 9e36d7d..c72267e 100644 --- a/include/jinja2cpp/template_env.h +++ b/include/jinja2cpp/template_env.h @@ -14,6 +14,13 @@ namespace jinja2 class IErrorHandler; class IFilesystemHandler; +struct Settings +{ + bool useLineStatements = false; + bool trimBlocks = false; + bool lstripBlocks = false; +}; + class TemplateEnv { public: @@ -25,6 +32,11 @@ class TemplateEnv { return m_errorHandler; } + + const Settings& GetSettings() const {return m_settings;} + Settings& GetSettings() {return m_settings;} + void SetSettings(const Settings& setts) {m_settings = setts;} + void AddFilesystemHandler(std::string prefix, FilesystemHandlerPtr h) { m_filesystemHandlers.push_back(FsHandler{std::move(prefix), h}); @@ -44,6 +56,7 @@ class TemplateEnv FilesystemHandlerPtr handler; }; std::vector m_filesystemHandlers; + Settings m_settings; }; } // jinja2 diff --git a/src/template_impl.h b/src/template_impl.h index 09d0539..a54ba48 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -53,6 +53,8 @@ class TemplateImpl : public ITemplateImpl TemplateImpl(TemplateEnv* env) : m_env(env) { + if (env) + m_settings = env->GetSettings(); } auto GetRenderer() const {return m_renderer;} @@ -60,7 +62,7 @@ class TemplateImpl : public ITemplateImpl boost::optional> Load(std::basic_string tpl, std::string tplName) { m_template = std::move(tpl); - TemplateParser parser(&m_template, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName)); + TemplateParser parser(&m_template, m_settings, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName)); auto parseResult = parser.Parse(); if (!parseResult) @@ -72,31 +74,31 @@ class TemplateImpl : public ITemplateImpl void Render(std::basic_ostream& os, const ValuesMap& params) { - if (m_renderer) + if (!m_renderer) + return; + + InternalValueMap intParams; + for (auto& ip : params) { - InternalValueMap intParams; - for (auto& ip : params) - { - auto valRef = &ip.second.data(); - auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef); - if (!newParam) - intParams[ip.first] = std::move(ValueRef(static_cast(*valRef))); - else - intParams[ip.first] = newParam.get(); - } - RendererCallback callback(this); - RenderContext context(intParams, &callback); - InitRenderContext(context); - OutStream outStream( - [this, &os](const void* ptr, size_t length) { - os.write(reinterpret_cast(ptr), length); - }, - [this, &os](const InternalValue& val) { - Apply>(val, os); - } - ); - m_renderer->Render(outStream, context); + auto valRef = &ip.second.data(); + auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef); + if (!newParam) + intParams[ip.first] = std::move(ValueRef(static_cast(*valRef))); + else + intParams[ip.first] = newParam.get(); + } + RendererCallback callback(this); + RenderContext context(intParams, &callback); + InitRenderContext(context); + OutStream outStream( + [this, &os](const void* ptr, size_t length) { + os.write(reinterpret_cast(ptr), length); + }, + [this, &os](const InternalValue& val) { + Apply>(val, os); } + ); + m_renderer->Render(outStream, context); } InternalValueMap& InitRenderContext(RenderContext& context) @@ -150,6 +152,7 @@ class TemplateImpl : public ITemplateImpl private: TemplateEnv* m_env; + Settings m_settings; std::basic_string m_template; RendererPtr m_renderer; }; diff --git a/src/template_parser.h b/src/template_parser.h index e2268ed..b5b8654 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -13,6 +13,7 @@ #include "value_visitors.h" #include +#include #include @@ -224,9 +225,10 @@ class TemplateParser : public LexerHelper using ErrorInfo = ErrorInfoTpl; using ParseResult = nonstd::expected>; - TemplateParser(const string_t* tpl, std::string tplName) + TemplateParser(const string_t* tpl, const Settings& setts, std::string tplName) : m_template(tpl) , m_templateName(std::move(tplName)) + , m_settings(setts) , m_roughTokenizer(traits_t::GetRoughTokenizer()) , m_keywords(traits_t::GetKeywords()) { @@ -305,7 +307,10 @@ class TemplateParser : public LexerHelper m_currentBlockInfo.range.endOffset = 0; m_currentLineInfo.range = m_currentBlockInfo.range; m_currentLineInfo.lineNumber = 0; - m_currentBlockInfo.type = m_template->front() == '#' ? TextBlockType::LineStatement : TextBlockType::RawText; + if (m_settings.useLineStatements) + m_currentBlockInfo.type = m_template->front() == '#' ? TextBlockType::LineStatement : TextBlockType::RawText; + else + m_currentBlockInfo.type = TextBlockType::RawText; do { auto result = ParseRoughMatch(matchBegin, matchEnd); @@ -351,7 +356,10 @@ class TemplateParser : public LexerHelper m_currentBlockInfo.range.startOffset = m_currentLineInfo.range.startOffset; } - m_currentBlockInfo.type = (*m_template)[m_currentLineInfo.range.startOffset] == '#' ? TextBlockType::LineStatement : TextBlockType::RawText; + if (m_settings.useLineStatements) + m_currentBlockInfo.type = (*m_template)[m_currentLineInfo.range.startOffset] == '#' ? TextBlockType::LineStatement : TextBlockType::RawText; + else + m_currentBlockInfo.type = TextBlockType::RawText; } break; case RM_CommentBegin: @@ -371,13 +379,7 @@ class TemplateParser : public LexerHelper m_currentBlockInfo.range.startOffset = matchStart + 2; break; case RM_ExprBegin: - if (m_currentBlockInfo.type != TextBlockType::RawText) - { - break; - } - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; - m_currentBlockInfo.type = TextBlockType::Expression; + StartControlBlock(TextBlockType::Expression, matchStart); break; case RM_ExprEnd: if (m_currentBlockInfo.type == TextBlockType::RawText) @@ -385,17 +387,10 @@ class TemplateParser : public LexerHelper else if (m_currentBlockInfo.type != TextBlockType::Expression || (*m_template)[match.position() - 1] == '\'') break; - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); break; case RM_StmtBegin: - if (m_currentBlockInfo.type != TextBlockType::RawText) - { - break; - } - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; - m_currentBlockInfo.type = TextBlockType::Statement; + StartControlBlock(TextBlockType::Statement, matchStart); break; case RM_StmtEnd: if (m_currentBlockInfo.type == TextBlockType::RawText) @@ -403,13 +398,55 @@ class TemplateParser : public LexerHelper else if (m_currentBlockInfo.type != TextBlockType::Statement || (*m_template)[match.position() - 1] == '\'') break; - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); break; } return nonstd::expected(); } + + void StartControlBlock(TextBlockType blockType, size_t matchStart) + { + size_t startOffset = matchStart + 2; + size_t endOffset = matchStart; + if (m_currentBlockInfo.type != TextBlockType::RawText) + return; + else + endOffset = StripBlockLeft(m_currentBlockInfo, startOffset, endOffset); + + FinishCurrentBlock(endOffset); + if (startOffset < m_template->size()) + { + if ((*m_template)[startOffset] == '+' || + (*m_template)[startOffset] == '-') + ++ startOffset; + } + m_currentBlockInfo.range.startOffset = startOffset; + m_currentBlockInfo.type = blockType; + } + + size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset) + { + bool doStrip = m_settings.lstripBlocks; + if (ctrlCharPos < m_template->size()) + { + auto ctrlChar = (*m_template)[ctrlCharPos]; + doStrip = ctrlChar == '+' ? false : (ctrlChar == '-' ? true : doStrip); + } + if (!doStrip || currentBlockInfo.type != TextBlockType::RawText) + return endOffset; + + auto locale = std::locale(); + auto& tpl = *m_template; + for (; endOffset > 0; -- endOffset) + { + auto ch = tpl[endOffset - 1]; + if (!std::isspace(ch, locale) || ch == '\n') + break; + } + return endOffset; + } + nonstd::expected> DoFineParsing(std::shared_ptr renderers) { std::vector errors; @@ -559,12 +596,40 @@ class TemplateParser : public LexerHelper return string_t(); } - void FinishCurrentBlock(size_t position) + size_t FinishCurrentBlock(size_t position) { + bool doTrim = m_settings.trimBlocks && m_currentBlockInfo.type == TextBlockType::Statement; + size_t newPos = position + 2; + + if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0) + { + auto ctrlChar = (*m_template)[position - 1]; + doTrim = ctrlChar == '-' ? true : (ctrlChar == '+' ? false : doTrim); + if (ctrlChar == '+' || ctrlChar == '-') + -- position; + } + + if (doTrim) + { + auto locale = std::locale(); + for (;newPos < m_template->size(); ++ newPos) + { + auto ch = (*m_template)[newPos]; + if (ch == '\n') + { + ++ newPos; + break; + } + if (!std::isspace(ch, locale)) + break; + } + } m_currentBlockInfo.range.endOffset = position; m_textBlocks.push_back(m_currentBlockInfo); m_currentBlockInfo.type = TextBlockType::RawText; + return newPos; } + void FinishCurrentLine(int64_t position) { m_currentLineInfo.range.endOffset = static_cast(position); @@ -689,6 +754,7 @@ class TemplateParser : public LexerHelper private: const string_t* m_template; std::string m_templateName; + const Settings& m_settings; std::basic_regex m_roughTokenizer; std::basic_regex m_keywords; std::vector m_lines; diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index b5f7b6b..a568049 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -94,7 +94,6 @@ from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } - TEST(BasicTests, CommentedOutCodeSkip) { std::string source = R"(Hello World @@ -116,3 +115,351 @@ from Parser!)"; from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } + +TEST(BasicTests, StripLSpaces_1) +{ + std::string source = R"(Hello World + {{- ' --' }} +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + -- +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_2) +{ + std::string source = R"(Hello World + {{+ ' --' }} +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + -- +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_3) +{ + std::string source = R"(Hello World + {%- set delim = ' --' %}{{delim}}< +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + --< +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_4) +{ + std::string source = R"(Hello World + {%- set delim = ' --' %} {{+delim}}< +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + --< +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_5) +{ + std::string source = R"(Hello World + {%- set delim = ' --' %} {{-delim}}< +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + --< +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_6) +{ + std::string source = R"(Hello World + {%+ set delim = ' --' %} > {{-delim}}< +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + > --< +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_7) +{ + std::string source = R"({{-'Hello' }} World + {{- ' --' }} +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + -- +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripLSpaces_8) +{ + std::string source = R"({{+'Hello' }} World + {{- ' --' }} +from Parser!)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World + -- +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_1) +{ + std::string source = R"(Hello World {{ ' --' -}} +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World --from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_2) +{ + std::string source = R"(Hello World {{ ' --' +}} +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World -- +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_3) +{ + std::string source = R"(Hello World {{ ' --' +}})"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World --)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_4) +{ + std::string source = R"(Hello World {{ 'str1 ' -}} + {{- 'str2 '-}} + {{- 'str3 '-}} + {{- 'str4 '-}} +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + 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()); +} + +TEST(BasicTests, TrimSpaces_5) +{ + std::string source = R"(Hello World {% set delim = ' -- ' -%} >{{delim}}< +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World > -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_6) +{ + std::string source = R"(Hello World {% set delim = ' -- ' +%} >{{delim}}< +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World > -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_7) +{ + std::string source = R"(Hello World {% set delim = ' -- ' -%} +>{{delim}}< +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World > -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimSpaces_8) +{ + std::string source = "Hello World{% set delim = ' -- ' +%}\n>{{delim}}<\nfrom Parser!"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World> -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimStripSpacesMixed_1) +{ + std::string source = R"(Hello World -{% set delim = ' -- ' -%} +>{{delim}}< +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World -> -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, TrimStripSpacesMixed_2) +{ + std::string source = R"(Hello World -{% set delim = ' -- ' -%}- +>{{delim}}< +from Parser!)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + std::string result = tpl.RenderAsString(ValuesMap{}); + std::cout << result << std::endl; + std::string expectedResult = R"(Hello World -- +> -- < +from Parser!)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +}