From 37bb9fd29876c7e7b3b4e05a7cbe67232d88a21d Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 31 Aug 2018 23:46:06 +0300 Subject: [PATCH 001/206] [skip_ci] Update releases badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0368c165..f69205b8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build status](https://ci.appveyor.com/api/projects/status/19v2k3bl63jxl42f/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/Jinja2Cpp) [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) -[![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) +[![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/flexferrum/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/flexferrum/Jinja2Cpp/master/LICENSE) From 22061de3f204e00f049f2813747133ad903010d5 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 31 Aug 2018 23:53:42 +0300 Subject: [PATCH 002/206] [skip_ci] Typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f69205b8..5e000f2c 100644 --- a/README.md +++ b/README.md @@ -384,7 +384,7 @@ 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: +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 '**macro**'. The sample can be rewritten the following way: ```c++ {% macro MethodsDecl(class, access) %} {% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', access) %} From e1bc5ca218602999a9a042f4900454ef8e37364c Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 1 Sep 2018 00:07:34 +0300 Subject: [PATCH 003/206] [skip_ci] Update Readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5e000f2c..7b7e8363 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar - [Additional CMake build flags](#additional-cmake-build-flags) - [Link with you projects](#link-with-you-projects) - [Changelog](#changelog) + - [Version 0.9](#version-09) - [Version 0.6](#version-06) From ba8fa86ad7741c2168af0ac54e9f5dcddba6d7eb Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 1 Sep 2018 00:09:00 +0300 Subject: [PATCH 004/206] [skip_ci] Update Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b7e8363..7a425df9 100644 --- a/README.md +++ b/README.md @@ -418,7 +418,7 @@ inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} ) } {% endmacro %} -{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' enum.enumName ~ ' e']) %} +{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' ~ enum.enumName ~ ' e']) %} switch (e) { {% for item in enum.items %} From 6b7bbc1feae61715e2f2c02aba64ec4915b35ff3 Mon Sep 17 00:00:00 2001 From: Lisitsa Nikita Date: Thu, 6 Sep 2018 17:53:43 +0300 Subject: [PATCH 005/206] Fix a tiny bug in introductory example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7a425df9..9ea8d45c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ std::string source = R"( )"; Template tpl; +tpl.Load(source); std::string result = tpl.RenderAsString(ValuesMap()); ``` From e8b496f900655a6da672687ff0a6d7d96bb3bf71 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 19 Sep 2018 23:36:28 +0300 Subject: [PATCH 006/206] Improve PerfTests --- test/perf_test.cpp | 19 ++++++++++++++++++- thirdparty/nonstd/expected-light | 2 +- thirdparty/nonstd/variant-light | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/test/perf_test.cpp b/test/perf_test.cpp index 687a199e..96b5ef50 100644 --- a/test/perf_test.cpp +++ b/test/perf_test.cpp @@ -45,6 +45,23 @@ TEST(PerfTests, SimpleSubstituteText) std::cout << result << std::endl; } +TEST(PerfTests, ValueSubstituteText) +{ + std::string source = "{{ message }} from Parser!"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + jinja2::ValuesMap params = {{"message", 100500}}; + + std::cout << tpl.RenderAsString(params) << std::endl; + std::string result; + for (int n = 0; n < Iterations * 100; ++ n) + result = tpl.RenderAsString(params); + + std::cout << result << std::endl; +} + TEST(PerfTests, SimpleSubstituteFilterText) { std::string source = "{{ message | upper }} from Parser!"; @@ -116,7 +133,7 @@ TEST(PerfTests, ForLoopParamText) TEST(PerfTests, ForLoopIndexText) { - std::string source = "{% for i in range(20)%} {{i}}-{{loop.index}} {%endfor%}"; + std::string source = "{% for i in range(20)%} {{i ~ '-' ~ loop.index}} {%endfor%}"; Template tpl; ASSERT_TRUE(tpl.Load(source)); diff --git a/thirdparty/nonstd/expected-light b/thirdparty/nonstd/expected-light index a2359cf6..ae103c6e 160000 --- a/thirdparty/nonstd/expected-light +++ b/thirdparty/nonstd/expected-light @@ -1 +1 @@ -Subproject commit a2359cf61478d735a308e952b1c9840714f61386 +Subproject commit ae103c6e1bfdd7ba92c96f1ae7cb357de9a3c3fb diff --git a/thirdparty/nonstd/variant-light b/thirdparty/nonstd/variant-light index 9f62c6c5..30849020 160000 --- a/thirdparty/nonstd/variant-light +++ b/thirdparty/nonstd/variant-light @@ -1 +1 @@ -Subproject commit 9f62c6c564ea7c3e1de1f3d7c6072dd101952055 +Subproject commit 308490204d53fcd825163395e2278919b945701d From 6e65a8b62afdd46481a8c4134219c083d4daf34d Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 30 Sep 2018 00:36:20 +0300 Subject: [PATCH 007/206] Use (non)std variant instead boost::variant (#43) * Switch on actual Martin's Moene 'variant-lite' * boost::variant -> nonstd::variant (2) * boost::variant -> nonstd::variant (3) * boost::variant -> nonstd::variant (4) * boost::variant -> nonstd::variant (4) * Performance improvement * martinmoene/value-ptr-lite -> flexferrum/value-ptr-lite * Fix map retrival from jinja2::Value * Fix crash * Fix stream operator application * Completely remove boost from public headers --- .gitmodules | 15 ++- CMakeLists.txt | 2 + include/jinja2cpp/error_info.h | 2 +- include/jinja2cpp/filesystem_handler.h | 4 +- include/jinja2cpp/value.h | 57 +++++++--- src/error_info.cpp | 20 +++- src/expression_evaluator.cpp | 18 +-- src/filesystem_handler.cpp | 8 +- src/filters.cpp | 20 ++-- src/internal_value.cpp | 43 +++++--- src/internal_value.h | 146 +++++++++++++++++++++---- src/lexer.cpp | 2 +- src/render_context.h | 2 +- src/statements.cpp | 16 +-- src/string_converter_filter.cpp | 16 +-- src/template.cpp | 5 +- src/template_impl.h | 6 +- src/template_parser.h | 8 +- src/value_visitors.h | 82 +++++++++++--- thirdparty/nonstd/CMakeLists.txt | 10 +- thirdparty/nonstd/value-ptr-lite | 1 + 21 files changed, 353 insertions(+), 130 deletions(-) create mode 160000 thirdparty/nonstd/value-ptr-lite diff --git a/.gitmodules b/.gitmodules index e4355fec..0314b782 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "thirdparty/gtest"] - path = thirdparty/gtest - url = https://github.com/google/googletest.git + path = thirdparty/gtest + url = https://github.com/google/googletest.git [submodule "thirdparty/nonstd/variant-light"] - path = thirdparty/nonstd/variant-light - url = https://github.com/flexferrum/variant-lite.git + path = thirdparty/nonstd/variant-light + url = https://github.com/martinmoene/variant-lite.git [submodule "thirdparty/nonstd/expected-light"] - path = thirdparty/nonstd/expected-light - url = https://github.com/martinmoene/expected-lite.git + path = thirdparty/nonstd/expected-light + url = https://github.com/martinmoene/expected-lite.git +[submodule "thirdparty/nonstd/value-ptr-lite"] + path = thirdparty/nonstd/value-ptr-lite + url = https://github.com/flexferrum/value-ptr-lite.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 96df6f62..e643d69c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,8 @@ install(TARGETS ${LIB_TARGET_NAME} install (DIRECTORY include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/variant-light/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/value-ptr-light/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 5043605c..0dac9006 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -3,7 +3,7 @@ #include "value.h" -#include +#include #include #include diff --git a/include/jinja2cpp/filesystem_handler.h b/include/jinja2cpp/filesystem_handler.h index bd39b15d..5dbc7a59 100644 --- a/include/jinja2cpp/filesystem_handler.h +++ b/include/jinja2cpp/filesystem_handler.h @@ -1,7 +1,7 @@ #ifndef JINJA2CPP_FILESYSTEM_HANDLER_H #define JINJA2CPP_FILESYSTEM_HANDLER_H -#include +#include #include #include @@ -37,7 +37,7 @@ class MemoryFileSystem : public IFilesystemHandler WCharFileStreamPtr OpenWStream(const std::string& name) const override; private: - using FileContent = boost::variant; + using FileContent = nonstd::variant; std::unordered_map m_filesMap; }; diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 431bc736..1325811f 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -7,7 +7,8 @@ #include #include #include -#include +#include +#include namespace jinja2 { @@ -34,6 +35,7 @@ class GenericMap { public: GenericMap() = default; + GenericMap(std::function accessor) : m_accessor(std::move(accessor)) { @@ -89,13 +91,20 @@ class GenericList using ValuesList = std::vector; using ValuesMap = std::unordered_map; -using ValueData = boost::variant, boost::recursive_wrapper, GenericList, GenericMap>; +struct FunctionCallParams; + +using UserFunction = std::function; + +template +using RecWrapper = nonstd::value_ptr; class Value { public: + using ValueData = nonstd::variant, RecWrapper, GenericList, GenericMap, UserFunction>; + Value() = default; template - Value(T&& val, typename std::enable_if, Value>::value>::type* = nullptr) + Value(T&& val, typename std::enable_if, Value>::value && !std::is_same, ValuesList>::value>::type* = nullptr) : m_data(std::forward(val)) { } @@ -112,6 +121,22 @@ class Value { : m_data(static_cast(val)) { } + Value(const ValuesList& list) + : m_data(RecWrapper(list)) + { + } + Value(const ValuesMap& map) + : m_data(RecWrapper(map)) + { + } + Value(ValuesList&& list) noexcept + : m_data(RecWrapper(std::move(list))) + { + } + Value(ValuesMap&& map) noexcept + : m_data(RecWrapper(std::move(map))) + { + } const ValueData& data() const {return m_data;} @@ -119,44 +144,44 @@ class Value { bool isString() const { - return boost::get(&m_data) != nullptr; + return nonstd::get_if(&m_data) != nullptr; } auto& asString() { - return boost::get(m_data); + return nonstd::get(m_data); } auto& asString() const { - return boost::get(m_data); + return nonstd::get(m_data); } bool isList() const { - return boost::get(&m_data) != nullptr || boost::get(&m_data) != nullptr; + return nonstd::get_if>(&m_data) != nullptr || nonstd::get_if(&m_data) != nullptr; } auto& asList() { - return boost::get(m_data); + return *nonstd::get>(m_data).get(); } auto& asList() const { - return boost::get(m_data); + return *nonstd::get>(m_data).get(); } bool isMap() const { - return boost::get(&m_data) != nullptr || boost::get(&m_data) != nullptr; + return nonstd::get_if>(&m_data) != nullptr || nonstd::get_if(&m_data) != nullptr; } auto& asMap() { - return boost::get(m_data); + return *nonstd::get>(m_data).get(); } auto& asMap() const { - return boost::get(m_data); + return *nonstd::get>(m_data).get(); } bool isEmpty() const { - return boost::get(&m_data) != nullptr; + return nonstd::get_if(&m_data) != nullptr; } Value subscript(const Value& index) const; @@ -165,6 +190,12 @@ class Value { ValueData m_data; }; +struct FunctionCallParams +{ + ValuesMap kwParams; + ValuesList posParams; +}; + inline Value GenericMap::GetValueByName(const std::string& name) const { return m_accessor()->GetValueByName(name); diff --git a/src/error_info.cpp b/src/error_info.cpp index 6151e151..742629aa 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -7,7 +7,7 @@ namespace jinja2 namespace { template -struct ValueRenderer : boost::static_visitor +struct ValueRenderer { std::basic_ostream& os; @@ -37,7 +37,7 @@ struct ValueRenderer : boost::static_visitor else os << UNIVERSAL_STR(", "); - boost::apply_visitor(ValueRenderer(os), val.data()); + nonstd::visit(ValueRenderer(os), val.data()); } os << '}'; } @@ -54,12 +54,19 @@ struct ValueRenderer : boost::static_visitor os << UNIVERSAL_STR(", "); os << '{' << '"' << ConvertString>(val.first) << '"' << ','; - boost::apply_visitor(ValueRenderer(os), val.second.data()); + nonstd::visit(ValueRenderer(os), val.second.data()); os << '}'; } os << '}'; } + + template + void operator()(const RecWrapper& val) const + { + return this->operator()(const_cast(*val.get())); + } + void operator() (const GenericMap& /*val*/) const { } @@ -68,6 +75,11 @@ struct ValueRenderer : boost::static_visitor { } + + void operator() (const UserFunction& val) const + { + } + template void operator() (const T& val) const { @@ -78,7 +90,7 @@ struct ValueRenderer : boost::static_visitor template std::basic_ostream& operator << (std::basic_ostream& os, Value val) { - boost::apply_visitor(ValueRenderer(os), val.data()); + nonstd::visit(ValueRenderer(os), val.data()); return os; } } diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index d5bb2306..1bce7b02 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -89,7 +89,7 @@ InternalValue BinaryExpression::Evaluate(RenderContext& context) bool left = ConvertToBool(leftVal); if (left) left = ConvertToBool(rightVal); - result = left; + result = static_cast(left); break; } case jinja2::BinaryExpression::LogicalOr: @@ -97,7 +97,7 @@ InternalValue BinaryExpression::Evaluate(RenderContext& context) bool left = ConvertToBool(leftVal); if (!left) left = ConvertToBool(rightVal); - result = left; + result = static_cast(left); break; } case jinja2::BinaryExpression::LogicalEq: @@ -125,16 +125,16 @@ InternalValue BinaryExpression::Evaluate(RenderContext& context) auto leftStr = context.GetRendererCallback()->GetAsTargetString(leftVal); auto rightStr = context.GetRendererCallback()->GetAsTargetString(rightVal); TargetString resultStr; - std::string* nleftStr = boost::get(&leftStr); + std::string* nleftStr = GetIf(&leftStr); if (nleftStr != nullptr) { - auto* nrightStr = boost::get(&rightStr); + auto* nrightStr = GetIf(&rightStr); resultStr = *nleftStr + *nrightStr; } else { - auto* wleftStr = boost::get(&leftStr); - auto* wrightStr = boost::get(&rightStr); + auto* wleftStr = GetIf(&leftStr); + auto* wrightStr = GetIf(&rightStr); resultStr = *wleftStr + *wrightStr; } result = InternalValue(std::move(resultStr)); @@ -252,11 +252,11 @@ InternalValue CallExpression::Evaluate(RenderContext& values) void CallExpression::Render(OutStream& stream, RenderContext& values) { auto fnVal = m_valueRef->Evaluate(values); - Callable* callable = boost::get(&fnVal); + Callable* callable = GetIf(&fnVal); if (callable == nullptr) { fnVal = Subscript(fnVal, std::string("operator()")); - callable = boost::get(&fnVal); + callable = GetIf(&fnVal); if (callable == nullptr) { Expression::Render(stream, values); @@ -342,7 +342,7 @@ InternalValue CallExpression::CallLoopCycle(RenderContext& values) if (!loopFound) return InternalValue(); - auto loop = boost::get(&loopValP->second); + auto loop = GetIf(&loopValP->second); int64_t baseIdx = Apply(loop->GetValueByName("index0")); auto idx = static_cast(baseIdx % m_params.posParams.size()); return m_params.posParams[idx]->Evaluate(values); diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index a29cdbe3..fd7cd8cb 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -8,9 +8,9 @@ namespace jinja2 { -using TargetFileStream = boost::variant; +using TargetFileStream = nonstd::variant; -struct FileContentConverter : public boost::static_visitor +struct FileContentConverter { void operator() (const std::string& content, CharFileStreamPtr* sPtr) const { @@ -52,7 +52,7 @@ CharFileStreamPtr MemoryFileSystem::OpenStream(const std::string& name) const return result; TargetFileStream targetStream(&result); - boost::apply_visitor(FileContentConverter(), p->second, targetStream); + visit(FileContentConverter(), p->second, targetStream); return result; } @@ -65,7 +65,7 @@ WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const return result; TargetFileStream targetStream(&result); - boost::apply_visitor(FileContentConverter(), p->second, targetStream); + visit(FileContentConverter(), p->second, targetStream); return result; } diff --git a/src/filters.cpp b/src/filters.cpp index 1db7c579..53c5d1ac 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -193,7 +193,7 @@ DictSort::DictSort(FilterParams params) InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& context) { - const MapAdapter* map = boost::get(&baseVal); + const MapAdapter* map = GetIf(&baseVal); if (map == nullptr) return InternalValue(); @@ -245,7 +245,7 @@ InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& cont for (int64_t idx = 0; idx < map->GetSize(); ++ idx) { auto val = map->GetValueByIndex(idx); - auto& kvVal = boost::get(val); + auto kvVal = Get(val); tempVector.push_back(std::move(kvVal)); } @@ -788,7 +788,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> break; case ValueConverter::ToIntMode: case ValueConverter::RoundMode: - result = val; + result = InternalValue(static_cast(val)); break; default: break; @@ -803,7 +803,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> switch (m_params.mode) { case ValueConverter::ToFloatMode: - result = val; + result = static_cast(val); break; case ValueConverter::ToIntMode: result = static_cast(val); @@ -843,7 +843,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> } size_t GetSize() const override {return m_str->size();} - InternalValue GetValueByIndex(int64_t idx) const override {return m_str->substr(static_cast(idx), 1);} + InternalValue GetValueByIndex(int64_t idx) const override {return InternalValue(m_str->substr(static_cast(idx), 1));} const string* m_str; }; @@ -873,7 +873,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> if (*endBuff != 0) result = m_params.defValule; else - result = dblVal; + result = static_cast(dblVal); break; } case ValueConverter::ToIntMode: @@ -884,7 +884,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> if (*endBuff != 0) result = m_params.defValule; else - result = dblVal; + result = static_cast(dblVal); break; } case ValueConverter::ToListMode: @@ -908,7 +908,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> if (*endBuff != 0) result = m_params.defValule; else - result = dblVal; + result = static_cast(dblVal); break; } case ValueConverter::ToIntMode: @@ -918,7 +918,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> if (*endBuff != 0) result = m_params.defValule; else - result = dblVal; + result = static_cast(dblVal); break; } case ValueConverter::ToListMode: @@ -953,7 +953,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> params.mode = ValueConverter::ToIntMode; params.base = static_cast(10); InternalValue intVal = Apply(val, params); - T* result = boost::get(&intVal); + T* result = GetIf(&intVal); if (result == nullptr) return defValue; diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 67a00233..2bda5c9d 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -6,10 +6,11 @@ namespace jinja2 struct SubscriptionVisitor : public visitors::BaseVisitor<> { - using BaseVisitor::operator (); + using BaseVisitor<>::operator (); InternalValue operator() (const MapAdapter& values, const std::string& field) const { + // std::cout << "operator() (const MapAdapter& values, const std::string& field)" << ": values.size() = " << values.GetSize() << ", field = " << field << std::endl; if (!values.HasValue(field)) return InternalValue(); @@ -18,6 +19,7 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> InternalValue operator() (const ListAdapter& values, int64_t index) const { + // std::cout << "operator() (const ListAdapter& values, int64_t index)" << ": values.size() = " << values.GetSize() << ", index = " << index << std::endl; if (index < 0 || static_cast(index) >= values.GetSize()) return InternalValue(); @@ -26,6 +28,7 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> InternalValue operator() (const MapAdapter& values, int64_t index) const { + // std::cout << "operator() (const MapAdapter& values, int64_t index)" << ": values.size() = " << values.GetSize() << ", index = " << index << std::endl; if (index < 0 || static_cast(index) >= values.GetSize()) return InternalValue(); @@ -35,6 +38,7 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> template InternalValue operator() (const std::basic_string& str, int64_t index) const { + // std::cout << "operator() (const std::basic_string& str, int64_t index)" << ": index = " << index << std::endl; if (index < 0 || static_cast(index) >= str.size()) return InternalValue(); @@ -42,8 +46,9 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return TargetString(std::move(result)); } - InternalValue operator() (const KeyValuePair& values, const std::string& field) + InternalValue operator() (const KeyValuePair& values, const std::string& field) const { + // std::cout << "operator() (const KeyValuePair& values, const std::string& field)" << ": field = " << field << std::endl; if (field == "key") return InternalValue(values.key); else if (field == "value") @@ -51,6 +56,13 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return InternalValue(); } +// +// template +// InternalValue operator() (T&&, U&&) const +// { +// std::cout << "operator() (T&&, U&&). T: " << typeid(T).name() << ", U: " << typeid(U).name() << std::endl; +// return InternalValue(); +// } }; InternalValue Subscript(const InternalValue& val, const InternalValue& subscript) @@ -65,13 +77,13 @@ InternalValue Subscript(const InternalValue& val, const std::string& subscript) std::string AsString(const InternalValue& val) { - auto* str = boost::get(&val); - auto* tstr = boost::get(&val); + auto* str = GetIf(&val); + auto* tstr = GetIf(&val); if (str != nullptr) return *str; else { - str = boost::get(tstr); + str = GetIf(tstr); if (str != nullptr) return *str; } @@ -122,7 +134,7 @@ ListAdapter ConvertToList(const InternalValue& val, InternalValue subscipt, bool isConverted = true; if (IsEmpty(subscipt)) - return result.get(); + return std::move(result.get()); return result.get().ToSubscriptedList(subscipt, false); } @@ -148,6 +160,9 @@ class ByVal ByVal(T&& val) : m_val(std::move(val)) {} + ~ByVal() + { + } const T& Get() const {return m_val;} T& Get() {return m_val;} @@ -165,8 +180,8 @@ class GenericListAdapter : public IListAccessor size_t GetSize() const override {return m_values.Get().GetSize();} InternalValue GetValueByIndex(int64_t idx) const override { - auto val = m_values.Get().GetValueByIndex(idx); - return boost::apply_visitor(visitors::InputValueConvertor(true), val.data()).get(); + const auto& val = m_values.Get().GetValueByIndex(idx); + return visit(visitors::InputValueConvertor(true), val.data()).get(); } private: Holder m_values; @@ -182,8 +197,8 @@ class ValuesListAdapter : public IListAccessor size_t GetSize() const override {return m_values.Get().size();} InternalValue GetValueByIndex(int64_t idx) const override { - auto val = m_values.Get()[idx]; - return boost::apply_visitor(visitors::InputValueConvertor(false), val.data()).get(); + const auto& val = m_values.Get()[idx]; + return visit(visitors::InputValueConvertor(false), val.data()).get(); } private: Holder m_values; @@ -316,7 +331,7 @@ class InternalValueMapAdapter : public IMapAccessor InternalValue Value2IntValue(const Value& val) { - auto result = boost::apply_visitor(visitors::InputValueConvertor(false), val.data()); + auto result = nonstd::visit(visitors::InputValueConvertor(false), val.data()); if (result) return result.get(); @@ -325,7 +340,7 @@ InternalValue Value2IntValue(const Value& val) InternalValue Value2IntValue(Value&& val) { - auto result = boost::apply_visitor(visitors::InputValueConvertor(true), val.data()); + auto result = nonstd::visit(visitors::InputValueConvertor(true), val.data()); if (result) return result.get(); @@ -347,7 +362,7 @@ class GenericMapAdapter : public IMapAccessor auto& map = val.asMap(); result.key = map["key"].asString(); result.value = Value2IntValue(std::move(map["value"])); - return result; + return MakeWrapped(result); } bool HasValue(const std::string& name) const override { @@ -389,7 +404,7 @@ class ValuesMapAdapter : public IMapAccessor result.key = p->first; result.value = Value2IntValue(p->second); - return result; + return MakeWrapped(std::move(result)); } bool HasValue(const std::string& name) const override { diff --git a/src/internal_value.h b/src/internal_value.h index 25fabf44..837a5f0b 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -4,6 +4,9 @@ #include #include #include +// #include +#include +#include namespace jinja2 { @@ -12,31 +15,75 @@ template class ReferenceWrapper { public: - using type = T; + using type = T; - ReferenceWrapper(T& ref) noexcept - : m_ptr(std::addressof(ref)) - { - } + ReferenceWrapper(T& ref) noexcept + : m_ptr(std::addressof(ref)) + { + } - ReferenceWrapper(T&&) = delete; - ReferenceWrapper(const ReferenceWrapper&) noexcept = default; + ReferenceWrapper(T&&) = delete; + ReferenceWrapper(const ReferenceWrapper&) noexcept = default; - // assignment - ReferenceWrapper& operator=(const ReferenceWrapper& x) noexcept = default; + // assignment + ReferenceWrapper& operator=(const ReferenceWrapper& x) noexcept = default; - // access - T& get() const noexcept - { - return *m_ptr; - } + // access + T& get() const noexcept + { + return *m_ptr; + } private: - T* m_ptr; + T* m_ptr; }; +template +class RecursiveWrapper +{ +public: + RecursiveWrapper() = default; + + RecursiveWrapper(const T& value) + : m_data(value) + {} + + RecursiveWrapper(T&& value) + : m_data(std::move(value)) + {} + + const T& GetValue() const {return m_data.get();} + T& GetValue() {return m_data.get();} + +private: + boost::recursive_wrapper m_data; + +#if 0 + enum class State + { + Undefined, + Inplace, + Ptr + }; + + State m_state; + + union + { + uint64_t dummy; + nonstd::value_ptr ptr; + } m_data; +#endif +}; + +template +auto MakeWrapped(T&& val) +{ + return RecursiveWrapper>(std::forward(val)); +} + using ValueRef = ReferenceWrapper; -using TargetString = boost::variant; +using TargetString = nonstd::variant; class ListAdapter; class MapAdapter; @@ -47,11 +94,66 @@ struct CallParams; struct KeyValuePair; class RendererBase; -using InternalValue = boost::variant, boost::recursive_wrapper, RendererBase*>; +using InternalValue = nonstd::variant, RecursiveWrapper, RendererBase*>; using InternalValueRef = ReferenceWrapper; using InternalValueMap = std::unordered_map; using InternalValueList = std::vector; +template +struct ValueGetter +{ + template + static auto& Get(V&& val) + { + return nonstd::get(std::forward(val)); + } + + template + static auto GetPtr(V* val) + { + return nonstd::get_if(val); + } +}; + +template +struct ValueGetter +{ + template + static auto& Get(V&& val) + { + auto& ref = nonstd::get>(std::forward(val)); + return ref.GetValue(); + } + + template + static auto GetPtr(V* val) + { + auto ref = nonstd::get_if>(val); + return !ref ? nullptr : &ref->GetValue(); + } +}; + +template +struct IsRecursive : std::false_type {}; + +template<> +struct IsRecursive : std::true_type {}; + +template<> +struct IsRecursive : std::true_type {}; + +template +auto& Get(V&& val) +{ + return ValueGetter::value>::Get(std::forward(val)); +} + +template +auto GetIf(V* val) +{ + return ValueGetter::value>::GetPtr(val); +} + struct IListAccessor { virtual ~IListAccessor() {} @@ -263,7 +365,7 @@ class Callable using ExpressionCallable = std::function; using StatementCallable = std::function; - using CallableHolder = boost::variant; + using CallableHolder = nonstd::variant; enum class Type { @@ -283,7 +385,7 @@ class Callable auto GetType() const { - return m_callable.which() == 0 ? Type::Expression : Type::Statement; + return m_callable.index() == 0 ? Type::Expression : Type::Statement; } auto& GetCallable() const @@ -293,12 +395,12 @@ class Callable auto& GetExpressionCallable() const { - return boost::get(m_callable); + return nonstd::get(m_callable); } auto& GetStatementCallable() const { - return boost::get(m_callable); + return nonstd::get(m_callable); } private: @@ -307,7 +409,7 @@ class Callable inline bool IsEmpty(const InternalValue& val) { - return boost::get(&val) != nullptr; + return nonstd::get_if(&val) != nullptr; } InternalValue Subscript(const InternalValue& val, const InternalValue& subscript); diff --git a/src/lexer.cpp b/src/lexer.cpp index e963f619..3ddee219 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -85,7 +85,7 @@ bool Lexer::ProcessSymbolOrKeyword(const lexertk::token&, Token& newToken) { newToken.type = Token::Identifier; auto id = m_helper->GetAsString(newToken.range); - newToken.value = id; + newToken.value = InternalValue(id); } else { diff --git a/src/render_context.h b/src/render_context.h index 988fa9e2..b61afddd 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -16,7 +16,7 @@ class TemplateImpl; struct IRendererCallback { virtual TargetString GetAsTargetString(const InternalValue& val) = 0; - virtual boost::variant>, ErrorInfo>, nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0; }; diff --git a/src/statements.cpp b/src/statements.cpp index 04c1027b..d91a04af 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -181,7 +181,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) BlocksRenderer* blockRenderer = nullptr; // static_cast(*parentTplPtr); for (auto& tplVal : parentTplsList) { - auto ptr = boost::get(&tplVal); + auto ptr = GetIf(&tplVal); if (!ptr) continue; @@ -210,11 +210,11 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) innerContext.ExitScope(); auto& globalScope = values.GetGlobalScope(); - auto selfMap = boost::get(&globalScope[std::string("self")]); + auto selfMap = GetIf(&globalScope[std::string("self")]); if (!selfMap->HasValue(m_name)) - selfMap->SetValue(m_name, Callable([this](const CallParams&, OutStream& stream, RenderContext& context) { + selfMap->SetValue(m_name, MakeWrapped(Callable([this](const CallParams&, OutStream& stream, RenderContext& context) { Render(stream, context); - })); + }))); } void BlockStatement::Render(OutStream& os, RenderContext& values) @@ -272,7 +272,7 @@ class ParentTemplateRenderer : public BlocksRenderer ExtendsStatement::BlocksCollection* m_blocks; }; -struct TemplateImplVisitor : public boost::static_visitor +struct TemplateImplVisitor { ExtendsStatement::BlocksCollection* m_blocks; @@ -302,7 +302,7 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) return; } auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName); - auto renderer = boost::apply_visitor(TemplateImplVisitor(&m_blocks), tpl); + auto renderer = visit(TemplateImplVisitor(&m_blocks), tpl); if (renderer) renderer->Render(os, values); } @@ -349,7 +349,7 @@ void MacroStatement::InvokeMacroRenderer(const CallParams& callParams, OutStream scope["kwargs"] = MapAdapter::CreateAdapter(std::move(kwArgs)); scope["varargs"] = ListAdapter::CreateAdapter(std::move(varArgs)); - scope["name"] = m_name; + scope["name"] = static_cast(m_name); scope["arguments"] = ListAdapter::CreateAdapter(std::move(arguments)); scope["defaults"] = ListAdapter::CreateAdapter(std::move(defaults)); @@ -386,7 +386,7 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values) return; auto& fnVal = macroPtr->second; - const Callable* callable = boost::get(&fnVal); + const Callable* callable = GetIf(&fnVal); if (callable == nullptr || callable->GetType() == Callable::Type::Expression) return; diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index a51e0e2f..b109982f 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -33,7 +33,7 @@ struct StringEncoder : public visitors::BaseVisitor<> static_cast(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);}); } - return result; + return InternalValue(result); } template @@ -177,7 +177,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex case TrimMode: result = ApplyStringConverter(baseVal, [](auto str) -> InternalValue { ba::trim_all(str); - return str; + return InternalValue(str); }); break; case TitleMode: @@ -205,7 +205,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex } isDelim = !isAlNum(ch); }); - result = wc; + result = InternalValue(wc); break; } case UpperMode: @@ -236,7 +236,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex for (int64_t n = 0; n < count; ++ n) ba::replace_first(str, oldStr, newStr); } - return str; + return InternalValue(str); }); break; case TruncateMode: @@ -246,7 +246,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex auto end = GetAsSameString(str, this->GetArgumentValue("end", context)); auto leeway = ConvertToInt(this->GetArgumentValue("leeway", context), 5); if (str.size() <= length) - return str; + return InternalValue(str); if (killWords) { @@ -255,7 +255,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex str.erase(str.begin() + length, str.end()); str += end; } - return str; + return InternalValue(str); } auto p = str.begin() + length; @@ -263,7 +263,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex { for (; leeway != 0 && p != str.end() && isAlNum(*p); -- leeway, ++ p); if (p == str.end()) - return str; + return InternalValue(str); } if (isAlNum(*p)) @@ -274,7 +274,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex ba::trim_right(str); str += end; - return str; + return InternalValue(str); }); break; case UrlEncodeMode: diff --git a/src/template.cpp b/src/template.cpp index d71e3075..b55ad5ee 100644 --- a/src/template.cpp +++ b/src/template.cpp @@ -71,9 +71,10 @@ void Template::Render(std::ostream& os, const jinja2::ValuesMap& params) std::string Template::RenderAsString(const jinja2::ValuesMap& params) { - std::ostringstream os; + std::string outStr; + outStr.reserve(10000); + std::ostringstream os(outStr); Render(os, params); - return os.str(); } diff --git a/src/template_impl.h b/src/template_impl.h index a54ba482..5706793d 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -81,7 +81,7 @@ class TemplateImpl : public ITemplateImpl for (auto& ip : params) { auto valRef = &ip.second.data(); - auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef); + auto newParam = visit(visitors::InputValueConvertor(), *valRef); if (!newParam) intParams[ip.first] = std::move(ValueRef(static_cast(*valRef))); else @@ -109,7 +109,7 @@ class TemplateImpl : public ITemplateImpl auto LoadTemplate(const std::string& fileName) { - using ResultType = boost::variant>, ErrorInfo>, nonstd::expected>, ErrorInfoW>>; @@ -139,7 +139,7 @@ class TemplateImpl : public ITemplateImpl return TargetString(os.str()); } - boost::variant>, ErrorInfo>, nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const override { diff --git a/src/template_parser.h b/src/template_parser.h index b5b8654b..1127ab68 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -100,10 +100,10 @@ struct ParserTraits : public ParserTraitsBase<> { endBuff = nullptr; double dblVal = strtod(buff, nullptr); - result = dblVal; + result = static_cast(dblVal); } else - result = val; + result = static_cast(val); } return result; } @@ -567,7 +567,7 @@ class TemplateParser : public LexerHelper Token tok; tok.type = type; tok.range = range; - tok.value = value; + tok.value = static_cast(value); return tok; } @@ -582,7 +582,7 @@ class TemplateParser : public LexerHelper return m_template->substr(tok.range.startOffset, tok.range.size()); else if (tok.type == Token::Identifier) { - if (tok.value.which() != 0) + if (tok.value.index() != 0) { std::basic_string tpl; return GetAsSameString(tpl, tok.value); diff --git a/src/value_visitors.h b/src/value_visitors.h index 9f4d06ef..cc3c56dc 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -18,12 +18,48 @@ namespace jinja2 namespace detail { +template +struct RecursiveUnwrapper +{ + V* m_visitor; + + RecursiveUnwrapper(V* v) + : m_visitor(v) + {} + + + template + static auto& UnwrapRecursive(T&& arg) + { + return std::forward(arg); + } + + template + static auto& UnwrapRecursive(const RecursiveWrapper& arg) + { + return arg.GetValue(); + } + + template + static auto& UnwrapRecursive(RecursiveWrapper& arg) + { + return arg.GetValue(); + } + + template + auto operator()(Args&& ... args) const + { + assert(m_visitor != nullptr); + return (*m_visitor)(UnwrapRecursive(std::forward(args))...); + } +}; + template auto ApplyUnwrapped(const InternalValue& val, Fn&& fn) { - auto valueRef = boost::get(&val); - auto targetString = boost::get(&val); - // auto internalValueRef = boost::get(&val); + auto valueRef = GetIf(&val); + auto targetString = GetIf(&val); + // auto internalValueRef = GetIf(&val); if (valueRef != nullptr) return fn(valueRef->get().data()); @@ -40,7 +76,8 @@ template auto Apply(const InternalValue& val, Args&& ... args) { return detail::ApplyUnwrapped(val, [&args...](auto& val) { - return boost::apply_visitor(V(args...), val); + auto v = V(args...); + return nonstd::visit(detail::RecursiveUnwrapper(&v), val); }); } @@ -49,7 +86,8 @@ auto Apply2(const InternalValue& val1, const InternalValue& val2, Args&& ... arg { return detail::ApplyUnwrapped(val1, [&val2, &args...](auto& uwVal1) { return detail::ApplyUnwrapped(val2, [&uwVal1, &args...](auto& uwVal2) { - return boost::apply_visitor(V(args...), uwVal1, uwVal2); + auto v = V(args...); + return nonstd::visit(detail::RecursiveUnwrapper(&v), uwVal1, uwVal2); }); }); } @@ -59,7 +97,7 @@ bool ConvertToBool(const InternalValue& val); namespace visitors { template -struct BaseVisitor : public boost::static_visitor +struct BaseVisitor { R operator() (const GenericMap&) const { @@ -100,7 +138,7 @@ struct BaseVisitor : public boost::static_visitor template -struct ValueRendererBase : public boost::static_visitor<> +struct ValueRendererBase { ValueRendererBase(std::basic_ostream& os) : m_os(&os) @@ -124,12 +162,17 @@ struct ValueRendererBase : public boost::static_visitor<> void operator()(const TargetString&) const {} void operator()(const KeyValuePair&) const {} void operator()(const Callable&) const {} + void operator()(const UserFunction&) const {} void operator()(const RendererBase*) const {} + template + void operator()(const boost::recursive_wrapper&) const {} + template + void operator()(const RecWrapper&) const {} std::basic_ostream* m_os; }; -struct InputValueConvertor : public boost::static_visitor> +struct InputValueConvertor { using result_t = boost::optional; @@ -190,11 +233,6 @@ struct InputValueConvertor : public boost::static_visitor + result_t operator()(const RecWrapper& val) const + { + return this->operator()(const_cast(*val.get())); + } + + template + result_t operator()(RecWrapper& val) const { - return result_t(InternalValue(MapAdapter::CreateAdapter(std::move(vals)))); + return this->operator()(*val.get()); } template @@ -700,7 +750,7 @@ struct BooleanEvaluator : BaseVisitor }; template -struct NumberEvaluator : public boost::static_visitor +struct NumberEvaluator { NumberEvaluator(TargetType def = 0) : m_def(def) {} diff --git a/thirdparty/nonstd/CMakeLists.txt b/thirdparty/nonstd/CMakeLists.txt index 874d3640..902e7720 100644 --- a/thirdparty/nonstd/CMakeLists.txt +++ b/thirdparty/nonstd/CMakeLists.txt @@ -3,11 +3,17 @@ project (nonstd) add_library(nonstd INTERFACE) add_library(ThirdParty::nonstd ALIAS nonstd) target_include_directories(nonstd SYSTEM - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include + ${CMAKE_CURRENT_SOURCE_DIR}/variant-light/include + ${CMAKE_CURRENT_SOURCE_DIR}/value-ptr-lite/include ) target_sources(nonstd - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include/nonstd/expected.hpp + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include/nonstd/expected.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/variant-light/include/nonstd/variant.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/value-ptr-lite/include/nonstd/value_ptr.hpp ) diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite new file mode 160000 index 00000000..93af1743 --- /dev/null +++ b/thirdparty/nonstd/value-ptr-lite @@ -0,0 +1 @@ +Subproject commit 93af17436beacf7ffd853a74c5e0c4025d181506 From 6cd8e313786584cf922eb8fd94574566f2f10565 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 2 Oct 2018 01:43:53 +0300 Subject: [PATCH 008/206] Implement 'applymacro' filter --- src/filters.cpp | 46 +++++++++++++++++++++++++++++++ src/filters.h | 11 ++++++++ src/internal_value.h | 23 +++++++++++++--- src/out_stream.h | 24 ++++++++++------ src/render_context.h | 1 + src/statements.cpp | 10 +++---- src/template_impl.h | 64 +++++++++++++++++++++++++++++++++++++------ test/filters_test.cpp | 51 ++++++++++++++++++++++++++++++++++ 8 files changed, 205 insertions(+), 25 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index 53c5d1ac..deafcea5 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -1,4 +1,5 @@ #include "filters.h" +#include "out_stream.h" #include "testers.h" #include "value_visitors.h" #include "value_helpers.h" @@ -31,6 +32,7 @@ struct FilterFactory std::unordered_map s_filters = { {"abs", FilterFactory::MakeCreator(filters::ValueConverter::AbsMode)}, + {"applymacro", &FilterFactory::Create}, {"attr", &FilterFactory::Create}, {"batch", FilterFactory::MakeCreator(filters::Slice::BatchMode)}, {"camelize", FilterFactory::MakeCreator(filters::StringConverter::CamelMode)}, @@ -308,6 +310,50 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte return ListAdapter::CreateAdapter(std::move(result)); } +ApplyMacro::ApplyMacro(FilterParams params) +{ + ParseParams({{"name", true}}, params); + m_mappingParams.kwParams = m_args.extraKwArgs; + m_mappingParams.posParams = m_args.extraPosArgs; +} + +InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& context) +{ + InternalValue macroName = GetArgumentValue("name", context); + if (IsEmpty(macroName)) + return InternalValue(); + + bool macroFound = false; + auto macroValPtr = context.FindValue(AsString(macroName), macroFound); + if (!macroFound) + return InternalValue(); + + const Callable* callable = GetIf(¯oValPtr->second); + if (callable == nullptr || callable->GetKind() != Callable::Macro) + return InternalValue(); + + CallParams callParams; + callParams.kwParams = m_mappingParams.kwParams; + callParams.posParams.reserve(m_mappingParams.posParams.size() + 1); + callParams.posParams.push_back(std::make_shared(baseVal)); + callParams.posParams.insert(callParams.posParams.end(), m_mappingParams.posParams.begin(), m_mappingParams.posParams.end()); + + InternalValue result; + if (callable->GetType() == Callable::Type::Expression) + { + result = callable->GetExpressionCallable()(callParams, context); + } + else + { + TargetString resultStr; + auto stream = context.GetRendererCallback()->GetStreamOnString(resultStr); + callable->GetStatementCallable()(callParams, stream, context); + result = std::move(resultStr); + } + + return result; +} + Map::Map(FilterParams params) { FilterParams newParams; diff --git a/src/filters.h b/src/filters.h index 23d9ac24..016d0041 100644 --- a/src/filters.h +++ b/src/filters.h @@ -22,6 +22,17 @@ class FilterBase : public FunctionBase, public ExpressionFilter::IExpressionFilt { }; +class ApplyMacro : public FilterBase +{ +public: + ApplyMacro(FilterParams params); + + InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + +private: + FilterParams m_mappingParams; +}; + class Attribute : public FilterBase { public: diff --git a/src/internal_value.h b/src/internal_value.h index 837a5f0b..c2782d82 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -362,6 +362,13 @@ struct KeyValuePair class Callable { public: + enum Kind + { + GlobalFunc, + SpecialFunc, + Macro, + UserCallable + }; using ExpressionCallable = std::function; using StatementCallable = std::function; @@ -373,13 +380,15 @@ class Callable Statement }; - Callable(ExpressionCallable&& callable) - : m_callable(std::move(callable)) + Callable(Kind kind, ExpressionCallable&& callable) + : m_kind(kind) + , m_callable(std::move(callable)) { } - Callable(StatementCallable&& callable) - : m_callable(std::move(callable)) + Callable(Kind kind, StatementCallable&& callable) + : m_kind(kind) + , m_callable(std::move(callable)) { } @@ -388,6 +397,11 @@ class Callable return m_callable.index() == 0 ? Type::Expression : Type::Statement; } + auto GetKind() const + { + return m_kind; + } + auto& GetCallable() const { return m_callable; @@ -404,6 +418,7 @@ class Callable } private: + Kind m_kind; CallableHolder m_callable; }; diff --git a/src/out_stream.h b/src/out_stream.h index b667deea..c2863426 100644 --- a/src/out_stream.h +++ b/src/out_stream.h @@ -3,32 +3,40 @@ #include "internal_value.h" #include +#include +#include namespace jinja2 { class OutStream { public: - OutStream(std::function chunkWriter, std::function valueWriter) - : m_bufferWriter(std::move(chunkWriter)) - , m_valueWriter(valueWriter) + struct StreamWriter { - } + virtual ~StreamWriter() {} + + virtual void WriteBuffer(const void* ptr, size_t length) = 0; + virtual void WriteValue(const InternalValue &val) = 0; + }; + + OutStream(std::function writerGetter) + : m_writerGetter(std::move(writerGetter)) + {} void WriteBuffer(const void* ptr, size_t length) { - m_bufferWriter(ptr, length); + m_writerGetter()->WriteBuffer(ptr, length); } void WriteValue(const InternalValue& val) { - m_valueWriter(val); + m_writerGetter()->WriteValue(val); } private: - std::function m_bufferWriter; - std::function m_valueWriter; + std::function m_writerGetter; }; + } // jinja2 #endif // OUT_STREAM_H diff --git a/src/render_context.h b/src/render_context.h index b61afddd..c87e09c1 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -16,6 +16,7 @@ class TemplateImpl; 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; diff --git a/src/statements.cpp b/src/statements.cpp index d91a04af..0dbfd1e0 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -24,7 +24,7 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende context["loop"] = MapAdapter::CreateAdapter(&loopVar); if (m_isRecursive) { - loopVar["operator()"] = Callable([this](const CallParams& params, OutStream& stream, RenderContext& context) { + loopVar["operator()"] = Callable(Callable::GlobalFunc, [this](const CallParams& params, OutStream& stream, RenderContext& context) { bool isSucceeded = false; auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded); if (!isSucceeded) @@ -200,7 +200,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) auto& scope = innerContext.EnterScope(); scope["$$__super_block"] = static_cast(this); - scope["super"] = Callable([this](const CallParams&, OutStream& stream, RenderContext& context) { + scope["super"] = Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { m_mainBody->Render(stream, context); }); if (!m_isScoped) @@ -212,7 +212,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) auto& globalScope = values.GetGlobalScope(); auto selfMap = GetIf(&globalScope[std::string("self")]); if (!selfMap->HasValue(m_name)) - selfMap->SetValue(m_name, MakeWrapped(Callable([this](const CallParams&, OutStream& stream, RenderContext& context) { + selfMap->SetValue(m_name, MakeWrapped(Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { Render(stream, context); }))); } @@ -322,7 +322,7 @@ void MacroStatement::Render(OutStream& os, RenderContext& values) { PrepareMacroParams(values); - values.GetCurrentScope()[m_name] = Callable([this](const CallParams& callParams, OutStream& stream, RenderContext& context) { + values.GetCurrentScope()[m_name] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(callParams, stream, context); }); } @@ -398,7 +398,7 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values) if (hasCallerVal) prevCaller = callerP->second; - curScope["caller"] = Callable([this](const CallParams& callParams, OutStream& stream, RenderContext& context) { + curScope["caller"] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(callParams, stream, context); }); diff --git a/src/template_impl.h b/src/template_impl.h index 5706793d..b4504dc9 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -44,6 +44,53 @@ struct TemplateLoader } }; +template +class GenericStreamWriter : public OutStream::StreamWriter +{ +public: + GenericStreamWriter(std::basic_ostream& os) + : m_os(os) + {} + + // StreamWriter interface + void WriteBuffer(const void* ptr, size_t length) override + { + m_os.write(reinterpret_cast(ptr), length); + } + void WriteValue(const InternalValue& val) override + { + Apply>(val, m_os); + } + +private: + std::basic_ostream& m_os; +}; + +template +class StringStreamWriter : public OutStream::StreamWriter +{ +public: + StringStreamWriter(std::basic_string* targetStr) + : m_targetStr(targetStr) + {} + + // StreamWriter interface + void WriteBuffer(const void* ptr, size_t length) override + { + m_targetStr->append(reinterpret_cast(ptr), length); + // m_os.write(reinterpret_cast(ptr), length); + } + void WriteValue(const InternalValue& val) override + { + std::basic_ostringstream os; + Apply>(val, os); + (*m_targetStr) += os.str(); + } + +private: + std::basic_string* m_targetStr; +}; + template class TemplateImpl : public ITemplateImpl { @@ -90,14 +137,8 @@ class TemplateImpl : public ITemplateImpl 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); - } - ); + OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); + m_renderer->Render(outStream, context); } @@ -139,6 +180,13 @@ class TemplateImpl : public ITemplateImpl return TargetString(os.str()); } + OutStream GetStreamOnString(TargetString& str) override + { + using string_t = std::basic_string; + str = string_t(); + return OutStream([writer = StringStreamWriter(&str.get())]() mutable -> OutStream::StreamWriter* {return &writer;}); + } + nonstd::variant>, ErrorInfo>, nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const override diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 987bc5b2..d8cbccac 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -70,6 +70,57 @@ TEST_P(FilterGroupByTest, Test) EXPECT_EQ(expectedResult, result); } +TEST(FilterGenericTestSingle, ApplyMacroTest) +{ + std::string source = R"( +{% macro test(str) %}{{ str | upper }}{% endmacro %} +{{ 'Hello World!' | applymacro(name='test') }} +{{ ['str1', 'str2', 'str3'] | map('applymacro', name='test') | join(', ') }} +)"; + + 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(PrepareTestData()); + std::cout << result << std::endl; + std::string expectedResult = R"( +HELLO WORLD! +STR1, STR2, STR3 +)"; + EXPECT_EQ(expectedResult, result); +} + +TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest) +{ + std::string source = R"( +{% macro joiner(list, delim) %}{{ list | map('applymacro', name='caller') | join(delim) }}{% endmacro %} +{% call(item) joiner(['str1', 'str2', 'str3'], '->') %}{{item | upper}}{% endcall %} + +)"; + + 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(PrepareTestData()); + std::cout << result << std::endl; + std::string expectedResult = R"( +STR1->STR2->STR3 +)"; + EXPECT_EQ(expectedResult, result); +} + INSTANTIATE_TEST_CASE_P(StringJoin, FilterGenericTest, ::testing::Values( InputOutputPair{"['str1', 'str2', 'str3'] | join", "str1str2str3"}, InputOutputPair{"['str1', 'str2', 'str3'] | join(' ')", "str1 str2 str3"}, From 63b89510172003bd5a882a6be3d764e91b5a2a41 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Tue, 11 Sep 2018 14:51:02 +0200 Subject: [PATCH 009/206] Use boost-cmake from github for the boost dependency --- .gitmodules | 4 ++ CMakeLists.txt | 127 +++++++---------------------------------------- thirdparty/boost | 1 + 3 files changed, 23 insertions(+), 109 deletions(-) create mode 160000 thirdparty/boost diff --git a/.gitmodules b/.gitmodules index 0314b782..8a433643 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ [submodule "thirdparty/nonstd/value-ptr-lite"] path = thirdparty/nonstd/value-ptr-lite url = https://github.com/flexferrum/value-ptr-lite.git +[submodule "thirdparty/boost"] + path = thirdparty/boost + url = https://github.com/Manu343726/boost-cmake.git + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index e643d69c..711d5f8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,50 +1,20 @@ cmake_minimum_required(VERSION 3.0) project(Jinja2Cpp VERSION 0.5.0) -list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED) - message (STATUS "This is DEBUG build with enabled Code Coverage") - set (CMAKE_BUILD_TYPE Debug) - include(code_coverage) - setup_target_for_coverage(${PROJECT_NAME}_coverage jinja2cpp_tests coverage) -endif() - -set(GTEST_ROOT $ENV{GTEST_DIR} CACHE PATH "Path to GTest/GMock library root") -if (NOT UNIX) - set(BOOST_ROOT $ENV{BOOST_DIR} CACHE PATH "Path to boost library root") - set(LIBRARY_TYPE STATIC CACHE PATH "Library link type") -else () - set(BOOST_ROOT "/usr" CACHE PATH "Path to boost library root") - set(LIBRARY_TYPE SHARED CACHE PATH "Library link type") -endif () - -if (NOT DEFINED WITH_TESTS) - set (WITH_TESTS TRUE) -endif () - -set (EXTRA_TEST_LIBS "") -set (CMAKE_CXX_STANDARD 14) -set (CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") if (NOT UNIX) - set (EXTRA_TEST_LIBS "stdc++") set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") - else () - include(CMakeFindDependencyMacro) - find_dependency(Threads) - set (EXTRA_TEST_LIBS Threads::Threads) endif () else () # MSVC - if (NOT DEFINED Boost_USE_STATIC_LIBS) - set (Boost_USE_STATIC_LIBS ON) - endif () if (NOT DEFINED MSVC_RUNTIME_TYPE) set (MSVC_RUNTIME_TYPE "/MD") endif () if (CMAKE_BUILD_TYPE MATCHES "Debug") - message("#######>>>>>>>>>>!!!!!!!!!!!!!! AAAAAAAAAAAAAAAAAAAAAAAAAAAA") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE}d") set (Boost_USE_DEBUG_RUNTIME ON) else () @@ -55,104 +25,43 @@ else () endif () endif() -if("${GTEST_ROOT}" STREQUAL "" AND WITH_TESTS) - set (THIRDPARTY_TARGETS ${THIRDPARTY_TARGETS} gtest) -endif() +set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") +add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/nonstd EXCLUDE_FROM_ALL) -if(NOT "${BOOST_ROOT}" STREQUAL "") - list (APPEND CMAKE_PREFIX_PATH ${BOOST_ROOT}) - set (Boost_DIR ${BOOST_ROOT}) +if(CMAKE_SOURCEDIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(JINJA2CPP_IS_MAIN_PROEJCT TRUE) +else() + set(JINJA2CPP_IS_MAIN_PROEJCT FALSE) endif() -make_directory (${CMAKE_CURRENT_BINARY_DIR}/thirdparty) -execute_process ( - COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty - RESULT_VARIABLE THIRDPARTY_BUILD_RESULT - ) - -if (THIRDPARTY_BUILD_RESULT EQUAL 0 AND THIRDPARTY_TARGETS) - execute_process ( - COMMAND ${CMAKE_COMMAND} --build . --target ${THIRDPARTY_TARGETS} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty - RESULT_VARIABLE THIRDPARTY_BUILD_RESULT - ) -endif () - -if (NOT THIRDPARTY_BUILD_RESULT EQUAL 0) - message (FATAL_ERROR "Can't build thirdparty libraries") -endif () - -if("${GTEST_ROOT}" STREQUAL "" AND WITH_TESTS) - set (GTEST_ROOT ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/gtest/install) - set (GTEST_SELF_BUILD ON) -endif () - -if(NOT "${GTEST_ROOT}" STREQUAL "" AND WITH_TESTS) - list (APPEND CMAKE_PREFIX_PATH ${GTEST_ROOT}) - set (Gtest_DIR ${GTEST_ROOT}) - message(STATUS "GTest library search path: ${Gtest_DIR}") - if (NOT GTEST_SELF_BUILD) - find_package(GTest) - else () - set (Gtest_DIR ${GTEST_ROOT}) - find_package(GTest) - endif () - if (NOT GTEST_INCLUDE_DIRS) - if (MSVC AND NOT GTEST_INCLUDE_DIRS) - set (GTEST_MSVC_SEARCH "MT") - find_package(GTest) - else () - set (GTEST_BOTH_LIBRARIES "optimized;${GTEST_ROOT}/lib/libgtest.a;debug;${GTEST_ROOT}/lib/libgtestd.a;optimized;${GTEST_ROOT}/lib/libgtest_main.a;debug;${GTEST_ROOT}/lib/libgtest_maind.a") - set (GTEST_INCLUDE_DIRS ${GTEST_ROOT}/include) - endif () - endif () -endif() - -if (WITH_TESTS) - find_package(Boost COMPONENTS system filesystem REQUIRED) -else () - find_package(Boost) -endif () - -add_subdirectory (thirdparty/nonstd) +option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROEJCT}) include(collect_sources) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/include - ) - set (LIB_TARGET_NAME jinja2cpp) CollectSources(Sources Headers ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) CollectSources(PublicSources PublicHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include) -if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") -endif() - add_library(${LIB_TARGET_NAME} STATIC ${Sources} ${Headers} ${PublicHeaders} ) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd Boost::boost) # Boost::system Boost::filesystem) +target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd boost_variant boost_filesystem boost_algorithm) target_include_directories(${LIB_TARGET_NAME} - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -if (WITH_TESTS) +if (JINJA2CPP_BUILD_TESTS) enable_testing() - - include_directories( - ${GTEST_INCLUDE_DIRS} - ) + add_subdirectory(thirdparty/gtest) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) - target_link_libraries(jinja2cpp_tests ${GTEST_BOTH_LIBRARIES} ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS} ${Boost_LIBRARIES}) + target_link_libraries(jinja2cpp_tests gtest gtest_main ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS}) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl @@ -164,6 +73,8 @@ if (WITH_TESTS) add_custom_target(CopyTestData ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl ) + + add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) endif () install(TARGETS ${LIB_TARGET_NAME} @@ -176,5 +87,3 @@ install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include install (DIRECTORY thirdparty/nonstd/variant-light/include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/value-ptr-light/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) - -add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) diff --git a/thirdparty/boost b/thirdparty/boost new file mode 160000 index 00000000..83b7a7af --- /dev/null +++ b/thirdparty/boost @@ -0,0 +1 @@ +Subproject commit 83b7a7af2130a9d23077cb9a3a593e5306a37ecc From 7fcfee3af1aff8d6d189aeaa5edbf0cd0b597202 Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Wed, 19 Sep 2018 09:24:32 +0200 Subject: [PATCH 010/206] Enable -Wall and -Werror --- CMakeLists.txt | 4 ++++ src/error_info.cpp | 2 +- src/expression_evaluator.cpp | 16 ++++++++-------- src/filters.cpp | 17 +++-------------- src/internal_value.h | 4 ++-- src/lexertk.h | 2 -- src/string_converter_filter.cpp | 5 +++-- src/template_parser.h | 2 +- src/testers.cpp | 4 ++-- src/value_visitors.h | 7 +------ 10 files changed, 25 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 711d5f8d..ec43d7d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,10 @@ target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd boost_variant target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +if(NOT MSVC) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) +endif() + if (JINJA2CPP_BUILD_TESTS) enable_testing() add_subdirectory(thirdparty/gtest) diff --git a/src/error_info.cpp b/src/error_info.cpp index 742629aa..ff22d01c 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -140,7 +140,7 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e if (extraParams.size() > 1) { os << UNIVERSAL_STR(". Expected: "); - for (int i = 1; i < extraParams.size(); ++ i) + for (std::size_t i = 1; i < extraParams.size(); ++ i) { if (i != 1) os << UNIVERSAL_STR(", "); diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index 1bce7b02..c75a7cba 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -425,9 +425,9 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo ++ argIdx; } - int startPosArg = firstMandatoryIdx == -1 ? 0 : firstMandatoryIdx; - int curPosArg = startPosArg; - int eatenPosArgs = 0; + std::size_t startPosArg = firstMandatoryIdx == -1 ? 0 : firstMandatoryIdx; + std::size_t curPosArg = startPosArg; + std::size_t eatenPosArgs = 0; // Determine the range for positional arguments scanning bool isFirstTime = true; @@ -445,7 +445,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo int prevNotFound = argsInfo[startPosArg].prevNotFound; if (prevNotFound != -1) { - startPosArg = prevNotFound; + startPosArg = static_cast(prevNotFound); } else if (curPosArg == args.size()) { @@ -456,20 +456,20 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo int nextPosArg = argsInfo[curPosArg].nextNotFound; if (nextPosArg == -1) break; - curPosArg = nextPosArg; + curPosArg = static_cast(nextPosArg); } } // Map positional params to the desired arguments - int curArg = startPosArg; - for (int idx = 0; idx < eatenPosArgs && curArg != -1; ++ idx, curArg = argsInfo[curArg].nextNotFound) + auto curArg = static_cast(startPosArg); + for (std::size_t idx = 0; idx < eatenPosArgs && curArg != -1; ++ idx, curArg = argsInfo[curArg].nextNotFound) { result.args[argsInfo[curArg].info->name] = params.posParams[idx]; argsInfo[curArg].state = Positional; } // Fill default arguments (if missing) and check for mandatory - for (int idx = 0; idx < argsInfo.size(); ++ idx) + for (std::size_t idx = 0; idx < argsInfo.size(); ++ idx) { auto& argInfo = argsInfo[idx]; switch (argInfo.state) diff --git a/src/filters.cpp b/src/filters.cpp index deafcea5..6007c3f2 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -244,9 +244,9 @@ InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& cont std::vector tempVector; tempVector.reserve(map->GetSize()); - for (int64_t idx = 0; idx < map->GetSize(); ++ idx) + for (std::size_t idx = 0; idx < map->GetSize(); ++ idx) { - auto val = map->GetValueByIndex(idx); + auto val = map->GetValueByIndex(static_cast(idx)); auto kvVal = Get(val); tempVector.push_back(std::move(kvVal)); } @@ -569,17 +569,6 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte return ConvertToBool(cmpRes); }; - auto equalComparator = [&attrName, &compType](auto& val1, auto& val2) { - InternalValue cmpRes; - - if (IsEmpty(attrName)) - cmpRes = Apply2(val1, val2, BinaryExpression::LogicalEq, compType); - else - cmpRes = Apply2(Subscript(val1, attrName), Subscript(val2, attrName), BinaryExpression::LogicalEq, compType); - - return ConvertToBool(cmpRes); - }; - switch (m_mode) { case FirstItemMode: @@ -618,7 +607,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte case ReverseMode: { InternalValueList resultList(list.GetSize()); - for (int n = 0; n < list.GetSize(); ++ n) + for (std::size_t n = 0; n < list.GetSize(); ++ n) resultList[list.GetSize() - n - 1] = list.GetValueByIndex(n); result = ListAdapter::CreateAdapter(std::move(resultList)); diff --git a/src/internal_value.h b/src/internal_value.h index c2782d82..887da838 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -294,7 +294,7 @@ class ListAdapter::Iterator void increment() { ++ m_current; - m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(m_current); + m_currentVal = m_current == static_cast(m_list->GetSize()) ? InternalValue() : m_list->GetValueByIndex(m_current); } bool equal(const Iterator& other) const @@ -303,7 +303,7 @@ class ListAdapter::Iterator return other.m_list == nullptr ? true : other.equal(*this); if (other.m_list == nullptr) - return m_current == m_list->GetSize(); + return m_current == static_cast(m_list->GetSize()); return this->m_list == other.m_list && this->m_current == other.m_current; } diff --git a/src/lexertk.h b/src/lexertk.h index 06a1df9e..432472ee 100644 --- a/src/lexertk.h +++ b/src/lexertk.h @@ -851,14 +851,12 @@ namespace lexertk ++s_itr_; - bool escaped_found = false; bool escaped = false; while (!is_end(s_itr_)) { if (!escaped && ('\\' == *s_itr_)) { - escaped_found = true; escaped = true; ++s_itr_; diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index b109982f..a22ba1b1 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -162,6 +162,7 @@ StringConverter::StringConverter(FilterParams params, StringConverter::Mode mode case TruncateMode: ParseParams({{"length", false, static_cast(255)}, {"killwords", false, false}, {"end", false, std::string("...")}, {"leeway", false}}, params); break; + default: break; } } @@ -245,12 +246,12 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex auto killWords = ConvertToBool(this->GetArgumentValue("killwords", context)); auto end = GetAsSameString(str, this->GetArgumentValue("end", context)); auto leeway = ConvertToInt(this->GetArgumentValue("leeway", context), 5); - if (str.size() <= length) + if (static_cast(str.size()) <= length) return InternalValue(str); if (killWords) { - if (str.size() > (length + leeway)) + if (static_cast(str.size()) > (length + leeway)) { str.erase(str.begin() + length, str.end()); str += end; diff --git a/src/template_parser.h b/src/template_parser.h index 1127ab68..f17c930e 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -701,7 +701,7 @@ class TemplateParser : public LexerHelper if (actualHeadLen == headLen) { - for (int i = 0; i < col - actualHeadLen - spacePrefixLen; ++ i) + for (std::size_t i = 0; i < col - actualHeadLen - spacePrefixLen; ++ i) os << toCharT(' '); } for (int i = 0; i < actualHeadLen; ++ i) diff --git a/src/testers.cpp b/src/testers.cpp index 7f803044..468c26ec 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -210,14 +210,14 @@ bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context) if (valKind == ValueKind::Integer) { auto intVal = ConvertToInt(val); - result = testMode == (intVal & 1) == (EvenTest ? 0 : 1); + result = (testMode == (intVal & 1)) == (EvenTest ? 0 : 1); } else if (valKind == ValueKind::Double) { auto dblVal = ConvertToDouble(val); int64_t intVal = dblVal; if (dblVal == intVal) - result = testMode == (intVal & 1) == (EvenTest ? 0 : 1); + result = (testMode == (intVal & 1)) == (EvenTest ? 0 : 1); } return result; }; diff --git a/src/value_visitors.h b/src/value_visitors.h index cc3c56dc..3f6a387c 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -795,13 +795,8 @@ struct StringJoiner : BaseVisitor<> } }; -namespace -{ -inline std::string GetSampleString(); -} - template -struct StringConverterImpl : public BaseVisitor()(GetSampleString()))> +struct StringConverterImpl : public BaseVisitor()(std::declval()))> { using R = decltype(std::declval()(std::string())); using BaseVisitor::operator (); From e77db9d6bbfb6bd6e89722575ebfe283cab6f955 Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Wed, 19 Sep 2018 10:02:09 +0200 Subject: [PATCH 011/206] Disable Wparentheses warning This warning is triggered by boost_assert. See https://github.com/boostorg/mpl/issues/31 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec43d7d6..2bbd6a50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) if(NOT MSVC) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error-parentheses) endif() if (JINJA2CPP_BUILD_TESTS) From 2f0533b119c55c09d42870fdc629649ba2d675c2 Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Wed, 19 Sep 2018 10:16:47 +0200 Subject: [PATCH 012/206] Fix -Wpessimizing-move and -Wabsolute-value warnings See https://gitlab.com/Manu343726/tinyrefl/-/jobs/99151647 --- src/filters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index 6007c3f2..c0f01d58 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -650,7 +650,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte int idx = 0; for (auto& v : list) - items.push_back(std::move(Item{IsEmpty(attrName) ? v : Subscript(v, attrName), idx ++})); + items.push_back(Item{IsEmpty(attrName) ? v : Subscript(v, attrName), idx ++}); std::sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) { auto cmpRes = Apply2(i1.val, i2.val, BinaryExpression::LogicalLt, compType); @@ -819,7 +819,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> result = InternalValue(static_cast(val)); break; case ValueConverter::AbsMode: - result = InternalValue(static_cast(abs(val))); + result = InternalValue(static_cast(std::abs(val))); break; case ValueConverter::ToIntMode: case ValueConverter::RoundMode: From 656019e453325ad0106f166fc591eaca70d8acb7 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Wed, 19 Sep 2018 10:27:18 +0200 Subject: [PATCH 013/206] Remove unused lambda capture See https://gitlab.com/Manu343726/tinyrefl/-/jobs/99158574 --- src/filters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filters.cpp b/src/filters.cpp index c0f01d58..cf196c4e 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -665,7 +665,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte }); items.erase(end, items.end()); - std::sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) { + std::sort(items.begin(), items.end(), [](auto& i1, auto& i2) { return i1.idx < i2.idx; }); From 81f2874dbdc2564d0d3926b4ba75bef8ddbe66e7 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Wed, 19 Sep 2018 10:55:08 +0200 Subject: [PATCH 014/206] Remove more warnings --- CMakeLists.txt | 4 +++- src/string_converter_filter.cpp | 2 +- src/template_impl.h | 3 +-- src/template_parser.h | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bbd6a50..daa31651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,9 @@ target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) if(NOT MSVC) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error-parentheses) + target_compile_options(boost_assert INTERFACE -Wno-error-parentheses -Wno-parentheses) + target_compile_options(boost_filesystem PRIVATE -Wno-error-deprecated-declarations -Wno-deprecated-declarations) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) endif() if (JINJA2CPP_BUILD_TESTS) diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index a22ba1b1..c7f63def 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -197,7 +197,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex case WordCountMode: { int64_t wc = 0; - ApplyStringConverter(baseVal, [isDelim = true, &wc, &isAlpha, &isAlNum](auto ch, auto&& fn) mutable { + ApplyStringConverter(baseVal, [isDelim = true, &wc, &isAlNum](auto ch, auto&& fn) mutable { if (isDelim && isAlNum(ch)) { isDelim = false; diff --git a/src/template_impl.h b/src/template_impl.h index b4504dc9..e711f34e 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -130,7 +130,7 @@ class TemplateImpl : public ITemplateImpl auto valRef = &ip.second.data(); auto newParam = visit(visitors::InputValueConvertor(), *valRef); if (!newParam) - intParams[ip.first] = std::move(ValueRef(static_cast(*valRef))); + intParams[ip.first] = ValueRef(static_cast(*valRef)); else intParams[ip.first] = newParam.get(); } @@ -138,7 +138,6 @@ class TemplateImpl : public ITemplateImpl RenderContext context(intParams, &callback); InitRenderContext(context); OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); - m_renderer->Render(outStream, context); } diff --git a/src/template_parser.h b/src/template_parser.h index f17c930e..cb1deb62 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -514,7 +514,7 @@ class TemplateParser : public LexerHelper return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, {range.startOffset, range.startOffset + 1})); tokenizer.begin(); - Lexer lexer([this, &tokenizer, adjust = range.startOffset]() mutable { + Lexer lexer([&tokenizer, adjust = range.startOffset]() mutable { lexertk::token tok = tokenizer.next_token(); tok.position += adjust; return tok; From 83525eec8a1d0e92c7d3976ec53376c8c77b853a Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Thu, 20 Sep 2018 11:52:19 +0200 Subject: [PATCH 015/206] Enable conditional reflection of types --- include/jinja2cpp/reflected_value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index a2813c3e..541e44da 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -22,7 +22,7 @@ struct TypeReflected : TypeReflectedImpl -template +template struct TypeReflection : TypeReflectedImpl { }; From 18eea37653036b19f56314c88d5f52958f0b4b08 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Thu, 20 Sep 2018 12:58:21 +0200 Subject: [PATCH 016/206] include missing std headers in reflected_value.h --- include/jinja2cpp/reflected_value.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 541e44da..2c2499f3 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -2,6 +2,12 @@ #define JINJA2_REFLECTED_VALUE_H #include "value.h" +#include +#include +#include +#include +#include +#include namespace jinja2 { From 7171e609e8edf1dc1743c30b95f56319ba7b7e21 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Thu, 20 Sep 2018 13:54:38 +0200 Subject: [PATCH 017/206] Add FieldAccessor alias at the namespace level FieldAccessor alias of the base class cannot be accessed from a class template --- include/jinja2cpp/reflected_value.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 2c2499f3..097fec02 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -20,10 +20,13 @@ struct TypeReflectedImpl : std::integral_constant { }; +template +using FieldAccessor = std::function; + template struct TypeReflected : TypeReflectedImpl { - using FieldAccessor = std::function; + using FieldAccessor = jinja2::FieldAccessor; }; From e5c5f13b307063833b63fd5d2de17101cc227f91 Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Tue, 2 Oct 2018 12:14:02 +0200 Subject: [PATCH 018/206] Correctly initialize JINJA2CPP_BUILD_TESTS variable It should be true by default if we're building Jinja2Cpp as the main CMake project, else (as a dependency of an upper cmake project) should be false by default. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index daa31651..1feb7d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL " add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) add_subdirectory(thirdparty/nonstd EXCLUDE_FROM_ALL) -if(CMAKE_SOURCEDIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") set(JINJA2CPP_IS_MAIN_PROEJCT TRUE) else() set(JINJA2CPP_IS_MAIN_PROEJCT FALSE) From e57c66aab45e21b9cdddb96e4303474b5bf1b8cb Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Tue, 2 Oct 2018 18:50:42 +0200 Subject: [PATCH 019/206] Disable maybe-uninitialized errors --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1feb7d19..a9b10132 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ target_include_directories(${LIB_TARGET_NAME} if(NOT MSVC) target_compile_options(boost_assert INTERFACE -Wno-error-parentheses -Wno-parentheses) target_compile_options(boost_filesystem PRIVATE -Wno-error-deprecated-declarations -Wno-deprecated-declarations) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error-maybe-uninitialized) endif() if (JINJA2CPP_BUILD_TESTS) From be2c68acba81f0cb6afb593cb482e09b6b619d2e Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Tue, 2 Oct 2018 18:57:36 +0200 Subject: [PATCH 020/206] Use the right switch to disable specific warnings as errors --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9b10132..2d9ec9fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,9 +56,9 @@ target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) if(NOT MSVC) - target_compile_options(boost_assert INTERFACE -Wno-error-parentheses -Wno-parentheses) - target_compile_options(boost_filesystem PRIVATE -Wno-error-deprecated-declarations -Wno-deprecated-declarations) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error-maybe-uninitialized) + target_compile_options(boost_assert INTERFACE -Wno-error=parentheses -Wno-parentheses) + target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations -Wno-deprecated-declarations) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error=maybe-uninitialized) endif() if (JINJA2CPP_BUILD_TESTS) From 03cefe276f99f186c0feea347a15bcd22f1853a5 Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Date: Wed, 3 Oct 2018 01:26:56 +0200 Subject: [PATCH 021/206] Use CheckCXXCompilerFlag module to disable flags if supported --- CMakeLists.txt | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d9ec9fc..95603f25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.0.2) project(Jinja2Cpp VERSION 0.5.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -55,10 +55,25 @@ target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd boost_variant target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + if(NOT MSVC) - target_compile_options(boost_assert INTERFACE -Wno-error=parentheses -Wno-parentheses) - target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations -Wno-deprecated-declarations) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-error=maybe-uninitialized) + # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + + if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) + endif() + if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) + endif() + if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) + endif() + + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) endif() if (JINJA2CPP_BUILD_TESTS) From a82647b3554163548ac665de33834fc75cb47fa0 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 3 Oct 2018 01:14:09 +0300 Subject: [PATCH 022/206] Fix build with MSVC --- CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95603f25..76839935 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,19 +10,23 @@ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") endif () else () + set (COMMON_MSVC_OPTS "/wd4503 /DBOOST_ALL_NO_LIB") + # MSVC if (NOT DEFINED MSVC_RUNTIME_TYPE) set (MSVC_RUNTIME_TYPE "/MD") + set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif () if (CMAKE_BUILD_TYPE MATCHES "Debug") - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE}d") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE}d ${COMMON_MSVC_OPTS}") set (Boost_USE_DEBUG_RUNTIME ON) else () - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_TYPE}") - set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${MSVC_RUNTIME_TYPE}") + set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/PROFILE") set (Boost_USE_DEBUG_RUNTIME OFF) endif () + endif() set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") From 75c99110e4bc7b9a37b9e3065be4e144512a8601 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 4 Oct 2018 09:29:04 +0000 Subject: [PATCH 023/206] Test for macro usage from parent template in child template --- test/extends_test.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/extends_test.cpp b/test/extends_test.cpp index 300e9100..a9d811d6 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -198,3 +198,31 @@ TEST_F(ExtendsTest, ScopedBlocksExtends) ->0123456789<-)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } + + +TEST_F(ExtendsTest, MacroUsage) +{ + m_templateFs->AddFile("base.j2tpl", R"(Hello World! +{% macro testMacro(str) %}{{ str | upper }}{% endmacro %} +{% block regularBlock %}{% endblock regularBlock%} +{% block scopedBlock scoped %}{% endblock scopedBlock%} +)"); + m_templateFs->AddFile("derived.j2tpl", R"({% extends "base.j2tpl" %} +{% block regularBlock %}->{{ testMacro('RegularMacroText') }}<-{% endblock %} +Some Stuff +{% block scopedBlock %}->{{ testMacro('ScopedMacroText') }}<-{% endblock %} +)"); + + auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); + auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); + + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + 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::cout << result << std::endl; + expectedResult = R"(Hello World! +-><-->SCOPEDMACROTEXT<-)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} From e2f31df8b2467071e4fb4a38abc42bb9e703b341 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Thu, 4 Oct 2018 12:01:11 +0200 Subject: [PATCH 024/206] Do not require reflected types to be default constructible --- include/jinja2cpp/reflected_value.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 097fec02..94b92202 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace jinja2 { @@ -91,11 +92,11 @@ class ReflectedMapImpl : public ReflectedMapImplBase> template Value GetField(Fn&& accessor) const { - return accessor(m_valuePtr ? *m_valuePtr : m_value); + return accessor(m_valuePtr ? *m_valuePtr : m_value.get()); } private: - T m_value; + boost::optional m_value; const T* m_valuePtr = nullptr; }; From 540086dcdd82edddf861eba0c1d0d966c0f7ed5e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 6 Oct 2018 02:44:38 +0300 Subject: [PATCH 025/206] Refcounted-based internal value (#62) * Refcounted-based internal value * Fix build on gcc * Fix build with gcc * Fix build with gcc * Fix tests * Fix build on clang * Fix build * Fix .travis.yml --- .travis.yml | 6 +- src/expression_evaluator.cpp | 17 ++++- src/expression_evaluator.h | 10 ++- src/expression_parser.cpp | 5 +- src/filters.cpp | 19 ++++- src/internal_value.cpp | 38 ++++++++-- src/internal_value.h | 142 ++++++++++++++++++++++++++++++----- src/template_parser.h | 2 +- src/value_visitors.h | 8 +- test/expressions_test.cpp | 5 +- test/filters_test.cpp | 2 +- test/test_tools.h | 54 +++++++++++-- 12 files changed, 260 insertions(+), 48 deletions(-) diff --git a/.travis.yml b/.travis.yml index f10fbc5e..303b888b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,11 +60,11 @@ matrix: before_install: - date -u - uname -a - - sudo add-apt-repository -y ppa:samuel-bachmann/boost - - sudo apt-get update -qq +# - sudo add-apt-repository -y ppa:samuel-bachmann/boost +# - sudo apt-get update -qq install: - - sudo apt-get install libboost1.60-all-dev +# - sudo apt-get install libboost1.60-all-dev script: - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index c75a7cba..a1369198 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace jinja2 { @@ -55,7 +56,18 @@ InternalValue ValueRefExpression::Evaluate(RenderContext& values) InternalValue SubscriptExpression::Evaluate(RenderContext& values) { - return Subscript(m_value->Evaluate(values), m_subscriptExpr->Evaluate(values)); + InternalValue cur = m_value->Evaluate(values); + + for (auto idx : m_subscriptExprs) + { + auto subscript = idx->Evaluate(values); + auto newVal = Subscript(cur, subscript); + if (cur.ShouldExtendLifetime()) + newVal.SetParentData(cur); + std::swap(newVal, cur); + } + + return cur; } InternalValue UnaryExpression::Evaluate(RenderContext& values) @@ -252,7 +264,7 @@ InternalValue CallExpression::Evaluate(RenderContext& values) void CallExpression::Render(OutStream& stream, RenderContext& values) { auto fnVal = m_valueRef->Evaluate(values); - Callable* callable = GetIf(&fnVal); + const Callable* callable = GetIf(&fnVal); if (callable == nullptr) { fnVal = Subscript(fnVal, std::string("operator()")); @@ -325,6 +337,7 @@ InternalValue CallExpression::CallGlobalRange(RenderContext& values) { return m_start + m_step * idx; } + bool ShouldExtendLifetime() const override {return false;} private: int64_t m_start; diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index fab44320..95ae654b 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -100,15 +100,19 @@ class ValueRefExpression : public Expression class SubscriptExpression : public Expression { public: - SubscriptExpression(ExpressionEvaluatorPtr value, ExpressionEvaluatorPtr subscriptExpr) + SubscriptExpression(ExpressionEvaluatorPtr value) : m_value(value) - , m_subscriptExpr(subscriptExpr) { } InternalValue Evaluate(RenderContext& values) override; + void AddIndex(ExpressionEvaluatorPtr value) + { + m_subscriptExprs.push_back(value); + } + private: ExpressionEvaluatorPtr m_value; - ExpressionEvaluatorPtr m_subscriptExpr; + std::vector> m_subscriptExprs; }; class ConstantExpression : public Expression diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index bd7945cd..67a709cc 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -471,6 +471,7 @@ ExpressionParser::ParseResult ExpressionParser::ParseCallParams(LexS ExpressionParser::ParseResult> ExpressionParser::ParseSubscript(LexScanner& lexer, ExpressionEvaluatorPtr valueRef) { + ExpressionEvaluatorPtr result = std::make_shared(valueRef); for (Token tok = lexer.NextToken(); tok.type == '.' || tok.type == '['; tok = lexer.NextToken()) { ParseResult> indexExpr; @@ -496,12 +497,12 @@ ExpressionParser::ParseResult> ExpressionPars return MakeParseError(ErrorCode::ExpectedSquareBracket, lexer.PeekNextToken()); } - valueRef = std::make_shared(valueRef, *indexExpr); + result->AddIndex(*indexExpr); } lexer.ReturnToken(); - return valueRef; + return result; } ExpressionParser::ParseResult> ExpressionParser::ParseFilterExpression(LexScanner& lexer) diff --git a/src/filters.cpp b/src/filters.cpp index cf196c4e..901d86a9 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -256,7 +256,14 @@ InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& cont else std::sort(tempVector.begin(), tempVector.end(), [comparator](auto& l, auto& r) {return comparator(l, r);}); - InternalValueList resultList(tempVector.begin(), tempVector.end()); + InternalValueList resultList; + for (auto& tmpVal : tempVector) + { + auto resultVal = InternalValue(std::move(tmpVal)); + if (baseVal.ShouldExtendLifetime()) + resultVal.SetParentData(baseVal); + resultList.push_back(std::move(resultVal)); + } return InternalValue(ListAdapter::CreateAdapter(std::move(resultList))); } @@ -879,6 +886,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> size_t GetSize() const override {return m_str->size();} InternalValue GetValueByIndex(int64_t idx) const override {return InternalValue(m_str->substr(static_cast(idx), 1));} + bool ShouldExtendLifetime() const override {return false;} const string* m_str; }; @@ -892,6 +900,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> size_t GetSize() const override {return m_map->GetSize();} InternalValue GetValueByIndex(int64_t idx) const override {return m_map->GetValueByIndex(idx);} + bool ShouldExtendLifetime() const override {return false;} const MapAdapter* m_map; }; @@ -988,7 +997,7 @@ struct ValueConverterImpl : visitors::BaseVisitor<> params.mode = ValueConverter::ToIntMode; params.base = static_cast(10); InternalValue intVal = Apply(val, params); - T* result = GetIf(&intVal); + const T* result = GetIf(&intVal); if (result == nullptr) return defValue; @@ -1006,7 +1015,11 @@ InternalValue ValueConverter::Filter(const InternalValue& baseVal, RenderContext params.base = GetArgumentValue("base", context); params.prec = GetArgumentValue("precision", context); params.roundMethod = GetArgumentValue("method", context); - return Apply(baseVal, params); + auto result = Apply(baseVal, params); + if (baseVal.ShouldExtendLifetime()) + result.SetParentData(baseVal); + + return result; } } // filters } // jinja2 diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 2bda5c9d..46d77060 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -149,6 +149,7 @@ class ByRef const T& Get() const {return *m_val;} T& Get() {return *const_cast(m_val);} + bool ShouldExtendLifetime() const {return false;} private: const T* m_val; }; @@ -166,10 +167,29 @@ class ByVal const T& Get() const {return m_val;} T& Get() {return m_val;} + bool ShouldExtendLifetime() const {return false;} private: T m_val; }; +template +class BySharedVal +{ +public: + BySharedVal(T&& val) + : m_val(std::make_shared(std::move(val))) + {} + ~BySharedVal() + { + } + + const T& Get() const {return *m_val;} + T& Get() {return *m_val;} + bool ShouldExtendLifetime() const {return true;} +private: + std::shared_ptr m_val; +}; + template class Holder> class GenericListAdapter : public IListAccessor { @@ -183,6 +203,7 @@ class GenericListAdapter : public IListAccessor const auto& val = m_values.Get().GetValueByIndex(idx); return visit(visitors::InputValueConvertor(true), val.data()).get(); } + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; }; @@ -200,6 +221,7 @@ class ValuesListAdapter : public IListAccessor const auto& val = m_values.Get()[idx]; return visit(visitors::InputValueConvertor(false), val.data()).get(); } + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; }; @@ -214,6 +236,7 @@ ListAdapter ListAdapter::CreateAdapter(InternalValueList&& values) size_t GetSize() const override {return m_values.size();} InternalValue GetValueByIndex(int64_t idx) const override {return m_values[static_cast(idx)];} + bool ShouldExtendLifetime() const override {return false;} private: InternalValueList m_values; }; @@ -233,12 +256,12 @@ ListAdapter ListAdapter::CreateAdapter(const ValuesList& values) ListAdapter ListAdapter::CreateAdapter(GenericList&& values) { - return ListAdapter([accessor = GenericListAdapter(std::move(values))]() {return &accessor;}); + return ListAdapter([accessor = GenericListAdapter(std::move(values))]() {return &accessor;}); } ListAdapter ListAdapter::CreateAdapter(ValuesList&& values) { - return ListAdapter([accessor = ValuesListAdapter(std::move(values))]() {return &accessor;}); + return ListAdapter([accessor = ValuesListAdapter(std::move(values))]() {return &accessor;}); } template class Holder> @@ -253,6 +276,7 @@ class SubscriptedListAdapter : public IListAccessor { return Subscript(m_values.Get().GetValueByIndex(idx), m_subscript); } + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; InternalValue m_subscript; @@ -264,7 +288,7 @@ ListAdapter ListAdapter::ToSubscriptedList(const InternalValue& subscript, bool return ListAdapter([accessor = SubscriptedListAdapter(*this, subscript)]() {return &accessor;}); ListAdapter tmp(*this); - return ListAdapter([accessor = SubscriptedListAdapter(std::move(tmp), subscript)]() {return &accessor;}); + return ListAdapter([accessor = SubscriptedListAdapter(std::move(tmp), subscript)]() {return &accessor;}); } InternalValueList ListAdapter::ToValueList() const @@ -325,6 +349,7 @@ class InternalValueMapAdapter : public IMapAccessor } return false; } + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; }; @@ -380,7 +405,7 @@ class GenericMapAdapter : public IMapAccessor { return m_values.Get().GetKeys(); } - + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; @@ -428,6 +453,7 @@ class ValuesMapAdapter : public IMapAccessor return result; } + bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} private: Holder m_values; }; @@ -450,7 +476,7 @@ MapAdapter MapAdapter::CreateAdapter(const GenericMap& values) MapAdapter MapAdapter::CreateAdapter(GenericMap&& values) { - return MapAdapter([accessor = GenericMapAdapter(std::move(values))]() mutable {return &accessor;}); + return MapAdapter([accessor = GenericMapAdapter(std::move(values))]() mutable {return &accessor;}); } MapAdapter MapAdapter::CreateAdapter(const ValuesMap& values) @@ -460,7 +486,7 @@ MapAdapter MapAdapter::CreateAdapter(const ValuesMap& values) MapAdapter MapAdapter::CreateAdapter(ValuesMap&& values) { - return MapAdapter([accessor = ValuesMapAdapter(std::move(values))]() mutable {return &accessor;}); + return MapAdapter([accessor = ValuesMapAdapter(std::move(values))]() mutable {return &accessor;}); } } // jinja2 diff --git a/src/internal_value.h b/src/internal_value.h index 887da838..a9ca9a58 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -94,7 +94,9 @@ struct CallParams; struct KeyValuePair; class RendererBase; -using InternalValue = nonstd::variant, RecursiveWrapper, RendererBase*>; +class InternalValue; +using InternalValueData = nonstd::variant, RecursiveWrapper, RendererBase*>; + using InternalValueRef = ReferenceWrapper; using InternalValueMap = std::unordered_map; using InternalValueList = std::vector; @@ -105,11 +107,19 @@ struct ValueGetter template static auto& Get(V&& val) { - return nonstd::get(std::forward(val)); + return nonstd::get(std::forward(val).GetData()); } + static auto GetPtr(const InternalValue* val); + + + static auto GetPtr(InternalValue* val); + + + + template - static auto GetPtr(V* val) + static auto GetPtr(V* val, std::enable_if_t::value>* ptr = nullptr) { return nonstd::get_if(val); } @@ -125,8 +135,12 @@ struct ValueGetter return ref.GetValue(); } + static auto GetPtr(const InternalValue* val); + + static auto GetPtr(InternalValue* val); + template - static auto GetPtr(V* val) + static auto GetPtr(V* val, std::enable_if_t::value>* ptr = nullptr) { auto ref = nonstd::get_if>(val); return !ref ? nullptr : &ref->GetValue(); @@ -142,24 +156,13 @@ struct IsRecursive : std::true_type {}; template<> struct IsRecursive : std::true_type {}; -template -auto& Get(V&& val) -{ - return ValueGetter::value>::Get(std::forward(val)); -} - -template -auto GetIf(V* val) -{ - return ValueGetter::value>::GetPtr(val); -} - struct IListAccessor { virtual ~IListAccessor() {} virtual size_t GetSize() const = 0; virtual InternalValue GetValueByIndex(int64_t idx) const = 0; + virtual bool ShouldExtendLifetime() const = 0; }; using ListAccessorProvider = std::function; @@ -201,6 +204,15 @@ class ListAdapter return 0; } InternalValue GetValueByIndex(int64_t idx) const; + bool ShouldExtendLifetime() const + { + if (m_accessorProvider && m_accessorProvider()) + { + return m_accessorProvider()->ShouldExtendLifetime(); + } + + return false; + } ListAdapter ToSubscriptedList(const InternalValue& subscript, bool asRef = false) const; InternalValueList ToValueList() const; @@ -265,11 +277,68 @@ class MapAdapter return false; } + bool ShouldExtendLifetime() const + { + if (m_accessorProvider && m_accessorProvider()) + { + return m_accessorProvider()->ShouldExtendLifetime(); + } + + return false; + } private: MapAccessorProvider m_accessorProvider; }; + +class InternalValue +{ +public: + InternalValue() = default; + + template + InternalValue(T&& val, typename std::enable_if, InternalValue>::value>::type* = nullptr) + : m_data(std::forward(val)) + { + } + + auto& GetData() const {return m_data;} + auto& GetData() {return m_data;} + + void SetParentData(const InternalValue& val) + { + m_parentData = val.GetData(); + } + + void SetParentData(InternalValue&& val) + { + m_parentData = std::move(val.GetData()); + } + + bool ShouldExtendLifetime() const + { + if (m_parentData.index() != 0) + return true; + + const MapAdapter* ma = nonstd::get_if(&m_data); + if (ma != nullptr) + return ma->ShouldExtendLifetime(); + + const ListAdapter* la = nonstd::get_if(&m_data); + if (la != nullptr) + return la->ShouldExtendLifetime(); + + return false; + } + + bool IsEmpty() const {return m_data.index() == 0;} + +private: + InternalValueData m_data; + InternalValueData m_parentData; +}; + class ListAdapter::Iterator : public boost::iterator_facade< Iterator, @@ -318,6 +387,45 @@ class ListAdapter::Iterator mutable InternalValue m_currentVal; }; +template +inline auto ValueGetter::GetPtr(const InternalValue* val) +{ + return nonstd::get_if(&val->GetData()); +} + +template +inline auto ValueGetter::GetPtr(InternalValue* val) +{ + return nonstd::get_if(&val->GetData()); +} + +template +inline auto ValueGetter::GetPtr(const InternalValue* val) +{ + auto ref = nonstd::get_if>(&val->GetData()); + return !ref ? nullptr : &ref->GetValue(); +} + +template +inline auto ValueGetter::GetPtr(InternalValue* val) +{ + auto ref = nonstd::get_if>(&val->GetData()); + return !ref ? nullptr : &ref->GetValue(); +} + +template +auto& Get(V&& val) +{ + return ValueGetter::value>::Get(std::forward(val).GetData()); +} + +template +auto GetIf(V* val) +{ + return ValueGetter::value>::GetPtr(val); +} + + inline InternalValue ListAdapter::GetValueByIndex(int64_t idx) const { if (m_accessorProvider && m_accessorProvider()) @@ -424,7 +532,7 @@ class Callable inline bool IsEmpty(const InternalValue& val) { - return nonstd::get_if(&val) != nullptr; + return nonstd::get_if(&val.GetData()) != nullptr; } InternalValue Subscript(const InternalValue& val, const InternalValue& subscript); diff --git a/src/template_parser.h b/src/template_parser.h index cb1deb62..432b7dbf 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -582,7 +582,7 @@ class TemplateParser : public LexerHelper return m_template->substr(tok.range.startOffset, tok.range.size()); else if (tok.type == Token::Identifier) { - if (tok.value.index() != 0) + if (!tok.value.IsEmpty()) { std::basic_string tpl; return GetAsSameString(tpl, tok.value); diff --git a/src/value_visitors.h b/src/value_visitors.h index 3f6a387c..62fa15e9 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -55,7 +55,7 @@ struct RecursiveUnwrapper }; template -auto ApplyUnwrapped(const InternalValue& val, Fn&& fn) +auto ApplyUnwrapped(const InternalValueData& val, Fn&& fn) { auto valueRef = GetIf(&val); auto targetString = GetIf(&val); @@ -75,7 +75,7 @@ auto ApplyUnwrapped(const InternalValue& val, Fn&& fn) template auto Apply(const InternalValue& val, Args&& ... args) { - return detail::ApplyUnwrapped(val, [&args...](auto& val) { + return detail::ApplyUnwrapped(val.GetData(), [&args...](auto& val) { auto v = V(args...); return nonstd::visit(detail::RecursiveUnwrapper(&v), val); }); @@ -84,8 +84,8 @@ auto Apply(const InternalValue& val, Args&& ... args) template auto Apply2(const InternalValue& val1, const InternalValue& val2, Args&& ... args) { - return detail::ApplyUnwrapped(val1, [&val2, &args...](auto& uwVal1) { - return detail::ApplyUnwrapped(val2, [&uwVal1, &args...](auto& uwVal2) { + return detail::ApplyUnwrapped(val1.GetData(), [&val2, &args...](auto& uwVal1) { + return detail::ApplyUnwrapped(val2.GetData(), [&uwVal1, &args...](auto& uwVal2) { auto v = V(args...); return nonstd::visit(detail::RecursiveUnwrapper(&v), uwVal1, uwVal2); }); diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 70dc6d54..90b8e98f 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -207,5 +207,8 @@ INSTANTIATE_TEST_CASE_P(ComplexSubscriptionTest, ExpressionSubstitutionTest, ::t InputOutputPair{"reflectedList[1].strValue[0]", "t"}, InputOutputPair{"(reflectedList[1]).strValue[0]", "t"}, InputOutputPair{"(reflectedList | first).strValue[0]", "t"}, - InputOutputPair{"reflectedVal.strValue[0]", "t"} + InputOutputPair{"reflectedVal.strValue[0]", "t"}, + InputOutputPair{"reflectedVal.innerStruct.strValue", "Hello World!"}, + InputOutputPair{"reflectedVal.innerStructList[5].strValue", "Hello World!"}, + InputOutputPair{"reflectedVal.tmpStructList[5].strValue", "Hello World!"} )); diff --git a/test/filters_test.cpp b/test/filters_test.cpp index d8cbccac..c0c56429 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -377,7 +377,7 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"}, InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"}, InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, - InputOutputPair{"reflectedVal | dictsort | pprint", "['boolValue': false, 'dblValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'wstrValue': '']"} + InputOutputPair{"reflectedVal | dictsort | pprint", "['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} )); INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values( diff --git a/test/test_tools.h b/test/test_tools.h index be61898e..ea04e7f0 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -22,13 +22,26 @@ class InputOutputPairTest : public Base { }; +struct TestInnerStruct +{ + ~TestInnerStruct() {isAlive = false;} + + bool isAlive = true; + std::string strValue = "Hello World!"; +}; + struct TestStruct { + ~TestStruct() {isAlive = false;} + + bool isAlive = true; int64_t intValue; double dblValue; bool boolValue; std::string strValue; std::wstring wstrValue; + std::shared_ptr innerStruct; + std::vector> innerStructList; }; inline jinja2::ValuesMap PrepareTestData() @@ -57,6 +70,9 @@ inline jinja2::ValuesMap PrepareTestData() std::shared_ptr emptyTestStruct; std::shared_ptr filledTestStruct = std::make_shared(sampleStruct); + sampleStruct.innerStruct = std::make_shared(); + for (int n = 0; n < 10; ++ n) + sampleStruct.innerStructList.push_back(std::make_shared()); return jinja2::ValuesMap { {"intValue", 3}, @@ -127,17 +143,45 @@ TEST_P(TestName, Test) \ namespace jinja2 { +template<> +struct TypeReflection : TypeReflected +{ + static auto& GetAccessors() + { + static std::unordered_map accessors = { + {"strValue", [](const TestInnerStruct& obj) {assert(obj.isAlive); return obj.strValue;}}, + }; + + return accessors; + } +}; + template<> struct TypeReflection : TypeReflected { static auto& GetAccessors() { static std::unordered_map accessors = { - {"intValue", [](const TestStruct& obj) {return obj.intValue;}}, - {"dblValue", [](const TestStruct& obj) {return obj.dblValue;}}, - {"boolValue", [](const TestStruct& obj) { return obj.boolValue;}}, - {"strValue", [](const TestStruct& obj) {return obj.strValue;}}, - {"wstrValue", [](const TestStruct& obj) {return obj.wstrValue;}}, + {"intValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.intValue;}}, + {"dblValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.dblValue;}}, + {"boolValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.boolValue;}}, + {"strValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.strValue;}}, + {"wstrValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.wstrValue;}}, + {"innerStruct", [](const TestStruct& obj) + { + assert(obj.isAlive); + return obj.innerStruct ? jinja2::Reflect(obj.innerStruct) : Value(); + }}, + {"innerStructList", [](const TestStruct& obj) {assert(obj.isAlive); return jinja2::Reflect(obj.innerStructList);}}, + {"tmpStructList", [](const TestStruct& obj) + { + assert(obj.isAlive); + using list_t = std::vector>; + list_t vals; + for (int n = 0; n < 10; ++ n) + vals.push_back(std::make_shared()); + return jinja2::Reflect(list_t(vals.begin(), vals.end())); + }}, }; return accessors; From a146f5bdae19d6fe2eff165f03ccf20cde4ddce8 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 7 Oct 2018 00:49:53 +0300 Subject: [PATCH 026/206] Fix lstrip space control behavour --- src/template_parser.h | 12 +++++++++--- test/basic_tests.cpp | 23 ++++++++--------------- test/extends_test.cpp | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/template_parser.h b/src/template_parser.h index 432b7dbf..1471e7ad 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -428,20 +428,26 @@ class TemplateParser : public LexerHelper size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset) { bool doStrip = m_settings.lstripBlocks; + bool doTotalStrip = false; if (ctrlCharPos < m_template->size()) { auto ctrlChar = (*m_template)[ctrlCharPos]; - doStrip = ctrlChar == '+' ? false : (ctrlChar == '-' ? true : doStrip); + if (ctrlChar == '+') + doStrip = false; + else + doTotalStrip = ctrlChar == '-'; + + doStrip |= doTotalStrip; } if (!doStrip || currentBlockInfo.type != TextBlockType::RawText) return endOffset; auto locale = std::locale(); auto& tpl = *m_template; - for (; endOffset > 0; -- endOffset) + for (; endOffset != currentBlockInfo.range.startOffset && endOffset > 0; -- endOffset) { auto ch = tpl[endOffset - 1]; - if (!std::isspace(ch, locale) || ch == '\n') + if (!std::isspace(ch, locale) || (!doTotalStrip && ch == '\n')) break; } return endOffset; diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index a568049e..facf0c3c 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -127,8 +127,7 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - -- + std::string expectedResult = R"(Hello World -- from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } @@ -161,15 +160,14 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - --< + 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 + std::string source = "Hello World\t\t \t" R"( {%- set delim = ' --' %} {{+delim}}< from Parser!)"; @@ -178,16 +176,14 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - --< + 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}}< + std::string source = R"(Hello World{%- set delim = ' --' %} {{-delim}}< from Parser!)"; Template tpl; @@ -195,8 +191,7 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - --< + std::string expectedResult = R"(Hello World --< from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } @@ -229,8 +224,7 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - -- + std::string expectedResult = R"(Hello World -- from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } @@ -246,8 +240,7 @@ from Parser!)"; std::string result = tpl.RenderAsString(ValuesMap{}); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World - -- + std::string expectedResult = R"(Hello World -- from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } diff --git a/test/extends_test.cpp b/test/extends_test.cpp index a9d811d6..4cb8fe7a 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -226,3 +226,25 @@ Some Stuff -><-->SCOPEDMACROTEXT<-)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } + +TEST_F(ExtendsTest, MacroUsageWithTrimming) +{ + m_templateFs->AddFile("base.j2tpl", R"({% macro testMacro(str) -%} +#{{ str | upper }}# +{%- endmacro %} +{%- block body scoped%}{% endblock body%})"); + m_templateFs->AddFile("derived.j2tpl", +R"({% extends "base.j2tpl" %}{% block body %}->{{ testMacro('RegularMacroText') }}<-{% endblock %})"); + + auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); + auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); + + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}); + std::cout << baseResult << std::endl; + std::string expectedResult = ""; + EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}); + std::cout << result << std::endl; + expectedResult = R"(->#REGULARMACROTEXT#<-)"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} From 169d038e458f0fd511b10595d213fd0947d71085 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 23 Oct 2018 09:58:14 +0000 Subject: [PATCH 027/206] Fix 'install' target for build #74 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76839935..a0ac6427 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,5 +110,5 @@ install(TARGETS ${LIB_TARGET_NAME} install (DIRECTORY include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/variant-light/include/ DESTINATION include) -install (DIRECTORY thirdparty/nonstd/value-ptr-light/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/value-ptr-lite/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) From f0f8ee199db6a7c092c0f4742265dc6c0bbc7baf Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Tue, 23 Oct 2018 16:45:03 +0200 Subject: [PATCH 028/206] Rewrite handling of dependencies to use find_package Moved all thirdparty handling code (or at least as much as possible) to thirdparty/CMakeLists.txt. Now all dependencies are searched using standard find_package() calls first, and if not found the config falls back to use the appropiate submodule. Also, updated gtest version (Latest gtest versions include a cmake setup, which simplifies the config {no build external project script needneeded}), and variant-lite to the latest release. --- CMakeLists.txt | 23 +--------- thirdparty/CMakeLists.txt | 94 +++++++++++++++++++++++++++++++++------ thirdparty/gtest | 2 +- 3 files changed, 84 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0ac6427..61a14cf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,7 @@ else () endif() -set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") -add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) -add_subdirectory(thirdparty/nonstd EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") set(JINJA2CPP_IS_MAIN_PROEJCT TRUE) @@ -54,35 +52,18 @@ add_library(${LIB_TARGET_NAME} STATIC ${PublicHeaders} ) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd boost_variant boost_filesystem boost_algorithm) +target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite boost_variant boost_filesystem boost_algorithm) target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) if(NOT MSVC) - # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - - if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) - endif() - if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) - endif() - if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) - endif() - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) endif() if (JINJA2CPP_BUILD_TESTS) enable_testing() - add_subdirectory(thirdparty/gtest) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 0e349e24..fa88c040 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,17 +1,85 @@ -cmake_minimum_required(VERSION 3.0) -project (thirdparty) -list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) +find_package(expected-lite QUIET) +if(expected-lite_FOUND) + add_library(expected-lite ALIAS expected-lite::expected-lite) +else() + message(STATUS "expected-lite not found, using submodule") + add_subdirectory(nonstd/expected-light EXCLUDE_FROM_ALL) +endif() -include (build_thirdparty) +find_package(variant-lite QUIET) +if(variant-lite_FOUND) + add_library(variant-lite ALIAS variant-lite::variant-lite) +else() + message(STATUS "variant-lite not found, using submodule") + add_subdirectory(nonstd/variant-light EXCLUDE_FROM_ALL) + # There's a bug in the lib, the target does not include the header include dirs. + # See https://github.com/martinmoene/variant-lite/issues/25 + target_include_directories(variant-lite INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nonstd/variant-light/include") +endif() -if (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") - if (MSVC_RUNTIME_TYPE STREQUAL "/MD" OR NOT MSVC_RUNTIME_TYPE) - set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE") - else () - set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE") - endif () -endif () +find_package(value-ptr-lite QUIET) +if(value-ptr-lite_FOUND) + add_library(value-ptr-lite ALIAS value-ptr-lite::value-ptr-lite) +else() + message(STATUS "value-ptr-lite not found, using submodule") + add_subdirectory(nonstd/value-ptr-lite EXCLUDE_FROM_ALL) + add_library(value-ptr-lite ALIAS value_ptr-lite) +endif() -BuildThirdparty(gtest ${CMAKE_CURRENT_SOURCE_DIR}/gtest include/gmock/gmock.h "${GTEST_EXTRA_OPTIONS}") -add_subdirectory (nonstd) +find_package(boost_filesystem QUIET) +find_package(boost_algorithm QUIET) +find_package(boost_variant QUIET) +find_package(boost_optional QUIET) + +if(boost_filesystem_FOUND AND + boost_algorithm_FOUND AND + boost_variant_FOUND AND + boost_optional_FOUND) + add_library(boost_filesystem ALIAS boost_filesystem::boost_filesystem) + add_library(boost_algorithm ALIAS boost_algorithm::boost_algorithm) + add_library(boost_variant ALIAS boost_variant::boost_variant) + add_library(boost_optional ALIAS boost_optional::boost_optional) +else() + message(STATUS "One or more boost modules not found, using submodule") + set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") + add_subdirectory(boost EXCLUDE_FROM_ALL) + + if(NOT MSVC) + # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + + if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) + endif() + if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) + endif() + if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) + endif() + endif() +endif() + +if(JINJA2CPP_BUILD_TESTS) + find_package(gtest QUIET) + + if(gtest_FOUND) + add_library(gtest ALIAS gtest::gtest) + else() + message(STATUS "expected-lite not found, using submodule") + + if(MSVC) + if (MSVC_RUNTIME_TYPE STREQUAL "/MD" OR NOT MSVC_RUNTIME_TYPE) + set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE" CACHE INTERNAL "") + else () + set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE" CACHE INTERNAL "") + endif () + endif () + + add_subdirectory(gtest EXCLUDE_FROM_ALL) + endif() +endif() diff --git a/thirdparty/gtest b/thirdparty/gtest index dc043e1c..2fe3bd99 160000 --- a/thirdparty/gtest +++ b/thirdparty/gtest @@ -1 +1 @@ -Subproject commit dc043e1ca6bd509a92452ed54e817b6979869372 +Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2 From 88f520da70da7c6eb0320f0ff9c8522952d49a1f Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Tue, 23 Oct 2018 16:59:02 +0200 Subject: [PATCH 029/206] Update submodules before CMake enters their directories --- thirdparty/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fa88c040..ad8d243c 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,9 +1,15 @@ +function(update_submodule submodule) + find_package(Git REQUIRED) + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init thirdparty/${submodule} + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") +endfunction() find_package(expected-lite QUIET) if(expected-lite_FOUND) add_library(expected-lite ALIAS expected-lite::expected-lite) else() message(STATUS "expected-lite not found, using submodule") + update_submodule(nonstd/expected-light) add_subdirectory(nonstd/expected-light EXCLUDE_FROM_ALL) endif() @@ -12,6 +18,7 @@ if(variant-lite_FOUND) add_library(variant-lite ALIAS variant-lite::variant-lite) else() message(STATUS "variant-lite not found, using submodule") + update_submodule(nonstd/variant-light) add_subdirectory(nonstd/variant-light EXCLUDE_FROM_ALL) # There's a bug in the lib, the target does not include the header include dirs. # See https://github.com/martinmoene/variant-lite/issues/25 @@ -23,6 +30,7 @@ if(value-ptr-lite_FOUND) add_library(value-ptr-lite ALIAS value-ptr-lite::value-ptr-lite) else() message(STATUS "value-ptr-lite not found, using submodule") + update_submodule(nonstd/value-ptr-lite) add_subdirectory(nonstd/value-ptr-lite EXCLUDE_FROM_ALL) add_library(value-ptr-lite ALIAS value_ptr-lite) endif() @@ -42,6 +50,7 @@ if(boost_filesystem_FOUND AND add_library(boost_optional ALIAS boost_optional::boost_optional) else() message(STATUS "One or more boost modules not found, using submodule") + update_submodule(boost) set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") add_subdirectory(boost EXCLUDE_FROM_ALL) @@ -71,6 +80,7 @@ if(JINJA2CPP_BUILD_TESTS) add_library(gtest ALIAS gtest::gtest) else() message(STATUS "expected-lite not found, using submodule") + update_submodule(gtest) if(MSVC) if (MSVC_RUNTIME_TYPE STREQUAL "/MD" OR NOT MSVC_RUNTIME_TYPE) From 1d3bbecd511036025fb9469ba41b6443d0c225ff Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Tue, 23 Oct 2018 17:16:51 +0200 Subject: [PATCH 030/206] Use workaround function to write aliases to imported targets CMake does not support calling add_library(name ALIAS lib) with an imported library as aliased library. See https://cmake.org/pipermail/cmake/2015-May/060576.html --- thirdparty/CMakeLists.txt | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index ad8d243c..5d86f4d5 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -4,9 +4,29 @@ function(update_submodule submodule) WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") endfunction() +function(imported_target_alias ALIAS) + # For some unknown reason CMake does not support creating alias + # libraries from IMPORTED libraries. This function is an ugly workaround + # to get the same + + cmake_parse_arguments("__ALIAS" + "" + "ALIAS" + "" + ${ARGN} + ) + + if(NOT __ALIAS_ALIAS) + message(FATAL_ERROR "imported_target_alias invoked with wrong arguments, missing ALIAS") + endif() + + add_library(${ALIAS} INTERFACE) + target_link_libraries(${ALIAS} INTERFACE ${__ALIAS_ALIAS}) +endfunction() + find_package(expected-lite QUIET) if(expected-lite_FOUND) - add_library(expected-lite ALIAS expected-lite::expected-lite) + imported_target_alias(expected-lite ALIAS expected-lite::expected-lite) else() message(STATUS "expected-lite not found, using submodule") update_submodule(nonstd/expected-light) @@ -15,7 +35,7 @@ endif() find_package(variant-lite QUIET) if(variant-lite_FOUND) - add_library(variant-lite ALIAS variant-lite::variant-lite) + imported_target_alias(variant-lite ALIAS variant-lite::variant-lite) else() message(STATUS "variant-lite not found, using submodule") update_submodule(nonstd/variant-light) @@ -27,7 +47,7 @@ endif() find_package(value-ptr-lite QUIET) if(value-ptr-lite_FOUND) - add_library(value-ptr-lite ALIAS value-ptr-lite::value-ptr-lite) + imported_target_alias(value-ptr-lite ALIAS value-ptr-lite::value-ptr-lite) else() message(STATUS "value-ptr-lite not found, using submodule") update_submodule(nonstd/value-ptr-lite) @@ -44,10 +64,10 @@ if(boost_filesystem_FOUND AND boost_algorithm_FOUND AND boost_variant_FOUND AND boost_optional_FOUND) - add_library(boost_filesystem ALIAS boost_filesystem::boost_filesystem) - add_library(boost_algorithm ALIAS boost_algorithm::boost_algorithm) - add_library(boost_variant ALIAS boost_variant::boost_variant) - add_library(boost_optional ALIAS boost_optional::boost_optional) + imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) + imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) + imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) + imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) else() message(STATUS "One or more boost modules not found, using submodule") update_submodule(boost) @@ -77,7 +97,7 @@ if(JINJA2CPP_BUILD_TESTS) find_package(gtest QUIET) if(gtest_FOUND) - add_library(gtest ALIAS gtest::gtest) + imported_target_alias(gtest ALIAS gtest::gtest) else() message(STATUS "expected-lite not found, using submodule") update_submodule(gtest) From 475db6ecfe22a3371bf9043bf894feb609f2450e Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Wed, 24 Oct 2018 15:14:00 +0200 Subject: [PATCH 031/206] Find optional-lite library too --- CMakeLists.txt | 5 +++-- thirdparty/CMakeLists.txt | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61a14cf5..16705b00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ else () endif() -add_subdirectory(thirdparty) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") set(JINJA2CPP_IS_MAIN_PROEJCT TRUE) @@ -52,7 +51,9 @@ add_library(${LIB_TARGET_NAME} STATIC ${PublicHeaders} ) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite boost_variant boost_filesystem boost_algorithm) +add_subdirectory(thirdparty) + +target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 5d86f4d5..2bfc44da 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -34,15 +34,21 @@ else() endif() find_package(variant-lite QUIET) -if(variant-lite_FOUND) +find_package(optional-lite QUIET) +if(variant-lite_FOUND AND optional-lite_FOUND) imported_target_alias(variant-lite ALIAS variant-lite::variant-lite) + imported_target_alias(optional-lite ALIAS optional-lite::optional-lite) else() - message(STATUS "variant-lite not found, using submodule") + message(STATUS "variant-lite or optional-lite not found, using submodule") update_submodule(nonstd/variant-light) add_subdirectory(nonstd/variant-light EXCLUDE_FROM_ALL) # There's a bug in the lib, the target does not include the header include dirs. # See https://github.com/martinmoene/variant-lite/issues/25 target_include_directories(variant-lite INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nonstd/variant-light/include") + + # Fake target until we use separated optional-lite as submodule + # See https://github.com/martinmoene/variant-lite/issues/19 + add_library(optional-lite ALIAS variant-lite) endif() find_package(value-ptr-lite QUIET) @@ -52,6 +58,7 @@ else() message(STATUS "value-ptr-lite not found, using submodule") update_submodule(nonstd/value-ptr-lite) add_subdirectory(nonstd/value-ptr-lite EXCLUDE_FROM_ALL) + add_library(value-ptr-lite ALIAS value_ptr-lite) endif() From abca5dd9b59cb6158b101f4f8751b8e5a64627b9 Mon Sep 17 00:00:00 2001 From: Manu343726 Date: Thu, 25 Oct 2018 10:18:04 +0200 Subject: [PATCH 032/206] Append boost libs to not override libraries requested by Jinja2Cpp users --- thirdparty/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 2bfc44da..79c3251d 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -78,7 +78,8 @@ if(boost_filesystem_FOUND AND else() message(STATUS "One or more boost modules not found, using submodule") update_submodule(boost) - set(BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional CACHE INTERNAL "") + list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) + set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") add_subdirectory(boost EXCLUDE_FROM_ALL) if(NOT MSVC) From 96a9e12f5357341811ac982ae21db9b41090580c Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 26 Oct 2018 16:56:41 +0300 Subject: [PATCH 033/206] Debug Win CI builds --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16705b00..41fb2f28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.0.2) project(Jinja2Cpp VERSION 0.5.0) +cmake_policy(SET CMP0074 OLD) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_CXX_STANDARD 14) From 2722df7c229a458b4484f4ed018f1cad9f328a55 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 26 Oct 2018 16:57:36 +0300 Subject: [PATCH 034/206] [skip ci] revert changes from master --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41fb2f28..16705b00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,6 @@ cmake_minimum_required(VERSION 3.0.2) project(Jinja2Cpp VERSION 0.5.0) -cmake_policy(SET CMP0074 OLD) - list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_CXX_STANDARD 14) From 8871737218711de70f4752411304b57e935ad5f2 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 04:56:55 +0300 Subject: [PATCH 035/206] Preparing release 0.9.1: warnings fix, Win CI build fix, build system improvement (#81) * Fix build with C++17 enabled and system-provided boost package * Fix build * Fix build * Build scripts cleanup and Readme file update * Fix readme, improve appveyor matrix * Debug Win-based CI * Debug Win-based CI * Debug Win-based CI * Fix runtime type bypass to gtest submodule * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI builds * Debug Win CI build Fix MSVC warnings * Debug Win CI build * [skip ci] Update readme --- .travis.yml | 27 ++++++++-- CMakeLists.txt | 36 ++++++++++--- README.md | 22 ++++++-- appveyor.yml | 47 +++++++++++++---- include/jinja2cpp/value.h | 16 +++++- src/error_info.cpp | 2 +- src/expression_evaluator.cpp | 2 +- src/filesystem_handler.cpp | 8 +-- src/filters.cpp | 18 +++---- src/internal_value.h | 12 ++--- src/statements.cpp | 6 +-- src/string_converter_filter.cpp | 2 +- src/template_parser.cpp | 8 +-- src/testers.cpp | 2 +- src/value_visitors.h | 4 +- thirdparty/CMakeLists.txt | 91 +++++++++++++++++++++++---------- 16 files changed, 216 insertions(+), 87 deletions(-) diff --git a/.travis.yml b/.travis.yml index 303b888b..dbda5c85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,26 @@ matrix: sources: ['ubuntu-toolchain-r-test'] packages: ['cmake', 'g++-7'] + - os: linux + compiler: gcc + env: + COMPILER=g++-7 + CMAKE_CXX_FLAGS=-std=c++17 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['cmake', 'g++-7'] + + - os: linux + compiler: gcc + env: + COMPILER=g++-7 + SYSTEM_BOOST_PACKAGE=YES + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['cmake', 'g++-7'] + - os: linux compiler: clang env: COMPILER=clang++-5.0 @@ -60,11 +80,10 @@ matrix: before_install: - date -u - uname -a -# - sudo add-apt-repository -y ppa:samuel-bachmann/boost -# - sudo apt-get update -qq + - if [[ "${SYSTEM_BOOST_PACKAGE}" != "" ]]; then sudo add-apt-repository -y ppa:samuel-bachmann/boost && sudo apt-get update -qq; fi install: -# - sudo apt-get install libboost1.60-all-dev + - if [[ "${SYSTEM_BOOST_PACKAGE}" != "" ]]; then sudo apt-get install libboost1.60-all-dev; fi script: - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi @@ -73,7 +92,7 @@ script: - $CXX --version - mkdir -p build && cd build - - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG .. && cmake --build . --config $BUILD_CONFIG -- -j4 + - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS .. && cmake --build . --config $BUILD_CONFIG -- -j4 - ctest -C $BUILD_CONFIG -V diff --git a/CMakeLists.txt b/CMakeLists.txt index 16705b00..a75966c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,12 @@ cmake_minimum_required(VERSION 3.0.2) -project(Jinja2Cpp VERSION 0.5.0) +project(Jinja2Cpp VERSION 0.9.1) + +if (${CMAKE_VERSION} VERSION_GREATER "3.12") + cmake_policy(SET CMP0074 OLD) +endif () list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") if (NOT UNIX) set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") @@ -13,12 +15,11 @@ else () set (COMMON_MSVC_OPTS "/wd4503 /DBOOST_ALL_NO_LIB") # MSVC - if (NOT DEFINED MSVC_RUNTIME_TYPE) - set (MSVC_RUNTIME_TYPE "/MD") - set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) + if (CMAKE_BUILD_TYPE MATCHES "Debug" AND MSVC_RUNTIME_TYPE) + set (MSVC_RUNTIME_TYPE "${MSVC_RUNTIME_TYPE}d") endif () if (CMAKE_BUILD_TYPE MATCHES "Debug") - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE}d ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") set (Boost_USE_DEBUG_RUNTIME ON) else () set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") @@ -51,6 +52,9 @@ add_library(${LIB_TARGET_NAME} STATIC ${PublicHeaders} ) +string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) +set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") + add_subdirectory(thirdparty) target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) @@ -61,14 +65,32 @@ target_include_directories(${LIB_TARGET_NAME} if(NOT MSVC) target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) +else () + target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() +target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) +set_target_properties(${LIB_TARGET_NAME} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON) + if (JINJA2CPP_BUILD_TESTS) enable_testing() CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) target_link_libraries(jinja2cpp_tests gtest gtest_main ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS}) + + get_target_property(TEST_CXX_STD jinja2cpp_tests CXX_STANDARD) + + string (FIND "${CURRENT_CXX_FLAGS}" "-std" TEST_FLAGS_STD_POS) + string (FIND "${TEST_CXX_STD}" "NOTFOUND" TEST_CXX_STD_NOTFOUND_POS) + + if (NOT MSVC AND TEST_FLAGS_STD_POS EQUAL -1 AND NOT (TEST_CXX_STD_NOTFOUND_POS EQUAL -1)) + set_target_properties(jinja2cpp_tests PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON) + endif () add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl diff --git a/README.md b/README.md index 9ea8d45c..a1eb30b7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/flexferrum/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/flexferrum/Jinja2Cpp/master/LICENSE) +[![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. Unlike [inja](https://github.com/pantor/inja) lib, you have to build Jinja2Cpp, but it has only one dependence: boost. @@ -33,7 +34,9 @@ C++ implementation of big subset of Jinja2 template engine features. This librar - [Build and install](#build-and-install) - [Additional CMake build flags](#additional-cmake-build-flags) - [Link with you projects](#link-with-you-projects) +- [Acknowledgments](#acknowledgments) - [Changelog](#changelog) + - [Version 0.9.1](#version-091) - [Version 0.9](#version-09) - [Version 0.6](#version-06) @@ -526,8 +529,8 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f - Linux gcc 6.0 - Linux gcc 7.0 - Linux clang 5.0 -- Microsoft Visual Studio 2015 x86 -- Microsoft Visual Studio 2017 x86 +- Microsoft Visual Studio 2015 x86, x64 +- Microsoft Visual Studio 2017 x86, x64 # Build and install 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. @@ -573,7 +576,7 @@ In order to compile Jinja2Cpp you need: ## Additional CMake build flags You can define (via -D command line CMake option) the following build flags: -* **WITH_TESTS** (default TRUE) - build or not Jinja2Cpp tests. +* **JINJA2CPP_BUILD_TESTS** (default TRUE) - build or not Jinja2Cpp tests. * **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). * **LIBRARY_TYPE** Could be STATIC (default for Windows platform) or SHARED (default for Linux). Specify the type of Jinja2Cpp library to build. @@ -615,7 +618,20 @@ target_link_libraries(YourTarget #... ``` +# Acknowledgments +Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. + +Thanks to @martinmoene for perfectly implemented xxx-lite libraries. + # Changelog +## Version 0.9.1 +* `applymacro` filter added which allows to apply arbitrary macro as a filter +* dependencies to boost removed from the public interface +* CMake scripts improved +* Various bugs fixed +* Improve reflection +* Warnings cleanup + ## Version 0.9 * Support of 'extents'/'block' statements * Support of 'macro'/'call' statements diff --git a/appveyor.yml b/appveyor.yml index 89d7a5a9..dde3d0ca 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,29 +1,54 @@ -version: 0.9.{build} +version: 0.9.1.{build} os: - Visual Studio 2015 - Visual Studio 2017 +platform: + - Win32 + - x64 + +configuration: + - Debug + - Release + environment: matrix: - - PLATFORM: x64 - - PLATFORM: x86 + - BUILD_PLATFORM: x64 + MSVC_RUNTIME_TYPE: /MD + - BUILD_PLATFORM: x64 + MSVC_RUNTIME_TYPE: /MT + - BUILD_PLATFORM: x64 + MSVC_RUNTIME_TYPE: + - BUILD_PLATFORM: x86 + MSVC_RUNTIME_TYPE: /MD + - BUILD_PLATFORM: x86 + MSVC_RUNTIME_TYPE: /MT + - BUILD_PLATFORM: x86 + MSVC_RUNTIME_TYPE: matrix: fast_finish: false + exclude: + - platform: Win32 + BUILD_PLATFORM: x64 + - platform: x64 + BUILD_PLATFORM: x86 init: - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 - -install: - - git submodule -q update --init + - set BOOST_ROOT=C:\Libraries\boost_1_65_1 + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.1;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" set PATH=%BOOST_ROOT%\lib64-msvc-14.1;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.0;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x64" set PATH=%BOOST_ROOT%\lib64-msvc-14.0;%PATH% build_script: - mkdir -p build && cd build - - set BOOST_DIR=C:\Libraries\boost_1_65_1 - - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release + - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%configuration% -DMSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% - cmake --build . --target all test_script: diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 1325811f..6bf3a9c3 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -102,7 +102,13 @@ class Value { public: using ValueData = nonstd::variant, RecWrapper, GenericList, GenericMap, UserFunction>; - Value() = default; + Value(); + Value(const Value& val); + Value(Value&& val); + ~Value(); + + Value& operator =(const Value&); + Value& operator =(Value&&); template Value(T&& val, typename std::enable_if, Value>::value && !std::is_same, ValuesList>::value>::type* = nullptr) : m_data(std::forward(val)) @@ -211,6 +217,14 @@ inline Value GenericList::GetValueByIndex(int64_t index) const return m_accessor()->GetValueByIndex(index); } +inline Value::Value() = default; +inline Value::Value(const Value& val) = default; +inline Value::Value(Value&& val) = default; +inline Value::~Value() = default; +inline Value& Value::operator =(const Value&) = default; +inline Value& Value::operator =(Value&&) = default; + + } // jinja2 #endif // JINJA2_VALUE_H diff --git a/src/error_info.cpp b/src/error_info.cpp index ff22d01c..df9ba4de 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -76,7 +76,7 @@ struct ValueRenderer } - void operator() (const UserFunction& val) const + void operator() (const UserFunction&) const { } diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index a1369198..43562ed2 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -455,7 +455,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo continue; } - int prevNotFound = argsInfo[startPosArg].prevNotFound; + prevNotFound = argsInfo[startPosArg].prevNotFound; if (prevNotFound != -1) { startPosArg = static_cast(prevNotFound); diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index fd7cd8cb..744aefe4 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -21,13 +21,13 @@ struct FileContentConverter { sPtr->reset(new std::wistringstream(content)); } - void operator() (const std::wstring& content, CharFileStreamPtr* sPtr) const + void operator() (const std::wstring&, CharFileStreamPtr*) const { // CharFileStreamPtr stream(new std::istringstream(content), [](std::istream* s) {delete static_cast(s);}); // std::swap(*sPtr, stream); } - void operator() (const std::string& content, WCharFileStreamPtr* sPtr) const + void operator() (const std::string&, WCharFileStreamPtr*) const { // WCharFileStreamPtr stream(new std::wistringstream(content), [](std::wistream* s) {delete static_cast(s);}); // std::swap(*sPtr, stream); @@ -86,7 +86,7 @@ CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const if (result->good()) return result; - return CharFileStreamPtr(nullptr, [](std::istream* s){}); + return CharFileStreamPtr(nullptr, [](std::istream*){}); } WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const @@ -99,7 +99,7 @@ WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const if (result->good()) return result; - return WCharFileStreamPtr(nullptr, [](std::wistream* s){;}); + return WCharFileStreamPtr(nullptr, [](std::wistream*){;}); } } // jinja2 diff --git a/src/filters.cpp b/src/filters.cpp index 901d86a9..df45a0fd 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -469,7 +469,7 @@ struct PrettyPrinter : visitors::BaseVisitor return "'"s + str + "'"s; } - InternalValue operator()(const std::wstring& str) const + InternalValue operator()(const std::wstring&) const { return "''"s; } @@ -479,7 +479,7 @@ struct PrettyPrinter : visitors::BaseVisitor return val ? "true"s : "false"s; } - InternalValue operator()(EmptyValue val) const + InternalValue operator()(EmptyValue) const { return "none"s; } @@ -515,7 +515,7 @@ Random::Random(FilterParams params) } -InternalValue Random::Filter(const InternalValue& baseVal, RenderContext& context) +InternalValue Random::Filter(const InternalValue&, RenderContext&) { return InternalValue(); } @@ -687,32 +687,32 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte return result; } -Serialize::Serialize(FilterParams params, Serialize::Mode mode) +Serialize::Serialize(FilterParams, Serialize::Mode) { } -InternalValue Serialize::Filter(const InternalValue& baseVal, RenderContext& context) +InternalValue Serialize::Filter(const InternalValue&, RenderContext&) { return InternalValue(); } -Slice::Slice(FilterParams params, Slice::Mode mode) +Slice::Slice(FilterParams, Slice::Mode) { } -InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context) +InternalValue Slice::Filter(const InternalValue&, RenderContext&) { return InternalValue(); } -StringFormat::StringFormat(FilterParams params, StringFormat::Mode mode) +StringFormat::StringFormat(FilterParams, StringFormat::Mode) { } -InternalValue StringFormat::Filter(const InternalValue& baseVal, RenderContext& context) +InternalValue StringFormat::Filter(const InternalValue&, RenderContext&) { return InternalValue(); } diff --git a/src/internal_value.h b/src/internal_value.h index a9ca9a58..aa5731c3 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -111,15 +111,9 @@ struct ValueGetter } static auto GetPtr(const InternalValue* val); - - static auto GetPtr(InternalValue* val); - - - - template - static auto GetPtr(V* val, std::enable_if_t::value>* ptr = nullptr) + static auto GetPtr(V* val, std::enable_if_t::value>* = nullptr) { return nonstd::get_if(val); } @@ -140,7 +134,7 @@ struct ValueGetter static auto GetPtr(InternalValue* val); template - static auto GetPtr(V* val, std::enable_if_t::value>* ptr = nullptr) + static auto GetPtr(V* val, std::enable_if_t::value>* = nullptr) { auto ref = nonstd::get_if>(val); return !ref ? nullptr : &ref->GetValue(); @@ -172,7 +166,7 @@ struct IMapAccessor : public IListAccessor virtual bool HasValue(const std::string& name) const = 0; virtual InternalValue GetValueByName(const std::string& name) const = 0; virtual std::vector GetKeys() const = 0; - virtual bool SetValue(std::string name, const InternalValue& val) {return false;} + virtual bool SetValue(std::string, const InternalValue&) {return false;} }; using MapAccessorProvider = std::function; diff --git a/src/statements.cpp b/src/statements.cpp index 0dbfd1e0..c7406290 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -318,7 +318,7 @@ void MacroStatement::PrepareMacroParams(RenderContext& values) } } -void MacroStatement::Render(OutStream& os, RenderContext& values) +void MacroStatement::Render(OutStream&, RenderContext& values) { PrepareMacroParams(values); @@ -373,7 +373,7 @@ void MacroStatement::SetupCallArgs(const std::vector& argsInfo, co varArgs.push_back(a->Evaluate(context)); } -void MacroStatement::SetupMacroScope(InternalValueMap& scope) +void MacroStatement::SetupMacroScope(InternalValueMap&) { ; } @@ -410,7 +410,7 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values) values.GetCurrentScope().erase("caller"); } -void MacroCallStatement::SetupMacroScope(InternalValueMap& scope) +void MacroCallStatement::SetupMacroScope(InternalValueMap&) { } diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index c7f63def..bedaded8 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -197,7 +197,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex case WordCountMode: { int64_t wc = 0; - ApplyStringConverter(baseVal, [isDelim = true, &wc, &isAlNum](auto ch, auto&& fn) mutable { + ApplyStringConverter(baseVal, [isDelim = true, &wc, &isAlNum](auto ch, auto&&) mutable { if (isDelim && isAlNum(ch)) { isDelim = false; diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 44455684..5b6454aa 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -157,7 +157,7 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseEndFor(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndFor(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); @@ -232,7 +232,7 @@ StatementsParser::ParseResult StatementsParser::ParseElIf(LexScanner& lexer, Sta return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseEndIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndIf(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); @@ -501,7 +501,7 @@ nonstd::expected StatementsParser::ParseMacroParams(Lex return std::move(items); } -StatementsParser::ParseResult StatementsParser::ParseEndMacro(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndMacro(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); @@ -569,7 +569,7 @@ StatementsParser::ParseResult StatementsParser::ParseCall(LexScanner& lexer, Sta return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseEndCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndCall(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); diff --git a/src/testers.cpp b/src/testers.cpp index 468c26ec..b83c4d49 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -215,7 +215,7 @@ bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context) else if (valKind == ValueKind::Double) { auto dblVal = ConvertToDouble(val); - int64_t intVal = dblVal; + int64_t intVal = static_cast(dblVal); if (dblVal == intVal) result = (testMode == (intVal & 1)) == (EvenTest ? 0 : 1); } diff --git a/src/value_visitors.h b/src/value_visitors.h index 62fa15e9..b86c30b8 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -244,7 +244,7 @@ struct InputValueConvertor return result_t(InternalValue(MapAdapter::CreateAdapter(vals))); } - result_t operator() (const UserFunction& val) const + result_t operator() (const UserFunction&) const { return result_t(); } @@ -848,7 +848,7 @@ auto ApplyStringConverter(const InternalValue& str, Fn&& fn) } template -auto GetAsSameString(const std::basic_string& s, const InternalValue& val) +auto GetAsSameString(const std::basic_string&, const InternalValue& val) { return Apply>(val); } diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 79c3251d..7a4f6013 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -62,6 +62,23 @@ else() add_library(value-ptr-lite ALIAS value_ptr-lite) endif() +if(MSVC) + set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) + if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") + string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) + string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) + if (NOT THIRDPARTY_MT_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MT") + elseif (NOT THIRDPARTY_MD_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MD") + else () + message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") + set (THIRDPARTY_RUNTIME_TYPE "/MD") + endif() + endif () +endif () + + find_package(boost_filesystem QUIET) find_package(boost_algorithm QUIET) find_package(boost_variant QUIET) @@ -76,27 +93,49 @@ if(boost_filesystem_FOUND AND imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) else() - message(STATUS "One or more boost modules not found, using submodule") - update_submodule(boost) - list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) - set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") - add_subdirectory(boost EXCLUDE_FROM_ALL) - - if(NOT MSVC) - # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - - if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) - endif() - if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) - endif() - if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) + if (MSVC) + if (NOT DEFINED Boost_USE_STATIC_LIBS) + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (Boost_USE_STATIC_LIBS OFF) + set (Boost_USE_STATIC_RUNTIME OFF) + else () + set (Boost_USE_STATIC_LIBS ON) + set (Boost_USE_STATIC_RUNTIME ON) + endif () + endif () + endif () + + find_package(Boost COMPONENTS system filesystem QUIET) + + if (Boost_FOUND) + imported_target_alias(boost_filesystem ALIAS Boost::filesystem) + imported_target_alias(boost_algorithm ALIAS Boost::boost) + imported_target_alias(boost_variant ALIAS Boost::boost) + imported_target_alias(boost_optional ALIAS Boost::boost) + else() + message(STATUS "One or more boost modules not found, using submodule") + update_submodule(boost) + list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) + set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") + add_subdirectory(boost EXCLUDE_FROM_ALL) + + if(NOT MSVC) + # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + + if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) + endif() + if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) + endif() + if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) + endif() + else () endif() endif() endif() @@ -111,11 +150,11 @@ if(JINJA2CPP_BUILD_TESTS) update_submodule(gtest) if(MSVC) - if (MSVC_RUNTIME_TYPE STREQUAL "/MD" OR NOT MSVC_RUNTIME_TYPE) - set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE" CACHE INTERNAL "") - else () - set (GTEST_EXTRA_OPTIONS "-Dgtest_force_shared_crt=TRUE" CACHE INTERNAL "") - endif () + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) + else () + set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) + endif () endif () add_subdirectory(gtest EXCLUDE_FROM_ALL) From 3c5017c786af24c4f715a39bab6b38a1f9a9e702 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 12:59:53 +0300 Subject: [PATCH 036/206] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a1eb30b7..3b2cbbf0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. Unlike [inja](https://github.com/pantor/inja) lib, you have to build Jinja2Cpp, but it has only one dependence: boost. + # Table of contents From 45de8d394c9a8e68cd53ae60a6ea3078130e9424 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 13:03:08 +0300 Subject: [PATCH 037/206] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3b2cbbf0..a1eb30b7 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. Unlike [inja](https://github.com/pantor/inja) lib, you have to build Jinja2Cpp, but it has only one dependence: boost. - # Table of contents From 2daa2fc0e4b6abe3194b3e625e1b8413b392218c Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 16:34:59 +0300 Subject: [PATCH 038/206] Remove wrong-spelled submodules --- .gitmodules | 6 ------ thirdparty/nonstd/expected-light | 1 - thirdparty/nonstd/variant-light | 1 - 3 files changed, 8 deletions(-) delete mode 160000 thirdparty/nonstd/expected-light delete mode 160000 thirdparty/nonstd/variant-light diff --git a/.gitmodules b/.gitmodules index 8a433643..2365dbc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,6 @@ [submodule "thirdparty/gtest"] path = thirdparty/gtest url = https://github.com/google/googletest.git -[submodule "thirdparty/nonstd/variant-light"] - path = thirdparty/nonstd/variant-light - url = https://github.com/martinmoene/variant-lite.git -[submodule "thirdparty/nonstd/expected-light"] - path = thirdparty/nonstd/expected-light - url = https://github.com/martinmoene/expected-lite.git [submodule "thirdparty/nonstd/value-ptr-lite"] path = thirdparty/nonstd/value-ptr-lite url = https://github.com/flexferrum/value-ptr-lite.git diff --git a/thirdparty/nonstd/expected-light b/thirdparty/nonstd/expected-light deleted file mode 160000 index ae103c6e..00000000 --- a/thirdparty/nonstd/expected-light +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae103c6e1bfdd7ba92c96f1ae7cb357de9a3c3fb diff --git a/thirdparty/nonstd/variant-light b/thirdparty/nonstd/variant-light deleted file mode 160000 index 30849020..00000000 --- a/thirdparty/nonstd/variant-light +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 308490204d53fcd825163395e2278919b945701d From ac84be983226367eba5eaf66c13c2c6cb10f81ba Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 16:42:56 +0300 Subject: [PATCH 039/206] Fix typo: "nonstd-light" -> "nonstd-lite" --- .gitmodules | 6 ++++++ CMakeLists.txt | 4 ++-- include/jinja2cpp/error_info.h | 2 -- thirdparty/CMakeLists.txt | 10 +++++----- thirdparty/nonstd/expected-lite | 1 + thirdparty/nonstd/variant-lite | 1 + 6 files changed, 15 insertions(+), 9 deletions(-) create mode 160000 thirdparty/nonstd/expected-lite create mode 160000 thirdparty/nonstd/variant-lite diff --git a/.gitmodules b/.gitmodules index 2365dbc0..e42d90c6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,9 @@ path = thirdparty/boost url = https://github.com/Manu343726/boost-cmake.git branch = master +[submodule "thirdparty/nonstd/expected-lite"] + path = thirdparty/nonstd/expected-lite + url = https://github.com/martinmoene/expected-lite.git +[submodule "thirdparty/nonstd/variant-lite"] + path = thirdparty/nonstd/variant-lite + url = https://github.com/martinmoene/variant-lite.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a75966c5..5d0d364d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,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 (DIRECTORY thirdparty/nonstd/variant-light/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/expected-lite/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/variant-lite/include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/value-ptr-lite/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 0dac9006..aad7c081 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -3,8 +3,6 @@ #include "value.h" -#include - #include #include diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 7a4f6013..04072b67 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -29,8 +29,8 @@ if(expected-lite_FOUND) imported_target_alias(expected-lite ALIAS expected-lite::expected-lite) else() message(STATUS "expected-lite not found, using submodule") - update_submodule(nonstd/expected-light) - add_subdirectory(nonstd/expected-light EXCLUDE_FROM_ALL) + update_submodule(nonstd/expected-lite) + add_subdirectory(nonstd/expected-lite EXCLUDE_FROM_ALL) endif() find_package(variant-lite QUIET) @@ -40,11 +40,11 @@ if(variant-lite_FOUND AND optional-lite_FOUND) imported_target_alias(optional-lite ALIAS optional-lite::optional-lite) else() message(STATUS "variant-lite or optional-lite not found, using submodule") - update_submodule(nonstd/variant-light) - add_subdirectory(nonstd/variant-light EXCLUDE_FROM_ALL) + update_submodule(nonstd/variant-lite) + add_subdirectory(nonstd/variant-lite EXCLUDE_FROM_ALL) # There's a bug in the lib, the target does not include the header include dirs. # See https://github.com/martinmoene/variant-lite/issues/25 - target_include_directories(variant-lite INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nonstd/variant-light/include") + target_include_directories(variant-lite INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nonstd/variant-lite/include") # Fake target until we use separated optional-lite as submodule # See https://github.com/martinmoene/variant-lite/issues/19 diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite new file mode 160000 index 00000000..6944fb26 --- /dev/null +++ b/thirdparty/nonstd/expected-lite @@ -0,0 +1 @@ +Subproject commit 6944fb266f4cbcb2f4b9ed29189a23a4505d6db1 diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite new file mode 160000 index 00000000..bc3e47de --- /dev/null +++ b/thirdparty/nonstd/variant-lite @@ -0,0 +1 @@ +Subproject commit bc3e47de7c16e5b7583da7f48073aa2937ec627f From 664cc3b4ad8bc3aa978747cb95734a0d8a4b657d Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 17:21:12 +0300 Subject: [PATCH 040/206] [skip ci] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a1eb30b7..2a59e3cb 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,7 @@ You can define (via -D command line CMake option) the following build flags: * **JINJA2CPP_BUILD_TESTS** (default TRUE) - build or not Jinja2Cpp tests. * **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). +* **BOOST_ROOT** - Path to the prebuilt boost installation * **LIBRARY_TYPE** Could be STATIC (default for Windows platform) or SHARED (default for Linux). Specify the type of Jinja2Cpp library to build. # Link with you projects From 72a4fdc50289b786e5e25dd52a1c52bdda20c42f Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 27 Oct 2018 19:11:13 +0300 Subject: [PATCH 041/206] [skip ci] Reference to the conan.io package added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2a59e3cb..795403c7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/flexferrum/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/flexferrum/Jinja2Cpp/master/LICENSE) +[ ![Download](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. Unlike [inja](https://github.com/pantor/inja) lib, you have to build Jinja2Cpp, but it has only one dependence: boost. From 81f98d65c3a2dff1dcc6dfe1b798fde197807c87 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 30 Oct 2018 00:30:39 +0300 Subject: [PATCH 042/206] Fix '/DBOOST_ALL_NO_LIB' definition passing #80 --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d0d364d..a2f2473f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,8 @@ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") endif () else () - set (COMMON_MSVC_OPTS "/wd4503 /DBOOST_ALL_NO_LIB") + set (COMMON_MSVC_OPTS "/wd4503") + add_definition(/DBOOST_ALL_NO_LIB) # MSVC if (CMAKE_BUILD_TYPE MATCHES "Debug" AND MSVC_RUNTIME_TYPE) From 92246b125b3d69449b51f963be0e7e4e6309d83f Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 30 Oct 2018 00:34:12 +0300 Subject: [PATCH 043/206] Fix build --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2f2473f..993eabfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES endif () else () set (COMMON_MSVC_OPTS "/wd4503") - add_definition(/DBOOST_ALL_NO_LIB) + add_definitions(/DBOOST_ALL_NO_LIB) # MSVC if (CMAKE_BUILD_TYPE MATCHES "Debug" AND MSVC_RUNTIME_TYPE) From 53ada10b836aa92c8b24a4a999cf40eca3c22dcb Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 31 Oct 2018 01:33:01 +0300 Subject: [PATCH 044/206] Update value-ptr-lite reference --- thirdparty/nonstd/value-ptr-lite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 93af1743..3f6a4020 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 93af17436beacf7ffd853a74c5e0c4025d181506 +Subproject commit 3f6a402062dcf459dd8627902f9df9cb4f652747 From ff456566b81224ee59d3b95cdec3239da1dcb3de Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 1 Nov 2018 01:07:01 +0300 Subject: [PATCH 045/206] Update references to the thirdparty xxx-lite libs --- thirdparty/nonstd/expected-lite | 2 +- thirdparty/nonstd/value-ptr-lite | 2 +- thirdparty/nonstd/variant-lite | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index 6944fb26..6f5f176e 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit 6944fb266f4cbcb2f4b9ed29189a23a4505d6db1 +Subproject commit 6f5f176efb8a3f88979c30b52d05efc8979fea6f diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 3f6a4020..aa3728c2 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 3f6a402062dcf459dd8627902f9df9cb4f652747 +Subproject commit aa3728c2dbe89bbe342036aa4597808bdeb22b48 diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite index bc3e47de..9fd357b4 160000 --- a/thirdparty/nonstd/variant-lite +++ b/thirdparty/nonstd/variant-lite @@ -1 +1 @@ -Subproject commit bc3e47de7c16e5b7583da7f48073aa2937ec627f +Subproject commit 9fd357b4b51f9a4abb510b0346dac4ee680c0d65 From 6ffd211993d8e8f04422da1779621b51baffc78a Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 31 Oct 2018 23:05:37 +0000 Subject: [PATCH 046/206] Fix build with gcc/clang --- src/value_visitors.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value_visitors.h b/src/value_visitors.h index b86c30b8..0bf17168 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -29,7 +29,7 @@ struct RecursiveUnwrapper template - static auto& UnwrapRecursive(T&& arg) + static auto UnwrapRecursive(T&& arg) { return std::forward(arg); } From f0485b293b9d3c77907ac7f3ba83c9683714c98e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 1 Nov 2018 00:34:26 +0000 Subject: [PATCH 047/206] Fix value passing by rv-refs --- src/value_visitors.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/value_visitors.h b/src/value_visitors.h index 0bf17168..a128ab8a 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace jinja2 { @@ -29,9 +30,9 @@ struct RecursiveUnwrapper template - static auto UnwrapRecursive(T&& arg) + static const auto& UnwrapRecursive(const T& arg) { - return std::forward(arg); + return arg; // std::forward(arg); } template @@ -40,17 +41,17 @@ struct RecursiveUnwrapper return arg.GetValue(); } - template - static auto& UnwrapRecursive(RecursiveWrapper& arg) - { - return arg.GetValue(); - } +// template +// static auto& UnwrapRecursive(RecursiveWrapper& arg) +// { +// return arg.GetValue(); +// } template - auto operator()(Args&& ... args) const + auto operator()(const Args& ... args) const { assert(m_visitor != nullptr); - return (*m_visitor)(UnwrapRecursive(std::forward(args))...); + return (*m_visitor)(UnwrapRecursive(args)...); } }; From 853f64a17ced8a5d36fe428239b65e3630f47241 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 13 Nov 2018 19:08:49 +0300 Subject: [PATCH 048/206] User-defined callable implementation (#87) #39 * Introduce UserCallable type * Basic implementation of user-defined callable and params binding * Add missing header * bool_constant -> integral_constant * Fix build under gcc * Fix UCInvoker compilation with gcc/clang * Implement user-defined callables (2) * Introduce UserCallable type * Basic implementation of user-defined callable and params binding * Add missing header * bool_constant -> integral_constant * Fix build under gcc * Fix UCInvoker compilation with gcc/clang * Introduce UserCallable type * Basic implementation of user-defined callable and params binding * Fix build under gcc * Implement user-defined callables (2) * Implement user-defined callables (3) * Introduce UserCallable type * Basic implementation of user-defined callable and params binding * Fix build under gcc * Implement user-defined callables (2) * Implement user-defined callables (3) * Update submodules and add optional-lite * InternalValue -> Value conversion during user callable call * Small refactor of list/map convertion for callables * Fix clang compilation errors * Add tests for map type conversion for the user callables * Debug test coverage CI build * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Debug CI coverage collection * Slightly improve test coverage of internal_value.cpp * Enable argument packs passing to the user callable * Implement user-defined filters and testers * Fix build with MSVC --- .gitmodules | 3 + .travis.yml | 9 +- CMakeLists.txt | 30 ++- include/jinja2cpp/reflected_value.h | 12 -- include/jinja2cpp/user_callable.h | 117 +++++++++++ include/jinja2cpp/value.h | 97 ++++++--- src/error_info.cpp | 2 +- src/expression_evaluator.cpp | 60 +++++- src/expression_evaluator.h | 1 + src/filters.cpp | 73 +++++-- src/filters.h | 13 ++ src/generic_adapters.h | 47 +++++ src/internal_value.cpp | 214 ++++++++++++++------ src/internal_value.h | 52 +++-- src/testers.cpp | 34 +++- src/testers.h | 11 ++ src/value_visitors.h | 9 +- test/expressions_test.cpp | 4 + test/filters_test.cpp | 2 +- test/user_callable_test.cpp | 293 ++++++++++++++++++++++++++++ thirdparty/CMakeLists.txt | 92 ++++----- thirdparty/nonstd/optional-lite | 1 + thirdparty/nonstd/value-ptr-lite | 2 +- 23 files changed, 988 insertions(+), 190 deletions(-) create mode 100644 include/jinja2cpp/user_callable.h create mode 100644 src/generic_adapters.h create mode 100644 test/user_callable_test.cpp create mode 160000 thirdparty/nonstd/optional-lite diff --git a/.gitmodules b/.gitmodules index e42d90c6..517eb01f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "thirdparty/nonstd/variant-lite"] path = thirdparty/nonstd/variant-lite url = https://github.com/martinmoene/variant-lite.git +[submodule "thirdparty/nonstd/optional-lite"] + path = thirdparty/nonstd/optional-lite + url = https://github.com/martinmoene/optional-lite.git diff --git a/.travis.yml b/.travis.yml index dbda5c85..ebf4cb54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,8 +26,7 @@ matrix: compiler: gcc env: COMPILER=g++-6 - CMAKE_OPTS="-DCOVERAGE_ENABLED=TRUE" - BUILD_CONFIG=Debug + COLLECT_COVERAGE=1 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -86,20 +85,20 @@ install: - if [[ "${SYSTEM_BOOST_PACKAGE}" != "" ]]; then sudo apt-get install libboost1.60-all-dev; fi script: + - export BUILD_TARGET="all" - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi - if [[ "${BUILD_CONFIG}" == "" ]]; then export BUILD_CONFIG="Release"; fi - - uname -a + - if [[ "${COLLECT_COVERAGE}" != "" ]]; then export BUILD_CONFIG="Debug" && export CMAKE_OPTS="-DCOVERAGE_ENABLED=TRUE"; fi - $CXX --version - mkdir -p build && cd build - - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS .. && cmake --build . --config $BUILD_CONFIG -- -j4 + - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - ctest -C $BUILD_CONFIG -V after_success: # Creating report - echo "Uploading code coverate report" - - cd build - lcov --directory . --capture --output-file coverage.info # capture coverage info - lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter out system - lcov --list coverage.info #debug info diff --git a/CMakeLists.txt b/CMakeLists.txt index 993eabfc..ddb345ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,25 @@ cmake_minimum_required(VERSION 3.0.2) project(Jinja2Cpp VERSION 0.9.1) -if (${CMAKE_VERSION} VERSION_GREATER "3.12") +if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) endif () list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED) + message (STATUS "This is DEBUG build with enabled Code Coverage") + set (CMAKE_BUILD_TYPE Debug) + include(code_coverage) + setup_target_for_coverage(build_coverage jinja2cpp_tests coverage) +endif() + if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") if (NOT UNIX) set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") endif () else () - set (COMMON_MSVC_OPTS "/wd4503") + set (COMMON_MSVC_OPTS "/wd4503 /bigobj") add_definitions(/DBOOST_ALL_NO_LIB) # MSVC @@ -55,7 +62,7 @@ add_library(${LIB_TARGET_NAME} STATIC string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") - + add_subdirectory(thirdparty) target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) @@ -66,6 +73,11 @@ target_include_directories(${LIB_TARGET_NAME} if(NOT MSVC) target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) + if (COVERAGE_ENABLED) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -g PUBLIC -O0 --coverage -fprofile-arcs -ftest-coverage) + else () + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) + endif () else () target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() @@ -81,12 +93,15 @@ if (JINJA2CPP_BUILD_TESTS) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) target_link_libraries(jinja2cpp_tests gtest gtest_main ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS}) - + if (COVERAGE_ENABLED) + target_link_libraries(jinja2cpp_tests gcov) + endif () + get_target_property(TEST_CXX_STD jinja2cpp_tests CXX_STANDARD) - + string (FIND "${CURRENT_CXX_FLAGS}" "-std" TEST_FLAGS_STD_POS) string (FIND "${TEST_CXX_STD}" "NOTFOUND" TEST_CXX_STD_NOTFOUND_POS) - + if (NOT MSVC AND TEST_FLAGS_STD_POS EQUAL -1 AND NOT (TEST_CXX_STD_NOTFOUND_POS EQUAL -1)) set_target_properties(jinja2cpp_tests PROPERTIES CXX_STANDARD 14 @@ -104,6 +119,8 @@ if (JINJA2CPP_BUILD_TESTS) DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl ) + add_dependencies(jinja2cpp_tests CopyTestData) + add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) endif () @@ -116,4 +133,5 @@ install (DIRECTORY include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/expected-lite/include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/variant-lite/include/ DESTINATION include) install (DIRECTORY thirdparty/nonstd/value-ptr-lite/include/ DESTINATION include) +install (DIRECTORY thirdparty/nonstd/optional-lite/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 94b92202..47fdb319 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -67,18 +67,6 @@ class ReflectedMapImplBase : public MapItemAccessor { return Derived::GetAccessors().size(); } - virtual Value GetValueByIndex(int64_t idx) const override - { - const auto& accessors = Derived::GetAccessors(); - auto p = accessors.begin(); - std::advance(p, idx); - - ValuesMap result; - result["key"] = p->first; - result["value"] = static_cast(this)->GetField(p->second); - - return result; - } }; template diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h new file mode 100644 index 00000000..f3fc548e --- /dev/null +++ b/include/jinja2cpp/user_callable.h @@ -0,0 +1,117 @@ +#ifndef USER_CALLABLE_H +#define USER_CALLABLE_H + +#include "value.h" + +#include + +namespace jinja2 +{ +namespace detail +{ +template +struct CanBeCalled : std::false_type {}; + +template +struct CanBeCalled::value>::type> : std::true_type {}; + +template +struct UCInvoker +{ + const Fn& fn; + const UserCallableParams& params; + + template + struct FuncTester + { + template + static auto TestFn(F&& f) -> decltype(Value(f(std::declval()...))); + static auto TestFn(...) -> char; + + using result_type = decltype(TestFn(std::declval())); + }; + + UCInvoker(const Fn& f, const UserCallableParams& p) + : fn(f) + , params(p) + {} + + template + auto operator()(Args&& ... args) const -> std::enable_if_t>::value, Value> + { + return Value(fn(args...)); + } + + template + auto operator()(Args&& ... args) const -> std::enable_if_t>::value, Value> + { + return Value(); + } + +}; + +const Value& GetParamValue(const UserCallableParams& params, const ArgInfo& info) +{ + // static Value empty; + auto p = params.args.find(info.paramName); + if (p != params.args.end()) + return p->second; + else if (info.paramName == "**kwargs") + return params.extraKwArgs; + else if (info.paramName == "*args") + return params.extraPosArgs; + + return info.defValue; +} + +template +struct ParamUnwrapper +{ + V* m_visitor; + + ParamUnwrapper(V* v) + : m_visitor(v) + {} + + + template + static const auto& UnwrapRecursive(const T& arg) + { + return arg; // std::forward(arg); + } + + template + static auto& UnwrapRecursive(const RecWrapper& arg) + { + return arg.value(); + } + + template + auto operator()(const Args& ... args) const + { + assert(m_visitor != nullptr); + return (*m_visitor)(UnwrapRecursive(args)...); + } +}; + +template +Value InvokeUserCallable(Fn&& fn, const UserCallableParams& params, ArgDescr&& ... ad) +{ + auto invoker = UCInvoker(fn, params); + return nonstd::visit(ParamUnwrapper>(&invoker), GetParamValue(params, ad).data()...); +} +} // detail + +template +auto MakeCallable(Fn&& f, ArgDescr&& ... ad) +{ + return UserCallable { + [=, fn = std::forward(f)](const UserCallableParams& params) { + return detail::InvokeUserCallable(fn, params, ad...); + }, + {ArgInfo(std::forward(ad))...} + }; +} +} // jinja2 + +#endif // USER_CALLABLE_H diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 6bf3a9c3..b755edb1 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -7,12 +7,18 @@ #include #include #include +#include #include +#include #include namespace jinja2 { -struct EmptyValue {}; +struct EmptyValue +{ + template + operator T() const {return T{};} +}; class Value; struct ListItemAccessor @@ -23,9 +29,10 @@ struct ListItemAccessor virtual Value GetValueByIndex(int64_t idx) const = 0; }; -struct MapItemAccessor : public ListItemAccessor +struct MapItemAccessor { virtual ~MapItemAccessor() {} + virtual size_t GetSize() const = 0; virtual bool HasValue(const std::string& name) const = 0; virtual Value GetValueByName(const std::string& name) const = 0; virtual std::vector GetKeys() const = 0; @@ -43,18 +50,21 @@ class GenericMap bool HasValue(const std::string& name) const { - return m_accessor()->HasValue(name); + return m_accessor ? m_accessor()->HasValue(name) : false; } Value GetValueByName(const std::string& name) const; size_t GetSize() const { - return m_accessor()->GetSize(); + return m_accessor ? m_accessor()->GetSize() : 0; } - Value GetValueByIndex(int64_t index) const; auto GetKeys() const { - return m_accessor()->GetKeys(); + return m_accessor ? m_accessor()->GetKeys() : std::vector(); + } + auto GetAccessor() const + { + return m_accessor(); } std::function m_accessor; @@ -71,7 +81,7 @@ class GenericList size_t GetSize() const { - return m_accessor()->GetSize(); + return m_accessor ? m_accessor()->GetSize() : 0ULL; } Value GetValueByIndex(int64_t idx) const; @@ -91,26 +101,32 @@ class GenericList using ValuesList = std::vector; using ValuesMap = std::unordered_map; -struct FunctionCallParams; - -using UserFunction = std::function; +struct UserCallableArgs; +struct ParamInfo; +struct UserCallable; template using RecWrapper = nonstd::value_ptr; -class Value { +class Value +{ public: - using ValueData = nonstd::variant, RecWrapper, GenericList, GenericMap, UserFunction>; + using ValueData = nonstd::variant, RecWrapper, GenericList, GenericMap, RecWrapper>; + template + struct AnyOf : public std::false_type {}; + + template + struct AnyOf : public std::integral_constant, H>::value || AnyOf::value> {}; Value(); Value(const Value& val); Value(Value&& val); ~Value(); - + Value& operator =(const Value&); Value& operator =(Value&&); template - Value(T&& val, typename std::enable_if, Value>::value && !std::is_same, ValuesList>::value>::type* = nullptr) + Value(T&& val, typename std::enable_if::value>::type* = nullptr) : m_data(std::forward(val)) { } @@ -135,6 +151,7 @@ class Value { : m_data(RecWrapper(map)) { } + Value(const UserCallable& callable); Value(ValuesList&& list) noexcept : m_data(RecWrapper(std::move(list))) { @@ -143,6 +160,7 @@ class Value { : m_data(RecWrapper(std::move(map))) { } + Value(UserCallable&& callable); const ValueData& data() const {return m_data;} @@ -196,25 +214,60 @@ class Value { ValueData m_data; }; -struct FunctionCallParams +struct UserCallableParams { - ValuesMap kwParams; - ValuesList posParams; + ValuesMap args; + Value extraPosArgs; + Value extraKwArgs; + bool paramsParsed = false; + + Value operator[](const std::string& paramName) const + { + auto p = args.find(paramName); + if (p == args.end()) + return Value(); + + return p->second; + } }; -inline Value GenericMap::GetValueByName(const std::string& name) const +struct ArgInfo +{ + std::string paramName; + bool isMandatory; + Value defValue; + + ArgInfo(std::string name, bool isMandat = false, Value defVal = Value()) + : paramName(std::move(name)) + , isMandatory(isMandat) + , defValue(std::move(defVal)) + {} +}; + +struct UserCallable +{ + std::function callable; + std::vector argsInfo; +}; + +inline Value::Value(const UserCallable& callable) + : m_data(RecWrapper(callable)) +{ +} + +inline Value::Value(UserCallable&& callable) + : m_data(RecWrapper(std::move(callable))) { - return m_accessor()->GetValueByName(name); } -inline Value GenericMap::GetValueByIndex(int64_t index) const +inline Value GenericMap::GetValueByName(const std::string& name) const { - return m_accessor()->GetValueByIndex(index); + return m_accessor ? m_accessor()->GetValueByName(name) : Value(); } inline Value GenericList::GetValueByIndex(int64_t index) const { - return m_accessor()->GetValueByIndex(index); + return m_accessor ? m_accessor()->GetValueByIndex(index) : Value(); } inline Value::Value() = default; diff --git a/src/error_info.cpp b/src/error_info.cpp index df9ba4de..72eed03a 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -76,7 +76,7 @@ struct ValueRenderer } - void operator() (const UserFunction&) const + void operator() (const UserCallable& val) const { } diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index 43562ed2..b8b51fe7 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -1,5 +1,6 @@ #include "expression_evaluator.h" #include "filters.h" +#include "generic_adapters.h" #include "internal_value.h" #include "out_stream.h" #include "testers.h" @@ -255,7 +256,7 @@ InternalValue CallExpression::Evaluate(RenderContext& values) case LoopCycleFn: return CallLoopCycle(values); default: - break; + return CallArbitraryFn(values); } return InternalValue(); @@ -286,6 +287,33 @@ void CallExpression::Render(OutStream& stream, RenderContext& values) } } +InternalValue CallExpression::CallArbitraryFn(RenderContext& values) +{ + auto fnVal = m_valueRef->Evaluate(values); + Callable* callable = GetIf(&fnVal); + if (callable == nullptr) + { + fnVal = Subscript(fnVal, std::string("operator()")); + callable = GetIf(&fnVal); + if (callable == nullptr) + return InternalValue(); + } + + auto kind = callable->GetKind(); + if (kind != Callable::GlobalFunc && kind != Callable::UserCallable) + return InternalValue(); + + if (callable->GetType() == Callable::Type::Expression) + { + return callable->GetExpressionCallable()(m_params, values); + } + + TargetString resultStr; + auto stream = values.GetRendererCallback()->GetStreamOnString(resultStr); + callable->GetStatementCallable()(m_params, stream, values); + return resultStr; +} + InternalValue CallExpression::CallGlobalRange(RenderContext& values) { bool isArgsParsed = true; @@ -317,7 +345,7 @@ InternalValue CallExpression::CallGlobalRange(RenderContext& values) return InternalValue(); } - class RangeGenerator : public IListAccessor + class RangeGenerator : public ListAccessorImpl { public: RangeGenerator(int64_t start, int64_t stop, int64_t step) @@ -333,11 +361,17 @@ InternalValue CallExpression::CallGlobalRange(RenderContext& values) auto count = distance / m_step; return count < 0 ? 0 : static_cast(count); } - InternalValue GetValueByIndex(int64_t idx) const override + InternalValue GetItem(int64_t idx) const override { return m_start + m_step * idx; } + bool ShouldExtendLifetime() const override {return false;} + GenericList CreateGenericList() const override + { + return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); + } + private: int64_t m_start; @@ -368,7 +402,8 @@ enum ArgState NotFound, NotFoundMandatory, Keyword, - Positional + Positional, + Ignored }; enum ParamState @@ -406,6 +441,13 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo for (auto& argInfo : args) { argsInfo[argIdx].info = &argInfo; + + if (argInfo.name == "*args" || argInfo.name=="**kwargs") + { + argsInfo[argIdx ++].state = Ignored; + continue; + } + auto p = params.kwParams.find(argInfo.name); if (p != params.kwParams.end()) { @@ -444,7 +486,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo // Determine the range for positional arguments scanning bool isFirstTime = true; - for (; eatenPosArgs < posParamsInfo.size(); ++ eatenPosArgs) + for (; eatenPosArgs < posParamsInfo.size() && startPosArg < args.size(); eatenPosArgs = eatenPosArgs + (argsInfo[startPosArg].state == Ignored ? 0 : 1)) { if (isFirstTime) { @@ -454,7 +496,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo isFirstTime = false; continue; } - + prevNotFound = argsInfo[startPosArg].prevNotFound; if (prevNotFound != -1) { @@ -475,8 +517,11 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo // Map positional params to the desired arguments auto curArg = static_cast(startPosArg); - for (std::size_t idx = 0; idx < eatenPosArgs && curArg != -1; ++ idx, curArg = argsInfo[curArg].nextNotFound) + for (std::size_t idx = 0; idx < eatenPosArgs && curArg != -1 && static_cast(curArg) < argsInfo.size(); ++ idx, curArg = argsInfo[curArg].nextNotFound) { + if (argsInfo[curArg].state == Ignored) + continue; + result.args[argsInfo[curArg].info->name] = params.posParams[idx]; argsInfo[curArg].state = Positional; } @@ -489,6 +534,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo { case Positional: case Keyword: + case Ignored: continue; case NotFound: { diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index 95ae654b..db644958 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -271,6 +271,7 @@ class CallExpression : public Expression auto& GetParams() const {return m_params;} private: + InternalValue CallArbitraryFn(RenderContext &values); InternalValue CallGlobalRange(RenderContext &values); InternalValue CallLoopCycle(RenderContext &values); diff --git a/src/filters.cpp b/src/filters.cpp index df45a0fd..d6bdb317 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -3,6 +3,7 @@ #include "testers.h" #include "value_visitors.h" #include "value_helpers.h" +#include "generic_adapters.h" #include #include @@ -84,7 +85,7 @@ extern FilterPtr CreateFilter(std::string filterName, CallParams params) { auto p = s_filters.find(filterName); if (p == s_filters.end()) - return FilterPtr(); + return std::make_shared(std::move(filterName), std::move(params)); return p->second(std::move(params)); } @@ -244,11 +245,10 @@ InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& cont std::vector tempVector; tempVector.reserve(map->GetSize()); - for (std::size_t idx = 0; idx < map->GetSize(); ++ idx) + for (auto& key : map->GetKeys()) { - auto val = map->GetValueByIndex(static_cast(idx)); - auto kvVal = Get(val); - tempVector.push_back(std::move(kvVal)); + auto val = map->GetValueByName(key); + tempVector.push_back(KeyValuePair{key, val}); } if (ConvertToBool(isReverseVal)) @@ -876,33 +876,42 @@ struct ValueConverterImpl : visitors::BaseVisitor<> } template - struct StringAdapter : public IListAccessor + struct StringAdapter : public ListAccessorImpl> { using string = std::basic_string; StringAdapter(const string* str) - : m_str(str) + : m_str(*str) { } - size_t GetSize() const override {return m_str->size();} - InternalValue GetValueByIndex(int64_t idx) const override {return InternalValue(m_str->substr(static_cast(idx), 1));} + size_t GetSize() const override {return m_str.size();} + InternalValue GetItem(int64_t idx) const override {return InternalValue(m_str.substr(static_cast(idx), 1));} bool ShouldExtendLifetime() const override {return false;} + GenericList CreateGenericList() const override + { + return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); + } - const string* m_str; + const string m_str; }; - struct Map2ListAdapter : public IListAccessor + struct Map2ListAdapter : public ListAccessorImpl { Map2ListAdapter(const MapAdapter* map) - : m_map(map) + : m_values(map->GetKeys()) { } - size_t GetSize() const override {return m_map->GetSize();} - InternalValue GetValueByIndex(int64_t idx) const override {return m_map->GetValueByIndex(idx);} - bool ShouldExtendLifetime() const override {return false;} + size_t GetSize() const override {return m_values.size();} + InternalValue GetItem(int64_t idx) const override {return m_values[idx];} + bool ShouldExtendLifetime() const override {return true;} + GenericList CreateGenericList() const override + { + // return m_values.Get(); + return GenericList([list = *this]() -> const ListItemAccessor* {return &list;}); + } - const MapAdapter* m_map; + const std::vector m_values; }; InternalValue operator()(const std::string& val) const @@ -1021,5 +1030,37 @@ InternalValue ValueConverter::Filter(const InternalValue& baseVal, RenderContext return result; } + +UserDefinedFilter::UserDefinedFilter(std::string filterName, FilterParams params) + : m_filterName(std::move(filterName)) +{ + ParseParams({{"*args"}, {"**kwargs"}}, params); + m_callParams.kwParams = m_args.extraKwArgs; + m_callParams.posParams = m_args.extraPosArgs; +} + +InternalValue UserDefinedFilter::Filter(const InternalValue& baseVal, RenderContext& context) +{ + bool filterFound = false; + auto filterValPtr = context.FindValue(m_filterName, filterFound); + if (!filterFound) + return InternalValue(); + + const Callable* callable = GetIf(&filterValPtr->second); + if (callable == nullptr || callable->GetKind() != Callable::UserCallable) + return InternalValue(); + + CallParams callParams; + callParams.kwParams = m_callParams.kwParams; + callParams.posParams.reserve(m_callParams.posParams.size() + 1); + callParams.posParams.push_back(std::make_shared(baseVal)); + callParams.posParams.insert(callParams.posParams.end(), m_callParams.posParams.begin(), m_callParams.posParams.end()); + + InternalValue result; + if (callable->GetType() != Callable::Type::Expression) + return InternalValue(); + + return callable->GetExpressionCallable()(callParams, context); +} } // filters } // jinja2 diff --git a/src/filters.h b/src/filters.h index 016d0041..10d84c2c 100644 --- a/src/filters.h +++ b/src/filters.h @@ -244,6 +244,19 @@ class ValueConverter : public FilterBase private: Mode m_mode; }; + +class UserDefinedFilter : public FilterBase +{ +public: + UserDefinedFilter(std::string filterName, FilterParams params); + + InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + +private: + std::string m_filterName; + FilterParams m_callParams; +}; + } // filters } // jinja2 diff --git a/src/generic_adapters.h b/src/generic_adapters.h new file mode 100644 index 00000000..513816ab --- /dev/null +++ b/src/generic_adapters.h @@ -0,0 +1,47 @@ +#ifndef GENERIC_ADAPTERS_H +#define GENERIC_ADAPTERS_H + +#include +#include "internal_value.h" + +namespace jinja2 +{ +template +class ListItemAccessorImpl : public ListItemAccessor +{ +public: + Value GetValueByIndex(int64_t idx) const + { + return IntValue2Value(static_cast(this)->GetItem(idx)); + } +}; + +template +class ListAccessorImpl : public IListAccessor, public ListItemAccessorImpl +{ +public: +// GenericList CreateGenericList() const override +// { +// return GenericList([accessor = this]() -> const ListItemAccessor* {return accessor;}); +// } +}; + +template +class MapItemAccessorImpl : public MapItemAccessor +{ +public: + Value GetValueByName(const std::string& name) const + { + return IntValue2Value(static_cast(this)->GetItem(name)); + } +}; + +template +class MapAccessorImpl : public IMapAccessor, public MapItemAccessorImpl +{ +public: +}; + +} // jinja2 + +#endif // GENERIC_ADAPTERS_H diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 46d77060..9df19892 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -1,5 +1,7 @@ #include "internal_value.h" #include "value_visitors.h" +#include "expression_evaluator.h" +#include "generic_adapters.h" namespace jinja2 { @@ -28,11 +30,7 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> InternalValue operator() (const MapAdapter& values, int64_t index) const { - // std::cout << "operator() (const MapAdapter& values, int64_t index)" << ": values.size() = " << values.GetSize() << ", index = " << index << std::endl; - if (index < 0 || static_cast(index) >= values.GetSize()) - return InternalValue(); - - return values.GetValueByIndex(index); + return InternalValue(); } template @@ -56,13 +54,7 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return InternalValue(); } -// -// template -// InternalValue operator() (T&&, U&&) const -// { -// std::cout << "operator() (T&&, U&&). T: " << typeid(T).name() << ", U: " << typeid(U).name() << std::endl; -// return InternalValue(); -// } + }; InternalValue Subscript(const InternalValue& val, const InternalValue& subscript) @@ -198,30 +190,40 @@ class GenericListAdapter : public IListAccessor GenericListAdapter(U&& values) : m_values(std::forward(values)) {} size_t GetSize() const override {return m_values.Get().GetSize();} - InternalValue GetValueByIndex(int64_t idx) const override + InternalValue GetItem(int64_t idx) const override { const auto& val = m_values.Get().GetValueByIndex(idx); return visit(visitors::InputValueConvertor(true), val.data()).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericList CreateGenericList() const override + { + // return m_values.Get(); + return GenericList([list = m_values]() -> const ListItemAccessor* {return list.Get().GetAccessor();}); + } private: Holder m_values; }; template class Holder> -class ValuesListAdapter : public IListAccessor +class ValuesListAdapter : public ListAccessorImpl> { public: template ValuesListAdapter(U&& values) : m_values(std::forward(values)) {} size_t GetSize() const override {return m_values.Get().size();} - InternalValue GetValueByIndex(int64_t idx) const override + InternalValue GetItem(int64_t idx) const override { const auto& val = m_values.Get()[idx]; return visit(visitors::InputValueConvertor(false), val.data()).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericList CreateGenericList() const override + { + // return m_values.Get(); + return GenericList([list = *this]() -> const ListItemAccessor* {return &list;}); + } private: Holder m_values; }; @@ -229,14 +231,18 @@ class ValuesListAdapter : public IListAccessor ListAdapter ListAdapter::CreateAdapter(InternalValueList&& values) { - class Adapter : public IListAccessor + class Adapter : public ListAccessorImpl { public: explicit Adapter(InternalValueList&& values) : m_values(std::move(values)) {} size_t GetSize() const override {return m_values.size();} - InternalValue GetValueByIndex(int64_t idx) const override {return m_values[static_cast(idx)];} + InternalValue GetItem(int64_t idx) const override {return m_values[static_cast(idx)];} bool ShouldExtendLifetime() const override {return false;} + GenericList CreateGenericList() const override + { + return GenericList([adapter = *this]() -> const ListItemAccessor* {return &adapter;}); + } private: InternalValueList m_values; }; @@ -265,18 +271,22 @@ ListAdapter ListAdapter::CreateAdapter(ValuesList&& values) } template class Holder> -class SubscriptedListAdapter : public IListAccessor +class SubscriptedListAdapter : public ListAccessorImpl> { public: template SubscriptedListAdapter(U&& values, const InternalValue& subscript) : m_values(std::forward(values)), m_subscript(subscript) {} size_t GetSize() const override {return m_values.Get().GetSize();} - InternalValue GetValueByIndex(int64_t idx) const override + InternalValue GetItem(int64_t idx) const override { return Subscript(m_values.Get().GetValueByIndex(idx), m_subscript); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericList CreateGenericList() const override + { + return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); + } private: Holder m_values; InternalValue m_subscript; @@ -299,29 +309,18 @@ InternalValueList ListAdapter::ToValueList() const } template class Holder, bool CanModify> -class InternalValueMapAdapter : public IMapAccessor +class InternalValueMapAdapter : public MapAccessorImpl> { public: template InternalValueMapAdapter(U&& values) : m_values(std::forward(values)) {} size_t GetSize() const override {return m_values.Get().size();} - InternalValue GetValueByIndex(int64_t idx) const override - { - KeyValuePair result; - auto p = m_values.Get().begin(); - std::advance(p, idx); - - result.key = p->first; - result.value = p->second; - - return InternalValue(std::move(result)); - } bool HasValue(const std::string& name) const override { return m_values.Get().count(name) != 0; } - InternalValue GetValueByName(const std::string& name) const override + InternalValue GetItem(const std::string& name) const override { auto& vals = m_values.Get(); auto p = vals.find(name); @@ -350,6 +349,10 @@ class InternalValueMapAdapter : public IMapAccessor return false; } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericMap CreateGenericMap() const override + { + return GenericMap([accessor = *this]() -> const MapItemAccessor* {return &accessor;}); + } private: Holder m_values; }; @@ -373,27 +376,18 @@ InternalValue Value2IntValue(Value&& val) } template class Holder> -class GenericMapAdapter : public IMapAccessor +class GenericMapAdapter : public MapAccessorImpl> { public: template GenericMapAdapter(U&& values) : m_values(std::forward(values)) {} size_t GetSize() const override {return m_values.Get().GetSize();} - InternalValue GetValueByIndex(int64_t idx) const override - { - auto val = m_values.Get().GetValueByIndex(idx); - KeyValuePair result; - auto& map = val.asMap(); - result.key = map["key"].asString(); - result.value = Value2IntValue(std::move(map["value"])); - return MakeWrapped(result); - } bool HasValue(const std::string& name) const override { return m_values.Get().HasValue(name); } - InternalValue GetValueByName(const std::string& name) const override + InternalValue GetItem(const std::string& name) const override { auto val = m_values.Get().GetValueByName(name); if (val.isEmpty()) @@ -406,6 +400,10 @@ class GenericMapAdapter : public IMapAccessor return m_values.Get().GetKeys(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericMap CreateGenericMap() const override + { + return GenericMap([accessor = *this]() -> const MapItemAccessor* {return accessor.m_values.Get().GetAccessor();}); + } private: Holder m_values; @@ -413,29 +411,18 @@ class GenericMapAdapter : public IMapAccessor template class Holder> -class ValuesMapAdapter : public IMapAccessor +class ValuesMapAdapter : public MapAccessorImpl> { public: template ValuesMapAdapter(U&& values) : m_values(std::forward(values)) {} size_t GetSize() const override {return m_values.Get().size();} - InternalValue GetValueByIndex(int64_t idx) const override - { - KeyValuePair result; - auto p = m_values.Get().begin(); - std::advance(p, idx); - - result.key = p->first; - result.value = Value2IntValue(p->second); - - return MakeWrapped(std::move(result)); - } bool HasValue(const std::string& name) const override { return m_values.Get().count(name) != 0; } - InternalValue GetValueByName(const std::string& name) const override + InternalValue GetItem(const std::string& name) const override { auto& vals = m_values.Get(); auto p = vals.find(name); @@ -454,6 +441,10 @@ class ValuesMapAdapter : public IMapAccessor return result; } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + GenericMap CreateGenericMap() const override + { + return GenericMap([accessor = *this]() -> const MapItemAccessor* {return &accessor;}); + } private: Holder m_values; }; @@ -489,4 +480,115 @@ MapAdapter MapAdapter::CreateAdapter(ValuesMap&& values) return MapAdapter([accessor = ValuesMapAdapter(std::move(values))]() mutable {return &accessor;}); } +struct OutputValueConvertor +{ + using result_t = Value; + + result_t operator()(const EmptyValue&) const {return result_t();} + result_t operator()(const MapAdapter& adapter) const {return result_t(adapter.CreateGenericMap());} + result_t operator()(const ListAdapter& adapter) const {return result_t(adapter.CreateGenericList());} + result_t operator()(const ValueRef& ref) const + { + return ref.get(); + } + result_t operator()(const TargetString& str) const + { + switch (str.index()) + { + case 0: + return str.get(); + default: + return str.get(); + } + } + result_t operator()(const KeyValuePair& pair) const + { + return ValuesMap{{"key", IntValue2Value(pair.key)}, {"value", IntValue2Value(pair.value)}}; + } + 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();} + + template + result_t operator()(const RecWrapper& val) const + { + return this->operator()(const_cast(*val.get())); + } + + template + result_t operator()(RecWrapper& val) const + { + return this->operator()(*val.get()); + } + + template + result_t operator() (T&& val) const + { + return result_t(std::forward(val)); + } + + bool m_byValue; +}; + +Value IntValue2Value(const InternalValue& val) +{ + return Apply(val); +} + +UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderContext& context, const std::vector& argsInfo) +{ + UserCallableParams result; + + ParsedArguments args = helpers::ParseCallParams(argsInfo, params, result.paramsParsed); + if (!result.paramsParsed) + return result; + + for (auto& argInfo : argsInfo) + { + if (argInfo.name == "*args" || argInfo.name == "**kwargs") + continue; + + auto p = args.args.find(argInfo.name); + if (p == args.args.end()) + { + result.args[argInfo.name] = IntValue2Value(argInfo.defaultVal); + continue; + } + + const auto& v = p->second->Evaluate(context); + result.args[argInfo.name] = IntValue2Value(v); + } + + ValuesMap extraKwArgs; + for (auto p : args.extraKwArgs) + extraKwArgs[p.first] = IntValue2Value(p.second->Evaluate(context)); + result.extraKwArgs = Value(std::move(extraKwArgs)); + + ValuesList extraPosArgs; + for (auto p : args.extraPosArgs) + extraPosArgs.push_back(IntValue2Value(p->Evaluate(context))); + result.extraPosArgs = Value(std::move(extraPosArgs)); + + return result; +} + +namespace visitors +{ + +InputValueConvertor::result_t InputValueConvertor::ConvertUserCallable(const UserCallable& val) +{ + std::vector args; + for (auto& pi : val.argsInfo) + { + args.emplace_back(pi.paramName, pi.isMandatory, Value2IntValue(pi.defValue)); + } + + return InternalValue(Callable(Callable::UserCallable, [&val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue { + auto ucParams = PrepareUserCallableParams(params, context, argsInfo); + return Value2IntValue(val.callable(ucParams)); + })); +} + +} // visitors + } // jinja2 diff --git a/src/internal_value.h b/src/internal_value.h index aa5731c3..0cc418b7 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -111,7 +111,9 @@ struct ValueGetter } static auto GetPtr(const InternalValue* val); + static auto GetPtr(InternalValue* val); + template static auto GetPtr(V* val, std::enable_if_t::value>* = nullptr) { @@ -155,18 +157,22 @@ struct IListAccessor virtual ~IListAccessor() {} virtual size_t GetSize() const = 0; - virtual InternalValue GetValueByIndex(int64_t idx) const = 0; + virtual InternalValue GetItem(int64_t idx) const = 0; + virtual GenericList CreateGenericList() const = 0; virtual bool ShouldExtendLifetime() const = 0; }; using ListAccessorProvider = std::function; -struct IMapAccessor : public IListAccessor +struct IMapAccessor { + virtual size_t GetSize() const = 0; virtual bool HasValue(const std::string& name) const = 0; - virtual InternalValue GetValueByName(const std::string& name) const = 0; + virtual InternalValue GetItem(const std::string& name) const = 0; virtual std::vector GetKeys() const = 0; virtual bool SetValue(std::string, const InternalValue&) {return false;} + virtual GenericMap CreateGenericMap() const = 0; + virtual bool ShouldExtendLifetime() const = 0; }; using MapAccessorProvider = std::function; @@ -210,6 +216,13 @@ class ListAdapter ListAdapter ToSubscriptedList(const InternalValue& subscript, bool asRef = false) const; InternalValueList ToValueList() const; + GenericList CreateGenericList() const + { + if (m_accessorProvider && m_accessorProvider) + return m_accessorProvider()->CreateGenericList(); + + return GenericList(); + } class Iterator; @@ -242,7 +255,7 @@ class MapAdapter return 0; } - InternalValue GetValueByIndex(int64_t idx) const; + // InternalValue GetValueByIndex(int64_t idx) const; bool HasValue(const std::string& name) const { if (m_accessorProvider && m_accessorProvider()) @@ -281,6 +294,14 @@ class MapAdapter return false; } + GenericMap CreateGenericMap() const + { + if (m_accessorProvider && m_accessorProvider()) + return m_accessorProvider()->CreateGenericMap(); + + return GenericMap(); + } + private: MapAccessorProvider m_accessorProvider; }; @@ -392,7 +413,7 @@ inline auto ValueGetter::GetPtr(InternalValue* val) { return nonstd::get_if(&val->GetData()); } - + template inline auto ValueGetter::GetPtr(const InternalValue* val) { @@ -424,27 +445,27 @@ inline InternalValue ListAdapter::GetValueByIndex(int64_t idx) const { if (m_accessorProvider && m_accessorProvider()) { - return m_accessorProvider()->GetValueByIndex(idx); + return m_accessorProvider()->GetItem(idx); } return InternalValue(); } -inline InternalValue MapAdapter::GetValueByIndex(int64_t idx) const -{ - if (m_accessorProvider && m_accessorProvider()) - { - return m_accessorProvider()->GetValueByIndex(idx); - } +//inline InternalValue MapAdapter::GetValueByIndex(int64_t idx) const +//{ +// if (m_accessorProvider && m_accessorProvider()) +// { +// return static_cast(m_accessorProvider())->GetItem(idx); +// } - return InternalValue(); -} +// return InternalValue(); +//} inline InternalValue MapAdapter::GetValueByName(const std::string& name) const { if (m_accessorProvider && m_accessorProvider()) { - return m_accessorProvider()->GetValueByName(name); + return m_accessorProvider()->GetItem(name); } return InternalValue(); @@ -534,6 +555,7 @@ InternalValue Subscript(const InternalValue& val, const std::string& subscript); std::string AsString(const InternalValue& val); ListAdapter ConvertToList(const InternalValue& val, bool& isConverted); ListAdapter ConvertToList(const InternalValue& val, InternalValue subscipt, bool& isConverted); +Value IntValue2Value(const InternalValue& val); } // jinja2 diff --git a/src/testers.cpp b/src/testers.cpp index b83c4d49..bf1b41df 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -54,7 +54,7 @@ TesterPtr CreateTester(std::string testerName, CallParams params) { auto p = s_testers.find(testerName); if (p == s_testers.end()) - return TesterPtr(); + return std::make_shared(std::move(testerName), std::move(params)); return p->second(std::move(params)); } @@ -325,5 +325,37 @@ bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context) } return result; } + +UserDefinedTester::UserDefinedTester(std::string testerName, TesterParams params) + : m_testerName(std::move(testerName)) +{ + ParseParams({{"*args"}, {"**kwargs"}}, params); + m_callParams.kwParams = m_args.extraKwArgs; + m_callParams.posParams = m_args.extraPosArgs; +} + +bool UserDefinedTester::Test(const InternalValue& baseVal, RenderContext& context) +{ + bool testerFound = false; + auto testerValPtr = context.FindValue(m_testerName, testerFound); + if (!testerFound) + return false; + + const Callable* callable = GetIf(&testerValPtr->second); + if (callable == nullptr || callable->GetKind() != Callable::UserCallable) + return false; + + CallParams callParams; + callParams.kwParams = m_callParams.kwParams; + callParams.posParams.reserve(m_callParams.posParams.size() + 1); + callParams.posParams.push_back(std::make_shared(baseVal)); + callParams.posParams.insert(callParams.posParams.end(), m_callParams.posParams.begin(), m_callParams.posParams.end()); + + InternalValue result; + if (callable->GetType() != Callable::Type::Expression) + return false; + + return ConvertToBool(callable->GetExpressionCallable()(callParams, context)); +} } } diff --git a/src/testers.h b/src/testers.h index 992ac21c..f42ea8c0 100644 --- a/src/testers.h +++ b/src/testers.h @@ -70,6 +70,17 @@ class ValueTester : public TesterBase Mode m_mode; }; +class UserDefinedTester : public TesterBase +{ +public: + UserDefinedTester(std::string filterName, TesterParams params); + + bool Test(const InternalValue& baseVal, RenderContext& context) override; + +private: + std::string m_testerName; + TesterParams m_callParams; +}; } // testers } // jinja2 diff --git a/src/value_visitors.h b/src/value_visitors.h index a128ab8a..154a0d92 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -163,7 +163,7 @@ struct ValueRendererBase void operator()(const TargetString&) const {} void operator()(const KeyValuePair&) const {} void operator()(const Callable&) const {} - void operator()(const UserFunction&) const {} + void operator()(const UserCallable&) const {} void operator()(const RendererBase*) const {} template void operator()(const boost::recursive_wrapper&) const {} @@ -245,9 +245,9 @@ struct InputValueConvertor return result_t(InternalValue(MapAdapter::CreateAdapter(vals))); } - result_t operator() (const UserFunction&) const + result_t operator() (const UserCallable& val) const { - return result_t(); + return ConvertUserCallable(val); } template @@ -268,7 +268,10 @@ struct InputValueConvertor return result_t(InternalValue(std::forward(val))); } + static result_t ConvertUserCallable(const UserCallable& val); + bool m_byValue; + }; template diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 90b8e98f..f8180040 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -157,6 +157,7 @@ INSTANTIATE_TEST_CASE_P(IndexSubscriptionTest, ExpressionSubstitutionTest, ::tes InputOutputPair{"intValue[0]", ""}, InputOutputPair{"doubleValue[0]", ""}, InputOutputPair{"stringValue[0]", "r"}, + InputOutputPair{"stringValue[100]", ""}, InputOutputPair{"boolTrueValue[0]", ""}, InputOutputPair{"boolFalseValue[0]", ""}, InputOutputPair{"intList[-1]", ""}, @@ -169,6 +170,9 @@ INSTANTIATE_TEST_CASE_P(IndexSubscriptionTest, ExpressionSubstitutionTest, ::tes InputOutputPair{"mapValue['stringVal']", "string100.5"}, InputOutputPair{"mapValue['boolValue']", "true"}, InputOutputPair{"mapValue['intVAl']", ""}, + InputOutputPair{"mapValue[0]", ""}, + InputOutputPair{"(mapValue | dictsort | first)['key']", "boolValue"}, + InputOutputPair{"(mapValue | dictsort | first)['value']", "true"}, InputOutputPair{"reflectedVal['intValue']", "0"}, InputOutputPair{"reflectedVal['dblValue']", "0"}, InputOutputPair{"reflectedVal['boolValue']", "false"}, diff --git a/test/filters_test.cpp b/test/filters_test.cpp index c0c56429..2da70b9c 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -426,7 +426,7 @@ INSTANTIATE_TEST_CASE_P(Convert, FilterGenericTest, ::testing::Values( InputOutputPair{"'100' | int(10, base=8) | pprint", "64"}, InputOutputPair{"'100' | int(10, base=16) | pprint", "256"}, InputOutputPair{"'100' | list | pprint", "['1', '0', '0']"}, - InputOutputPair{"{'name'='itemName', 'val'='itemValue'} | list | pprint", "['name': 'itemName', 'val': 'itemValue']"} + InputOutputPair{"{'name'='itemName', 'val'='itemValue'} | list | pprint", "['name', 'val']"} )); INSTANTIATE_TEST_CASE_P(Trim, FilterGenericTest, ::testing::Values( diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp new file mode 100644 index 00000000..696dd7e7 --- /dev/null +++ b/test/user_callable_test.cpp @@ -0,0 +1,293 @@ +#include +#include + +#include "gtest/gtest.h" + +#include "jinja2cpp/template.h" +#include "jinja2cpp/user_callable.h" +#include "test_tools.h" + +using namespace jinja2; + +TEST(UserCallableTest, SimpleUserCallable) +{ + std::string source = R"( +{{ test() }} +{{ test() }} +)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::UserCallable uc; + uc.callable = [](auto&)->jinja2::Value {return "Hello World!";}; + jinja2::ValuesMap params; + params["test"] = std::move(uc); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +Hello World! +Hello World! +)"; + EXPECT_EQ(expectedResult, result); +} + +TEST(UserCallableTest, SimpleUserCallableWithParams1) +{ + std::string source = R"( +{{ test('Hello', 'World!') }} +{{ test(str2='World!', str1='Hello') }} +)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::UserCallable uc; + uc.callable = [](auto& params)->jinja2::Value { + return params["str1"].asString() + " " + params["str2"].asString(); + }; + uc.argsInfo = {{"str1", true}, {"str2", true}}; + jinja2::ValuesMap params; + params["test"] = std::move(uc); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +Hello World! +Hello World! +)"; + EXPECT_EQ(expectedResult, result); +} + +TEST(UserCallableTest, SimpleUserCallableWithParams2) +{ + std::string source = R"( +{{ test('Hello', 'World!') }} +{{ test(str2='World!', str1='Hello') }} +{{ test(str2='World!') }} +{{ test('Hello') }} +)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::ValuesMap params; + params["test"] = MakeCallable( + [](const std::string& str1, const std::string& str2) { + return str1 + " " + str2; + }, + ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} + ); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +Hello World! +Hello World! + World! +Hello default +)"; + EXPECT_EQ(expectedResult, result); +} + +struct UserCallableParamConvertTestTag; +using UserCallableParamConvertTest = InputOutputPairTest; + +TEST_P(UserCallableParamConvertTest, Test) +{ + auto& testParam = GetParam(); + std::string source = "{{" + testParam.tpl + " | pprint }}"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::ValuesMap params = PrepareTestData(); + + params["BoolFn"] = MakeCallable([](bool val) {return val;}, ArgInfo{"val"}); + params["IntFn"] = MakeCallable([](int val) {return val;}, ArgInfo{"val"}); + params["Int64Fn"] = MakeCallable([](int64_t val) {return val;}, ArgInfo{"val"}); + params["DoubleFn"] = MakeCallable([](double val) {return val;}, ArgInfo{"val"}); + params["StringFn"] = MakeCallable([](const std::string& val) {return val;}, ArgInfo{"val"}); + params["WStringFn"] = MakeCallable([](const std::wstring& val) {return val;}, ArgInfo{"val"}); + params["GListFn"] = MakeCallable([](const GenericList& val) {return val;}, ArgInfo{"val"}); + params["GMapFn"] = MakeCallable([](const GenericMap& val) {return val;}, ArgInfo{"val"}); + params["VarArgsFn"] = MakeCallable([](const ValuesList& val) { + return val; + }, ArgInfo{"*args"}); + params["VarKwArgsFn"] = MakeCallable([](const ValuesMap& val) { + return val; + }, ArgInfo{"**kwargs"}); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = testParam.result; + EXPECT_EQ(expectedResult, result); +} + +struct UserCallableFilterTestTag; +using UserCallableFilterTest = InputOutputPairTest; + +TEST_P(UserCallableFilterTest, Test) +{ + auto& testParam = GetParam(); + std::string source = "{{ " + testParam.tpl + " }}"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::ValuesMap params = PrepareTestData(); + + params["surround"] = MakeCallable( + [](const std::string& val, const std::string& before, const std::string& after) {return before + val + after;}, + ArgInfo{"val"}, + ArgInfo{"before", false, ">>> "}, + ArgInfo{"after", false, " <<<"} + ); + params["joiner"] = MakeCallable([](const std::string& delim, const ValuesList& items) { + std::ostringstream os; + bool isFirst = true; + for (auto& v : items) + { + if (isFirst) + isFirst = false; + else + os << delim; + os << v.asString(); + + } + return os.str(); + }, ArgInfo{"delim"}, ArgInfo{"*args"}); + params["tester"] = MakeCallable([](const std::string& testValue, const std::string& pattern) { + return testValue == pattern; + }, ArgInfo{"testVal"}, ArgInfo{"pattern"}); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = testParam.result; + EXPECT_EQ(expectedResult, result); +} + +INSTANTIATE_TEST_CASE_P(BoolParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"BoolFn()", "false"}, + InputOutputPair{"BoolFn(true)", "true"} + )); + +INSTANTIATE_TEST_CASE_P(IntParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"IntFn()", "0"}, + InputOutputPair{"IntFn(10)", "10"}, + InputOutputPair{"IntFn(10.123)", "10"} + )); + +INSTANTIATE_TEST_CASE_P(Int64ParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"Int64Fn()", "0"}, + InputOutputPair{"Int64Fn(10)", "10"}, + InputOutputPair{"Int64Fn(10.123)", "10"} + )); + +INSTANTIATE_TEST_CASE_P(DoubleParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"DoubleFn()", "0"}, + InputOutputPair{"DoubleFn(10)", "10"}, + InputOutputPair{"DoubleFn(10.123)", "10.123"} + )); + +INSTANTIATE_TEST_CASE_P(VarArgsParamsConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"VarArgsFn()", "[]"}, + InputOutputPair{"VarArgsFn(10, 'abc', false)", "[10, 'abc', false]"}, + InputOutputPair{"VarArgsFn(10.123, (1, 2, 3), arg=1)", "[10.123, [1, 2, 3]]"} + )); + +INSTANTIATE_TEST_CASE_P(VarKwArgsParamsConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"VarKwArgsFn()", "{}"}, + InputOutputPair{"VarKwArgsFn(arg1=10, arg2='abc', arg3=false) | dictsort", + "['arg1': 10, 'arg2': 'abc', 'arg3': false]"}, + InputOutputPair{"VarKwArgsFn(arg1=10.123, arg2=(1, 2, 3), 1) | dictsort", + "['arg1': 10.123, 'arg2': [1, 2, 3]]"} + )); + +INSTANTIATE_TEST_CASE_P(StringParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"StringFn()", "''"}, + InputOutputPair{"StringFn('Hello World')", "'Hello World'"}, + InputOutputPair{"StringFn(stringValue)", "'rain'"} + )); + +INSTANTIATE_TEST_CASE_P(ListParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"GListFn()", "[]"}, + InputOutputPair{"GListFn([1, 2, 3, 4])", "[1, 2, 3, 4]"}, + InputOutputPair{"GListFn(intList)", "[9, 0, 8, 1, 7, 2, 6, 3, 5, 4]"}, + InputOutputPair{"GListFn(reflectedIntVector)", "[9, 0, 8, 1, 7, 2, 6, 3, 5, 4]"}, + InputOutputPair{"GListFn(range(10))", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"}, + InputOutputPair{"GListFn([1, 2, 3, 4] | sort)","[1, 2, 3, 4]"}, + InputOutputPair{"GListFn(intList | sort)", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"}, + InputOutputPair{"GListFn(reflectedVal.tmpStructList)", + "[{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}]"}, + InputOutputPair{"GListFn(reflectedIntVector | sort)", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"}, + InputOutputPair{"GListFn(range(10) | sort)", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"}, + InputOutputPair{"GListFn({'name'='itemName', 'val'='itemValue'} | list) | sort", "['name', 'val']"}, + InputOutputPair{"GListFn({'name'='itemName', 'val'='itemValue'} | list | sort)", "['name', 'val']"} + )); + +INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"GMapFn()", "{}"}, + InputOutputPair{"GMapFn({'key'=10})", "{'key': 10}"}, + InputOutputPair{"GMapFn(simpleMapValue) | dictsort", + "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, + InputOutputPair{"GMapFn(reflectedVal) | dictsort", + "['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, " + "'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', " + "'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}], 'wstrValue': '']"}, + InputOutputPair{"GMapFn(reflectedVal.innerStruct) | dictsort", "['strValue': 'Hello World!']"} + )); + +INSTANTIATE_TEST_CASE_P(UserDefinedFilter, UserCallableFilterTest, ::testing::Values( + InputOutputPair{"'Hello World' | surround", ">>> Hello World <<<"}, + InputOutputPair{"'Hello World' | surround(before='### ')", "### Hello World <<<"}, + InputOutputPair{"'Hello World' | surround(after=' ###', before='### ')", "### Hello World ###"}, + InputOutputPair{"'Hello World' | surround('### ', ' ###')", "### Hello World ###"}, + InputOutputPair{"'Hello World' | joiner(delim=', ', '1', '2', '3')", "Hello World, 1, 2, 3"}, + InputOutputPair{"'Hello World' | joiner(delim=', ', '1', '2', '3')", "Hello World, 1, 2, 3"}, + InputOutputPair{"('A', 'B', 'C') | map('surround', before='> ', after=' <') | pprint", "['> A <', '> B <', '> C <']"}, + InputOutputPair{"'str1' if 'str1' is tester('str1') else 'str2'", "str1"}, + InputOutputPair{"'str1' if 'str3' is tester(pattern='str1') else 'str2'", "str2"}, + InputOutputPair{"('str1', 'A', 'str1', 'B', 'str1', 'C') | select('tester', 'str1') | map('surround', before='> ', after=' <') | pprint", "['> str1 <', '> str1 <', '> str1 <']"} + )); + diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 04072b67..1ac941d8 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -34,21 +34,25 @@ else() endif() find_package(variant-lite QUIET) -find_package(optional-lite QUIET) -if(variant-lite_FOUND AND optional-lite_FOUND) +if(variant-lite_FOUND) imported_target_alias(variant-lite ALIAS variant-lite::variant-lite) - imported_target_alias(optional-lite ALIAS optional-lite::optional-lite) else() - message(STATUS "variant-lite or optional-lite not found, using submodule") + message(STATUS "variant-lite not found, using submodule") update_submodule(nonstd/variant-lite) add_subdirectory(nonstd/variant-lite EXCLUDE_FROM_ALL) - # There's a bug in the lib, the target does not include the header include dirs. - # See https://github.com/martinmoene/variant-lite/issues/25 - target_include_directories(variant-lite INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/nonstd/variant-lite/include") - # Fake target until we use separated optional-lite as submodule - # See https://github.com/martinmoene/variant-lite/issues/19 - add_library(optional-lite ALIAS variant-lite) + add_library(variant-lite ALIAS variant-lite) +endif() + +find_package(optional-lite QUIET) +if(optional-lite_FOUND) + imported_target_alias(optional-lite ALIAS optional-lite::optional-lite) +else() + message(STATUS "optional-lite not found, using submodule") + update_submodule(nonstd/optional-lite) + add_subdirectory(nonstd/optional-lite EXCLUDE_FROM_ALL) + + add_library(optional-lite ALIAS optional-lite) endif() find_package(value-ptr-lite QUIET) @@ -63,19 +67,19 @@ else() endif() if(MSVC) - set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) - if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") - string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) - string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) - if (NOT THIRDPARTY_MT_POS EQUAL -1) - set (THIRDPARTY_RUNTIME_TYPE "/MT") - elseif (NOT THIRDPARTY_MD_POS EQUAL -1) - set (THIRDPARTY_RUNTIME_TYPE "/MD") - else () - message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") - set (THIRDPARTY_RUNTIME_TYPE "/MD") - endif() - endif () + set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) + if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") + string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) + string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) + if (NOT THIRDPARTY_MT_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MT") + elseif (NOT THIRDPARTY_MD_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MD") + else () + message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") + set (THIRDPARTY_RUNTIME_TYPE "/MD") + endif() + endif () endif () @@ -93,20 +97,20 @@ if(boost_filesystem_FOUND AND imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) else() - if (MSVC) - if (NOT DEFINED Boost_USE_STATIC_LIBS) - if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") - set (Boost_USE_STATIC_LIBS OFF) - set (Boost_USE_STATIC_RUNTIME OFF) - else () - set (Boost_USE_STATIC_LIBS ON) - set (Boost_USE_STATIC_RUNTIME ON) - endif () - endif () - endif () - + if (MSVC) + if (NOT DEFINED Boost_USE_STATIC_LIBS) + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (Boost_USE_STATIC_LIBS OFF) + set (Boost_USE_STATIC_RUNTIME OFF) + else () + set (Boost_USE_STATIC_LIBS ON) + set (Boost_USE_STATIC_RUNTIME ON) + endif () + endif () + endif () + find_package(Boost COMPONENTS system filesystem QUIET) - + if (Boost_FOUND) imported_target_alias(boost_filesystem ALIAS Boost::filesystem) imported_target_alias(boost_algorithm ALIAS Boost::boost) @@ -118,14 +122,14 @@ else() list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") add_subdirectory(boost EXCLUDE_FROM_ALL) - + if(NOT MSVC) # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - + if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) endif() @@ -135,7 +139,7 @@ else() if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) endif() - else () + else () endif() endif() endif() @@ -150,11 +154,11 @@ if(JINJA2CPP_BUILD_TESTS) update_submodule(gtest) if(MSVC) - if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") - set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) - else () - set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) - endif () + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) + else () + set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) + endif () endif () add_subdirectory(gtest EXCLUDE_FROM_ALL) diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite new file mode 160000 index 00000000..28904980 --- /dev/null +++ b/thirdparty/nonstd/optional-lite @@ -0,0 +1 @@ +Subproject commit 289049804c2ee66a98dab305a920808e42eac844 diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index aa3728c2..47e202e6 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit aa3728c2dbe89bbe342036aa4597808bdeb22b48 +Subproject commit 47e202e60386b0cb5805be500e6a07ae2725ad87 From fc24a0b63fec8f1f23177da41fb832531d61b104 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 16 Nov 2018 19:18:11 +0300 Subject: [PATCH 049/206] Make the most of the keywords context-dependent #73 (#88) --- README.md | 35 +++++++++++++++ src/expression_parser.cpp | 66 ++++++++++++++++------------ src/filters.cpp | 4 +- src/lexer.cpp | 20 ++++++++- src/lexer.h | 91 +++++++++++++++++++++++++++++++++++---- src/template_parser.cpp | 77 +++++++++++++++------------------ src/template_parser.h | 76 ++++++++++++++++---------------- test/errors_test.cpp | 4 +- test/filters_test.cpp | 6 +-- 9 files changed, 257 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 795403c7..ed827249 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar - ['set' statement](#set-statement) - ['extends' statement](#extends-statement) - [Macros](#macros) + - [User-defined callables](#user-defined-callables) - [Error reporting](#error-reporting) - [Other features](#other-features) - [Current Jinja2 support](#current-jinja2-support) @@ -437,6 +438,40 @@ inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} ) 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). +### 'applymacro' filter + +With help of `applymacro` filter macro can be called in filtering context. `applymacro` works similar to `map` (or `test`) filter with one exception: instead of name of other filter it takes name of macro via `macro` param and pass the rest of arguments to it. The object which is been filtered is passed as the first positional argument. For example: + +``` +{% macro toUpper(str) %}{{ str | upper }}{% endmacro %} +{{ 'Hello World!' | applymacro(macro='toUpper') }} +``` + +produces the result `HELLO WORLD`. `applymacro` can be applied to the sequences via `map` filter. Also, macro name can be `caller`. In this case outer `call` statement will be invoked during macro application. + +## User-defined callables + +Not only C++ types can be reflected into Jinja2 template context, but the functions (and lambdas, and any other callable objects) as well. These refelected callable objects are called 'user-defined callables' and can be accessed from Jinja2 templates in the same manner as any other callables (like macros or global functions). In order to reflect callable object into Jinja2 context the `jinja2::MakeCallable` method should be used: + +```c++ +jinja2::ValuesMap params; + params["concat"] = MakeCallable( + [](const std::string& str1, const std::string& str2) { + return str1 + " " + str2; + }, + ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} + ); +``` + +As a first parameter this method takes the callable itself. It can be lambda, the std::function<> instance or pointer to function. The rest of params are callable arguments descriptors, which are provided via `ArgInfo` structure. In the sample above user-defined callable `concat` is introduced, which take two argument: `str1` and `str2`. This callable can be accessed from the template in the following ways: + +``` +{{ concat('Hello', 'World!') }} +{{ concat(str2='World!', str1='Hello') }} +{{ concat(str2='World!') }} +{{ concat('Hello') }} +``` + ## 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 diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index 67a709cc..6aec9651 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -1,6 +1,7 @@ #include "expression_parser.h" #include +#include namespace jinja2 { @@ -59,7 +60,7 @@ ExpressionParser::ParseResult> E evaluator->SetFilter(*filter); } - if (includeIfPart && lexer.EatIfEqual(Token::If)) + if (includeIfPart && lexer.EatIfEqual(Keyword::If)) { if (includeIfPart) { @@ -79,7 +80,7 @@ ExpressionParser::ParseResult> ExpressionPars { auto left = ParseLogicalAnd(lexer); - if (left && lexer.EatIfEqual(Token::LogicalOr)) + if (left && lexer.EatIfEqual(Keyword::LogicalOr)) { auto right = ParseLogicalOr(lexer); if (!right) @@ -95,7 +96,7 @@ ExpressionParser::ParseResult> ExpressionPars { auto left = ParseLogicalCompare(lexer); - if (left && lexer.EatIfEqual(Token::LogicalAnd)) + if (left && lexer.EatIfEqual(Keyword::LogicalAnd)) { auto right = ParseLogicalAnd(lexer); if (!right) @@ -135,29 +136,33 @@ ExpressionParser::ParseResult> ExpressionPars case Token::LessEqual: operation = BinaryExpression::LogicalLe; break; - case Token::In: - operation = BinaryExpression::In; - break; - case Token::Is: - { - Token nextTok = lexer.NextToken(); - if (nextTok != Token::Identifier) - return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok); - - std::string name = AsString(nextTok.value); - ParseResult params; - - if (lexer.EatIfEqual('(')) - params = ParseCallParams(lexer); - - if (!params) - return params.get_unexpected(); - - return std::make_shared(*left, std::move(name), std::move(*params)); - } default: - lexer.ReturnToken(); - return left; + switch (lexer.GetAsKeyword(tok)) + { + case Keyword::In: + operation = BinaryExpression::In; + break; + case Keyword::Is: + { + Token nextTok = lexer.NextToken(); + if (nextTok != Token::Identifier) + return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok); + + std::string name = AsString(nextTok.value); + ParseResult params; + + if (lexer.EatIfEqual('(')) + params = ParseCallParams(lexer); + + if (!params) + return params.get_unexpected(); + + return std::make_shared(*left, std::move(name), std::move(*params)); + } + default: + lexer.ReturnToken(); + return left; + } } auto right = ParseStringConcat(lexer); @@ -263,7 +268,7 @@ ExpressionParser::ParseResult> ExpressionPars ExpressionParser::ParseResult> ExpressionParser::ParseUnaryPlusMinus(LexScanner& lexer) { Token tok = lexer.NextToken(); - if (tok != '+' && tok != '-' && tok != Token::LogicalNot) + if (tok != '+' && tok != '-' && lexer.GetAsKeyword(tok) != Keyword::LogicalNot) { lexer.ReturnToken(); return ParseValueExpression(lexer); @@ -279,14 +284,21 @@ ExpressionParser::ParseResult> ExpressionPars ExpressionParser::ParseResult> ExpressionParser::ParseValueExpression(LexScanner& lexer) { Token tok = lexer.NextToken(); + static const std::unordered_set forbiddenKw = {Keyword::Is, Keyword::In, Keyword::If, Keyword::Else}; ParseResult> valueRef; switch (tok.type) { case Token::Identifier: + { + auto kwType = lexer.GetAsKeyword(tok); + if (forbiddenKw.count(kwType) != 0) + return MakeParseError(ErrorCode::UnexpectedToken, tok); + valueRef = std::make_shared(AsString(tok.value)); break; + } case Token::IntegerNum: case Token::FloatNum: case Token::String: @@ -565,7 +577,7 @@ ExpressionParser::ParseResult> ExpressionPa return testExpr.get_unexpected(); ParseResult> altValue; - if (lexer.PeekNextToken() == Token::Else) + if (lexer.GetAsKeyword(lexer.PeekNextToken()) == Keyword::Else) { lexer.EatToken(); auto value = ParseFullExpression(lexer); diff --git a/src/filters.cpp b/src/filters.cpp index d6bdb317..674ff416 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -319,14 +319,14 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte ApplyMacro::ApplyMacro(FilterParams params) { - ParseParams({{"name", true}}, params); + ParseParams({{"macro", true}}, params); m_mappingParams.kwParams = m_args.extraKwArgs; m_mappingParams.posParams = m_args.extraPosArgs; } InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& context) { - InternalValue macroName = GetArgumentValue("name", context); + InternalValue macroName = GetArgumentValue("macro", context); if (IsEmpty(macroName)) return InternalValue(); diff --git a/src/lexer.cpp b/src/lexer.cpp index 3ddee219..9dc92a56 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -80,7 +80,25 @@ bool Lexer::ProcessNumber(const lexertk::token&, Token& newToken) bool Lexer::ProcessSymbolOrKeyword(const lexertk::token&, Token& newToken) { - Token::Type tokType = m_helper->GetKeyword(newToken.range); + Keyword kwType = m_helper->GetKeyword(newToken.range); + Token::Type tokType = Token::Unknown; + + switch (kwType) + { + case Keyword::None: + tokType = Token::None; + break; + case Keyword::True: + tokType = Token::True; + break; + case Keyword::False: + tokType = Token::False; + break; + default: + tokType = Token::Unknown; + break; + } + if (tokType == Token::Unknown) { newToken.type = Token::Identifier; diff --git a/src/lexer.h b/src/lexer.h index 426910b5..2ec1045b 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -52,18 +52,18 @@ struct Token GreaterEqual, StarStar, DashDash, - LogicalOr, - LogicalAnd, - LogicalNot, MulMul, DivDiv, True, False, None, - In, - Is, // Keywords + LogicalOr, + LogicalAnd, + LogicalNot, + In, + Is, For, Endfor, If, @@ -83,6 +83,8 @@ struct Token EndSet, Include, Import, + Recursive, + Scoped, // Template control CommentBegin, @@ -92,7 +94,7 @@ struct Token ExprBegin, ExprEnd, }; - + Type type = Unknown; CharRange range = {0, 0}; InternalValue value; @@ -119,11 +121,47 @@ struct Token } }; +enum class Keyword +{ + Unknown, + + // Keywords + LogicalOr, + LogicalAnd, + LogicalNot, + True, + False, + None, + In, + Is, + For, + Endfor, + If, + Else, + ElIf, + EndIf, + Block, + EndBlock, + Extends, + Macro, + EndMacro, + Call, + EndCall, + Filter, + EndFilter, + Set, + EndSet, + Include, + Import, + Recursive, + Scoped +}; + struct LexerHelper { virtual std::string GetAsString(const CharRange& range) = 0; virtual InternalValue GetAsValue(const CharRange& range, Token::Type type) = 0; - virtual Token::Type GetKeyword(const CharRange& range) = 0; + virtual Keyword GetKeyword(const CharRange& range) = 0; virtual char GetCharAt(size_t pos) = 0; }; @@ -142,6 +180,8 @@ class Lexer { return m_tokens; } + + auto GetHelper() const {return m_helper;} private: bool ProcessNumber(const lexertk::token& token, Token& newToken); @@ -188,6 +228,7 @@ class LexScanner }; LexScanner(const Lexer& lexer) + : m_helper(lexer.GetHelper()) { m_state.m_begin = lexer.GetTokens().begin(); m_state.m_end = lexer.GetTokens().end(); @@ -252,7 +293,27 @@ class LexScanner return type == Token::Type::Eof; } - if (m_state.m_cur->type == type) + return EatIfEqualImpl(tok, [type](const Token& t) {return t.type == type;}); + } + + auto GetAsKeyword(const Token& tok) const + { + return m_helper->GetKeyword(tok.range); + } + + bool EatIfEqual(Keyword kwType, Token* tok = nullptr) + { + if (m_state.m_cur == m_state.m_end) + return false; + + return EatIfEqualImpl(tok, [this, kwType](const Token& t) {return GetAsKeyword(t) == kwType;}); + } + +private: + template + bool EatIfEqualImpl(Token* tok, Fn&& predicate) + { + if (predicate(*m_state.m_cur)) { if (tok) *tok = *m_state.m_cur; @@ -265,6 +326,8 @@ class LexScanner private: State m_state; + LexerHelper* m_helper; + static const Token& EofToken() { static Token eof; @@ -275,4 +338,16 @@ class LexScanner } // jinja2 +namespace std +{ +template<> +struct hash +{ + size_t operator()(jinja2::Keyword kw) const + { + return std::hash{}(static_cast(kw)); + } +}; +} + #endif // LEXER_H diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 5b6454aa..565799ab 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -9,55 +9,55 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme Token tok = lexer.NextToken(); ParseResult result; - switch (tok.type) + switch (lexer.GetAsKeyword(tok)) { - case jinja2::Token::For: + case Keyword::For: result = ParseFor(lexer, statementsInfo, tok); break; - case jinja2::Token::Endfor: + case Keyword::Endfor: result = ParseEndFor(lexer, statementsInfo, tok); break; - case jinja2::Token::If: + case Keyword::If: result = ParseIf(lexer, statementsInfo, tok); break; - case jinja2::Token::Else: + case Keyword::Else: result = ParseElse(lexer, statementsInfo, tok); break; - case jinja2::Token::ElIf: + case Keyword::ElIf: result = ParseElIf(lexer, statementsInfo, tok); break; - case jinja2::Token::EndIf: + case Keyword::EndIf: result = ParseEndIf(lexer, statementsInfo, tok); break; - case jinja2::Token::Set: + case Keyword::Set: result = ParseSet(lexer, statementsInfo, tok); break; - case jinja2::Token::Block: + case Keyword::Block: result = ParseBlock(lexer, statementsInfo, tok); break; - case jinja2::Token::EndBlock: + case Keyword::EndBlock: result = ParseEndBlock(lexer, statementsInfo, tok); break; - case jinja2::Token::Extends: + case Keyword::Extends: result = ParseExtends(lexer, statementsInfo, tok); break; - case jinja2::Token::Macro: + case Keyword::Macro: result = ParseMacro(lexer, statementsInfo, tok); break; - case jinja2::Token::EndMacro: + case Keyword::EndMacro: result = ParseEndMacro(lexer, statementsInfo, tok); break; - case jinja2::Token::Call: + case Keyword::Call: result = ParseCall(lexer, statementsInfo, tok); break; - case jinja2::Token::EndCall: + case Keyword::EndCall: result = ParseEndCall(lexer, statementsInfo, tok); break; - case jinja2::Token::Filter: - case jinja2::Token::EndFilter: - case jinja2::Token::EndSet: - case jinja2::Token::Include: - case jinja2::Token::Import: + case Keyword::Filter: + case Keyword::EndFilter: + case Keyword::EndSet: + case Keyword::Include: + case Keyword::Import: return MakeParseError(ErrorCode::YetUnsupported, tok); default: return MakeParseError(ErrorCode::UnexpectedToken, tok); @@ -92,7 +92,7 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat if (vars.empty()) return MakeParseError(ErrorCode::ExpectedIdentifier, lexer.PeekNextToken()); - if (!lexer.EatIfEqual(Token::In)) + if (!lexer.EatIfEqual(Keyword::In)) { Token tok1 = lexer.PeekNextToken(); Token tok2 = tok1; @@ -115,25 +115,13 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat Token flagsTok; bool isRecursive = false; - if (lexer.EatIfEqual(Token::Identifier, &flagsTok)) + if (lexer.EatIfEqual(Keyword::Recursive, &flagsTok)) { - auto flagsName = AsString(flagsTok.value); - if (flagsName != "recursive") - { - auto tok2 = flagsTok; - tok2.type = Token::Identifier; - tok2.range.endOffset = tok2.range.startOffset; - tok2.value = std::string("recursive"); - auto tok3 = flagsTok; - tok3.type = Token::If; - return MakeParseError(ErrorCode::ExpectedToken, flagsTok, {tok2, tok3}); - } - isRecursive = true; } ExpressionEvaluatorPtr<> ifExpr; - if (lexer.EatIfEqual(Token::If)) + if (lexer.EatIfEqual(Keyword::If)) { auto parsedExpr = exprPraser.ParseFullExpression(lexer, false); if (!parsedExpr) @@ -146,8 +134,10 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat auto tok2 = tok1; tok2.type = Token::If; auto tok3 = tok1; - tok3.type = Token::Eof; - return MakeParseError(ErrorCode::ExpectedToken, tok1, {tok2, tok3}); + tok3.type = Token::Recursive; + auto tok4 = tok1; + tok4.type = Token::Eof; + return MakeParseError(ErrorCode::ExpectedToken, tok1, {tok2, tok3, tok4}); } auto renderer = std::make_shared(vars, *valueExpr, ifExpr, isRecursive); @@ -335,18 +325,19 @@ StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, St else { bool isScoped = false; - if (lexer.EatIfEqual(Token::Identifier, &nextTok)) + if (lexer.EatIfEqual(Keyword::Scoped, &nextTok)) + isScoped = true; + else { - auto id = AsString(nextTok.value); - if (id != "scoped") + nextTok = lexer.PeekNextToken(); + if (nextTok != Token::Eof) { auto tok2 = nextTok; - tok2.range.startOffset = tok2.range.endOffset; - tok2.value = std::string("scoped"); + tok2.type = Token::Scoped; return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2}); } - isScoped = true; } + blockRenderer = std::make_shared(blockName, isScoped); } diff --git a/src/template_parser.h b/src/template_parser.h index 1471e7ad..dac59eec 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -32,7 +32,7 @@ struct ParserTraits; struct KeywordsInfo { MultiStringLiteral name; - Token::Type type; + Keyword type; }; struct TokenStrInfo : MultiStringLiteral @@ -49,7 +49,7 @@ template struct ParserTraitsBase { static Token::Type s_keywords[]; - static KeywordsInfo s_keywordsInfo[30]; + static KeywordsInfo s_keywordsInfo[32]; static std::unordered_map s_tokens; }; @@ -732,7 +732,7 @@ class TemplateParser : public LexerHelper return traits_t::RangeToNum(*m_template, range, type); return InternalValue(); } - Token::Type GetKeyword(const CharRange& range) override + Keyword GetKeyword(const CharRange& range) override { auto matchBegin = sregex_iterator(m_template->begin() + range.startOffset, m_template->begin() + range.endOffset, m_keywords); auto matchEnd = sregex_iterator(); @@ -740,7 +740,7 @@ class TemplateParser : public LexerHelper auto matches = std::distance(matchBegin, matchEnd); // One line, no customization if (matches == 0) - return Token::Unknown; + return Keyword::Unknown; auto& match = *matchBegin; for (size_t idx = 1; idx != match.size(); ++ idx) @@ -751,7 +751,7 @@ class TemplateParser : public LexerHelper } } - return Token::Unknown; + return Keyword::Unknown; } char GetCharAt(size_t /*pos*/) override { @@ -770,37 +770,39 @@ class TemplateParser : public LexerHelper }; template -KeywordsInfo ParserTraitsBase::s_keywordsInfo[30] = { - {UNIVERSAL_STR("for"), Token::For}, - {UNIVERSAL_STR("endfor"), Token::Endfor}, - {UNIVERSAL_STR("in"), Token::In}, - {UNIVERSAL_STR("if"), Token::If}, - {UNIVERSAL_STR("else"), Token::Else}, - {UNIVERSAL_STR("elif"), Token::ElIf}, - {UNIVERSAL_STR("endif"), Token::EndIf}, - {UNIVERSAL_STR("or"), Token::LogicalOr}, - {UNIVERSAL_STR("and"), Token::LogicalAnd}, - {UNIVERSAL_STR("not"), Token::LogicalNot}, - {UNIVERSAL_STR("is"), Token::Is}, - {UNIVERSAL_STR("block"), Token::Block}, - {UNIVERSAL_STR("endblock"), Token::EndBlock}, - {UNIVERSAL_STR("extends"), Token::Extends}, - {UNIVERSAL_STR("macro"), Token::Macro}, - {UNIVERSAL_STR("endmacro"), Token::EndMacro}, - {UNIVERSAL_STR("call"), Token::Call}, - {UNIVERSAL_STR("endcall"), Token::EndCall}, - {UNIVERSAL_STR("filter"), Token::Filter}, - {UNIVERSAL_STR("endfilter"), Token::EndFilter}, - {UNIVERSAL_STR("set"), Token::Set}, - {UNIVERSAL_STR("endset"), Token::EndSet}, - {UNIVERSAL_STR("include"), Token::Include}, - {UNIVERSAL_STR("import"), Token::Import}, - {UNIVERSAL_STR("true"), Token::True}, - {UNIVERSAL_STR("false"), Token::False}, - {UNIVERSAL_STR("True"), Token::True}, - {UNIVERSAL_STR("False"), Token::False}, - {UNIVERSAL_STR("none"), Token::None}, - {UNIVERSAL_STR("None"), Token::None}, +KeywordsInfo ParserTraitsBase::s_keywordsInfo[32] = { + {UNIVERSAL_STR("for"), Keyword::For}, + {UNIVERSAL_STR("endfor"), Keyword::Endfor}, + {UNIVERSAL_STR("in"), Keyword::In}, + {UNIVERSAL_STR("if"), Keyword::If}, + {UNIVERSAL_STR("else"), Keyword::Else}, + {UNIVERSAL_STR("elif"), Keyword::ElIf}, + {UNIVERSAL_STR("endif"), Keyword::EndIf}, + {UNIVERSAL_STR("or"), Keyword::LogicalOr}, + {UNIVERSAL_STR("and"), Keyword::LogicalAnd}, + {UNIVERSAL_STR("not"), Keyword::LogicalNot}, + {UNIVERSAL_STR("is"), Keyword::Is}, + {UNIVERSAL_STR("block"), Keyword::Block}, + {UNIVERSAL_STR("endblock"), Keyword::EndBlock}, + {UNIVERSAL_STR("extends"), Keyword::Extends}, + {UNIVERSAL_STR("macro"), Keyword::Macro}, + {UNIVERSAL_STR("endmacro"), Keyword::EndMacro}, + {UNIVERSAL_STR("call"), Keyword::Call}, + {UNIVERSAL_STR("endcall"), Keyword::EndCall}, + {UNIVERSAL_STR("filter"), Keyword::Filter}, + {UNIVERSAL_STR("endfilter"), Keyword::EndFilter}, + {UNIVERSAL_STR("set"), Keyword::Set}, + {UNIVERSAL_STR("endset"), Keyword::EndSet}, + {UNIVERSAL_STR("include"), Keyword::Include}, + {UNIVERSAL_STR("import"), Keyword::Import}, + {UNIVERSAL_STR("true"), Keyword::True}, + {UNIVERSAL_STR("false"), Keyword::False}, + {UNIVERSAL_STR("True"), Keyword::True}, + {UNIVERSAL_STR("False"), Keyword::False}, + {UNIVERSAL_STR("none"), Keyword::None}, + {UNIVERSAL_STR("None"), Keyword::None}, + {UNIVERSAL_STR("recursive"), Keyword::Recursive}, + {UNIVERSAL_STR("scoped"), Keyword::Scoped}, }; template @@ -857,6 +859,8 @@ std::unordered_map ParserTraitsBase::s_tokens = { {Token::EndSet, UNIVERSAL_STR("endset")}, {Token::Include, UNIVERSAL_STR("include")}, {Token::Import, UNIVERSAL_STR("import")}, + {Token::Recursive, UNIVERSAL_STR("recursive")}, + {Token::Scoped, UNIVERSAL_STR("scoped")}, {Token::CommentBegin, UNIVERSAL_STR("{#")}, {Token::CommentEnd, UNIVERSAL_STR("#}")}, {Token::StmtBegin, UNIVERSAL_STR("{%")}, diff --git a/test/errors_test.cpp b/test/errors_test.cpp index e4ba7560..dd0e83b3 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -141,9 +141,9 @@ INSTANTIATE_TEST_CASE_P(StatementsTest, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{% for i in range(10,)%}", "noname.j2tpl:1:22: error: Unexpected token: ')'\n{% for i in range(10,)%}\n ---^-------"}, InputOutputPair{"{% for i in range(10) rec%}", - "noname.j2tpl:1:23: error: Unexpected token 'rec'. Expected: 'recursive', 'if'\n{% for i in range(10) rec%}\n ---^-------"}, + "noname.j2tpl:1:23: error: Unexpected token 'rec'. Expected: 'if', 'recursive', '<>'\n{% for i in range(10) rec%}\n ---^-------"}, InputOutputPair{"{% for i in range(10) endfor%}", - "noname.j2tpl:1:23: error: Unexpected token 'endfor'. Expected: 'if', '<>'\n{% for i in range(10) endfor%}\n ---^-------"}, + "noname.j2tpl:1:23: error: Unexpected token 'endfor'. Expected: 'if', 'recursive', '<>'\n{% for i in range(10) endfor%}\n ---^-------"}, InputOutputPair{"{% for i in range(10) if {key} %}", "noname.j2tpl:1:27: error: String expected\n{% for i in range(10) if {key} %}\n ---^-------"}, InputOutputPair{"{% for i in range(10) if true else hello %}", diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 2da70b9c..84de6f0c 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -74,8 +74,8 @@ TEST(FilterGenericTestSingle, ApplyMacroTest) { std::string source = R"( {% macro test(str) %}{{ str | upper }}{% endmacro %} -{{ 'Hello World!' | applymacro(name='test') }} -{{ ['str1', 'str2', 'str3'] | map('applymacro', name='test') | join(', ') }} +{{ 'Hello World!' | applymacro(macro='test') }} +{{ ['str1', 'str2', 'str3'] | map('applymacro', macro='test') | join(', ') }} )"; Template tpl; @@ -99,7 +99,7 @@ STR1, STR2, STR3 TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest) { std::string source = R"( -{% macro joiner(list, delim) %}{{ list | map('applymacro', name='caller') | join(delim) }}{% endmacro %} +{% macro joiner(list, delim) %}{{ list | map('applymacro', macro='caller') | join(delim) }}{% endmacro %} {% call(item) joiner(['str1', 'str2', 'str3'], '->') %}{{item | upper}}{% endcall %} )"; From d103f67f40e76709c479725196180e491ff86da5 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Sat, 12 Jan 2019 20:10:08 +0300 Subject: [PATCH 050/206] polish public CMake options and fix README --- CMakeLists.txt | 30 ++++++++++++++++++------------ README.md | 7 ++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ddb345ab..e4173a0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,12 +40,19 @@ endif() if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") - set(JINJA2CPP_IS_MAIN_PROEJCT TRUE) + set(JINJA2CPP_IS_MAIN_PROJECT TRUE) else() - set(JINJA2CPP_IS_MAIN_PROEJCT FALSE) + set(JINJA2CPP_IS_MAIN_PROJECT FALSE) endif() -option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROEJCT}) +option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROJECT}) +option(JINJA2CPP_STRICT_WARNINGS "Enable additional warnings and treat them as errors" ON) +option(JINJA2CPP_BUILD_SHARED "Build shared linkage version of Jinja2Cpp" OFF) +if (JINJA2CPP_BUILD_SHARED) + set(LIB_LINK_TYPE SHARED) +else() + set(LIB_LINK_TYPE STATIC) +endif() include(collect_sources) @@ -54,11 +61,11 @@ set (LIB_TARGET_NAME jinja2cpp) CollectSources(Sources Headers ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) CollectSources(PublicSources PublicHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include) -add_library(${LIB_TARGET_NAME} STATIC +add_library(${LIB_TARGET_NAME} ${LIB_LINK_TYPE} ${Sources} ${Headers} ${PublicHeaders} - ) +) string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") @@ -70,17 +77,16 @@ target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) - +if(JINJA2CPP_STRICT_WARNINGS) if(NOT MSVC) target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) - if (COVERAGE_ENABLED) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -g PUBLIC -O0 --coverage -fprofile-arcs -ftest-coverage) - else () - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) - endif () else () target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() +endif() +if (COVERAGE_ENABLED AND NOT MSVC) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -g PUBLIC -O0 --coverage -fprofile-arcs -ftest-coverage) +endif () target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) set_target_properties(${LIB_TARGET_NAME} PROPERTIES @@ -117,7 +123,7 @@ if (JINJA2CPP_BUILD_TESTS) add_custom_target(CopyTestData ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl - ) + ) add_dependencies(jinja2cpp_tests CopyTestData) diff --git a/README.md b/README.md index ed827249..47722200 100644 --- a/README.md +++ b/README.md @@ -569,7 +569,7 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f - Microsoft Visual Studio 2017 x86, x64 # Build and install -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. +Jinja2Cpp has five external dependencies: boost library (at least version 1.55) and several header-only dependecies from nonstd project(expected-lite, variant-lite, value-ptr-lite, optional-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: @@ -612,10 +612,11 @@ In order to compile Jinja2Cpp you need: ## Additional CMake build flags You can define (via -D command line CMake option) the following build flags: -* **JINJA2CPP_BUILD_TESTS** (default TRUE) - build or not Jinja2Cpp tests. +* **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2Cpp tests. +* **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). +* **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. * **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). * **BOOST_ROOT** - Path to the prebuilt boost installation -* **LIBRARY_TYPE** Could be STATIC (default for Windows platform) or SHARED (default for Linux). Specify the type of Jinja2Cpp library to build. # Link with you projects Jinja2Cpp is shipped with cmake finder script. So you can: From fa8aaeec8ee78f49544e5ce798bcd0b7d0dd627b Mon Sep 17 00:00:00 2001 From: rmorozov Date: Tue, 29 Jan 2019 16:49:22 +0300 Subject: [PATCH 051/206] Fix packaging and nonstd modules search logic, when using externally provided third party libs (#93) * polish public CMake options and fix README * update deps * small fixes * update value ptr dep * fix build * return back * fix packaging and nonstd modules search * revert to alias usage * fix value-ptr * update deps * fix gtest dep --- .gitmodules | 2 +- CMakeLists.txt | 8 ++------ thirdparty/CMakeLists.txt | 24 ++++++++++++------------ thirdparty/boost | 2 +- thirdparty/gtest | 2 +- thirdparty/nonstd/expected-lite | 2 +- thirdparty/nonstd/optional-lite | 2 +- thirdparty/nonstd/value-ptr-lite | 2 +- thirdparty/nonstd/variant-lite | 2 +- 9 files changed, 21 insertions(+), 25 deletions(-) diff --git a/.gitmodules b/.gitmodules index 517eb01f..f097ab51 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/google/googletest.git [submodule "thirdparty/nonstd/value-ptr-lite"] path = thirdparty/nonstd/value-ptr-lite - url = https://github.com/flexferrum/value-ptr-lite.git + url = https://github.com/martinmoene/value-ptr-lite.git [submodule "thirdparty/boost"] path = thirdparty/boost url = https://github.com/Manu343726/boost-cmake.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e4173a0f..c100a2ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,9 +70,9 @@ add_library(${LIB_TARGET_NAME} ${LIB_LINK_TYPE} string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") -add_subdirectory(thirdparty) +include(thirdparty/CMakeLists.txt) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) +target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_filesystem) target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) @@ -136,8 +136,4 @@ install(TARGETS ${LIB_TARGET_NAME} ARCHIVE DESTINATION lib/static) install (DIRECTORY include/ DESTINATION include) -install (DIRECTORY thirdparty/nonstd/expected-lite/include/ DESTINATION include) -install (DIRECTORY thirdparty/nonstd/variant-lite/include/ DESTINATION include) -install (DIRECTORY thirdparty/nonstd/value-ptr-lite/include/ DESTINATION include) -install (DIRECTORY thirdparty/nonstd/optional-lite/include/ DESTINATION include) install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 1ac941d8..6b740d70 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -26,42 +26,42 @@ endfunction() find_package(expected-lite QUIET) if(expected-lite_FOUND) - imported_target_alias(expected-lite ALIAS expected-lite::expected-lite) + imported_target_alias(expected-lite ALIAS nonstd::expected-lite) else() message(STATUS "expected-lite not found, using submodule") update_submodule(nonstd/expected-lite) - add_subdirectory(nonstd/expected-lite EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/nonstd/expected-lite EXCLUDE_FROM_ALL) endif() find_package(variant-lite QUIET) if(variant-lite_FOUND) - imported_target_alias(variant-lite ALIAS variant-lite::variant-lite) + imported_target_alias(variant-lite ALIAS nonstd::variant-lite) else() message(STATUS "variant-lite not found, using submodule") update_submodule(nonstd/variant-lite) - add_subdirectory(nonstd/variant-lite EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/nonstd/variant-lite EXCLUDE_FROM_ALL) add_library(variant-lite ALIAS variant-lite) endif() find_package(optional-lite QUIET) if(optional-lite_FOUND) - imported_target_alias(optional-lite ALIAS optional-lite::optional-lite) + imported_target_alias(optional-lite ALIAS nonstd::optional-lite) else() message(STATUS "optional-lite not found, using submodule") update_submodule(nonstd/optional-lite) - add_subdirectory(nonstd/optional-lite EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) add_library(optional-lite ALIAS optional-lite) endif() -find_package(value-ptr-lite QUIET) -if(value-ptr-lite_FOUND) - imported_target_alias(value-ptr-lite ALIAS value-ptr-lite::value-ptr-lite) +find_package(value_ptr-lite QUIET) +if(value_ptr-lite_FOUND) + imported_target_alias(value-ptr-lite ALIAS nonstd::value_ptr-lite) else() message(STATUS "value-ptr-lite not found, using submodule") update_submodule(nonstd/value-ptr-lite) - add_subdirectory(nonstd/value-ptr-lite EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/nonstd/value-ptr-lite EXCLUDE_FROM_ALL) add_library(value-ptr-lite ALIAS value_ptr-lite) endif() @@ -150,7 +150,7 @@ if(JINJA2CPP_BUILD_TESTS) if(gtest_FOUND) imported_target_alias(gtest ALIAS gtest::gtest) else() - message(STATUS "expected-lite not found, using submodule") + message(STATUS "gtest not found, using submodule") update_submodule(gtest) if(MSVC) @@ -161,6 +161,6 @@ if(JINJA2CPP_BUILD_TESTS) endif () endif () - add_subdirectory(gtest EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/gtest EXCLUDE_FROM_ALL) endif() endif() diff --git a/thirdparty/boost b/thirdparty/boost index 83b7a7af..009c3843 160000 --- a/thirdparty/boost +++ b/thirdparty/boost @@ -1 +1 @@ -Subproject commit 83b7a7af2130a9d23077cb9a3a593e5306a37ecc +Subproject commit 009c3843b49a56880d988ffdca6d909f881edb3d diff --git a/thirdparty/gtest b/thirdparty/gtest index 2fe3bd99..bf07131c 160000 --- a/thirdparty/gtest +++ b/thirdparty/gtest @@ -1 +1 @@ -Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2 +Subproject commit bf07131c1d0a4e001daeee8936089f8b438b7f30 diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index 6f5f176e..d6685ea3 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit 6f5f176efb8a3f88979c30b52d05efc8979fea6f +Subproject commit d6685ea36dae0964004d8f35beb254d5dadbdc43 diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite index 28904980..a2cec490 160000 --- a/thirdparty/nonstd/optional-lite +++ b/thirdparty/nonstd/optional-lite @@ -1 +1 @@ -Subproject commit 289049804c2ee66a98dab305a920808e42eac844 +Subproject commit a2cec49027cfd522da99f39d63d51f4816717346 diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 47e202e6..1648dc3b 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 47e202e60386b0cb5805be500e6a07ae2725ad87 +Subproject commit 1648dc3b99e4ae44743f680e76c8289b2fc0971e diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite index 9fd357b4..fb2f3a8f 160000 --- a/thirdparty/nonstd/variant-lite +++ b/thirdparty/nonstd/variant-lite @@ -1 +1 @@ -Subproject commit 9fd357b4b51f9a4abb510b0346dac4ee680c0d65 +Subproject commit fb2f3a8f822ad649e7c0849b4859c1fcf864a200 From 96395c9b2a0d632a23a38a2d88aab3c719fe34ef Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 29 Jan 2019 18:09:04 +0300 Subject: [PATCH 052/206] Fix build with the 'boost' shipped as a submodule --- thirdparty/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 6b740d70..b589ddf4 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -121,7 +121,7 @@ else() update_submodule(boost) list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") - add_subdirectory(boost EXCLUDE_FROM_ALL) + add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) if(NOT MSVC) # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs From 61935526115910603de3b69575d6c761322ec9cf Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 29 Jan 2019 18:18:41 +0300 Subject: [PATCH 053/206] Add missing boost libraries as a dependencies --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c100a2ae..9f1a62cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") include(thirdparty/CMakeLists.txt) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_filesystem) +target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) target_include_directories(${LIB_TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) From 5c6a29839fe62bde49076811b05e0711aeb0b741 Mon Sep 17 00:00:00 2001 From: Nic30 Date: Sat, 23 Feb 2019 20:15:33 +0100 Subject: [PATCH 054/206] [skip ci] README.md - rm note about boost as only dep Jinja2Cpp has five external dependencies: boost library (at least version 1.55) and several header-only dependecies from nonstd project(expected-lite, variant-lite, value-ptr-lite, optional-lite). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47722200..127ac477 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [ ![Download](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) -C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. Unlike [inja](https://github.com/pantor/inja) lib, you have to build Jinja2Cpp, but it has only one dependence: boost. +C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. # Table of contents From 4150e81b2ed864abf3e529ef65fe286ef9d76ddc Mon Sep 17 00:00:00 2001 From: Orivej Desh Date: Wed, 20 Mar 2019 10:17:37 +0000 Subject: [PATCH 055/206] Fix filters_tests that depend on sorting order (#100) The switch to stable_sort makes the `unique' operator deterministically return the first unique elements. Fixes Convert/FilterGenericTest/14: jinja2cpp/test/test_tools.h:128 Expected: expectedResult Which is: "['name', 'val']" To be equal to: result Which is: "['val', 'name']" and Unique/ListIteratorTest/9: jinja2cpp/test/filters_test.cpp:28 Expected: expectedResult Which is: "test string 0, test string 1" To be equal to: result Which is: "test string 0, test string 5" --- src/filters.cpp | 4 ++-- test/filters_test.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index 674ff416..03f38711 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -659,7 +659,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte for (auto& v : list) items.push_back(Item{IsEmpty(attrName) ? v : Subscript(v, attrName), idx ++}); - std::sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) { + std::stable_sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) { auto cmpRes = Apply2(i1.val, i2.val, BinaryExpression::LogicalLt, compType); return ConvertToBool(cmpRes); @@ -672,7 +672,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte }); items.erase(end, items.end()); - std::sort(items.begin(), items.end(), [](auto& i1, auto& i2) { + std::stable_sort(items.begin(), items.end(), [](auto& i1, auto& i2) { return i1.idx < i2.idx; }); diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 84de6f0c..957a9f49 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -426,7 +426,7 @@ INSTANTIATE_TEST_CASE_P(Convert, FilterGenericTest, ::testing::Values( InputOutputPair{"'100' | int(10, base=8) | pprint", "64"}, InputOutputPair{"'100' | int(10, base=16) | pprint", "256"}, InputOutputPair{"'100' | list | pprint", "['1', '0', '0']"}, - InputOutputPair{"{'name'='itemName', 'val'='itemValue'} | list | pprint", "['name', 'val']"} + InputOutputPair{"{'name'='itemName', 'val'='itemValue'} | list | sort | pprint", "['name', 'val']"} )); INSTANTIATE_TEST_CASE_P(Trim, FilterGenericTest, ::testing::Values( From a1809f720681ca86333ad5bf5a74115b984655e3 Mon Sep 17 00:00:00 2001 From: Orivej Desh Date: Wed, 20 Mar 2019 12:15:09 +0000 Subject: [PATCH 056/206] Tag ListAdapter::Iterator as a forward iterator (#99) This fixes the build with libcxx which currently fails with: In file included from jinja2cpp/src/filters.cpp:1: In file included from jinja2cpp/src/filters.h:4: In file included from jinja2cpp/src/expression_evaluator.h:4: In file included from jinja2cpp/src/internal_value.h:4: In file included from jinja2cpp/include/jinja2cpp/value.h:6: In file included from libcxx/include/vector:274: In file included from libcxx/include/__bit_reference:15: libcxx/include/algorithm:2493:5: error: static_assert failed due to requirement '__is_forward_iterator::value' "std::max_element requires a ForwardIterator" static_assert(__is_forward_iterator<_ForwardIterator>::value, ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- src/internal_value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal_value.h b/src/internal_value.h index 0cc418b7..91f51655 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -358,7 +358,7 @@ class ListAdapter::Iterator : public boost::iterator_facade< Iterator, const InternalValue, - boost::single_pass_traversal_tag> + boost::forward_traversal_tag> { public: Iterator() From 5c03004be424e61191062af2ded93723d19b62b5 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 2 Apr 2019 12:27:11 +0300 Subject: [PATCH 057/206] [skip ci] Notes about build with C++17 standard Some notes about build with C++17 standard enabled for the project. Thanks @cyrixsimon ( #101 ) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 127ac477..98dee566 100644 --- a/README.md +++ b/README.md @@ -655,6 +655,8 @@ target_link_libraries(YourTarget ) #... ``` +4. Build with C++17 standard enabled +In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD` macro in the build settings. # Acknowledgments Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. From c925d574f6c771b56b3d980ff1c541faae200f73 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 25 Apr 2019 14:36:44 +0300 Subject: [PATCH 058/206] [skip ci] Fix references --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98dee566..bfc1ef71 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ [![Build status](https://ci.appveyor.com/api/projects/status/19v2k3bl63jxl42f/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/Jinja2Cpp) [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) -[![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) -[![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/flexferrum/Jinja2Cpp/issues) -[![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/flexferrum/Jinja2Cpp/master/LICENSE) +[![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) +[![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) +[![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) [ ![Download](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) From 0347815572897b29cf6b64c99a260dd31c3fe466 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 25 Apr 2019 14:41:55 +0300 Subject: [PATCH 059/206] [skip ci] Fix references --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bfc1ef71..198787a7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) -[![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) +[![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) [ ![Download](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) From 0b54722697c5a9e4900287c7a462b4755327a209 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 25 Apr 2019 17:12:39 +0300 Subject: [PATCH 060/206] [skip ci] Update logo --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 198787a7..d3e8f9e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Jinja2Cpp +
+ +# Jinja2С++ [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) From 7636b422b6a7c8cb6d08bbe258b8b7e1a60c20bf Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 30 Apr 2019 17:57:08 +0300 Subject: [PATCH 061/206] [skip ci] Fix travis badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3e8f9e0..18fd8b22 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) -[![Build Status](https://travis-ci.org/flexferrum/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/flexferrum/Jinja2Cpp) +[![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) [![Build status](https://ci.appveyor.com/api/projects/status/19v2k3bl63jxl42f/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/Jinja2Cpp) [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) From 79e043bd25cf7247c9209294621375baa131a6d2 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 30 Apr 2019 17:58:09 +0300 Subject: [PATCH 062/206] [skip ci] Fix conan badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18fd8b22..208e6643 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) -[ ![Download](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) +[ ![conan.io](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. From 634dbf06e7b00612be87a2c4607b6d4697335656 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 09:47:30 +0300 Subject: [PATCH 063/206] Cmake deps mode (#102) * Add JINJA2CPP_DEPS_MODE setting for CMakeLists.txt * Add 'external-boost' deps mode * Add 'external' deps management mode * Add explicit nothrow ctors for ErrorInfo/ParserError * Update submodules refs * fix ref * Add nothrow move-ctor for jinja2::Value * Fix nothrow move-ctor for ErrorInfoTpl * Fix error handling issues * Add 'conan-build' dependency management mode * Fix library list propagation * Remove boost::optional from the public headers * Fix conan package libs reference * Fix 'conan-build' deps management mode * Adopt conan-build mode for 'cmake-find-package' generator * Rework installation scenario for 'internal' deps management mode * Fix installation scripts * Fix build for some installation scenarios * Fix installation for "conan-*" dep management modes * Clean up cmake modules * Fix build --- CMakeLists.txt | 87 ++++++++-- ...jinja2cpp-config-deps-conan-build.cmake.in | 0 .../jinja2cpp-config-deps-conan.cmake.in | 0 ...ja2cpp-config-deps-external-boost.cmake.in | 15 ++ .../jinja2cpp-config-deps-external.cmake.in | 45 +++++ .../jinja2cpp-config-deps-internal.cmake.in | 0 cmake/public/jinja2cpp-config.cmake.in | 81 +++++++++ include/jinja2cpp/error_info.h | 33 ++++ include/jinja2cpp/reflected_value.h | 10 +- include/jinja2cpp/value.h | 18 +- jinja2cpp.pc.in | 12 ++ src/error_handling.h | 19 +++ src/template_env.cpp | 12 +- thirdparty/CMakeLists.txt | 159 ++++-------------- thirdparty/external_boost_deps.cmake | 45 +++++ thirdparty/gtest | 2 +- thirdparty/internal_deps.cmake | 22 +++ thirdparty/nonstd/CMakeLists.txt | 21 --- thirdparty/nonstd/expected-lite | 2 +- thirdparty/nonstd/optional-lite | 2 +- thirdparty/nonstd/value-ptr-lite | 2 +- thirdparty/nonstd/variant-lite | 2 +- thirdparty/thirdparty-conan-build.cmake | 10 ++ thirdparty/thirdparty-external-boost.cmake | 4 + thirdparty/thirdparty-external.cmake | 50 ++++++ thirdparty/thirdparty-internal.cmake | 35 ++++ 26 files changed, 517 insertions(+), 171 deletions(-) create mode 100644 cmake/public/jinja2cpp-config-deps-conan-build.cmake.in create mode 100644 cmake/public/jinja2cpp-config-deps-conan.cmake.in create mode 100644 cmake/public/jinja2cpp-config-deps-external-boost.cmake.in create mode 100644 cmake/public/jinja2cpp-config-deps-external.cmake.in create mode 100644 cmake/public/jinja2cpp-config-deps-internal.cmake.in create mode 100644 cmake/public/jinja2cpp-config.cmake.in create mode 100644 jinja2cpp.pc.in create mode 100644 thirdparty/external_boost_deps.cmake create mode 100644 thirdparty/internal_deps.cmake delete mode 100644 thirdparty/nonstd/CMakeLists.txt create mode 100644 thirdparty/thirdparty-conan-build.cmake create mode 100644 thirdparty/thirdparty-external-boost.cmake create mode 100644 thirdparty/thirdparty-external.cmake create mode 100644 thirdparty/thirdparty-internal.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1a62cc..e029e278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,18 @@ -cmake_minimum_required(VERSION 3.0.2) +cmake_minimum_required(VERSION 3.0.1) project(Jinja2Cpp VERSION 0.9.1) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) endif () +set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") +if (NOT JINJA2CPP_DEPS_MODE) + set (JINJA2CPP_DEPS_MODE "internal") +endif () + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED) @@ -48,6 +56,7 @@ endif() option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROJECT}) option(JINJA2CPP_STRICT_WARNINGS "Enable additional warnings and treat them as errors" ON) option(JINJA2CPP_BUILD_SHARED "Build shared linkage version of Jinja2Cpp" OFF) + if (JINJA2CPP_BUILD_SHARED) set(LIB_LINK_TYPE SHARED) else() @@ -72,10 +81,12 @@ set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") include(thirdparty/CMakeLists.txt) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC expected-lite variant-lite value-ptr-lite optional-lite boost_variant boost_filesystem boost_algorithm) +target_link_libraries(${LIB_TARGET_NAME} PUBLIC ${JINJA2CPP_PUBLIC_LIBS} PRIVATE ${JINJA2CPP_PRIVATE_LIBS}) target_include_directories(${LIB_TARGET_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + PUBLIC + $ + $) if(JINJA2CPP_STRICT_WARNINGS) if(NOT MSVC) @@ -89,9 +100,17 @@ if (COVERAGE_ENABLED AND NOT MSVC) endif () target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) + set_target_properties(${LIB_TARGET_NAME} PROPERTIES CXX_STANDARD 14 - CXX_STANDARD_REQUIRED ON) + CXX_STANDARD_REQUIRED ON + VERSION ${PROJECT_VERSION} + SOVERSION 1 + ) + +set_property(TARGET ${LIB_TARGET_NAME} PROPERTY PUBLIC_HEADER ${PublicHeaders} ${JINJA2CPP_EXTRA_PUBLIC_HEADERS}) + +configure_file(jinja2cpp.pc.in jinja2cpp.pc @ONLY) if (JINJA2CPP_BUILD_TESTS) enable_testing() @@ -130,10 +149,58 @@ if (JINJA2CPP_BUILD_TESTS) add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) endif () -install(TARGETS ${LIB_TARGET_NAME} - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib/static) +set (JINJA2CPP_INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/${LIB_TARGET_NAME}) +set (JINJA2CPP_TMP_CONFIG_PATH cmake/config) -install (DIRECTORY include/ DESTINATION include) -install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake) + +macro (Jinja2CppGetTargetIncludeDir infix target) + message (STATUS "infix: ${infix} target: ${target}") + + if (TARGET ${target}) + set (_J2CPP_VAR_NAME JINJA2CPP_${infix}_INCLUDE_DIRECTORIES) + get_target_property(${_J2CPP_VAR_NAME} ${target} INTERFACE_INCLUDE_DIRECTORIES) + endif () +endmacro () + +Jinja2CppGetTargetIncludeDir(EXPECTED-LITE expected-lite) +Jinja2CppGetTargetIncludeDir(VARIANT-LITE variant-lite) +Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) +Jinja2CppGetTargetIncludeDir(VALUE-PTR-LITE value-ptr-lite) + +# Workaround for #14444 bug of CMake (https://gitlab.kitware.com/cmake/cmake/issues/14444) +# We can't use EXPORT feature of 'install' as is due to limitation of subproject's targets installation +# So jinja2cpp-config.cmake should be written manually + +install(TARGETS ${LIB_TARGET_NAME} + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jinja2cpp + ) + +install (FILES ${CMAKE_BINARY_DIR}/jinja2cpp.pc + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) + +install(EXPORT InstallTargets + FILE jinja2cpp-cfg.cmake + DESTINATION ${JINJA2CPP_INSTALL_CONFIG_DIR}) + +configure_package_config_file( + cmake/public/jinja2cpp-config.cmake.in + ${JINJA2CPP_TMP_CONFIG_PATH}/jinja2cpp-config.cmake + INSTALL_DESTINATION ${JINJA2CPP_TMP_CONFIG_PATH} + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + +configure_package_config_file( + cmake/public/jinja2cpp-config-deps-${JINJA2CPP_DEPS_MODE}.cmake.in + ${JINJA2CPP_TMP_CONFIG_PATH}/jinja2cpp-config-deps.cmake + INSTALL_DESTINATION ${JINJA2CPP_TMP_CONFIG_PATH} + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + +install (FILES + ${CMAKE_CURRENT_BINARY_DIR}/${JINJA2CPP_TMP_CONFIG_PATH}/${LIB_TARGET_NAME}-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${JINJA2CPP_TMP_CONFIG_PATH}/${LIB_TARGET_NAME}-config-deps.cmake + DESTINATION ${JINJA2CPP_INSTALL_CONFIG_DIR}) diff --git a/cmake/public/jinja2cpp-config-deps-conan-build.cmake.in b/cmake/public/jinja2cpp-config-deps-conan-build.cmake.in new file mode 100644 index 00000000..e69de29b diff --git a/cmake/public/jinja2cpp-config-deps-conan.cmake.in b/cmake/public/jinja2cpp-config-deps-conan.cmake.in new file mode 100644 index 00000000..e69de29b diff --git a/cmake/public/jinja2cpp-config-deps-external-boost.cmake.in b/cmake/public/jinja2cpp-config-deps-external-boost.cmake.in new file mode 100644 index 00000000..89fcaf5f --- /dev/null +++ b/cmake/public/jinja2cpp-config-deps-external-boost.cmake.in @@ -0,0 +1,15 @@ +macro (Jinja2CppAddBoostDep name) + if (TARGET Boost::${name}) + list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) + elseif (TARGET boost_${name}) + list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) + endif () +endmacro () + +Jinja2CppAddBoostDep(variant) +Jinja2CppAddBoostDep(filesystem) +Jinja2CppAddBoostDep(algorithm) + +set_property(TARGET jinja2cpp PROPERTY + INTERFACE_LINK_LIBRARIES ${JINJA2CPP_INTERFACE_LINK_LIBRARIES} +) diff --git a/cmake/public/jinja2cpp-config-deps-external.cmake.in b/cmake/public/jinja2cpp-config-deps-external.cmake.in new file mode 100644 index 00000000..1d64e430 --- /dev/null +++ b/cmake/public/jinja2cpp-config-deps-external.cmake.in @@ -0,0 +1,45 @@ +# Create imported target expected-lite +add_library(expected-lite INTERFACE IMPORTED) + +set_target_properties(expected-lite PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_EXPECTED-LITE_INCLUDE_DIRECTORIES@" +) + +# Create imported target variant-lite +add_library(variant-lite INTERFACE IMPORTED) + +set_target_properties(variant-lite PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_VARIANT-LITE_INCLUDE_DIRECTORIES@" +) + +# Create imported target optional-lite +add_library(optional-lite INTERFACE IMPORTED) + +set_target_properties(optional-lite PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_OPTIONAL-LITE_INCLUDE_DIRECTORIES@" +) + +# Create imported target value-ptr-lite +add_library(value-ptr-lite INTERFACE IMPORTED) + +set_target_properties(value-ptr-lite PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_VALUE-PTR-LITE_INCLUDE_DIRECTORIES@" +) + +set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite value-ptr-lite optional-lite) + +macro (Jinja2CppAddBoostDep name) + if (TARGET Boost::${name}) + list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) + elseif (TARGET boost_${name}) + list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) + endif () +endmacro () + +Jinja2CppAddBoostDep(variant) +Jinja2CppAddBoostDep(filesystem) +Jinja2CppAddBoostDep(algorithm) + +set_property(TARGET jinja2cpp PROPERTY + INTERFACE_LINK_LIBRARIES ${JINJA2CPP_INTERFACE_LINK_LIBRARIES} +) diff --git a/cmake/public/jinja2cpp-config-deps-internal.cmake.in b/cmake/public/jinja2cpp-config-deps-internal.cmake.in new file mode 100644 index 00000000..e69de29b diff --git a/cmake/public/jinja2cpp-config.cmake.in b/cmake/public/jinja2cpp-config.cmake.in new file mode 100644 index 00000000..76c2d2b1 --- /dev/null +++ b/cmake/public/jinja2cpp-config.cmake.in @@ -0,0 +1,81 @@ +# Based on generated file by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6) + +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget jinja2cpp) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target jinja2cpp +add_library(jinja2cpp STATIC IMPORTED) + +set_target_properties(jinja2cpp PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# INTERFACE_LINK_LIBRARIES "nonstd::expected-lite;nonstd::variant-lite;nonstd::value_ptr-lite;nonstd::optional-lite;\$;\$;\$" + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/jinja2cpp-cfg-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +include(${_DIR}/jinja2cpp-config-deps.cmake) + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index aad7c081..5b169191 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -5,6 +5,7 @@ #include #include +#include namespace jinja2 { @@ -57,6 +58,38 @@ class ErrorInfoTpl : m_errorData(std::move(data)) {} + ~ErrorInfoTpl() noexcept + { + static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); + static_assert(std::is_nothrow_move_assignable>::value, "Should be nothrow-moveable"); + } + ErrorInfoTpl(const ErrorInfoTpl&) = default; + ErrorInfoTpl(ErrorInfoTpl&& val) noexcept + : m_errorData(std::move(val.m_errorData)) + { } + + + ErrorInfoTpl& operator =(const ErrorInfoTpl&) = default; + ErrorInfoTpl& operator =(ErrorInfoTpl&& val) noexcept + { + if (this == &val) + return *this; + + std::swap(m_errorData.code, val.m_errorData.code); + std::swap(m_errorData.srcLoc, val.m_errorData.srcLoc); + std::swap(m_errorData.relatedLocs, val.m_errorData.relatedLocs); + std::swap(m_errorData.extraParams, val.m_errorData.extraParams); + std::swap(m_errorData.locationDescr, val.m_errorData.locationDescr); + + return *this; + } ErrorCode GetCode() const { diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 47fdb319..c9739a10 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -2,13 +2,15 @@ #define JINJA2_REFLECTED_VALUE_H #include "value.h" + +#include + #include #include #include #include #include #include -#include namespace jinja2 { @@ -80,11 +82,13 @@ class ReflectedMapImpl : public ReflectedMapImplBase> template Value GetField(Fn&& accessor) const { - return accessor(m_valuePtr ? *m_valuePtr : m_value.get()); + if (!m_valuePtr && !m_value) + return Value(); + return accessor(m_valuePtr ? *m_valuePtr : m_value.value()); } private: - boost::optional m_value; + nonstd::optional m_value; const T* m_valuePtr = nullptr; }; diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index b755edb1..57b1faeb 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -120,11 +120,11 @@ class Value Value(); Value(const Value& val); - Value(Value&& val); + Value(Value&& val) noexcept; ~Value(); Value& operator =(const Value&); - Value& operator =(Value&&); + Value& operator =(Value&&) noexcept; template Value(T&& val, typename std::enable_if::value>::type* = nullptr) : m_data(std::forward(val)) @@ -272,10 +272,20 @@ inline Value GenericList::GetValueByIndex(int64_t index) const inline Value::Value() = default; inline Value::Value(const Value& val) = default; -inline Value::Value(Value&& val) = default; +inline Value::Value(Value&& val) noexcept + : m_data(std::move(val.m_data)) +{ +} inline Value::~Value() = default; inline Value& Value::operator =(const Value&) = default; -inline Value& Value::operator =(Value&&) = default; +inline Value& Value::operator =(Value&& val) noexcept +{ + if (this == &val) + return *this; + + m_data.swap(val.m_data); + return *this; +} } // jinja2 diff --git a/jinja2cpp.pc.in b/jinja2cpp.pc.in new file mode 100644 index 00000000..a3ccaf10 --- /dev/null +++ b/jinja2cpp.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + + Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ + + Requires: +Libs: -L${libdir} -ljinja2cpp +Cflags: -I${includedir} -Dvariant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD \ No newline at end of file diff --git a/src/error_handling.h b/src/error_handling.h index 2a2e0916..9a73dd3a 100644 --- a/src/error_handling.h +++ b/src/error_handling.h @@ -24,6 +24,25 @@ struct ParseError , errorToken(tok) , relatedTokens(toks) {} + ParseError(const ParseError&) = default; + ParseError(ParseError&& other) noexcept(true) + : errorCode(std::move(other.errorCode)) + , errorToken(std::move(other.errorToken)) + , relatedTokens(std::move(other.relatedTokens)) + {} + + ParseError& operator =(const ParseError&) = default; + ParseError& operator =(ParseError&& error) noexcept + { + if (this == &error) + return *this; + + std::swap(errorCode, error.errorCode); + std::swap(errorToken, error.errorToken); + std::swap(relatedTokens, error.relatedTokens); + + return *this; + } ErrorCode errorCode; Token errorToken; diff --git a/src/template_env.cpp b/src/template_env.cpp index ba5deb97..fca86c04 100644 --- a/src/template_env.cpp +++ b/src/template_env.cpp @@ -10,6 +10,7 @@ struct TemplateFunctions; template<> struct TemplateFunctions { + using ResultType = nonstd::expected; static Template CreateTemplate(TemplateEnv* env) { return Template(env); @@ -23,6 +24,7 @@ struct TemplateFunctions template<> struct TemplateFunctions { + using ResultType = nonstd::expected; static TemplateW CreateTemplate(TemplateEnv* env) { return TemplateW(env); @@ -37,7 +39,8 @@ template auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers) { using Functions = TemplateFunctions; - auto result = Functions::CreateTemplate(env); + using ResultType = typename Functions::ResultType; + auto tpl = Functions::CreateTemplate(env); for (auto& fh : filesystemHandlers) { @@ -48,12 +51,13 @@ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesyste auto stream = Functions::LoadFile(fileName, fh.handler.get()); if (stream) { - result.Load(*stream); - break; + auto res = tpl.Load(*stream); + if (!res) + return ResultType(res.get_unexpected()); } } - return result; + return ResultType(tpl); } nonstd::expected TemplateEnv::LoadTemplate(std::string fileName) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index b589ddf4..0145c686 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,3 +1,19 @@ +if(MSVC) + set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) + if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") + string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) + string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) + if (NOT THIRDPARTY_MT_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MT") + elseif (NOT THIRDPARTY_MD_POS EQUAL -1) + set (THIRDPARTY_RUNTIME_TYPE "/MD") + else () + message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") + set (THIRDPARTY_RUNTIME_TYPE "/MD") + endif() + endif () +endif () + function(update_submodule submodule) find_package(Git REQUIRED) execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init thirdparty/${submodule} @@ -24,125 +40,7 @@ function(imported_target_alias ALIAS) target_link_libraries(${ALIAS} INTERFACE ${__ALIAS_ALIAS}) endfunction() -find_package(expected-lite QUIET) -if(expected-lite_FOUND) - imported_target_alias(expected-lite ALIAS nonstd::expected-lite) -else() - message(STATUS "expected-lite not found, using submodule") - update_submodule(nonstd/expected-lite) - add_subdirectory(thirdparty/nonstd/expected-lite EXCLUDE_FROM_ALL) -endif() - -find_package(variant-lite QUIET) -if(variant-lite_FOUND) - imported_target_alias(variant-lite ALIAS nonstd::variant-lite) -else() - message(STATUS "variant-lite not found, using submodule") - update_submodule(nonstd/variant-lite) - add_subdirectory(thirdparty/nonstd/variant-lite EXCLUDE_FROM_ALL) - - add_library(variant-lite ALIAS variant-lite) -endif() - -find_package(optional-lite QUIET) -if(optional-lite_FOUND) - imported_target_alias(optional-lite ALIAS nonstd::optional-lite) -else() - message(STATUS "optional-lite not found, using submodule") - update_submodule(nonstd/optional-lite) - add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) - - add_library(optional-lite ALIAS optional-lite) -endif() - -find_package(value_ptr-lite QUIET) -if(value_ptr-lite_FOUND) - imported_target_alias(value-ptr-lite ALIAS nonstd::value_ptr-lite) -else() - message(STATUS "value-ptr-lite not found, using submodule") - update_submodule(nonstd/value-ptr-lite) - add_subdirectory(thirdparty/nonstd/value-ptr-lite EXCLUDE_FROM_ALL) - - add_library(value-ptr-lite ALIAS value_ptr-lite) -endif() - -if(MSVC) - set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) - if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") - string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) - string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) - if (NOT THIRDPARTY_MT_POS EQUAL -1) - set (THIRDPARTY_RUNTIME_TYPE "/MT") - elseif (NOT THIRDPARTY_MD_POS EQUAL -1) - set (THIRDPARTY_RUNTIME_TYPE "/MD") - else () - message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") - set (THIRDPARTY_RUNTIME_TYPE "/MD") - endif() - endif () -endif () - - -find_package(boost_filesystem QUIET) -find_package(boost_algorithm QUIET) -find_package(boost_variant QUIET) -find_package(boost_optional QUIET) - -if(boost_filesystem_FOUND AND - boost_algorithm_FOUND AND - boost_variant_FOUND AND - boost_optional_FOUND) - imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) - imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) - imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) - imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) -else() - if (MSVC) - if (NOT DEFINED Boost_USE_STATIC_LIBS) - if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") - set (Boost_USE_STATIC_LIBS OFF) - set (Boost_USE_STATIC_RUNTIME OFF) - else () - set (Boost_USE_STATIC_LIBS ON) - set (Boost_USE_STATIC_RUNTIME ON) - endif () - endif () - endif () - - find_package(Boost COMPONENTS system filesystem QUIET) - - if (Boost_FOUND) - imported_target_alias(boost_filesystem ALIAS Boost::filesystem) - imported_target_alias(boost_algorithm ALIAS Boost::boost) - imported_target_alias(boost_variant ALIAS Boost::boost) - imported_target_alias(boost_optional ALIAS Boost::boost) - else() - message(STATUS "One or more boost modules not found, using submodule") - update_submodule(boost) - list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) - set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") - add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) - - if(NOT MSVC) - # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - - if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) - target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) - endif() - if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) - target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) - endif() - if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) - target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) - endif() - else () - endif() - endif() -endif() +include (./thirdparty/thirdparty-${JINJA2CPP_DEPS_MODE}.cmake) if(JINJA2CPP_BUILD_TESTS) find_package(gtest QUIET) @@ -154,13 +52,26 @@ if(JINJA2CPP_BUILD_TESTS) update_submodule(gtest) if(MSVC) - if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") - set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) - else () - set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) - endif () + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) + else () + set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) + endif () endif () add_subdirectory(thirdparty/gtest EXCLUDE_FROM_ALL) endif() endif() + +if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) + set (JINJA2CPP_PRIVATE_LIBS boost_variant boost_filesystem boost_algorithm) +else () + set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) +endif () + + +if (NOT DEFINED JINJA2_PUBLIC_LIBS_INT) + set (JINJA2CPP_PUBLIC_LIBS expected-lite variant-lite value-ptr-lite optional-lite) +else () + set (JINJA2CPP_PUBLIC_LIBS ${JINJA2_PUBLIC_LIBS_INT}) +endif () diff --git a/thirdparty/external_boost_deps.cmake b/thirdparty/external_boost_deps.cmake new file mode 100644 index 00000000..52eb0161 --- /dev/null +++ b/thirdparty/external_boost_deps.cmake @@ -0,0 +1,45 @@ +find_package(boost_filesystem QUIET) +find_package(boost_algorithm QUIET) +find_package(boost_variant QUIET) +find_package(boost_optional QUIET) + +if(boost_filesystem_FOUND AND + boost_algorithm_FOUND AND + boost_variant_FOUND AND + boost_optional_FOUND) + imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) + imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) + imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) + imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) + + +else() + if (MSVC) + if (NOT DEFINED Boost_USE_STATIC_LIBS) + if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") + set (Boost_USE_STATIC_LIBS OFF) + set (Boost_USE_STATIC_RUNTIME OFF) + else () + set (Boost_USE_STATIC_LIBS ON) + set (Boost_USE_STATIC_RUNTIME ON) + endif () + endif () + endif () + + find_package(Boost COMPONENTS system filesystem QUIET REQUIRED) + + if (Boost_FOUND) + imported_target_alias(boost_filesystem ALIAS Boost::filesystem) + imported_target_alias(boost_algorithm ALIAS Boost::boost) + imported_target_alias(boost_variant ALIAS Boost::boost) + imported_target_alias(boost_optional ALIAS Boost::boost) + endif () +endif () + +install(TARGETS boost_filesystem boost_algorithm boost_variant boost_optional + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/boost + ) \ No newline at end of file diff --git a/thirdparty/gtest b/thirdparty/gtest index bf07131c..2fe3bd99 160000 --- a/thirdparty/gtest +++ b/thirdparty/gtest @@ -1 +1 @@ -Subproject commit bf07131c1d0a4e001daeee8936089f8b438b7f30 +Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2 diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake new file mode 100644 index 00000000..a12287d0 --- /dev/null +++ b/thirdparty/internal_deps.cmake @@ -0,0 +1,22 @@ +update_submodule(nonstd/expected-lite) +add_subdirectory(thirdparty/nonstd/expected-lite EXCLUDE_FROM_ALL) + +update_submodule(nonstd/variant-lite) +add_subdirectory(thirdparty/nonstd/variant-lite EXCLUDE_FROM_ALL) +add_library(variant-lite ALIAS variant-lite) + +update_submodule(nonstd/optional-lite) +add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) +add_library(optional-lite ALIAS optional-lite) + +update_submodule(nonstd/value-ptr-lite) +add_subdirectory(thirdparty/nonstd/value-ptr-lite EXCLUDE_FROM_ALL) +add_library(value-ptr-lite ALIAS value_ptr-lite) + +install (FILES + thirdparty/nonstd/expected-lite/include/nonstd/expected.hpp + thirdparty/nonstd/variant-lite/include/nonstd/variant.hpp + thirdparty/nonstd/optional-lite/include/nonstd/optional.hpp + thirdparty/nonstd/value-ptr-lite/include/nonstd/value_ptr.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd) + \ No newline at end of file diff --git a/thirdparty/nonstd/CMakeLists.txt b/thirdparty/nonstd/CMakeLists.txt deleted file mode 100644 index 902e7720..00000000 --- a/thirdparty/nonstd/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -project (nonstd) - -add_library(nonstd INTERFACE) -add_library(ThirdParty::nonstd ALIAS nonstd) -target_include_directories(nonstd SYSTEM - INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include - ${CMAKE_CURRENT_SOURCE_DIR}/variant-light/include - ${CMAKE_CURRENT_SOURCE_DIR}/value-ptr-lite/include - ) - -target_sources(nonstd - INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/expected-light/include/nonstd/expected.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/variant-light/include/nonstd/variant.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/value-ptr-lite/include/nonstd/value_ptr.hpp - ) - - - - diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index d6685ea3..ce6d1847 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit d6685ea36dae0964004d8f35beb254d5dadbdc43 +Subproject commit ce6d1847c5c9a80a707703f45ceda8d4ca3d2760 diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite index a2cec490..86db12f3 160000 --- a/thirdparty/nonstd/optional-lite +++ b/thirdparty/nonstd/optional-lite @@ -1 +1 @@ -Subproject commit a2cec49027cfd522da99f39d63d51f4816717346 +Subproject commit 86db12f3b6c2026ad83efd51c5ce3622bb06f1b8 diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 1648dc3b..49165a36 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 1648dc3b99e4ae44743f680e76c8289b2fc0971e +Subproject commit 49165a36361692d3414d2495bcd859e67784e8ef diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite index fb2f3a8f..fd33ea5e 160000 --- a/thirdparty/nonstd/variant-lite +++ b/thirdparty/nonstd/variant-lite @@ -1 +1 @@ -Subproject commit fb2f3a8f822ad649e7c0849b4859c1fcf864a200 +Subproject commit fd33ea5ee4f152d9c5e85b79b3f40c705b932015 diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake new file mode 100644 index 00000000..7aea1cd3 --- /dev/null +++ b/thirdparty/thirdparty-conan-build.cmake @@ -0,0 +1,10 @@ +message(STATUS "'conan-build' dependencies mode selected for Jinja2Cpp. All dependencies are taken as a conan packages") + +find_package(expected-lite) +find_package(variant-lite) +find_package(optional-lite) +find_package(value-ptr-lite) +find_package(boost) + +set (JINJA2_PRIVATE_LIBS_INT boost::boost) +set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite value-ptr-lite::value-ptr-lite optional-lite::optional-lite) diff --git a/thirdparty/thirdparty-external-boost.cmake b/thirdparty/thirdparty-external-boost.cmake new file mode 100644 index 00000000..019e9e8c --- /dev/null +++ b/thirdparty/thirdparty-external-boost.cmake @@ -0,0 +1,4 @@ +message(STATUS "'extnernal-boost' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules except of boost") + +include (./thirdparty/internal_deps.cmake) +include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake new file mode 100644 index 00000000..fc2cba7e --- /dev/null +++ b/thirdparty/thirdparty-external.cmake @@ -0,0 +1,50 @@ +message(STATUS "'external' dependencies mode selected for Jinja2Cpp. All dependencies are treated as external") + +include(FindPackageHandleStandardArgs) + +macro (FindHeaderOnlyLib HDR_PATH TARGET_NAME) + set (TARGET_NAME_INC_DIR ${TARGET_NAME}_INCLUDE_DIR) + find_path(${TARGET_NAME_INC_DIR} NAMES ${HDR_PATH}) + mark_as_advanced(${TARGET_NAME_INC_DIR}) + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(${TARGET_NAME} DEFAULT_MSG ${TARGET_NAME}_INCLUDE_DIR) + + if (${TARGET_NAME}_FOUND) + if (NOT TARGET ${TARGET_NAME}) + add_library(${TARGET_NAME} INTERFACE) + set_target_properties(${TARGET_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${${TARGET_NAME}_INCLUDE_DIR}") + endif () + endif () +endmacro () + +macro (find_hdr_package PKG_NAME HDR_PATH) + find_package(${PKG_NAME} QUIET) + if(NOT ${PKG_NAME}_FOUND) + FindHeaderOnlyLib(${HDR_PATH} ${PKG_NAME}) + endif () + + if(${PKG_NAME}_FOUND) + if (NOT TARGET ${PKG_NAME}) + imported_target_alias(${PKG_NAME} ALIAS "${PKG_NAME}::${PKG_NAME}") + endif () + else() + message(FATAL_ERROR "${PKG_NAME} not found!") + endif() +endmacro () + +find_hdr_package(expected-lite nonstd/expected.hpp) +find_hdr_package(variant-lite nonstd/variant.hpp) +find_hdr_package(optional-lite nonstd/optional.hpp) +find_hdr_package(value-ptr-lite nonstd/value_ptr.hpp) + +install(TARGETS expected-lite variant-lite optional-lite value-ptr-lite + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd + ) + +include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake new file mode 100644 index 00000000..1ae78a45 --- /dev/null +++ b/thirdparty/thirdparty-internal.cmake @@ -0,0 +1,35 @@ +message(STATUS "'internal' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules") + +include (./thirdparty/internal_deps.cmake) + +update_submodule(boost) +list(APPEND BOOST_CMAKE_LIBRARIES filesystem algorithm variant optional) +set(BOOST_CMAKE_LIBRARIES ${BOOST_CMAKE_LIBRARIES} CACHE INTERNAL "") +add_subdirectory(thirdparty/boost EXCLUDE_FROM_ALL) + +if(NOT MSVC) + # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-Wno-error=parentheses COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + check_cxx_compiler_flag(-Wno-error=deprecated-declarations COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + check_cxx_compiler_flag(-Wno-error=maybe-uninitialized COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + + if(COMPILER_HAS_WNO_ERROR_PARENTHESES_FLAG) + target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) + endif() + if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) + endif() + if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) + target_compile_options(boost_variant INTERFACE -Wno-error=maybe-uninitialized) + endif() + else () +endif() + +# install(TARGETS boost_filesystem boost::algorithm boost::variant boost::optional +# EXPORT InstallTargets +# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static +# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/boost +# ) From 748af77c724eed5634633fe7e3e3fa435cbf5e29 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 12:57:07 +0300 Subject: [PATCH 064/206] [skip ci] Upage Readme.md --- README.md | 558 ++++++------------------------------------------------ 1 file changed, 60 insertions(+), 498 deletions(-) diff --git a/README.md b/README.md index 208e6643..f12b3902 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) -[![Build status](https://ci.appveyor.com/api/projects/status/19v2k3bl63jxl42f/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/Jinja2Cpp) +[![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) @@ -16,36 +16,6 @@ C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. -# Table of contents - - - - - -- [Introduction](#introduction) -- [Getting started](#getting-started) - - [More complex example](#more-complex-example) - - [The simplest case](#the-simplest-case) - - [Reflection](#reflection) - - ['set' statement](#set-statement) - - ['extends' statement](#extends-statement) - - [Macros](#macros) - - [User-defined callables](#user-defined-callables) - - [Error reporting](#error-reporting) - - [Other features](#other-features) -- [Current Jinja2 support](#current-jinja2-support) -- [Supported compilers](#supported-compilers) -- [Build and install](#build-and-install) - - [Additional CMake build flags](#additional-cmake-build-flags) -- [Link with you projects](#link-with-you-projects) -- [Acknowledgments](#acknowledgments) -- [Changelog](#changelog) - - [Version 0.9.1](#version-091) - - [Version 0.9](#version-09) - - [Version 0.6](#version-06) - - - # Introduction Main features of Jinja2Cpp: @@ -62,6 +32,8 @@ Main features of Jinja2Cpp: For instance, this simple code: ```c++ +#include + std::string source = R"( {{ ("Hello", 'world') | join }}!!! {{ ("Hello", 'world') | join(', ') }}!!! @@ -118,433 +90,7 @@ Hello World!!! That's all! -## More complex example -Let's say you have the following enum: - -```c++ -enum Animals -{ - Dog, - Cat, - Monkey, - Elephant -}; -``` - -And you want to automatically produce string-to-enum and enum-to-string convertor. Like this: - -```c++ -inline const char* AnimalsToString(Animals e) -{ - switch (e) - { - case Dog: - return "Dog"; - case Cat: - return "Cat"; - case Monkey: - return "Monkey"; - case Elephant: - return "Elephant"; - } - return "Unknown Item"; -} -``` - -Of course, you can write this producer in the way like [this](https://github.com/flexferrum/autoprogrammer/blob/87a9dc8ff61c7bdd30fede249757b71984e4b954/src/generators/enum2string_generator.cpp#L140). It's too complicated for writing 'from scratch'. Actually, there is a better and simpler way. - -### The simplest case - -Firstly, you should define the simple jinja2 template (in the C++ manner): -```c++ -std::string enum2StringConvertor = R"( -inline const char* {{enumName}}ToString({{enumName}} e) -{ - switch (e) - { -{% for item in items %} - case {{item}}: - return "{{item}}"; -{% endfor %} - } - return "Unknown Item"; -})"; -``` -As you can see, this template is similar to the C++ sample code above, but some parts replaced by placeholders ("parameters"). These placeholders will be replaced with the actual text during template rendering process. In order to this happen, you should fill up the rendering parameters. This is a simple dictionary which maps the parameter name to the corresponding value: - -```c++ -jinja2::ValuesMap params { - {"enumName", "Animals"}, - {"items", {"Dog", "Cat", "Monkey", "Elephant"}}, -}; -``` -An finally, you can render this template with Jinja2Cpp library: - -```c++ -jinja2::Template tpl; -tpl.Load(enum2StringConvertor); -std::cout << tpl.RenderAsString(params); -``` -And you will get on the console the conversion function mentioned above! - -You can call 'Render' method many times, with different parameters set, from several threads. Everything will be fine: every time you call 'Render' you will get the result depended only on provided params. Also you can render some part of template many times (for different parameters) with help of 'for' loop and 'extend' statement (described below). It allows you to iterate through the list of items from the first to the last one and render the loop body for each item respectively. In this particular sample it allows to put as many 'case' blocks in conversion function as many items in the 'reflected' enum. - -### Reflection -Let's imagine you don't want to fill the enum descriptor by hand, but want to fill it with help of some code parsing tool ([autoprogrammer](https://github.com/flexferrum/autoprogrammer) or [cppast](https://github.com/foonathan/cppast)). In this case you can define structure like this: - -```c++ -// Enum declaration description -struct EnumDescriptor -{ -// Enumeration name -std::string enumName; -// Namespace scope prefix -std::string nsScope; -// Collection of enum items -std::vector enumItems; -}; -``` -This structure holds the enum name, enum namespace scope prefix, and list of enum items (we need just names). Then, you can populate instances of this descriptor automatically using chosen tool (ex. here: [clang-based enum2string converter generator](https://github.com/flexferrum/flex_lib/blob/accu2017/tools/codegen/src/main.cpp) ). For our sample we can create the instance manually: -```c++ -EnumDescriptor descr; -descr.enumName = "Animals"; -descr.nsScope = ""; -descr.enumItems = {"Dog", "Cat", "Monkey", "Elephant"}; -``` - -And now you need to transfer data from this internal enum descriptor to Jinja2 value params map. Of course it's possible to do it by hands: -```c++ -jinja2::ValuesMap params { - {"enumName", descr.enumName}, - {"nsScope", descr.nsScope}, - {"items", {descr.enumItems[0], descr.enumItems[1], descr.enumItems[2], descr.enumItems[3]}}, -}; -``` - -But actually, with Jinja2Cpp you don't have to do it manually. Library can do it for you. You just need to define reflection rules. Something like this: - -```c++ -namespace jinja2 -{ -template<> -struct TypeReflection : TypeReflected -{ - static auto& GetAccessors() - { - static std::unordered_map accessors = { - {"name", [](const EnumDescriptor& obj) {return obj.name;}}, - {"nsScope", [](const EnumDescriptor& obj) { return obj.nsScope;}}, - {"items", [](const EnumDescriptor& obj) {return Reflect(obj.items);}}, - }; - - return accessors; - } -}; -``` -And in this case you need to correspondingly change the template itself and it's invocation: -```c++ -std::string enum2StringConvertor = R"( -inline const char* {{enum.enumName}}ToString({{enum.enumName}} e) -{ - switch (e) - { -{% for item in enum.items %} - case {{item}}: - return "{{item}}"; -{% endfor %} - } - return "Unknown Item"; -})"; - -// ... - jinja2::ValuesMap params = { - {"enum", jinja2::Reflect(descr)}, - }; -// ... -``` -Every specified field will be reflected into Jinja2Cpp internal data structures and can be accessed from the template without additional efforts. Quite simply! As you can see, you can use 'dot' notation to access named members of some parameter as well, as index notation like this: `enum['enumName']`. With index notation you can access to the particular item of a list: `enum.items[3]` or `enum.items[itemIndex]` or `enum['items'][itemIndex]`. - -### 'set' statement -But what if enum `Animals` will be in the namespace? - -```c++ -namespace world -{ -enum Animals -{ - Dog, - Cat, - Monkey, - Elephant -}; -} -``` -In this case you need to prefix both enum name and it's items with namespace prefix in the generated code. Like this: -```c++ -std::string enum2StringConvertor = R"( -inline 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"; -})"; -``` -This template will produce 'world::' prefix for our new scoped enum (and enum itmes). And '::' for the ones in global scope. But you may want to eliminate the unnecessary global scope prefix. And you can do it this way: -```c++ -{% set prefix = enum.nsScope + '::' if enum.nsScope else '' %} -std::string enum2StringConvertor = R"(inline const char* {{enum.enumName}}ToString({{prefix}}::{{enum.enumName}} e) -{ - switch (e) - { -{% for item in enum.items %} - case {{prefix}}::{{item}}: - return "{{item}}"; -{% endfor %} - } - return "Unknown Item"; -})"; -``` -This template uses two significant jinja2 template features: -1. The 'set' statement. You can declare new variables in your template. And you can access them by the name. -2. if-expression. It works like a ternary '?:' operator in C/C++. In C++ the code from the sample could be written in this way: -```c++ -std::string prefix = !descr.nsScope.empty() ? descr.nsScope + "::" : ""; -``` -I.e. left part of this expression (before 'if') is a true-branch of the statement. Right part (after 'else') - false-branch, which can be omitted. As a condition you can use any expression convertible to bool. - -## 'extends' statement -In general, C++ header files look similar to each other. Almost every header file has got header guard, block of 'include' directives and then block of declarations wrapped into namespaces. So, if you have several different Jinja2 templates for header files production it can be a good idea to extract the common header structure into separate template. Like this: -```c++ -{% if headerGuard is defined %} - #ifndef {{headerGuard}} - #define {{headerGuard}} -{% else %} - #pragma once -{% endif %} - -{% for fileName in inputFiles | sort %} - #include "{{fileName}}" -{% endfor %} - -{% for fileName in extraHeaders | sort %} -{% if fileName is startsWith('<') %} - #include {{fileName}} -{% else %} - #include "{{fileName}}" -{% endif %} -{% endfor %} - -{% block generator_headers %}{% endblock %} - -{% block namespaced_decls %} -{% set ns = rootNamespace %} -{#ns | pprint} -{{rootNamespace | pprint} #} -{% block namespace_content scoped %}{%endblock%} -{% for ns in rootNamespace.innerNamespaces recursive %}namespace {{ns.name}} -{ -{{self.namespace_content()}} -{{ loop(ns.innerNamespaces) }} -} -{% endfor %} -{% endblock %} - -{% block global_decls %}{% endblock %} - -{% if headerGuard is defined %} - #endif // {{headerGuard}} -{% endif %} -``` - -In this sample you can see the '**block**' statements. They are placeholders. Each block is a part of generic template which can be replaced by more specific template which 'extends' generic: -```c++ -{% extends "header_skeleton.j2tpl" %} - -{% block namespaced_decls %}{{super()}}{% endblock %} - -{% block namespace_content %} -{% for class in ns.classes | sort(attribute="name") %} - -class {{ class.name }} -{ -public: - {% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'equalto', 'Public') %} - {{ method.fullPrototype }}; - {% endfor %} -protected: - {% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'equalto', 'Protected') %} - {{ method.fullPrototype }}; - {% endfor %} -private: - {% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', ['Private', 'Undefined']) %} - {{ method.fullPrototype }}; - {% endfor %} -}; - -{% endfor %} -{% endblock %} -``` - -'**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 '**macro**'. 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). - -### 'applymacro' filter - -With help of `applymacro` filter macro can be called in filtering context. `applymacro` works similar to `map` (or `test`) filter with one exception: instead of name of other filter it takes name of macro via `macro` param and pass the rest of arguments to it. The object which is been filtered is passed as the first positional argument. For example: - -``` -{% macro toUpper(str) %}{{ str | upper }}{% endmacro %} -{{ 'Hello World!' | applymacro(macro='toUpper') }} -``` - -produces the result `HELLO WORLD`. `applymacro` can be applied to the sequences via `map` filter. Also, macro name can be `caller`. In this case outer `call` statement will be invoked during macro application. - -## User-defined callables - -Not only C++ types can be reflected into Jinja2 template context, but the functions (and lambdas, and any other callable objects) as well. These refelected callable objects are called 'user-defined callables' and can be accessed from Jinja2 templates in the same manner as any other callables (like macros or global functions). In order to reflect callable object into Jinja2 context the `jinja2::MakeCallable` method should be used: - -```c++ -jinja2::ValuesMap params; - params["concat"] = MakeCallable( - [](const std::string& str1, const std::string& str2) { - return str1 + " " + str2; - }, - ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} - ); -``` - -As a first parameter this method takes the callable itself. It can be lambda, the std::function<> instance or pointer to function. The rest of params are callable arguments descriptors, which are provided via `ArgInfo` structure. In the sample above user-defined callable `concat` is introduced, which take two argument: `str1` and `str2`. This callable can be accessed from the template in the following ways: - -``` -{{ concat('Hello', 'World!') }} -{{ concat(str2='World!', str1='Hello') }} -{{ concat(str2='World!') }} -{{ concat('Hello') }} -``` - -## 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 -- Error description -- File name and position (1-based line, col) of the error -- Location description - -For example, this template: -``` -{{ {'key'=,} }} -``` -produces the following error message: -``` -noname.j2tpl:1:11: error: Expected expression, got: ',' -{{ {'key'=,} }} - ---^------- -``` - -## Other features -The render procedure is stateless, so you can perform several renderings simultaneously in different threads. Even if you pass parameters: - -```c++ - ValuesMap params = { - {"intValue", 3}, - {"doubleValue", 12.123f}, - {"stringValue", "rain"}, - {"boolFalseValue", false}, - {"boolTrueValue", true}, - }; - - std::string result = tpl.RenderAsString(params); - std::cout << result << std::endl; -``` - -Parameters could have the following types: -- std::string/std::wstring -- integer (int64_t) -- double -- boolean (bool) -- Tuples (also known as arrays) -- Dictionaries (also known as maps) - -Tuples and dictionaries can be mapped to the C++ types. So you can smoothly reflect your structures and collections into the template engine: - -```c++ -namespace jinja2 -{ -template<> -struct TypeReflection : TypeReflected -{ - static auto& GetAccessors() - { - static std::unordered_map accessors = { - {"name", [](const reflection::EnumInfo& obj) {return Reflect(obj.name);}}, - {"scopeSpecifier", [](const reflection::EnumInfo& obj) {return Reflect(obj.scopeSpecifier);}}, - {"namespaceQualifier", [](const reflection::EnumInfo& obj) { return obj.namespaceQualifier;}}, - {"isScoped", [](const reflection::EnumInfo& obj) {return obj.isScoped;}}, - {"items", [](const reflection::EnumInfo& obj) {return Reflect(obj.items);}}, - }; - - return accessors; - } -}; - -// ... - jinja2::ValuesMap params = { - {"enum", jinja2::Reflect(enumInfo)}, - }; -``` - -In this cases method 'jinja2::reflect' reflects regular C++ type into jinja2 template param. If type is a user-defined class or structure then handwritten mapper 'TypeReflection<>' should be provided. +More detailed examples and features describtion can be found in the documentation: https://jinja2cpp.dev/docs/usage # Current Jinja2 support Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Jinja2Cpp is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: @@ -571,9 +117,14 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f - Microsoft Visual Studio 2017 x86, x64 # Build and install -Jinja2Cpp has five external dependencies: boost library (at least version 1.55) and several header-only dependecies from nonstd project(expected-lite, variant-lite, value-ptr-lite, optional-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. +Jinja2Cpp has several external dependencies: +* `boost` library (at least version 1.55) +* `nonstd::expected-lite` (https://github.com/martinmoene/expected-lite) +* `nonstd::variant-lite` (https://github.com/martinmoene/variant-lite) +* `nonstd::value-ptr-lite` (https://github.com/martinmoene/value-ptr-lite) +* `nonstd::optional-lite`(https://github.com/martinmoene/optional-lite) -In order to compile Jinja2Cpp you need: +In simpliest case to compile Jinja2Cpp you need: 1. Install CMake build system (at least version 3.0) 2. Clone jinja2cpp repository and update submodules: @@ -611,6 +162,49 @@ In order to compile Jinja2Cpp you need: > ctest -C Release ``` +In this case Jinja2Cpp will be built with internally-shipped dependencies and install them respectively. But Jinja2Cpp supports build with externally-provided deps. Different Jinja2Cpp usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build + +## Usage conan.io dependency manager +Jinja2Cpp can be used as conan.io package. In this case you should do the following steps: + +1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html ) +2. Register the following remote conan.io repositories: + * https://api.bintray.com/conan/martinmoene/nonstd-lite + * https://api.bintray.com/conan/bincrafters/public-conan + * https://api.bintray.com/conan/manu343726/conan-packages + +The sample command is: `conan remote add martin https://api.bintray.com/conan/martinmoene/nonstd-lite` + +3. Add reference to Jinja2Cpp package (`jinja2cpp/0.9.1@Manu343726/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: + +```cmake +nclude (../../cmake/conan.cmake) +if (NOT MSVC) + set (CONAN_SETTINGS SETTINGS compiler.libcxx=libstdc++11) +endif () + +conan_cmake_run(REQUIRES + jinja2cpp/0.9.1@Manu343726/testing + gtest/1.7.0@bincrafters/stable + BASIC_SETUP + ${CONAN_SETTINGS} + OPTIONS + jinja2cpp:shared=False + gtest:shared=False + BUILD missing) + +set (TARGET_NAME jinja2cpp_build_test) + +add_executable (${TARGET_NAME} main.cpp) + +target_link_libraries (${TARGET_NAME} ${CONAN_LIBS}) +set_target_properties (${TARGET_NAME} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON) + +``` + + ## Additional CMake build flags You can define (via -D command line CMake option) the following build flags: @@ -618,46 +212,14 @@ You can define (via -D command line CMake option) the following build flags: * **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). * **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. * **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). -* **BOOST_ROOT** - Path to the prebuilt boost installation - -# Link with you projects -Jinja2Cpp is shipped with cmake finder script. So you can: - -1. Include Jinja2Cpp cmake scripts to the project: -```cmake -list (APPEND CMAKE_MODULE_PATH ${JINJA2CPP_INSTALL_DIR}/cmake) -``` +* **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: + * `internal` In this mode Jinja2Cpp build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. + * `external-boost` In this mode Jinja2Cpp build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. + * `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) + * `conan-build` Special mode for building Jinja2Cpp via conan recipe. -2. Use regular 'find' script: -```cmake -find_package(Jinja2Cpp) -``` -3. Add found paths to the project settings: -```cmake -#... -include_directories( - #... - ${JINJA2CPP_INCLUDE_DIR} - ) - -target_link_libraries(YourTarget - #... - ${JINJA2CPP_LIBRARY} - ) -#... -``` - -or just link with Jinja2Cpp target: -```cmake -#... -target_link_libraries(YourTarget - #... - Jinja2Cpp - ) -#... -``` -4. Build with C++17 standard enabled +## Build with C++17 standard enabled In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD` macro in the build settings. # Acknowledgments From 5cf56658ff2384573dd599abdff5c1e4c4cdd53d Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 12:58:46 +0300 Subject: [PATCH 065/206] [skip ci] Update readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f12b3902..66dc7268 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ In simpliest case to compile Jinja2Cpp you need: In this case Jinja2Cpp will be built with internally-shipped dependencies and install them respectively. But Jinja2Cpp supports build with externally-provided deps. Different Jinja2Cpp usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build -## Usage conan.io dependency manager +## Usage with conan.io dependency manager Jinja2Cpp can be used as conan.io package. In this case you should do the following steps: 1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html ) From 791e58046d5be88542ad08b981989ef9fb071d65 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 12:59:40 +0300 Subject: [PATCH 066/206] [skip ci] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66dc7268..a51e15ce 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ The sample command is: `conan remote add martin https://api.bintray.com/conan/ma 3. Add reference to Jinja2Cpp package (`jinja2cpp/0.9.1@Manu343726/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: ```cmake -nclude (../../cmake/conan.cmake) +include (../../cmake/conan.cmake) if (NOT MSVC) set (CONAN_SETTINGS SETTINGS compiler.libcxx=libstdc++11) endif () From b900d8cc32bca1a880ab2f11f13823fd505cddcc Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 21:29:56 +0300 Subject: [PATCH 067/206] Fix 'use after free' error when using reflected user-defined callables #103 --- include/jinja2cpp/user_callable.h | 13 ++++++++- src/filters.cpp | 7 ++++- src/internal_value.cpp | 4 +-- test/filters_test.cpp | 2 +- test/test_tools.h | 12 +++++++- test/user_callable_test.cpp | 47 ++++++++++++++++++++++++++++++- 6 files changed, 78 insertions(+), 7 deletions(-) diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h index f3fc548e..72d9121e 100644 --- a/include/jinja2cpp/user_callable.h +++ b/include/jinja2cpp/user_callable.h @@ -50,7 +50,7 @@ struct UCInvoker }; -const Value& GetParamValue(const UserCallableParams& params, const ArgInfo& info) +inline const Value& GetParamValue(const UserCallableParams& params, const ArgInfo& info) { // static Value empty; auto p = params.args.find(info.paramName); @@ -112,6 +112,17 @@ auto MakeCallable(Fn&& f, ArgDescr&& ... ad) {ArgInfo(std::forward(ad))...} }; } + +template +auto MakeCallable(Fn&& f) +{ + return UserCallable{ + [=, fn = std::forward(f)](const UserCallableParams& params) { + return fn(); + }, + {} + }; +} } // jinja2 #endif // USER_CALLABLE_H diff --git a/src/filters.cpp b/src/filters.cpp index 03f38711..9b53fdfa 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -484,7 +484,12 @@ struct PrettyPrinter : visitors::BaseVisitor return "none"s; } - InternalValue operator()(double val) const + InternalValue operator()(const Callable&) const + { + return ""s; + } + + InternalValue operator()(double val) const { std::ostringstream os; os << val; diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 9df19892..a64f94be 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -73,7 +73,7 @@ std::string AsString(const InternalValue& val) auto* tstr = GetIf(&val); if (str != nullptr) return *str; - else + else /* if (tstr != nullptr) */ { str = GetIf(tstr); if (str != nullptr) @@ -583,7 +583,7 @@ InputValueConvertor::result_t InputValueConvertor::ConvertUserCallable(const Use args.emplace_back(pi.paramName, pi.isMandatory, Value2IntValue(pi.defValue)); } - return InternalValue(Callable(Callable::UserCallable, [&val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue { + return InternalValue(Callable(Callable::UserCallable, [val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue { auto ucParams = PrepareUserCallableParams(params, context, argsInfo); return Value2IntValue(val.callable(ucParams)); })); diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 957a9f49..9c71496b 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -377,7 +377,7 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"}, InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"}, InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, - InputOutputPair{"reflectedVal | dictsort | pprint", "['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} + InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} )); INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values( diff --git a/test/test_tools.h b/test/test_tools.h index ea04e7f0..c2c9a420 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -4,6 +4,7 @@ #include #include #include +#include struct InputOutputPair { @@ -182,7 +183,16 @@ struct TypeReflection : TypeReflected vals.push_back(std::make_shared()); return jinja2::Reflect(list_t(vals.begin(), vals.end())); }}, - }; + {"basicCallable",[](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return obj.intValue; }); + }}, + {"getInnerStruct",[](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(obj.innerStruct); }); + }}, + {"getInnerStructValue",[](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(*obj.innerStruct); }); + }}, + }; return accessors; } diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index 696dd7e7..a904bc2d 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -109,6 +109,51 @@ Hello default EXPECT_EQ(expectedResult, result); } +TEST(UserCallableTest, ReflectedCallable) +{ + std::string source = R"( +{% set callable = reflected.basicCallable %}{{ callable() }} +{{ reflected.basicCallable() }} +{% set inner = reflected.getInnerStruct() %}{{ inner.strValue }} +{% set innerValue = reflected.getInnerStructValue() %}{{ innerValue.strValue }} +{{ innerReflected.strValue }} +)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + TestStruct reflected; + auto innerReflected = std::make_shared(); + innerReflected->strValue = "!!Hello World!!"; + reflected.intValue = 100500; + reflected.innerStruct = std::make_shared(); + reflected.innerStruct->strValue = "Hello World!"; + { + jinja2::ValuesMap params; + params["reflected"] = jinja2::Reflect(reflected); + params["innerReflected"] = jinja2::Reflect(innerReflected); + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +100500 +100500 +Hello World! +Hello World! +!!Hello World!! +)"; + EXPECT_EQ(expectedResult, result); + } + EXPECT_EQ(1L, innerReflected.use_count()); + EXPECT_EQ(1L, reflected.innerStruct.use_count()); +} + struct UserCallableParamConvertTestTag; using UserCallableParamConvertTest = InputOutputPairTest; @@ -266,7 +311,7 @@ INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing InputOutputPair{"GMapFn(simpleMapValue) | dictsort", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, InputOutputPair{"GMapFn(reflectedVal) | dictsort", - "['boolValue': false, 'dblValue': 0, 'innerStruct': {'strValue': 'Hello World!'}, " + "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, " "'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " From e1e6003198e28f937a479c8b0aa2d37ae6e4ac6e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sat, 11 May 2019 22:02:22 +0300 Subject: [PATCH 068/206] Change build mode for appveyor CI --- .travis.yml | 10 ---------- appveyor.yml | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index ebf4cb54..f61ef958 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,16 +50,6 @@ matrix: sources: ['ubuntu-toolchain-r-test'] packages: ['cmake', 'g++-7'] - - os: linux - compiler: gcc - env: - COMPILER=g++-7 - SYSTEM_BOOST_PACKAGE=YES - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-7'] - - os: linux compiler: clang env: COMPILER=clang++-5.0 diff --git a/appveyor.yml b/appveyor.yml index dde3d0ca..c1ab8b53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,7 +48,7 @@ init: build_script: - mkdir -p build && cd build - - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%configuration% -DMSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% + - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%configuration% -DMSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=external-boost - cmake --build . --target all test_script: From ef764464c6121c38a0e1ca7f23e3a701cbab18ae Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Mon, 13 May 2019 13:53:19 +0300 Subject: [PATCH 069/206] [skip ci] Fix ReadMe.md markdown style issues --- README.md | 174 +++++++++++++++++++++++++++--------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index a51e15ce..d53940d9 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,27 @@ [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) [![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) [![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d932d23e9288404ba44a1f500ab42778)](https://www.codacy.com/app/flexferrum/Jinja2Cpp?utm_source=github.com&utm_medium=referral&utm_content=flexferrum/Jinja2Cpp&utm_campaign=Badge_Grade) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/ff01fa4410ac417f8192dce78e919ece)](https://www.codacy.com/app/flexferrum/Jinja2Cpp_2?utm_source=github.com&utm_medium=referral&utm_content=jinja2cpp/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) -[ ![conan.io](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) +[![conan.io](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) -C++ implementation of big subset of Jinja2 template engine features. This library was inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. +C++ implementation of Jinja2 Python template engine. This library was originally inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. -# Introduction +## Introduction -Main features of Jinja2Cpp: -- Easy-to-use public interface. Just load templates and render them. -- Conformance to [Jinja2 specification](http://jinja.pocoo.org/docs/2.10/) -- 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. -- Control statements (set, for, if). -- Templates extention. -- Macros -- Rich error reporting. +Main features of Jinja2C++: +- Easy-to-use public interface. Just load templates and render them. +- Conformance to [Jinja2 specification](http://jinja.pocoo.org/docs/2.10/) +- 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. +- Control statements (set, for, if). +- Templates extention. +- Macros +- Rich error reporting. For instance, this simple code: @@ -56,7 +56,7 @@ Hello; world!!! hello; world!!! ``` -# Getting started +## Getting started In order to use Jinja2Cpp in your project you have to: * Clone the Jinja2Cpp repository @@ -64,19 +64,19 @@ In order to use Jinja2Cpp in your project you have to: * Link to your project. Usage of Jinja2Cpp in the code is pretty simple: -1. Declare the jinja2::Template object: +1. Declare the jinja2::Template object: ```c++ jinja2::Template tpl; ``` -2. Populate it with template: +2. Populate it with template: ```c++ tpl.Load("{{'Hello World' }}!!!"); ``` -3. Render the template: +3. Render the template: ```c++ std::cout << tpl.RenderAsString(jinja2::ValuesMap{}) << std::endl; @@ -90,58 +90,58 @@ Hello World!!! That's all! -More detailed examples and features describtion can be found in the documentation: https://jinja2cpp.dev/docs/usage +More detailed examples and features describtion can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) -# Current Jinja2 support +## Current Jinja2 support Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Jinja2Cpp is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: -- expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. -- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode**) -- big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) -- limited number of functions (**range**, **loop.cycle**) -- 'if' statement (with 'elif' and 'else' branches) -- 'for' statement (with 'else' branch and 'if' part support) -- 'extends' statement -- 'set' statement -- 'extends'/'block' statements -- 'macro'/'call' statements -- recursive loops -- space control - -# Supported compilers +- expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. +- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode**) +- big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) +- limited number of functions (**range**, **loop.cycle**) +- 'if' statement (with 'elif' and 'else' branches) +- 'for' statement (with 'else' branch and 'if' part support) +- 'extends' statement +- 'set' 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): -- Linux gcc 5.0 -- Linux gcc 6.0 -- Linux gcc 7.0 -- Linux clang 5.0 -- Microsoft Visual Studio 2015 x86, x64 -- Microsoft Visual Studio 2017 x86, x64 - -# Build and install +- Linux gcc 5.0 +- Linux gcc 6.0 +- Linux gcc 7.0 +- Linux clang 5.0 +- Microsoft Visual Studio 2015 x86, x64 +- Microsoft Visual Studio 2017 x86, x64 + +## Build and install Jinja2Cpp has several external dependencies: -* `boost` library (at least version 1.55) -* `nonstd::expected-lite` (https://github.com/martinmoene/expected-lite) -* `nonstd::variant-lite` (https://github.com/martinmoene/variant-lite) -* `nonstd::value-ptr-lite` (https://github.com/martinmoene/value-ptr-lite) -* `nonstd::optional-lite`(https://github.com/martinmoene/optional-lite) +- `boost` library (at least version 1.55) +- `nonstd::expected-lite` [https://github.com/martinmoene/expected-lite](https://github.com/martinmoene/expected-lite) +- `nonstd::variant-lite` [https://github.com/martinmoene/variant-lite](https://github.com/martinmoene/variant-lite) +- `nonstd::value-ptr-lite` [https://github.com/martinmoene/value-ptr-lite](https://github.com/martinmoene/value-ptr-lite) +- `nonstd::optional-lite` [https://github.com/martinmoene/optional-lite](https://github.com/martinmoene/optional-lite) In simpliest case to compile Jinja2Cpp you need: -1. Install CMake build system (at least version 3.0) -2. Clone jinja2cpp repository and update submodules: +1. Install CMake build system (at least version 3.0) +2. Clone jinja2cpp repository and update submodules: ``` > git clone https://github.com/flexferrum/Jinja2Cpp.git > git submodule -q update --init ``` -3. Create build directory: +3. Create build directory: ``` > cd Jinja2Cpp > mkdir build ``` -4. Run CMake and build the library: +4. Run CMake and build the library: ``` > cd build @@ -164,7 +164,7 @@ In simpliest case to compile Jinja2Cpp you need: In this case Jinja2Cpp will be built with internally-shipped dependencies and install them respectively. But Jinja2Cpp supports build with externally-provided deps. Different Jinja2Cpp usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build -## Usage with conan.io dependency manager +### Usage with conan.io dependency manager Jinja2Cpp can be used as conan.io package. In this case you should do the following steps: 1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html ) @@ -205,48 +205,48 @@ set_target_properties (${TARGET_NAME} PROPERTIES ``` -## Additional CMake build flags +### Additional CMake build flags You can define (via -D command line CMake option) the following build flags: -* **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2Cpp tests. -* **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). -* **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. -* **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). -* **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: - * `internal` In this mode Jinja2Cpp build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. - * `external-boost` In this mode Jinja2Cpp build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. - * `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) - * `conan-build` Special mode for building Jinja2Cpp via conan recipe. +- **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2Cpp tests. +- **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). +- **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. +- **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). +- **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: + - `internal` In this mode Jinja2Cpp build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. + - `external-boost` In this mode Jinja2Cpp build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. + - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) + - `conan-build` Special mode for building Jinja2Cpp via conan recipe. -## Build with C++17 standard enabled +### Build with C++17 standard enabled In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD` macro in the build settings. -# Acknowledgments +## Acknowledgments Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. Thanks to @martinmoene for perfectly implemented xxx-lite libraries. -# Changelog -## Version 0.9.1 -* `applymacro` filter added which allows to apply arbitrary macro as a filter -* dependencies to boost removed from the public interface -* CMake scripts improved -* Various bugs fixed -* Improve reflection -* Warnings cleanup - -## 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 -* 'Contatenate as string' operator ('~') has been implemented -* For-loop with 'if' condition has been implemented -* Fixed some bugs in parser +## Changelog +### Version 0.9.1 +- `applymacro` filter added which allows to apply arbitrary macro as a filter +- dependencies to boost removed from the public interface +- CMake scripts improved +- Various bugs fixed +- Improve reflection +- Warnings cleanup + +### 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](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](https://github.com/flexferrum/Jinja2Cpp/issues/8) +- 'Contatenate as string' operator ('~') has been implemented +- For-loop with 'if' condition has been implemented +- Fixed some bugs in parser From 2455d387e5b2ec2f1b3dab9daac5cd56539f15dc Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Mon, 13 May 2019 13:56:19 +0300 Subject: [PATCH 070/206] [skip ci] Jinja2Cpp -> Jinja2C++ --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d53940d9..4766bc39 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@
+
# Jinja2С++ +
[![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) @@ -58,12 +60,12 @@ hello; world!!! ## Getting started -In order to use Jinja2Cpp in your project you have to: -* Clone the Jinja2Cpp repository +In order to use Jinja2C++ in your project you have to: +* Clone the Jinja2C++ repository * Build it according with the instructions * Link to your project. -Usage of Jinja2Cpp in the code is pretty simple: +Usage of Jinja2C++ in the code is pretty simple: 1. Declare the jinja2::Template object: ```c++ From 3faeccaf0b9ddb2cc4f51dc359f6cc78640558be Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Mon, 13 May 2019 14:46:58 +0300 Subject: [PATCH 071/206] [skip ci] Fix formatting --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4766bc39..a2b94efc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@
-
# Jinja2С++ -
[![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) From ce86d59f3e7bcf32d1ea06bcdfd528d535669bc1 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 23 May 2019 15:40:34 +0300 Subject: [PATCH 072/206] Implemetation of 'include' and 'import' statements (#108) * Add render-time error handling * Add parser for 'include' statement * Small refactor of error reporting * Fix parser, add tests for 'include' statement * Fully implement 'include' statement * Add statement AST visitor * Add parser and tests for 'import'/'from' statements * Implement 'import' statement * Add extra tests for 'import' syntax * Add tests for the parsing errors --- CMakeLists.txt | 2 +- include/jinja2cpp/error_info.h | 5 +- include/jinja2cpp/template.h | 36 +++-- src/ast_visitor.h | 115 ++++++++++++++ src/error_info.cpp | 9 ++ src/internal_value.cpp | 2 +- src/internal_value.h | 2 +- src/lexer.h | 16 +- src/lexertk.h | 2 +- src/render_context.h | 48 ++++-- src/renderer.h | 25 ++- src/statements.cpp | 249 +++++++++++++++++++++++++++-- src/statements.h | 83 +++++++++- src/string_converter_filter.cpp | 10 +- src/template.cpp | 65 ++++---- src/template_env.cpp | 12 +- src/template_impl.h | 130 +++++++++++---- src/template_parser.cpp | 274 ++++++++++++++++++++++++++++---- src/template_parser.h | 23 ++- src/value_visitors.h | 16 +- test/basic_tests.cpp | 50 +++--- test/errors_test.cpp | 26 ++- test/expressions_test.cpp | 6 +- test/extends_test.cpp | 56 +++---- test/filters_test.cpp | 8 +- test/forloop_test.cpp | 28 ++-- test/if_test.cpp | 6 +- test/import_test.cpp | 63 ++++++++ test/includes_test.cpp | 119 ++++++++++++++ test/macro_test.cpp | 18 +-- test/perf_test.cpp | 36 ++--- test/set_test.cpp | 10 +- test/test_tools.h | 52 +++++- test/testers_test.cpp | 2 +- test/user_callable_test.cpp | 12 +- 35 files changed, 1325 insertions(+), 291 deletions(-) create mode 100644 src/ast_visitor.h create mode 100644 test/import_test.cpp create mode 100644 test/includes_test.cpp 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/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 5b169191..64d82576 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -30,7 +30,10 @@ enum class ErrorCode UnexpectedExprBegin, UnexpectedExprEnd, UnexpectedStmtBegin, - UnexpectedStmtEnd + UnexpectedStmtEnd, + TemplateNotFound, + TemplateNotParsed, + InvalidValueType, }; 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/ast_visitor.h b/src/ast_visitor.h new file mode 100644 index 00000000..b23bdbad --- /dev/null +++ b/src/ast_visitor.h @@ -0,0 +1,115 @@ +#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/error_info.cpp b/src/error_info.cpp index 72eed03a..31f19198 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -191,6 +191,15 @@ 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; + 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(); } 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/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/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 c87e09c1..1307fb55 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 @@ -51,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 @@ -92,16 +100,22 @@ class RenderContext RenderContext Clone(bool includeCurrentContext) const { if (!includeCurrentContext) - return RenderContext(*m_externalScope, m_rendererCallback); + return RenderContext(m_emptyScope, m_rendererCallback); 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/renderer.h b/src/renderer.h index e3b55779..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 @@ -17,18 +18,24 @@ namespace jinja2 class RendererBase { public: - virtual ~RendererBase() {} + virtual ~RendererBase() = default; 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(r); + m_renderers.push_back(std::move(r)); } void Render(OutStream& os, RenderContext& values) override { @@ -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,11 +67,13 @@ class RawTextRenderer : public RendererBase size_t m_length; }; -class ExpressionRenderer : public RendererBase +class ExpressionRenderer : public VisitableRendererBase { public: - ExpressionRenderer(ExpressionEvaluatorPtr<> expr) - : m_expression(expr) + VISITABLE_STATEMENT(); + + explicit ExpressionRenderer(ExpressionEvaluatorPtr<> expr) + : m_expression(std::move(expr)) { } diff --git a/src/statements.cpp b/src/statements.cpp index c7406290..809f2970 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) @@ -272,28 +274,50 @@ class ParentTemplateRenderer : public BlocksRenderer ExtendsStatement::BlocksCollection* m_blocks; }; +template struct TemplateImplVisitor { - ExtendsStatement::BlocksCollection* m_blocks; + // ExtendsStatement::BlocksCollection* m_blocks; + const Fn& m_fn; + bool m_throwError; - TemplateImplVisitor(ExtendsStatement::BlocksCollection* blocks) - : m_blocks(blocks) + explicit TemplateImplVisitor(const Fn& fn, bool throwError) + : m_fn(fn) + , m_throwError(throwError) {} 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); + if (!m_throwError && !tpl) + { + return Result{}; + } + else if (!tpl) + { + throw tpl.error(); + } + return m_fn(tpl.value()); } - RendererPtr operator()(EmptyValue) const + Result operator()(EmptyValue) const { - return RendererPtr(); + return Result(); } }; +template +Result VisitTemplateImpl(Arg&& tpl, bool throwError, Fn&& fn) +{ + return visit(TemplateImplVisitor(fn, throwError), 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,11 +326,207 @@ 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, true, [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, false, [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)); + } +} + +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, [](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 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, [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) { for (auto& p : m_params) @@ -415,5 +635,4 @@ void MacroCallStatement::SetupMacroScope(InternalValueMap&) } - } // jinja2 diff --git a/src/statements.h b/src/statements.h index d3440366..b341fb83 100644 --- a/src/statements.h +++ b/src/statements.h @@ -9,8 +9,10 @@ namespace jinja2 { -struct Statement : public RendererBase +class Statement : public VisitableRendererBase { +public: + VISITABLE_STATEMENT(); }; template @@ -30,6 +32,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 +71,8 @@ class ElseBranchStatement; class IfStatement : public Statement { public: + VISITABLE_STATEMENT(); + IfStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) { @@ -94,6 +100,8 @@ class IfStatement : public Statement class ElseBranchStatement : public Statement { public: + VISITABLE_STATEMENT(); + ElseBranchStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) { @@ -114,6 +122,8 @@ class ElseBranchStatement : public Statement class SetStatement : public Statement { public: + VISITABLE_STATEMENT(); + SetStatement(std::vector fields) : m_fields(std::move(fields)) { @@ -133,6 +143,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 +166,8 @@ class ParentBlockStatement : public Statement class BlockStatement : public Statement { public: + VISITABLE_STATEMENT(); + BlockStatement(std::string name) : m_name(std::move(name)) { @@ -175,6 +189,8 @@ class BlockStatement : public Statement class ExtendsStatement : public Statement { public: + VISITABLE_STATEMENT(); + using BlocksCollection = std::unordered_map>; ExtendsStatement(std::string name, bool isPath) @@ -195,9 +211,70 @@ class ExtendsStatement : public Statement void DoRender(OutStream &os, RenderContext &values); }; +class IncludeStatement : public Statement +{ +public: + VISITABLE_STATEMENT(); + + 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: + 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: + 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; +}; + class MacroStatement : public Statement { public: + VISITABLE_STATEMENT(); + MacroStatement(std::string name, MacroParams params) : m_name(std::move(name)) , m_params(std::move(params)) @@ -208,6 +285,7 @@ class MacroStatement : public Statement { m_mainBody = renderer; } + void Render(OutStream &os, RenderContext &values) override; protected: @@ -226,6 +304,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)) @@ -244,4 +324,5 @@ class MacroCallStatement : public MacroStatement }; } // jinja2 + #endif // STATEMENTS_H 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.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_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 e711f34e..e61568bb 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 = ""; + + return ErrorInfoTpl(errorData); + } - InternalValueMap intParams; - for (auto& ip : params) + try + { + 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); + } + catch (const ErrorInfoTpl& error) + { + return error; + } + catch (const std::exception& ex) { - 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(); + 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); } - RendererCallback callback(this); - RenderContext context(intParams, &callback); - InitRenderContext(context); - OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); - m_renderer->Render(outStream, context); + + return normalResult; } InternalValueMap& InitRenderContext(RenderContext& context) @@ -147,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 ResultType(TplOrError(std::static_pointer_cast(tplWrapper.value().m_impl))); + return LoadTemplate(name.value()); + } + + 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) {} @@ -193,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; }; @@ -201,6 +274,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..0b473a6e 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -53,11 +53,18 @@ 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::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::Include: - case Keyword::Import: return MakeParseError(ErrorCode::YetUnsupported, tok); default: return MakeParseError(ErrorCode::UnexpectedToken, tok); @@ -73,6 +80,38 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme return result; } +struct ErrorTokenConverter +{ + const Token& baseTok; + + explicit 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) { @@ -99,11 +138,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(); @@ -131,13 +166,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); @@ -331,11 +360,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); @@ -347,8 +372,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); @@ -362,7 +386,7 @@ StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, tok3.type = Token::Eof; return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2, tok3}); } - + if (nextTok == Token::Identifier) lexer.EatToken(); @@ -399,9 +423,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); @@ -437,12 +459,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)); @@ -581,4 +599,200 @@ 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 = lexer.PeekNextToken(); + bool isIgnoreMissing = false; + bool isWithContext = true; + bool hasIgnoreMissing = false; + if (lexer.EatIfEqual(Keyword::Ignore)) + { + if (lexer.EatIfEqual(Keyword::Missing)) + isIgnoreMissing = true; + else + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Missing); + + hasIgnoreMissing = true; + nextTok = lexer.PeekNextToken(); + } + + auto kw = lexer.GetAsKeyword(nextTok); + bool hasContextControl = 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 (hasIgnoreMissing) + return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::With, Token::Without); + + return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Ignore, Token::With, Token::Without); + } + + + auto renderer = std::make_shared(isIgnoreMissing, isWithContext); + renderer->SetIncludeNamesExpr(valueExpr); + statementsInfo.back().currentComposition->AddRenderer(renderer); + + 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; + bool hasContextControl = false; + bool isWithContext = false; + + for (;;) + { + bool hasComma = false; + if (!mappedNames.empty()) + { + if (!lexer.EatIfEqual(Token::Comma)) + hasComma = true;; + } + + nextTok = lexer.PeekNextToken(); + 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)) + 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)); + } + + 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 dac59eec..e2974efa 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,9 @@ 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); + ParseResult ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); }; template @@ -591,7 +594,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(); @@ -770,7 +773,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 +806,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 +871,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/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/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/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 %}", 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..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) { @@ -28,11 +16,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 +33,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 +53,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 +75,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 +93,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 +116,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 +149,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 +175,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 +204,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 +227,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/import_test.cpp b/test/import_test.cpp new file mode 100644 index 00000000..4aa2e40c --- /dev/null +++ b/test/import_test.cpp @@ -0,0 +1,63 @@ +#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", 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 }})"); + } +}; + +TEST_F(ImportTest, TestContextImports) +{ + jinja2::ValuesMap params{{"foo", 42}}; + + 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); + EXPECT_EQ("[|23]", result); + result = Render(R"({% from "module" import test with context %}{{ test() }})", params); + EXPECT_EQ("[42|23]", result); +} + +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/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/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..4154fd9a 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include struct InputOutputPair { @@ -123,13 +125,61 @@ 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); } }; +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)); + } + + jinja2::Template Load(std::string tplBody) + { + 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 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) + { + 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; 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 39f4e20892f6ca65f1ec31c7dea43b6402d7ce86 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 24 May 2019 10:51:38 +0300 Subject: [PATCH 073/206] [skip ci] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2b94efc..9f6c468b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) [![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) -[![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) +[![Coverage Status](https://codecov.io/gh/jinja2cpp/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ff01fa4410ac417f8192dce78e919ece)](https://www.codacy.com/app/flexferrum/Jinja2Cpp_2?utm_source=github.com&utm_medium=referral&utm_content=jinja2cpp/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) @@ -25,7 +25,7 @@ Main features of Jinja2C++: - Built-in reflection for C++ types. - Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions. - Control statements (set, for, if). -- Templates extention. +- Templates extention, including and importing - Macros - Rich error reporting. From 7e9f5da282f9405288151241c5f76d428500fa83 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Fri, 24 May 2019 10:53:28 +0300 Subject: [PATCH 074/206] [skip ci] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9f6c468b..69685e81 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - 'if' statement (with 'elif' and 'else' branches) - 'for' statement (with 'else' branch and 'if' part support) - 'extends' statement +- 'include' statement +- 'import'/'from' statements - 'set' statement - 'extends'/'block' statements - 'macro'/'call' statements From b93fd693d39044a4fc556ff5a68f69b36cfdc303 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 28 May 2019 14:09:29 +0300 Subject: [PATCH 075/206] Implement 'do' statement #105 (#109) * Implement 'do' extension statement * Add parsing errors test for 'do' statement --- include/jinja2cpp/error_info.h | 1 + include/jinja2cpp/template_env.h | 13 +++++++++++ src/error_info.cpp | 3 +++ src/expression_parser.cpp | 3 ++- src/expression_parser.h | 3 ++- src/lexer.h | 2 ++ src/statements.cpp | 4 ++++ src/statements.h | 13 +++++++++++ src/template_parser.cpp | 38 ++++++++++++++++++++++++-------- src/template_parser.h | 16 +++++++++++--- test/errors_test.cpp | 31 ++++++++++++++++++++++++++ test/expressions_test.cpp | 36 ++++++++++++++++++++++++++++++ 12 files changed, 149 insertions(+), 14 deletions(-) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 64d82576..16bfcc2f 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -34,6 +34,7 @@ enum class ErrorCode TemplateNotFound, TemplateNotParsed, InvalidValueType, + ExtensionDisabled, }; struct SourceLocation diff --git a/include/jinja2cpp/template_env.h b/include/jinja2cpp/template_env.h index c72267e8..3b94f2b4 100644 --- a/include/jinja2cpp/template_env.h +++ b/include/jinja2cpp/template_env.h @@ -14,11 +14,24 @@ namespace jinja2 class IErrorHandler; class IFilesystemHandler; +enum class Jinja2CompatMode +{ + None, + Vesrsion_2_10, +}; + struct Settings { + struct Extensions + { + bool Do = false; + }; + bool useLineStatements = false; bool trimBlocks = false; bool lstripBlocks = false; + Extensions extensions; + Jinja2CompatMode jinja2CompatMode = Jinja2CompatMode::None; }; class TemplateEnv diff --git a/src/error_info.cpp b/src/error_info.cpp index 31f19198..bc82c438 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -200,6 +200,9 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e case ErrorCode::InvalidValueType: os << UNIVERSAL_STR("Invalid value type"); break; + case ErrorCode::ExtensionDisabled: + os << UNIVERSAL_STR("Extension disabled"); + break; } os << std::endl << errInfo.GetLocationDescr(); } diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index 6aec9651..1c198541 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace jinja2 { @@ -16,7 +17,7 @@ auto ReplaceErrorIfPossible(T& result, const Token& pivotTok, ErrorCode newError return result.get_unexpected(); } -ExpressionParser::ExpressionParser() +ExpressionParser::ExpressionParser(const Settings& /* settings */) { } diff --git a/src/expression_parser.h b/src/expression_parser.h index 9ba0d909..43efd971 100644 --- a/src/expression_parser.h +++ b/src/expression_parser.h @@ -7,6 +7,7 @@ #include "renderer.h" #include +#include namespace jinja2 { @@ -16,7 +17,7 @@ class ExpressionParser template using ParseResult = nonstd::expected; - ExpressionParser(); + ExpressionParser(const Settings& settings); ParseResult Parse(LexScanner& lexer); ParseResult> ParseFullExpression(LexScanner& lexer, bool includeIfPart = true); ParseResult ParseCallParams(LexScanner& lexer); diff --git a/src/lexer.h b/src/lexer.h index 8cd6dfea..f16f4c63 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -92,6 +92,7 @@ struct Token Context, From, As, + Do, // Template control CommentBegin, @@ -169,6 +170,7 @@ enum class Keyword Context, From, As, + Do, }; struct LexerHelper diff --git a/src/statements.cpp b/src/statements.cpp index 809f2970..b5a081c2 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -635,4 +635,8 @@ void MacroCallStatement::SetupMacroScope(InternalValueMap&) } +void DoStatement::Render(OutStream& os, RenderContext& values) +{ + m_expr->Evaluate(values); +} } // jinja2 diff --git a/src/statements.h b/src/statements.h index b341fb83..bb7a3c85 100644 --- a/src/statements.h +++ b/src/statements.h @@ -322,6 +322,19 @@ class MacroCallStatement : public MacroStatement std::string m_macroName; CallParams m_callParams; }; + +class DoStatement : public Statement +{ +public: + VISITABLE_STATEMENT(); + + DoStatement(ExpressionEvaluatorPtr <> expr) : m_expr(expr) {} + + void Render(OutStream &os, RenderContext &values) override; + +private: + ExpressionEvaluatorPtr<> m_expr; +}; } // jinja2 diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 0b473a6e..6dc95bed 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -62,6 +62,11 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme case Keyword::From: result = ParseFrom(lexer, statementsInfo, tok); break; + case Keyword::Do: + if (!m_settings.extensions.Do) + return MakeParseError(ErrorCode::ExtensionDisabled, tok); + result = ParseDo(lexer, statementsInfo, tok); + break; case Keyword::Filter: case Keyword::EndFilter: case Keyword::EndSet: @@ -142,7 +147,7 @@ StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, Stat } auto pivotToken = lexer.PeekNextToken(); - ExpressionParser exprPraser; + ExpressionParser exprPraser(m_settings); auto valueExpr = exprPraser.ParseFullExpression(lexer, false); if (!valueExpr) return valueExpr.get_unexpected(); @@ -213,7 +218,7 @@ StatementsParser::ParseResult StatementsParser::ParseIf(LexScanner &lexer, State const Token &stmtTok) { auto pivotTok = lexer.PeekNextToken(); - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto valueExpr = exprParser.ParseFullExpression(lexer); if (!valueExpr) return MakeParseError(ErrorCode::ExpectedExpression, pivotTok); @@ -239,7 +244,7 @@ StatementsParser::ParseResult StatementsParser::ParseElIf(LexScanner& lexer, Sta , const Token& stmtTok) { auto pivotTok = lexer.PeekNextToken(); - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto valueExpr = exprParser.ParseFullExpression(lexer); if (!valueExpr) return MakeParseError(ErrorCode::ExpectedExpression, pivotTok); @@ -309,7 +314,7 @@ StatementsParser::ParseResult StatementsParser::ParseSet(LexScanner& lexer, Stat ExpressionEvaluatorPtr<> valueExpr; if (operTok == '=') { - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); if (!expr) return expr.get_unexpected(); @@ -478,7 +483,7 @@ nonstd::expected StatementsParser::ParseMacroParams(Lex if (lexer.EatIfEqual(')')) return std::move(items); - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); do { @@ -562,7 +567,7 @@ StatementsParser::ParseResult StatementsParser::ParseCall(LexScanner& lexer, Sta CallParams callParams; if (lexer.EatIfEqual('(')) { - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto result = exprParser.ParseCallParams(lexer); if (!result) return result.get_unexpected(); @@ -606,7 +611,7 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, // auto operTok = lexer.NextToken(); ExpressionEvaluatorPtr<> valueExpr; - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); if (!expr) return expr.get_unexpected(); @@ -662,7 +667,7 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) { ExpressionEvaluatorPtr<> valueExpr; - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); if (!expr) return expr.get_unexpected(); @@ -709,7 +714,7 @@ StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, S StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) { ExpressionEvaluatorPtr<> valueExpr; - ExpressionParser exprParser; + ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); if (!expr) return expr.get_unexpected(); @@ -795,4 +800,19 @@ StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, Sta return ParseResult(); } +StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + ExpressionEvaluatorPtr<> valueExpr; + ExpressionParser exprParser(m_settings); + auto expr = exprParser.ParseFullExpression(lexer); + if (!expr) + return expr.get_unexpected(); + valueExpr = *expr; + + auto renderer = std::make_shared(valueExpr); + statementsInfo.back().currentComposition->AddRenderer(renderer); + + return jinja2::StatementsParser::ParseResult(); +} + } diff --git a/src/template_parser.h b/src/template_parser.h index e2974efa..9a5e3552 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[39]; + static KeywordsInfo s_keywordsInfo[40]; static std::unordered_map s_tokens; }; @@ -194,6 +194,10 @@ class StatementsParser public: using ParseResult = nonstd::expected; + StatementsParser(const Settings& settings) + : m_settings(settings) + {} + ParseResult Parse(LexScanner& lexer, StatementInfoList& statementsInfo); private: @@ -216,6 +220,10 @@ class StatementsParser 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); + ParseResult ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + +private: + Settings m_settings; }; template @@ -532,7 +540,7 @@ class TemplateParser : public LexerHelper if (!lexer.Preprocess()) return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, {range.startOffset, range.startOffset + 1})); - P praser; + P praser(m_settings); LexScanner scanner(lexer); auto result = praser.Parse(scanner, std::forward(args)...); if (!result) @@ -773,7 +781,7 @@ class TemplateParser : public LexerHelper }; template -KeywordsInfo ParserTraitsBase::s_keywordsInfo[39] = { +KeywordsInfo ParserTraitsBase::s_keywordsInfo[40] = { {UNIVERSAL_STR("for"), Keyword::For}, {UNIVERSAL_STR("endfor"), Keyword::Endfor}, {UNIVERSAL_STR("in"), Keyword::In}, @@ -813,6 +821,7 @@ KeywordsInfo ParserTraitsBase::s_keywordsInfo[39] = { {UNIVERSAL_STR("context"), Keyword::Context}, {UNIVERSAL_STR("from"), Keyword::From}, {UNIVERSAL_STR("as"), Keyword::As}, + {UNIVERSAL_STR("do"), Keyword::Do}, }; template @@ -878,6 +887,7 @@ std::unordered_map ParserTraitsBase::s_tokens = { {Token::Context, UNIVERSAL_STR("context")}, {Token::From, UNIVERSAL_STR("form")}, {Token::As, UNIVERSAL_STR("as")}, + {Token::Do, UNIVERSAL_STR("do")}, {Token::CommentBegin, UNIVERSAL_STR("{#")}, {Token::CommentEnd, UNIVERSAL_STR("#}")}, {Token::StmtBegin, UNIVERSAL_STR("{%")}, diff --git a/test/errors_test.cpp b/test/errors_test.cpp index a71d3f73..c073beb9 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -7,7 +7,9 @@ using namespace jinja2; struct ErrorsGenericTestTag; +struct ErrorsGenericExtensionTestTag; using ErrorsGenericTest = InputOutputPairTest; +using ErrorsGenericExtensionsTest = InputOutputPairTest; TEST_P(ErrorsGenericTest, Test) @@ -27,6 +29,26 @@ TEST_P(ErrorsGenericTest, Test) EXPECT_EQ(expectedResult, result); } +TEST_P(ErrorsGenericExtensionsTest, Test) +{ + auto& testParam = GetParam(); + std::string source = testParam.tpl; + + TemplateEnv env; + env.GetSettings().extensions.Do = true; + + Template tpl(&env); + auto parseResult = tpl.Load(source); + EXPECT_FALSE(parseResult.has_value()); + + std::ostringstream errorDescr; + errorDescr << parseResult.error(); + std::string result = errorDescr.str(); + std::cout << result << std::endl; + std::string expectedResult = testParam.result; + EXPECT_EQ(expectedResult, result); +} + INSTANTIATE_TEST_CASE_P(BasicTest, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{{}}", "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"}, @@ -236,6 +258,15 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values( "noname.j2tpl:1:20: error: Unexpected token: '*'\n{% call name(param=*) %}{% endcall %}\n ---^-------"}, InputOutputPair{"{% block b %}{% endcall %}", "noname.j2tpl:1:17: error: Unexpected statement: 'endcall'\n{% block b %}{% endcall %}\n ---^-------"}, + InputOutputPair{"{% do 'Hello World' %}", + "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"}, InputOutputPair{"{{}}", "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"} )); + +INSTANTIATE_TEST_CASE_P(ExtensionStatementsTest, ErrorsGenericExtensionsTest, ::testing::Values( + InputOutputPair{"{% do %}", + "noname.j2tpl:1:7: error: Unexpected token: '<>'\n{% do %}\n ---^-------"}, + InputOutputPair{"{% do 1 + %}", + "noname.j2tpl:1:11: error: Unexpected token: '<>'\n{% do 1 + %}\n ---^-------"} + )); diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 31a58cd9..d6b6d98d 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -86,6 +86,42 @@ TEST(ExpressionsTest, IfExpression) EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } +TEST(ExpressionTest, DoStatement) +{ + std::string source = R"( +{{ data.strValue }}{% do setData('Inner Value') %} +{{ data.strValue }} +)"; + + TemplateEnv env; + env.GetSettings().extensions.Do = true; + + TestInnerStruct innerStruct; + innerStruct.strValue = "Outer Value"; + + ValuesMap params = { + {"data", Reflect(&innerStruct)}, + {"setData", MakeCallable( + [&innerStruct](const std::string& val) -> Value { + innerStruct.strValue = val; + return "String not to be shown"; + }, + ArgInfo{"val"}) + }, + }; + + Template tpl(&env); + + ASSERT_TRUE(tpl.Load(source)); + std::string result = tpl.RenderAsString(params).value(); + std::cout << result << std::endl; + std::string expectedResult = R"( +Outer ValueInner Value +)"; + + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + struct LogicalExprTestTag; using LogicalExprTest = InputOutputPairTest; From 5d0a6c2de88d6bde75075ca32677a6278d7146f4 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 28 May 2019 16:06:22 +0300 Subject: [PATCH 076/206] [skip ci] Fix references --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 69685e81..1b0a4707 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) -[![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) -[![Coverage Status](https://codecov.io/gh/jinja2cpp/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp) +[![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/jinja2cpp/jinja2cpp-n5hjm/branch/master) +[![Coverage Status](https://codecov.io/gh/jinja2cpp/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/jinja2cpp/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ff01fa4410ac417f8192dce78e919ece)](https://www.codacy.com/app/flexferrum/Jinja2Cpp_2?utm_source=github.com&utm_medium=referral&utm_content=jinja2cpp/Jinja2Cpp&utm_campaign=Badge_Grade) -[![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/flexferrum/Jinja2Cpp/releases) +[![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/jinja2cpp/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) [![conan.io](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) From 08365537d1d8a915a72eee7889562a16f6a04ed5 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 29 May 2019 08:06:35 +0300 Subject: [PATCH 077/206] Implemented 'with' statement (#110) --- src/expression_parser.h | 2 +- src/lexer.h | 2 ++ src/statements.cpp | 13 +++++++ src/statements.h | 23 +++++++++++- src/template_parser.cpp | 65 +++++++++++++++++++++++++++++++++ src/template_parser.h | 13 +++++-- test/errors_test.cpp | 23 ++++++++++-- test/set_test.cpp | 79 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 213 insertions(+), 7 deletions(-) diff --git a/src/expression_parser.h b/src/expression_parser.h index 43efd971..a8c98a59 100644 --- a/src/expression_parser.h +++ b/src/expression_parser.h @@ -41,7 +41,7 @@ class ExpressionParser ParseResult> ParseIfExpression(LexScanner& lexer); private: - ComposedRenderer* m_topLevelRenderer; + ComposedRenderer* m_topLevelRenderer = nullptr; }; } // jinja2 diff --git a/src/lexer.h b/src/lexer.h index f16f4c63..78ea48b4 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -86,6 +86,7 @@ struct Token Recursive, Scoped, With, + EndWith, Without, Ignore, Missing, @@ -164,6 +165,7 @@ enum class Keyword Recursive, Scoped, With, + EndWith, Without, Ignore, Missing, diff --git a/src/statements.cpp b/src/statements.cpp index b5a081c2..2f4095f0 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -639,4 +639,17 @@ void DoStatement::Render(OutStream& os, RenderContext& values) { m_expr->Evaluate(values); } + +void WithStatement::Render(OutStream& os, RenderContext& values) +{ + auto innerValues = values.Clone(true); + auto& scope = innerValues.EnterScope(); + + for (auto& var : m_scopeVars) + scope[var.first] = var.second->Evaluate(values); + + m_mainBody->Render(os, innerValues); + + innerValues.ExitScope(); +} } // jinja2 diff --git a/src/statements.h b/src/statements.h index bb7a3c85..2461b436 100644 --- a/src/statements.h +++ b/src/statements.h @@ -328,13 +328,34 @@ class DoStatement : public Statement public: VISITABLE_STATEMENT(); - DoStatement(ExpressionEvaluatorPtr <> expr) : m_expr(expr) {} + DoStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) {} void Render(OutStream &os, RenderContext &values) override; private: ExpressionEvaluatorPtr<> m_expr; }; + +class WithStatement : public Statement +{ +public: + VISITABLE_STATEMENT(); + + void SetScopeVars(std::vector>> vars) + { + m_scopeVars = std::move(vars); + } + void SetMainBody(RendererPtr renderer) + { + m_mainBody = renderer; + } + + void Render(OutStream &os, RenderContext &values) override; + +private: + std::vector>> m_scopeVars; + RendererPtr m_mainBody; +}; } // jinja2 diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 6dc95bed..bddd6f7b 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -67,6 +67,12 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme return MakeParseError(ErrorCode::ExtensionDisabled, tok); result = ParseDo(lexer, statementsInfo, tok); break; + case Keyword::With: + result = ParseWith(lexer, statementsInfo, tok); + break; + case Keyword::EndWith: + result = ParseEndWith(lexer, statementsInfo, tok); + break; case Keyword::Filter: case Keyword::EndFilter: case Keyword::EndSet: @@ -815,4 +821,63 @@ StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, State return jinja2::StatementsParser::ParseResult(); } +StatementsParser::ParseResult StatementsParser::ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + std::vector>> vars; + + ExpressionParser exprParser(m_settings); + while (lexer.PeekNextToken() == Token::Identifier) + { + auto nameTok = lexer.NextToken(); + if (!lexer.EatIfEqual('=')) + return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), '='); + + auto expr = exprParser.ParseFullExpression(lexer); + if (!expr) + return expr.get_unexpected(); + auto valueExpr = *expr; + + vars.emplace_back(AsString(nameTok.value), valueExpr); + + if (!lexer.EatIfEqual(',')) + break; + } + + auto nextTok = lexer.PeekNextToken(); + if (vars.empty()) + return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok); + + if (nextTok != Token::Eof) + return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Eof, ','); + + auto renderer = std::make_shared(); + renderer->SetScopeVars(std::move(vars)); + StatementInfo statementInfo = StatementInfo::Create(StatementInfo::WithStatement, stmtTok); + statementInfo.renderer = renderer; + statementsInfo.push_back(statementInfo); + + return ParseResult(); +} + +StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + if (statementsInfo.size() <= 1) + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + + StatementInfo info = statementsInfo.back(); + + if (info.type != StatementInfo::WithStatement) + { + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + } + + statementsInfo.pop_back(); + auto renderer = static_cast(info.renderer.get()); + renderer->SetMainBody(info.compositions[0]); + + statementsInfo.back().currentComposition->AddRenderer(info.renderer); + + return ParseResult(); +} + } diff --git a/src/template_parser.h b/src/template_parser.h index 9a5e3552..3351b240 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[40]; + static KeywordsInfo s_keywordsInfo[41]; static std::unordered_map s_tokens; }; @@ -166,7 +166,8 @@ struct StatementInfo BlockStatement, ParentBlockStatement, MacroStatement, - MacroCallStatement + MacroCallStatement, + WithStatement }; using ComposedPtr = std::shared_ptr; @@ -224,6 +225,10 @@ class StatementsParser private: Settings m_settings; + + ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token); + + ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); }; template @@ -781,7 +786,7 @@ class TemplateParser : public LexerHelper }; template -KeywordsInfo ParserTraitsBase::s_keywordsInfo[40] = { +KeywordsInfo ParserTraitsBase::s_keywordsInfo[41] = { {UNIVERSAL_STR("for"), Keyword::For}, {UNIVERSAL_STR("endfor"), Keyword::Endfor}, {UNIVERSAL_STR("in"), Keyword::In}, @@ -815,6 +820,7 @@ KeywordsInfo ParserTraitsBase::s_keywordsInfo[40] = { {UNIVERSAL_STR("recursive"), Keyword::Recursive}, {UNIVERSAL_STR("scoped"), Keyword::Scoped}, {UNIVERSAL_STR("with"), Keyword::With}, + {UNIVERSAL_STR("endwith"), Keyword::EndWith}, {UNIVERSAL_STR("without"), Keyword::Without}, {UNIVERSAL_STR("ignore"), Keyword::Ignore}, {UNIVERSAL_STR("missing"), Keyword::Missing}, @@ -881,6 +887,7 @@ std::unordered_map ParserTraitsBase::s_tokens = { {Token::Recursive, UNIVERSAL_STR("recursive")}, {Token::Scoped, UNIVERSAL_STR("scoped")}, {Token::With, UNIVERSAL_STR("with")}, + {Token::EndWith, UNIVERSAL_STR("endwith")}, {Token::Without, UNIVERSAL_STR("without")}, {Token::Ignore, UNIVERSAL_STR("ignore")}, {Token::Missing, UNIVERSAL_STR("missing")}, diff --git a/test/errors_test.cpp b/test/errors_test.cpp index c073beb9..b635a424 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -19,7 +19,7 @@ TEST_P(ErrorsGenericTest, Test) Template tpl; auto parseResult = tpl.Load(source); - EXPECT_FALSE(parseResult.has_value()); + ASSERT_FALSE(parseResult.has_value()); std::ostringstream errorDescr; errorDescr << parseResult.error(); @@ -39,7 +39,7 @@ TEST_P(ErrorsGenericExtensionsTest, Test) Template tpl(&env); auto parseResult = tpl.Load(source); - EXPECT_FALSE(parseResult.has_value()); + ASSERT_FALSE(parseResult.has_value()); std::ostringstream errorDescr; errorDescr << parseResult.error(); @@ -213,6 +213,7 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_1, ErrorsGenericTest, ::testing::Values( 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 ---^-------"}, @@ -260,6 +261,24 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values( "noname.j2tpl:1:17: error: Unexpected statement: 'endcall'\n{% block b %}{% endcall %}\n ---^-------"}, InputOutputPair{"{% do 'Hello World' %}", "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"}, + InputOutputPair{"{% with %}{% endif }", + "noname.j2tpl:1:9: error: Identifier expected\n{% with %}{% endif }\n ---^-------"}, + InputOutputPair{"{% with a %}{% endif }", + "noname.j2tpl:1:11: error: Unexpected token '<>'. Expected: '='\n{% with a %}{% endif }\n ---^-------"}, + InputOutputPair{"{% with a 42 %}{% endif }", + "noname.j2tpl:1:11: error: Unexpected token '42'. Expected: '='\n{% with a 42 %}{% endif }\n ---^-------"}, + InputOutputPair{"{% with a = %}{% endif }", + "noname.j2tpl:1:13: error: Unexpected token: '<>'\n{% with a = %}{% endif }\n ---^-------"}, + InputOutputPair{"{% with a = 42 b = 30 %}{% endif }", + "noname.j2tpl:1:16: error: Unexpected token 'b'. Expected: '<>', ','\n{% with a = 42 b = 30 %}{% endif }\n ---^-------"}, + InputOutputPair{"{% with a = 42, %}{% endif }", + "noname.j2tpl:1:22: error: Unexpected statement: 'endif'\n{% with a = 42, %}{% endif }\n ---^-------"}, +// FIXME: InputOutputPair{"{% with a = 42 %}", +// "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"}, + InputOutputPair{"{% with a = 42 %}{% endfor %}", + "noname.j2tpl:1:21: error: Unexpected statement: 'endfor'\n{% with a = 42 %}{% endfor %}\n ---^-------"}, + InputOutputPair{"{% set a = 42 %}{% endwith %}", + "noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% set a = 42 %}{% endwith %}\n ---^-------"}, InputOutputPair{"{{}}", "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"} )); diff --git a/test/set_test.cpp b/test/set_test.cpp index 1384cd61..eeadebaf 100644 --- a/test/set_test.cpp +++ b/test/set_test.cpp @@ -5,6 +5,8 @@ #include "jinja2cpp/template.h" +#include "test_tools.h" + using namespace jinja2; TEST(SetTest, SimpleSetTest) @@ -131,3 +133,80 @@ world: World )"; EXPECT_EQ(expectedResult, result); } + +using WithTest = TemplateEnvFixture; + +TEST_F(WithTest, SimpleTest) +{ + auto result = Render(R"( +{% with inner = 42 %} +{{ inner }} +{%- endwith %} +)", {}); + EXPECT_EQ("\n42", result); +} + +TEST_F(WithTest, MultiVarsTest) +{ + auto result = Render(R"( +{% with inner1 = 42, inner2 = 'Hello World' %} +{{ inner1 }} +{{ inner2 }} +{%- endwith %} +)", {}); + EXPECT_EQ("\n42\nHello World", result); +} + +TEST_F(WithTest, ScopeTest1) +{ + auto result = Render(R"( +{{ outer }} +{% with inner = 42, outer = 'Hello World' %} +{{ inner }} +{{ outer }} +{%- endwith %} +{{ outer }} +)", {{"outer", "World Hello"}}); + EXPECT_EQ("\nWorld Hello\n42\nHello WorldWorld Hello\n", result); +} + +TEST_F(WithTest, ScopeTest2) +{ + auto result = Render(R"( +{{ outer }} +{% with outer = 'Hello World', inner = outer %} +{{ inner }} +{{ outer }} +{%- endwith %} +{{ outer }} +)", {{"outer", "World Hello"}}); + EXPECT_EQ("\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n", result); +} + +TEST_F(WithTest, ScopeTest3) +{ + auto result = Render(R"( +{{ outer }} +{% with outer = 'Hello World' %} +{% set inner = outer %} +{{ inner }} +{{ outer }} +{%- endwith %} +{{ outer }} +)", {{"outer", "World Hello"}}); + EXPECT_EQ("\nWorld Hello\nHello World\nHello WorldWorld Hello\n", result); +} + +TEST_F(WithTest, ScopeTest4) +{ + auto result = Render(R"( +{% with inner1 = 42 %} +{% set inner2 = outer %} +{{ inner1 }} +{{ inner2 }} +{%- endwith %} +>> {{ inner1 }} << +>> {{ inner2 }} << +)", {{"outer", "World Hello"}}); + EXPECT_EQ("\n42\nWorld Hello>> <<\n>> <<\n", result); +} From dcbfbc5711f414ba3c9bf1fb5a77696d93ea887f Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 29 May 2019 10:26:58 +0300 Subject: [PATCH 078/206] [skip ci] Update readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b0a4707..c7e5d53d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Main features of Jinja2C++: - 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. -- Control statements (set, for, if). +- Control statements (set, for, if, do, with). - Templates extention, including and importing - Macros - Rich error reporting. @@ -100,12 +100,13 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - limited number of functions (**range**, **loop.cycle**) - 'if' statement (with 'elif' and 'else' branches) - 'for' statement (with 'else' branch and 'if' part support) -- 'extends' statement - 'include' statement - 'import'/'from' statements - 'set' statement - 'extends'/'block' statements - 'macro'/'call' statements +- 'with' statement +- 'do' extension statement - recursive loops - space control From 5219da4fdb4e7b09a480ad4d3ff9a5dbd5f27258 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 29 May 2019 11:20:00 +0300 Subject: [PATCH 079/206] [skip ci] Update reference to build status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7e5d53d..5c1ccb63 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) -[![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/jinja2cpp/jinja2cpp-n5hjm/branch/master) +[![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) [![Coverage Status](https://codecov.io/gh/jinja2cpp/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/jinja2cpp/Jinja2Cpp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ff01fa4410ac417f8192dce78e919ece)](https://www.codacy.com/app/flexferrum/Jinja2Cpp_2?utm_source=github.com&utm_medium=referral&utm_content=jinja2cpp/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/jinja2cpp/Jinja2Cpp/releases) From d60945247beb635201e08160d8cdcc473c44bcfd Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 30 May 2019 17:12:42 +0300 Subject: [PATCH 080/206] [skip ci] Add reference to Jinja2C++ compatibility table --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c1ccb63..17d171e9 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - recursive loops - space control +Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](Jinja2C++ Compatibility). + ## Supported compilers Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled feature): - Linux gcc 5.0 From 4231ceb57bdc0e713a436050b4c4f45a62f20805 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 30 May 2019 17:13:50 +0300 Subject: [PATCH 081/206] [skip ci] Fix reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17d171e9..ca27246e 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - recursive loops - space control -Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](Jinja2C++ Compatibility). +Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.dev/docs/j2_compatibility.html). ## Supported compilers Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled feature): From 4ee8e727cfb155264c816a99f821ff2b15b3d8ab Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 10:04:57 +0300 Subject: [PATCH 082/206] Release 0.9.2 preparation (#112) * Fix PVS studio warnings * Fix clang warning * Fix and improve diagnostic for imported/included templates #63 * Add extra tests for render-time diagnostic * Update thirdparty references * Update reference to variant-lite * Update reference to expected-lite * Fix build --- include/jinja2cpp/error_info.h | 2 + include/jinja2cpp/filesystem_handler.h | 2 +- src/error_info.cpp | 11 ++- src/expression_evaluator.cpp | 8 +-- src/expression_evaluator.h | 16 ++--- src/expression_parser.cpp | 13 ++-- src/expression_parser.h | 2 +- src/filesystem_handler.cpp | 2 +- src/filters.cpp | 6 +- src/function_base.h | 4 +- src/internal_value.h | 2 +- src/lexertk.h | 9 +-- src/statements.cpp | 27 +++++-- src/statements.h | 22 +++--- src/template_env.cpp | 2 +- src/template_impl.h | 4 +- src/template_parser.cpp | 19 +++-- src/template_parser.h | 14 ++-- src/testers.cpp | 4 +- src/value_visitors.h | 2 +- test/errors_test.cpp | 99 +++++++++++++++++++++++++- thirdparty/internal_deps.cmake | 2 +- thirdparty/nonstd/expected-lite | 2 +- thirdparty/nonstd/optional-lite | 2 +- thirdparty/nonstd/value-ptr-lite | 2 +- thirdparty/nonstd/variant-lite | 2 +- 26 files changed, 205 insertions(+), 75 deletions(-) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 16bfcc2f..98a84633 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -34,7 +34,9 @@ enum class ErrorCode TemplateNotFound, TemplateNotParsed, InvalidValueType, + InvalidTemplateName, ExtensionDisabled, + TemplateEnvAbsent, }; struct SourceLocation diff --git a/include/jinja2cpp/filesystem_handler.h b/include/jinja2cpp/filesystem_handler.h index 5dbc7a59..19fbb05f 100644 --- a/include/jinja2cpp/filesystem_handler.h +++ b/include/jinja2cpp/filesystem_handler.h @@ -48,7 +48,7 @@ class RealFileSystem : public IFilesystemHandler void SetRootFolder(std::string newRoot) { - m_rootFolder = newRoot; + m_rootFolder = std::move(newRoot); } CharFileStreamPtr OpenStream(const std::string& name) const override; diff --git a/src/error_info.cpp b/src/error_info.cpp index bc82c438..b19bace0 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -110,8 +110,11 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e os << UNIVERSAL_STR("Parse error"); break; case ErrorCode::UnexpectedException: - os << UNIVERSAL_STR("Unexpected exception occurred during template processing"); + { + auto& extraParams = errInfo.GetExtraParams(); + os << UNIVERSAL_STR("Unexpected exception occurred during template processing. Exception: ") << extraParams[0]; break; + } case ErrorCode::YetUnsupported: os << UNIVERSAL_STR("This feature has not been supported yet"); break; @@ -197,12 +200,18 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e case ErrorCode::TemplateNotFound: os << UNIVERSAL_STR("Template(s) not found: ") << errInfo.GetExtraParams()[0]; break; + case ErrorCode::InvalidTemplateName: + os << UNIVERSAL_STR("Invalid template name: ") << errInfo.GetExtraParams()[0]; + break; case ErrorCode::InvalidValueType: os << UNIVERSAL_STR("Invalid value type"); break; case ErrorCode::ExtensionDisabled: os << UNIVERSAL_STR("Extension disabled"); break; + case ErrorCode::TemplateEnvAbsent: + os << UNIVERSAL_STR("Template environment doesn't set"); + break; } os << std::endl << errInfo.GetLocationDescr(); } diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index b8b51fe7..9486985d 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -181,9 +181,9 @@ InternalValue DictCreator::Evaluate(RenderContext& context) return MapAdapter::CreateAdapter(std::move(result));; } -ExpressionFilter::ExpressionFilter(std::string filterName, CallParams params) +ExpressionFilter::ExpressionFilter(const std::string& filterName, CallParams params) { - m_filter = CreateFilter(std::move(filterName), std::move(params)); + m_filter = CreateFilter(filterName, std::move(params)); if (!m_filter) throw std::runtime_error("Can't find filter '" + filterName + "'"); } @@ -196,10 +196,10 @@ InternalValue ExpressionFilter::Evaluate(const InternalValue& baseVal, RenderCon return m_filter->Filter(baseVal, context); } -IsExpression::IsExpression(ExpressionEvaluatorPtr<> value, std::string tester, CallParams params) +IsExpression::IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParams params) : m_value(value) { - m_tester = CreateTester(std::move(tester), std::move(params)); + m_tester = CreateTester(tester, std::move(params)); if (!m_tester) throw std::runtime_error("Can't find tester '" + tester + "'"); } diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index db644958..17626008 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -49,7 +49,7 @@ struct ParsedArguments std::unordered_map> extraKwArgs; std::vector> extraPosArgs; - ExpressionEvaluatorPtr<> operator[](std::string name) const + ExpressionEvaluatorPtr<> operator[](const std::string& name) const { auto p = args.find(name); if (p == args.end()) @@ -67,15 +67,15 @@ class FullExpressionEvaluator : public ExpressionEvaluatorBase public: void SetExpression(ExpressionEvaluatorPtr expr) { - m_expression = expr; + m_expression = std::move(expr); } void SetFilter(ExpressionEvaluatorPtr expr) { - m_filter = expr; + m_filter = std::move(expr); } void SetTester(ExpressionEvaluatorPtr expr) { - m_tester = expr; + m_tester = std::move(expr); } InternalValue Evaluate(RenderContext& values) override; void Render(OutStream &stream, RenderContext &values) override; @@ -204,7 +204,7 @@ class IsExpression : public Expression using TesterFactoryFn = std::function (CallParams params)>; - IsExpression(ExpressionEvaluatorPtr<> value, std::string tester, CallParams params); + IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParams params); InternalValue Evaluate(RenderContext& context) override; private: @@ -293,12 +293,12 @@ class ExpressionFilter using FilterFactoryFn = std::function (CallParams params)>; - ExpressionFilter(std::string filterName, CallParams params); + ExpressionFilter(const std::string& filterName, CallParams params); InternalValue Evaluate(const InternalValue& baseVal, RenderContext& context); void SetParentFilter(std::shared_ptr parentFilter) { - m_parentFilter = parentFilter; + m_parentFilter = std::move(parentFilter); } private: std::shared_ptr m_filter; @@ -322,7 +322,7 @@ class IfExpression void SetAltValue(ExpressionEvaluatorPtr<> altValue) { - m_altValue = altValue; + m_altValue = std::move(altValue); } private: diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index 1c198541..7131b300 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -17,7 +17,7 @@ auto ReplaceErrorIfPossible(T& result, const Token& pivotTok, ErrorCode newError return result.get_unexpected(); } -ExpressionParser::ExpressionParser(const Settings& /* settings */) +ExpressionParser::ExpressionParser(const Settings& /* settings */, TemplateEnv* /* env */) { } @@ -63,13 +63,10 @@ ExpressionParser::ParseResult> E if (includeIfPart && lexer.EatIfEqual(Keyword::If)) { - if (includeIfPart) - { - auto ifExpr = ParseIfExpression(lexer); - if (!ifExpr) - return ifExpr.get_unexpected(); - evaluator->SetTester(*ifExpr); - } + auto ifExpr = ParseIfExpression(lexer); + if (!ifExpr) + return ifExpr.get_unexpected(); + evaluator->SetTester(*ifExpr); } saver.Commit(); diff --git a/src/expression_parser.h b/src/expression_parser.h index a8c98a59..5273e9c7 100644 --- a/src/expression_parser.h +++ b/src/expression_parser.h @@ -17,7 +17,7 @@ class ExpressionParser template using ParseResult = nonstd::expected; - ExpressionParser(const Settings& settings); + explicit ExpressionParser(const Settings& settings, TemplateEnv* env = nullptr); ParseResult Parse(LexScanner& lexer); ParseResult> ParseFullExpression(LexScanner& lexer, bool includeIfPart = true); ParseResult ParseCallParams(LexScanner& lexer); diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index 744aefe4..9cf1c087 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -71,7 +71,7 @@ WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const } RealFileSystem::RealFileSystem(std::string rootFolder) - : m_rootFolder(rootFolder) + : m_rootFolder(std::move(rootFolder)) { } diff --git a/src/filters.cpp b/src/filters.cpp index 9b53fdfa..a11e2168 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -537,10 +537,8 @@ SequenceAccessor::SequenceAccessor(FilterParams params, SequenceAccessor::Mode m case LengthMode: break; case MaxItemMode: - ParseParams({{"case_sensitive", false, InternalValue(false)}, {"attribute", false}}, params); - break; - case MinItemMode: - ParseParams({{"case_sensitive", false, InternalValue(false)}, {"attribute", false}}, params); + case MinItemMode: + ParseParams({{"case_sensitive", false, InternalValue(false)}, {"attribute", false}}, params); break; case RandomMode: case ReverseMode: diff --git a/src/function_base.h b/src/function_base.h index 5be168c3..2a1dd7ff 100644 --- a/src/function_base.h +++ b/src/function_base.h @@ -11,7 +11,7 @@ class FunctionBase public: protected: bool ParseParams(const std::initializer_list& argsInfo, const CallParams& params); - InternalValue GetArgumentValue(std::string argName, RenderContext& context, InternalValue defVal = InternalValue()); + InternalValue GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal = InternalValue()); protected: ParsedArguments m_args; @@ -26,7 +26,7 @@ inline bool FunctionBase::ParseParams(const std::initializer_list& return result; } -inline InternalValue FunctionBase::GetArgumentValue(std::string argName, RenderContext& context, InternalValue defVal) +inline InternalValue FunctionBase::GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal) { auto argExpr = m_args[argName]; return argExpr ? argExpr->Evaluate(context) : std::move(defVal); diff --git a/src/internal_value.h b/src/internal_value.h index fda704e8..44404771 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -218,7 +218,7 @@ class ListAdapter InternalValueList ToValueList() const; GenericList CreateGenericList() const { - if (m_accessorProvider && m_accessorProvider) + if (m_accessorProvider && m_accessorProvider()) return m_accessorProvider()->CreateGenericList(); return GenericList(); diff --git a/src/lexertk.h b/src/lexertk.h index 87091d63..7bb9fafa 100644 --- a/src/lexertk.h +++ b/src/lexertk.h @@ -349,7 +349,8 @@ namespace lexertk token() : type(e_none), - position(std::numeric_limits::max()) + position(std::numeric_limits::max()), + length(0) {} void clear() @@ -498,7 +499,7 @@ namespace lexertk s_end_ = 0; token_list_.clear(); token_itr_ = token_list_.end(); - store_token_itr_ = token_list_.end(); + store_token_itr_ = token_itr_; } inline bool process(const std::basic_string& str) @@ -542,7 +543,7 @@ namespace lexertk inline void begin() { token_itr_ = token_list_.begin(); - store_token_itr_ = token_list_.begin(); + store_token_itr_ = token_itr_; } inline void store() @@ -867,7 +868,7 @@ namespace lexertk if (endChar == *s_itr_) break; } - else if (escaped) + else escaped = false; ++s_itr_; diff --git a/src/statements.cpp b/src/statements.cpp index 2f4095f0..05373022 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -363,13 +363,28 @@ 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, false, [this](auto tplPtr) { - return CreateTemplateRenderer(tplPtr, m_withContext); - }); - if (renderer) + + try + { + auto renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { + return CreateTemplateRenderer(tplPtr, m_withContext); + }); + + if (renderer) + { + renderer->Render(os, values); + return true; + } + } + catch (const ErrorInfoTpl& err) + { + if (err.GetCode() != ErrorCode::FileNotFound) + throw; + } + catch (const ErrorInfoTpl& err) { - renderer->Render(os, values); - return true; + if (err.GetCode() != ErrorCode::FileNotFound) + throw; } return false; diff --git a/src/statements.h b/src/statements.h index 2461b436..273a220f 100644 --- a/src/statements.h +++ b/src/statements.h @@ -44,12 +44,12 @@ class ForStatement : public Statement void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void SetElseBody(RendererPtr renderer) { - m_elseBody = renderer; + m_elseBody = std::move(renderer); } void Render(OutStream& os, RenderContext& values) override; @@ -80,7 +80,7 @@ class IfStatement : public Statement void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void AddElseBranch(StatementPtr branch) @@ -110,7 +110,7 @@ class ElseBranchStatement : public Statement bool ShouldRender(RenderContext& values) const; void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void Render(OutStream& os, RenderContext& values) override; @@ -131,7 +131,7 @@ class SetStatement : public Statement void SetAssignmentExpr(ExpressionEvaluatorPtr<> expr) { - m_expr = expr; + m_expr = std::move(expr); } void Render(OutStream& os, RenderContext& values) override; @@ -153,7 +153,7 @@ class ParentBlockStatement : public Statement void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void Render(OutStream &os, RenderContext &values) override; @@ -177,7 +177,7 @@ class BlockStatement : public Statement void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void Render(OutStream &os, RenderContext &values) override; @@ -223,7 +223,7 @@ class IncludeStatement : public Statement void SetIncludeNamesExpr(ExpressionEvaluatorPtr<> expr) { - m_expr = expr; + m_expr = std::move(expr); } void Render(OutStream& os, RenderContext& values) override; @@ -244,7 +244,7 @@ class ImportStatement : public Statement void SetImportNameExpr(ExpressionEvaluatorPtr<> expr) { - m_nameExpr = expr; + m_nameExpr = std::move(expr); } void SetNamespace(std::string name) @@ -283,7 +283,7 @@ class MacroStatement : public Statement void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void Render(OutStream &os, RenderContext &values) override; @@ -347,7 +347,7 @@ class WithStatement : public Statement } void SetMainBody(RendererPtr renderer) { - m_mainBody = renderer; + m_mainBody = std::move(renderer); } void Render(OutStream &os, RenderContext &values) override; diff --git a/src/template_env.cpp b/src/template_env.cpp index 134cd3aa..4f912312 100644 --- a/src/template_env.cpp +++ b/src/template_env.cpp @@ -52,7 +52,7 @@ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesyste auto stream = Functions::LoadFile(fileName, fh.handler.get()); if (stream) { - auto res = tpl.Load(*stream); + auto res = tpl.Load(*stream, fileName); if (!res) return ResultType(res.get_unexpected()); diff --git a/src/template_impl.h b/src/template_impl.h index e61568bb..e960c75b 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -111,7 +111,7 @@ class TemplateImpl : public ITemplateImpl { m_template = std::move(tpl); m_templateName = tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName); - TemplateParser parser(&m_template, m_settings, m_templateName); + TemplateParser parser(&m_template, m_settings, m_env, m_templateName); auto parseResult = parser.Parse(); if (!parseResult) @@ -203,7 +203,7 @@ class TemplateImpl : public ITemplateImpl if (!name) { typename ErrorInfoTpl::Data errorData; - errorData.code = ErrorCode::UnexpectedException; + errorData.code = ErrorCode::InvalidTemplateName; errorData.srcLoc.col = 1; errorData.srcLoc.line = 1; errorData.srcLoc.fileName = m_templateName; diff --git a/src/template_parser.cpp b/src/template_parser.cpp index bddd6f7b..37a6a1a5 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -378,7 +378,7 @@ StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, St } StatementInfo statementInfo = StatementInfo::Create(blockType, stmtTok); - statementInfo.renderer = blockRenderer; + statementInfo.renderer = std::move(blockRenderer); statementsInfo.push_back(statementInfo); return ParseResult(); } @@ -427,6 +427,9 @@ StatementsParser::ParseResult StatementsParser::ParseExtends(LexScanner& lexer, if (statementsInfo.empty()) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + if (!m_env) + return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok); + Token tok = lexer.NextToken(); if (tok != Token::String && tok != Token::Identifier) { @@ -509,7 +512,7 @@ nonstd::expected StatementsParser::ParseMacroParams(Lex MacroParam p; p.paramName = AsString(name.value); - p.defaultValue = defVal; + p.defaultValue = std::move(defVal); items.push_back(std::move(p)); } while (lexer.EatIfEqual(',')); @@ -662,6 +665,8 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Ignore, Token::With, Token::Without); } + if (!m_env && !isIgnoreMissing) + return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok); auto renderer = std::make_shared(isIgnoreMissing, isWithContext); renderer->SetIncludeNamesExpr(valueExpr); @@ -672,6 +677,9 @@ StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) { + if (!m_env) + return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok); + ExpressionEvaluatorPtr<> valueExpr; ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); @@ -719,6 +727,9 @@ StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, S StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) { + if (!m_env) + return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok); + ExpressionEvaluatorPtr<> valueExpr; ExpressionParser exprParser(m_settings); auto expr = exprParser.ParseFullExpression(lexer); @@ -806,7 +817,7 @@ StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, Sta return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& /*stmtTok*/) { ExpressionEvaluatorPtr<> valueExpr; ExpressionParser exprParser(m_settings); @@ -859,7 +870,7 @@ StatementsParser::ParseResult StatementsParser::ParseWith(LexScanner& lexer, Sta return ParseResult(); } -StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& /*lexer*/, StatementInfoList& statementsInfo, const Token& stmtTok) { if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); diff --git a/src/template_parser.h b/src/template_parser.h index 3351b240..69f0e418 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -195,8 +195,9 @@ class StatementsParser public: using ParseResult = nonstd::expected; - StatementsParser(const Settings& settings) + StatementsParser(const Settings& settings, TemplateEnv* env) : m_settings(settings) + , m_env(env) {} ParseResult Parse(LexScanner& lexer, StatementInfoList& statementsInfo); @@ -225,6 +226,7 @@ class StatementsParser private: Settings m_settings; + TemplateEnv* m_env; ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token); @@ -241,10 +243,11 @@ class TemplateParser : public LexerHelper using ErrorInfo = ErrorInfoTpl; using ParseResult = nonstd::expected>; - TemplateParser(const string_t* tpl, const Settings& setts, std::string tplName) + TemplateParser(const string_t* tpl, const Settings& setts, TemplateEnv* env, std::string tplName) : m_template(tpl) , m_templateName(std::move(tplName)) , m_settings(setts) + , m_env(env) , m_roughTokenizer(traits_t::GetRoughTokenizer()) , m_keywords(traits_t::GetKeywords()) { @@ -545,7 +548,7 @@ class TemplateParser : public LexerHelper if (!lexer.Preprocess()) return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, {range.startOffset, range.startOffset + 1})); - P praser(m_settings); + P praser(m_settings, m_env); LexScanner scanner(lexer); auto result = praser.Parse(scanner, std::forward(args)...); if (!result) @@ -777,12 +780,13 @@ class TemplateParser : public LexerHelper const string_t* m_template; std::string m_templateName; const Settings& m_settings; + TemplateEnv* m_env = nullptr; std::basic_regex m_roughTokenizer; std::basic_regex m_keywords; std::vector m_lines; std::vector m_textBlocks; - LineInfo m_currentLineInfo; - TextBlockInfo m_currentBlockInfo; + LineInfo m_currentLineInfo = {}; + TextBlockInfo m_currentBlockInfo = {}; }; template diff --git a/src/testers.cpp b/src/testers.cpp index bf1b41df..7394a188 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -210,14 +210,14 @@ bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context) if (valKind == ValueKind::Integer) { auto intVal = ConvertToInt(val); - result = (testMode == (intVal & 1)) == (EvenTest ? 0 : 1); + result = (intVal & 1) == (testMode == EvenTest ? 0 : 1); } else if (valKind == ValueKind::Double) { auto dblVal = ConvertToDouble(val); int64_t intVal = static_cast(dblVal); if (dblVal == intVal) - result = (testMode == (intVal & 1)) == (EvenTest ? 0 : 1); + result = (intVal & 1) == (testMode == EvenTest ? 0 : 1); } return result; }; diff --git a/src/value_visitors.h b/src/value_visitors.h index da1f4095..75d01548 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -345,7 +345,7 @@ struct UnaryOperation : BaseVisitor switch (m_oper) { case jinja2::UnaryExpression::LogicalNot: - result = val ? false : true; + result = fabs(val) > std::numeric_limits::epsilon() ? false : true; break; case jinja2::UnaryExpression::UnaryPlus: result = +val; diff --git a/test/errors_test.cpp b/test/errors_test.cpp index b635a424..d88aa32e 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -11,13 +11,106 @@ struct ErrorsGenericExtensionTestTag; using ErrorsGenericTest = InputOutputPairTest; using ErrorsGenericExtensionsTest = InputOutputPairTest; +std::string ErrorToString(const jinja2::ErrorInfo& error) +{ + std::ostringstream errorDescr; + errorDescr << error; + return errorDescr.str(); +} + +TEST_F(TemplateEnvFixture, EnvironmentAbsentErrorsTest) +{ + Template tpl1; + auto parseResult = tpl1.Load("{% extends 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:4: error: Template environment doesn't set\n{% extends 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load("{% include 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:4: error: Template environment doesn't set\n{% include 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load("{% from 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:4: error: Template environment doesn't set\n{% from 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load("{% import 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:4: error: Template environment doesn't set\n{% import 'module' %}\n---^-------", ErrorToString(parseResult.error())); +} + +TEST_F(TemplateEnvFixture, RenderErrorsTest) +{ + Template tpl1; + auto renderResult = tpl1.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(":1:1: error: Template not parsed\n", ErrorToString(renderResult.error())); + + Template tpl2; + tpl2.Load(R"({{ foo() }})"); + renderResult = tpl2.RenderAsString({{"foo", MakeCallable([]() -> Value {throw std::runtime_error("Bang!"); })}}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:1: error: Unexpected exception occurred during template processing. Exception: Bang!\n", ErrorToString(renderResult.error())); + + Template tpl3(&m_env); + auto parseResult = tpl3.Load("{% import name as name %}"); + EXPECT_TRUE(parseResult.has_value()); + if (!parseResult) + std::cout << parseResult.error() << std::endl; + renderResult = tpl3.RenderAsString({{"name", 10}}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("noname.j2tpl:1:1: error: Invalid template name: 10\n", ErrorToString(renderResult.error())); +} + +TEST_F(TemplateEnvFixture, ErrorPropagationTest) +{ + AddFile("module", "{% for %}"); + Template tpl1(&m_env); + auto parseResult = tpl1.Load("{% extends 'module' %}"); + ASSERT_TRUE(parseResult.has_value()); + auto renderResult = tpl1.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + Template tpl2(&m_env); + parseResult = tpl2.Load("{% include 'module' %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl2.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + Template tpl3(&m_env); + parseResult = tpl3.Load("{% from 'module' import name %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl3.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + Template tpl4(&m_env); + parseResult = tpl4.Load("{% import 'module' as module %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl4.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ("module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); +} TEST_P(ErrorsGenericTest, Test) { auto& testParam = GetParam(); std::string source = testParam.tpl; - Template tpl; + TemplateEnv env; + Template tpl(&env); auto parseResult = tpl.Load(source); ASSERT_FALSE(parseResult.has_value()); @@ -277,8 +370,8 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values( // "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"}, InputOutputPair{"{% with a = 42 %}{% endfor %}", "noname.j2tpl:1:21: error: Unexpected statement: 'endfor'\n{% with a = 42 %}{% endfor %}\n ---^-------"}, - InputOutputPair{"{% set a = 42 %}{% endwith %}", - "noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% set a = 42 %}{% endwith %}\n ---^-------"}, + InputOutputPair{"{% if a == 42 %}{% endwith %}", + "noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% if a == 42 %}{% endwith %}\n ---^-------"}, InputOutputPair{"{{}}", "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"} )); diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index a12287d0..5cd58d60 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -11,7 +11,7 @@ add_library(optional-lite ALIAS optional-lite) update_submodule(nonstd/value-ptr-lite) add_subdirectory(thirdparty/nonstd/value-ptr-lite EXCLUDE_FROM_ALL) -add_library(value-ptr-lite ALIAS value_ptr-lite) +add_library(value-ptr-lite ALIAS value-ptr-lite) install (FILES thirdparty/nonstd/expected-lite/include/nonstd/expected.hpp diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index ce6d1847..d21c0869 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit ce6d1847c5c9a80a707703f45ceda8d4ca3d2760 +Subproject commit d21c086934ec664e350d5301b240f43ba203f342 diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite index 86db12f3..4c4da907 160000 --- a/thirdparty/nonstd/optional-lite +++ b/thirdparty/nonstd/optional-lite @@ -1 +1 @@ -Subproject commit 86db12f3b6c2026ad83efd51c5ce3622bb06f1b8 +Subproject commit 4c4da9074ac4c779bb81332fd82292edd8a6089d diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 49165a36..2fd703f8 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 49165a36361692d3414d2495bcd859e67784e8ef +Subproject commit 2fd703f8a8e6a6b0bd210f12313d4f18530ff0c3 diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite index fd33ea5e..7558be05 160000 --- a/thirdparty/nonstd/variant-lite +++ b/thirdparty/nonstd/variant-lite @@ -1 +1 @@ -Subproject commit fd33ea5ee4f152d9c5e85b79b3f40c705b932015 +Subproject commit 7558be05c20b9636512ead83e510b8dabf61870a From 9e222430ca0b42aada7e7579ccf85171dca33107 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 11:00:13 +0300 Subject: [PATCH 083/206] [skip ci] Change default dependency management mode internal -> external before the release --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc4a6684..975f22b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,9 @@ if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) endif () -set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") +set (JINJA2CPP_DEPS_MODE "external" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") if (NOT JINJA2CPP_DEPS_MODE) - set (JINJA2CPP_DEPS_MODE "internal") + set (JINJA2CPP_DEPS_MODE "external") endif () include(CMakePackageConfigHelpers) From bcf810ce220a05caf3d5fb39b8b23223c4e48e22 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 12:04:56 +0300 Subject: [PATCH 084/206] Add global scope bound to TemplateEnv --- include/jinja2cpp/template_env.h | 21 +++++++++++++++++++++ src/render_context.h | 13 ++++++++++--- src/template_impl.h | 29 +++++++++++++++++++++-------- test/includes_test.cpp | 4 +++- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/include/jinja2cpp/template_env.h b/include/jinja2cpp/template_env.h index 3b94f2b4..f8355432 100644 --- a/include/jinja2cpp/template_env.h +++ b/include/jinja2cpp/template_env.h @@ -7,6 +7,7 @@ #include "template.h" #include +#include namespace jinja2 { @@ -61,6 +62,24 @@ class TemplateEnv nonstd::expected LoadTemplate(std::string fileName); nonstd::expected LoadTemplateW(std::string fileName); + void AddGlobal(std::string name, Value val) + { + std::unique_lock l(m_guard); + m_globalValues[std::move(name)] = std::move(val); + } + + void RemoveGlobal(const std::string& name) + { + std::unique_lock l(m_guard); + m_globalValues.erase(name); + } + + template + void ApplyGlobals(Fn&& fn) + { + std::shared_lock l(m_guard); + fn(m_globalValues); + } private: IErrorHandler* m_errorHandler; struct FsHandler @@ -70,6 +89,8 @@ class TemplateEnv }; std::vector m_filesystemHandlers; Settings m_settings; + ValuesMap m_globalValues; + std::shared_timed_mutex m_guard; }; } // jinja2 diff --git a/src/render_context.h b/src/render_context.h index 1307fb55..9f986c7d 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -29,10 +29,11 @@ struct IRendererCallback class RenderContext { public: - RenderContext(const InternalValueMap& extValues, IRendererCallback* rendererCallback) + RenderContext(const InternalValueMap& extValues, const InternalValueMap& globalValues, IRendererCallback* rendererCallback) : m_rendererCallback(rendererCallback) { m_externalScope = &extValues; + m_globalScope = &globalValues; EnterScope(); (*m_currentScope)["self"] = MapAdapter::CreateAdapter(InternalValueMap()); } @@ -77,7 +78,12 @@ class RenderContext if (found) return valP; } - return finder(*m_externalScope); + + auto valP = finder(*m_externalScope); + if (found) + return valP; + + return finder(*m_globalScope); } auto& GetCurrentScope() const @@ -100,7 +106,7 @@ class RenderContext RenderContext Clone(bool includeCurrentContext) const { if (!includeCurrentContext) - return RenderContext(m_emptyScope, m_rendererCallback); + return RenderContext(m_emptyScope, *m_globalScope, m_rendererCallback); return RenderContext(*this); } @@ -112,6 +118,7 @@ class RenderContext private: InternalValueMap* m_currentScope; const InternalValueMap* m_externalScope; + const InternalValueMap* m_globalScope; InternalValueMap m_emptyScope; std::list m_scopes; IRendererCallback* m_rendererCallback; diff --git a/src/template_impl.h b/src/template_impl.h index e960c75b..91a241a3 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -138,18 +138,31 @@ class TemplateImpl : public ITemplateImpl try { + InternalValueMap extParams; InternalValueMap intParams; - for (auto& ip : params) + + auto convertFn = [&intParams](auto& params) { + 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(); + } + }; + + if (m_env) { - 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(); + m_env->ApplyGlobals(convertFn); + std::swap(extParams, intParams); } + + convertFn(params); + RendererCallback callback(this); - RenderContext context(intParams, &callback); + RenderContext context(intParams, extParams, &callback); InitRenderContext(context); OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); m_renderer->Render(outStream, context); diff --git a/test/includes_test.cpp b/test/includes_test.cpp index 8903f67c..82b7bb7d 100644 --- a/test/includes_test.cpp +++ b/test/includes_test.cpp @@ -12,8 +12,10 @@ class IncludeTest : public TemplateEnvFixture { TemplateEnvFixture::SetUp(); - AddFile("header", "[{{ foo }}|{{ 23 }}]"); + AddFile("header", "[{{ foo }}|{{ bar }}]"); AddFile("o_printer", "({{ o }})"); + + m_env.AddGlobal("bar", 23); } }; From 3eece25dadfb473c0c5fb1d6582ed6e9ea96dde1 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 12:29:06 +0300 Subject: [PATCH 085/206] Fix travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f61ef958..0b87370e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,7 +82,7 @@ script: - $CXX --version - mkdir -p build && cd build - - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 + - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - ctest -C $BUILD_CONFIG -V From 185fcd6cd57606d276f959f3bcdee483f66d414b Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 12:52:15 +0300 Subject: [PATCH 086/206] [skip ci] Update changelog --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index ca27246e..6ebc731b 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,28 @@ Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and Thanks to @martinmoene for perfectly implemented xxx-lite libraries. ## Changelog + +### Version 0.9.2 +#### Major changes +- User-defined callables implemented. Now you can define your own callable objects, pass them as input parameters and use them inside templates as regular (global) functions, filters or testers. See details here: https://jinja2cpp.dev/docs/usage/ud_callables.html +- Now you can define global (template environment-wide) parameters which are accessible for all templates bound to this environment. +- `include`, `import` and `from` statements implemented. Now it's possible to include other templates and use macros from other templates. +- `with` statement implemented +- `do` statement implemented +- Sample build projects for various Jinja2C++ usage variants created: https://github.com/jinja2cpp/examples-build +- Documentation site created for Jinja2C++: https://jinja2cpp.dev/ + +#### Minor changes +- Render-time error handling added +- Dependency management mode added to the build script +- Fix bugs with error reporting during the parse time +- Upgraded versions of external dependencies + +#### Breaking changes +- `RenderAsString` method now returns `nonstd::expected` instead of regular `std::string` +- Templates with `import`, `extends` and `include` generate errors if parsed without `TemplateEnv` set +- Release bundles (archives) are configured with `external` dependency management mode by default + ### Version 0.9.1 - `applymacro` filter added which allows to apply arbitrary macro as a filter - dependencies to boost removed from the public interface From 00a4dd0f5142f4f35b12bd2e89a481bca2fd8cad Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 12:55:29 +0300 Subject: [PATCH 087/206] [skip ci] Revert default deps management mode --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 975f22b2..fc4a6684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,9 @@ if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) endif () -set (JINJA2CPP_DEPS_MODE "external" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") +set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") if (NOT JINJA2CPP_DEPS_MODE) - set (JINJA2CPP_DEPS_MODE "external") + set (JINJA2CPP_DEPS_MODE "internal") endif () include(CMakePackageConfigHelpers) From dac6c5b427ec38c907db7150ec0ba3808617303e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 2 Jun 2019 22:15:18 +0300 Subject: [PATCH 088/206] [skip ci] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ebc731b..acce8a02 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ std::string source = R"( Template tpl; tpl.Load(source); -std::string result = tpl.RenderAsString(ValuesMap()); +std::string result = tpl.RenderAsString({}).value(); ``` produces the result string: @@ -79,7 +79,7 @@ tpl.Load("{{'Hello World' }}!!!"); 3. Render the template: ```c++ -std::cout << tpl.RenderAsString(jinja2::ValuesMap{}) << std::endl; +std::cout << tpl.RenderAsString({}).value() << std::endl; ``` and get: From 9235979ce822fba789b9a07bab453ec180cf0909 Mon Sep 17 00:00:00 2001 From: "Eugene V. Palchukovsky" Date: Thu, 13 Jun 2019 16:23:22 +0300 Subject: [PATCH 089/206] Bugfix issue 47 (#114) * Fixed pipe operator ('|') precedence #47 * Removed method FullExpressionEvaluator::SetFilter, as it isn't required anymore after bug #47 resolving. --- src/expression_evaluator.cpp | 14 +++++++++----- src/expression_evaluator.h | 20 +++++++++++++++----- src/expression_parser.cpp | 33 +++++++++++++++++++-------------- test/expressions_test.cpp | 18 ++++++++++++++++++ 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index 9486985d..a08be7dc 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -27,19 +27,17 @@ InternalValue FullExpressionEvaluator::Evaluate(RenderContext& values) if (!m_expression) return InternalValue(); - InternalValue origVal = m_expression->Evaluate(values); - if (m_filter) - origVal = m_filter->Evaluate(origVal, values); + auto result = m_expression->Evaluate(values); if (m_tester && !m_tester->Evaluate(values)) return m_tester->EvaluateAltValue(values); - return origVal; + return result; } void FullExpressionEvaluator::Render(OutStream& stream, RenderContext& values) { - if (!m_filter && !m_tester) + if (!m_tester) m_expression->Render(stream, values); else Expression::Render(stream, values); @@ -71,6 +69,12 @@ InternalValue SubscriptExpression::Evaluate(RenderContext& values) return cur; } +InternalValue FilteredExpression::Evaluate(RenderContext& values) +{ + auto origResult = m_expression->Evaluate(values); + return m_filter->Evaluate(origResult, values); +} + InternalValue UnaryExpression::Evaluate(RenderContext& values) { return Apply(m_expr->Evaluate(values), m_oper); diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index 17626008..8d35dac9 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -69,10 +69,6 @@ class FullExpressionEvaluator : public ExpressionEvaluatorBase { m_expression = std::move(expr); } - void SetFilter(ExpressionEvaluatorPtr expr) - { - m_filter = std::move(expr); - } void SetTester(ExpressionEvaluatorPtr expr) { m_tester = std::move(expr); @@ -81,7 +77,6 @@ class FullExpressionEvaluator : public ExpressionEvaluatorBase void Render(OutStream &stream, RenderContext &values) override; private: ExpressionEvaluatorPtr m_expression; - ExpressionEvaluatorPtr m_filter; ExpressionEvaluatorPtr m_tester; }; @@ -115,6 +110,21 @@ class SubscriptExpression : public Expression std::vector> m_subscriptExprs; }; +class FilteredExpression : public Expression +{ +public: + explicit FilteredExpression(ExpressionEvaluatorPtr expression, ExpressionEvaluatorPtr filter) + : m_expression(std::move(expression)) + , m_filter(std::move(filter)) + { + } + InternalValue Evaluate(RenderContext&) override; + +private: + ExpressionEvaluatorPtr m_expression; + ExpressionEvaluatorPtr m_filter; +}; + class ConstantExpression : public Expression { public: diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index 7131b300..5557ed4f 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -53,13 +53,6 @@ ExpressionParser::ParseResult> E return value.get_unexpected(); evaluator->SetExpression(*value); - if (lexer.EatIfEqual('|')) - { - auto filter = ParseFilterExpression(lexer); - if (!filter) - return filter.get_unexpected(); - evaluator->SetFilter(*filter); - } if (includeIfPart && lexer.EatIfEqual(Keyword::If)) { @@ -265,18 +258,30 @@ ExpressionParser::ParseResult> ExpressionPars ExpressionParser::ParseResult> ExpressionParser::ParseUnaryPlusMinus(LexScanner& lexer) { - Token tok = lexer.NextToken(); - if (tok != '+' && tok != '-' && lexer.GetAsKeyword(tok) != Keyword::LogicalNot) - { + const auto tok = lexer.NextToken(); + const auto isUnary = tok == '+' || tok == '-' || lexer.GetAsKeyword(tok) == Keyword::LogicalNot; + if (!isUnary) lexer.ReturnToken(); - return ParseValueExpression(lexer); - } - + auto subExpr = ParseValueExpression(lexer); if (!subExpr) return subExpr; - return std::make_shared(tok == '+' ? UnaryExpression::UnaryPlus : (tok == '-' ? UnaryExpression::UnaryMinus : UnaryExpression::LogicalNot), *subExpr); + ExpressionEvaluatorPtr result; + if (isUnary) + result = std::make_shared(tok == '+' ? UnaryExpression::UnaryPlus : (tok == '-' ? UnaryExpression::UnaryMinus : UnaryExpression::LogicalNot), *subExpr); + else + result = subExpr.value(); + + if (lexer.EatIfEqual('|')) + { + auto filter = ParseFilterExpression(lexer); + if (!filter) + return filter.get_unexpected(); + result = std::make_shared(std::move(result), *filter); + } + + return result; } ExpressionParser::ParseResult> ExpressionParser::ParseValueExpression(LexScanner& lexer) diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index d6b6d98d..2b6c5128 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -122,6 +122,24 @@ Outer ValueInner Value EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } +TEST(ExpressionsTest, PipeOperatorPrecedenceTest) +{ + const std::string source = R"(>> {{ 2 < '6' | int }} << + >> {{ -30 | abs < str | int }} <<)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const ValuesMap params = {{"str", "20"}}; + + const auto result = tpl.RenderAsString(params).value(); + std::cout << result << std::endl; + const std::string expectedResult = R"(>> true << + >> false <<)"; + + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + struct LogicalExprTestTag; using LogicalExprTest = InputOutputPairTest; From 504b8a5bdd45c028da076c76a7c29270438e5dab Mon Sep 17 00:00:00 2001 From: "Eugene V. Palchukovsky" Date: Mon, 17 Jun 2019 16:13:37 +0300 Subject: [PATCH 090/206] Support for "default" attribute for the "attr" and "map" filters to resolve #48 (#115) * Supported attribute "default" for filters "attr" and "map" to resolve #48. --- .gitignore | 2 ++ src/filters.cpp | 40 +++++++++++++++++++++----------- src/filters.h | 2 ++ test/filters_test.cpp | 12 +++++++++- test/test_tools.h | 7 ++++++ test/user_callable_test.cpp | 7 +++--- thirdparty/nonstd/value-ptr-lite | 2 +- 7 files changed, 54 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 5a434454..091eea38 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ *.out *.app +.* + build/ dist/ diff --git a/src/filters.cpp b/src/filters.cpp index a11e2168..ebdfe6f0 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -161,13 +161,16 @@ InternalValue Sort::Filter(const InternalValue& baseVal, RenderContext& context) Attribute::Attribute(FilterParams params) { - ParseParams({{"name", true}}, params); + ParseParams({{"name", true}, {"default", false}}, params); } InternalValue Attribute::Filter(const InternalValue& baseVal, RenderContext& context) { - InternalValue attrNameVal = GetArgumentValue("name", context); - return Subscript(baseVal, attrNameVal); + const auto attrNameVal = GetArgumentValue("name", context); + const auto result = Subscript(baseVal, attrNameVal); + if (result.IsEmpty()) + return GetArgumentValue("default", context); + return result; } Default::Default(FilterParams params) @@ -363,21 +366,32 @@ InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& co Map::Map(FilterParams params) { - FilterParams newParams; + ParseParams({{"filter", true}}, MakeParams(std::move(params))); + m_mappingParams.kwParams = m_args.extraKwArgs; + m_mappingParams.posParams = m_args.extraPosArgs; +} - if (params.kwParams.size() == 1 && params.posParams.empty() && params.kwParams.count("attribute") == 1) - { - newParams.kwParams["name"] = params.kwParams["attribute"]; - newParams.kwParams["filter"] = std::make_shared("attr"s); +FilterParams Map::MakeParams(FilterParams params) +{ + if (!params.posParams.empty() || params.kwParams.empty() || params.kwParams.size() > 2) { + return params; } - else + + const auto attributeIt = params.kwParams.find("attribute"); + if (attributeIt == params.kwParams.cend()) { - newParams = std::move(params); + return params; } - ParseParams({{"filter", true}}, newParams); - m_mappingParams.kwParams = m_args.extraKwArgs; - m_mappingParams.posParams = m_args.extraPosArgs; + FilterParams result; + result.kwParams["name"] = attributeIt->second; + result.kwParams["filter"] = std::make_shared("attr"s); + + const auto defaultIt = params.kwParams.find("default"); + if (defaultIt != params.kwParams.cend()) + result.kwParams["default"] = defaultIt->second; + + return result; } InternalValue Map::Filter(const InternalValue& baseVal, RenderContext& context) diff --git a/src/filters.h b/src/filters.h index 10d84c2c..f7f683a0 100644 --- a/src/filters.h +++ b/src/filters.h @@ -81,6 +81,8 @@ class Map : public FilterBase InternalValue Filter(const InternalValue& baseVal, RenderContext& context); private: + static FilterParams MakeParams(FilterParams); + FilterParams m_mappingParams; }; diff --git a/test/filters_test.cpp b/test/filters_test.cpp index f5c21817..aea45c52 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -238,6 +238,8 @@ INSTANTIATE_TEST_CASE_P(Unique, ListIteratorTest, ::testing::Values( INSTANTIATE_TEST_CASE_P(Attr, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'value'='itemValue'} | attr('key')", "itemName"}, InputOutputPair{"mapValue | attr('intVal')", "10"}, + InputOutputPair{"mapValue | attr('intVal', default='99')", "10"}, + InputOutputPair{"mapValue | attr('nonexistent', default='99')", "99"}, InputOutputPair{"mapValue | attr(name='dblVal')", "100.5"}, InputOutputPair{"mapValue | attr('stringVal')", "string100.5"}, InputOutputPair{"mapValue | attr('boolValue')", "true"}, @@ -247,6 +249,14 @@ INSTANTIATE_TEST_CASE_P(Attr, FilterGenericTest, ::testing::Values( INSTANTIATE_TEST_CASE_P(Map, ListIteratorTest, ::testing::Values( InputOutputPair{"reflectedList | map(attribute='intValue')", "0, 1, 2, 3, 4, 5, 6, 7, 8, 9"}, + InputOutputPair{"reflectedList | map(attribute='intValue', default='99')", + "0, 1, 2, 3, 4, 5, 6, 7, 8, 9"}, + InputOutputPair{"reflectedList | map(attribute='intEvenValue')", "0, , 2, , 4, , 6, , 8, "}, + InputOutputPair{"reflectedList | map(attribute='intEvenValue', default='99')", + "0, 99, 2, 99, 4, 99, 6, 99, 8, 99"}, + InputOutputPair{"reflectedList | map(attribute='nonexistent')", ", , , , , , , , , "}, + InputOutputPair{"reflectedList | map(attribute='nonexistent', default='99')", + "99, 99, 99, 99, 99, 99, 99, 99, 99, 99"}, InputOutputPair{"[[0, 1], [1, 2], [2, 3], [3, 4]] | map('first')", "0, 1, 2, 3"}, InputOutputPair{"[['str1', 'Str2'], ['str2', 'Str3'], ['str3', 'Str4'], ['str4', 'Str5']] | map('min')", "str1, str2, str3, str4"}, @@ -377,7 +387,7 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"}, InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"}, InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, - InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} + InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} )); INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values( diff --git a/test/test_tools.h b/test/test_tools.h index 4154fd9a..d50069ee 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -214,6 +214,13 @@ struct TypeReflection : TypeReflected { static std::unordered_map accessors = { {"intValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.intValue;}}, + {"intEvenValue", [](const TestStruct& obj) -> Value + { + assert(obj.isAlive); + if (obj.intValue % 2) + return {}; + return {obj.intValue}; + }}, {"dblValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.dblValue;}}, {"boolValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.boolValue;}}, {"strValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.strValue;}}, diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index e6e7d369..623ed695 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -315,11 +315,12 @@ INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing "'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " - "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intValue': 0, 'strValue': 'test string 0', " - "'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, " + "'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " - "{'strValue': 'Hello World!'}], 'wstrValue': '']"}, + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], " + "'wstrValue': '']"}, InputOutputPair{"GMapFn(reflectedVal.innerStruct) | dictsort", "['strValue': 'Hello World!']"} )); diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite index 2fd703f8..cb7f9ff0 160000 --- a/thirdparty/nonstd/value-ptr-lite +++ b/thirdparty/nonstd/value-ptr-lite @@ -1 +1 @@ -Subproject commit 2fd703f8a8e6a6b0bd210f12313d4f18530ff0c3 +Subproject commit cb7f9ff07efa9905bdba28d15d09bf0764833b0f From f162db1f173de92783ab6722fdc00c8b65f475fc Mon Sep 17 00:00:00 2001 From: "Eugene V. Palchukovsky" Date: Fri, 21 Jun 2019 08:22:16 +0300 Subject: [PATCH 091/206] Supports for escape characters in the string literals. Resolves #49. (#122) Supported next characters: \n, \r and \t. --- src/helpers.h | 49 ++++++++++++++++++++++++++++++++++++++++++ src/lexertk.h | 44 ------------------------------------- src/template_parser.h | 8 +++++-- test/basic_tests.cpp | 11 ++++++++++ test/helpers_tests.cpp | 22 +++++++++++++++++++ 5 files changed, 88 insertions(+), 46 deletions(-) create mode 100644 test/helpers_tests.cpp diff --git a/src/helpers.h b/src/helpers.h index d75ae2e2..b814be62 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -122,6 +122,55 @@ Dst ConvertString(Src&& from) return detail::StringConverter, std::decay_t>::DoConvert(std::forward(from)); } +//! CompileEscapes replaces escape characters by their meanings. +/** + * @param[in] s Characters sequence with zero or more escape characters. + * @return Characters sequence copy where replaced all escape characters by + * their meanings. + */ +template +Sequence CompileEscapes(Sequence s) +{ + auto itr1 = s.begin(); + auto itr2 = s.begin(); + const auto end = s.cend(); + + auto removalCount = 0; + + while (end != itr1) + { + if ('\\' == *itr1) + { + ++removalCount; + + if (end == ++itr1) + break; + if ('\\' != *itr1) + { + switch (*itr1) + { + case 'n': *itr1 = '\n'; break; + case 'r': *itr1 = '\r'; break; + case 't': *itr1 = '\t'; break; + default: break; + } + + continue; + } + } + + if (itr1 != itr2) + *itr2 = *itr1; + + ++itr1; + ++itr2; + } + + s.resize(s.size() - removalCount); + + return s; +} + } // jinja2 #endif // HELPERS_H diff --git a/src/lexertk.h b/src/lexertk.h index 7bb9fafa..dde6403d 100644 --- a/src/lexertk.h +++ b/src/lexertk.h @@ -281,50 +281,6 @@ namespace lexertk } }; -#if 0 - inline void cleanup_escapes(std::string& s) - { - typedef std::string::iterator str_itr_t; - - str_itr_t itr1 = s.begin(); - str_itr_t itr2 = s.begin(); - str_itr_t end = s.end (); - - std::size_t removal_count = 0; - - while (end != itr1) - { - if ('\\' == (*itr1)) - { - ++removal_count; - - if (end == ++itr1) - break; - else if ('\\' != (*itr1)) - { - switch (*itr1) - { - case 'n' : (*itr1) = '\n'; break; - case 'r' : (*itr1) = '\r'; break; - case 't' : (*itr1) = '\t'; break; - } - - continue; - } - } - - if (itr1 != itr2) - { - (*itr2) = (*itr1); - } - - ++itr1; - ++itr2; - } - - s.resize(s.size() - removal_count); - } -#endif } struct token diff --git a/src/template_parser.h b/src/template_parser.h index 69f0e418..c2841634 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -746,8 +746,12 @@ class TemplateParser : public LexerHelper InternalValue GetAsValue(const CharRange& range, Token::Type type) override { if (type == Token::String) - return InternalValue(m_template->substr(range.startOffset, range.size())); - else if (type == Token::IntegerNum || type == Token::FloatNum) + { + auto rawValue = CompileEscapes( + m_template->substr(range.startOffset, range.size())); + return InternalValue(std::move(rawValue)); + } + if (type == Token::IntegerNum || type == Token::FloatNum) return traits_t::RangeToNum(*m_template, range, type); return InternalValue(); } diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index f25eb753..b19f954b 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -456,3 +456,14 @@ from Parser!)"; from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } + +TEST(BasicTests, LiteralWithEscapeCharacters) +{ + Template tpl; + ASSERT_TRUE(tpl.Load(R"({{ 'Hello\t\nWorld\n\twith\nescape\tcharacters!' }})")); + + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + const std::string expectedResult = "Hello\t\nWorld\n\twith\nescape\tcharacters!"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} \ No newline at end of file diff --git a/test/helpers_tests.cpp b/test/helpers_tests.cpp new file mode 100644 index 00000000..49f78384 --- /dev/null +++ b/test/helpers_tests.cpp @@ -0,0 +1,22 @@ +#include "gtest/gtest.h" + +#include "../src/helpers.h" + +using namespace jinja2; + +TEST(Helpers, CompileEscapes) +{ + EXPECT_STREQ("\n", CompileEscapes(std::string{"\\n"}).c_str()); + EXPECT_STREQ("\t", CompileEscapes(std::string{"\\t"}).c_str()); + EXPECT_STREQ("\r", CompileEscapes(std::string{"\\r"}).c_str()); + EXPECT_STREQ("\r\n\t", CompileEscapes(std::string{R"(\r\n\t)"}).c_str()); + EXPECT_STREQ( + "aa\rbb\ncc\tdd", + CompileEscapes(std::string{R"(aa\rbb\ncc\tdd)"}).c_str()); + EXPECT_STREQ("", CompileEscapes(std::string{""}).c_str()); + EXPECT_STREQ( + "aa bb cc dd", + CompileEscapes(std::string{"aa bb cc dd"}).c_str()); +} + + From 02faa07aa49729a98d40ac400fd231961ad924c1 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 26 Jun 2019 01:55:40 +0300 Subject: [PATCH 092/206] Arbitrary range types implementation. Resolves #66 (#118) * Initial refactoring commit * Refactoring is in progress * Implement support for generated ranges usage as GenericList * Fix ref to value-ptr-lite * Implement generic list adapters * Move value-ptr-lite to the jinja2cpp include directory * Fix gcc warnings * Fix issues and code cleanup * Remove value-ptr-lite submodule * Fix build errors * Fix C++17 build/run issues * Fix travis run configs * Move forward optional-lite and fix travis build * Implement generic list iterator --- .gitmodules | 3 - .travis.yml | 18 +- CMakeLists.txt | 48 +- .../jinja2cpp-config-deps-external.cmake.in | 9 +- include/jinja2cpp/generic_list.h | 104 ++ include/jinja2cpp/generic_list_impl.h | 365 +++++ include/jinja2cpp/generic_list_iterator.h | 110 ++ include/jinja2cpp/reflected_value.h | 107 +- include/jinja2cpp/value.h | 46 +- include/jinja2cpp/value_ptr.hpp | 1306 +++++++++++++++++ src/expression_evaluator.cpp | 49 +- src/filters.cpp | 149 +- src/generic_adapters.h | 162 +- src/internal_value.cpp | 254 +++- src/internal_value.h | 99 +- src/statements.cpp | 118 +- src/statements.h | 2 + src/value_visitors.h | 4 +- test/forloop_test.cpp | 115 +- test/includes_test.cpp | 12 +- test/user_callable_test.cpp | 19 +- thirdparty/CMakeLists.txt | 2 +- thirdparty/internal_deps.cmake | 5 - thirdparty/nonstd/optional-lite | 2 +- thirdparty/nonstd/value-ptr-lite | 1 - thirdparty/thirdparty-conan-build.cmake | 3 +- thirdparty/thirdparty-external.cmake | 3 +- 27 files changed, 2802 insertions(+), 313 deletions(-) create mode 100644 include/jinja2cpp/generic_list.h create mode 100644 include/jinja2cpp/generic_list_impl.h create mode 100644 include/jinja2cpp/generic_list_iterator.h create mode 100644 include/jinja2cpp/value_ptr.hpp delete mode 160000 thirdparty/nonstd/value-ptr-lite diff --git a/.gitmodules b/.gitmodules index f097ab51..9b953978 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "thirdparty/gtest"] path = thirdparty/gtest url = https://github.com/google/googletest.git -[submodule "thirdparty/nonstd/value-ptr-lite"] - path = thirdparty/nonstd/value-ptr-lite - url = https://github.com/martinmoene/value-ptr-lite.git [submodule "thirdparty/boost"] path = thirdparty/boost url = https://github.com/Manu343726/boost-cmake.git diff --git a/.travis.yml b/.travis.yml index 0b87370e..eec5124d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ matrix: compiler: gcc env: COMPILER=g++-7 - CMAKE_CXX_FLAGS=-std=c++17 + EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -56,7 +56,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] - packages: ['cmake', 'clang-5.0', 'g++-6'] + packages: ['cmake', 'clang-5.0', 'g++-7'] - os: linux compiler: clang @@ -64,7 +64,17 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['cmake', 'clang-6.0', 'g++-6'] + packages: ['cmake', 'clang-6.0', 'g++-7'] + + - os: linux + compiler: clang + env: + COMPILER=clang++-6.0 + EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] + packages: ['cmake', 'clang-6.0', 'g++-7'] before_install: - date -u @@ -82,7 +92,7 @@ script: - $CXX --version - mkdir -p build && cd build - - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 + - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - ctest -C $BUILD_CONFIG -V diff --git a/CMakeLists.txt b/CMakeLists.txt index fc4a6684..7a05d488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,28 @@ cmake_minimum_required(VERSION 3.0.1) -project(Jinja2Cpp VERSION 0.9.2) +project(Jinja2Cpp VERSION 0.9.3) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) endif () +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + set(JINJA2CPP_IS_MAIN_PROJECT TRUE) +else() + set(JINJA2CPP_IS_MAIN_PROJECT FALSE) +endif() + set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") if (NOT JINJA2CPP_DEPS_MODE) set (JINJA2CPP_DEPS_MODE "internal") endif () +if (JINJA2CPP_IS_MAIN_PROJECT) + set (JINJA2CPP_CXX_STANDARD 14 CACHE STRING "Jinja2Cpp C++ standard to build with. C++14 is default") + if (NOT JINJA2CPP_CXX_STANDARD) + set (JINJA2CPP_CXX_STANDARD 14) + endif () +endif () + include(CMakePackageConfigHelpers) include(GNUInstallDirs) @@ -47,12 +60,6 @@ else () endif() -if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") - set(JINJA2CPP_IS_MAIN_PROJECT TRUE) -else() - set(JINJA2CPP_IS_MAIN_PROJECT FALSE) -endif() - option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROJECT}) option(JINJA2CPP_STRICT_WARNINGS "Enable additional warnings and treat them as errors" ON) option(JINJA2CPP_BUILD_SHARED "Build shared linkage version of Jinja2Cpp" OFF) @@ -102,14 +109,19 @@ endif () target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) set_target_properties(${LIB_TARGET_NAME} PROPERTIES - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED ON VERSION ${PROJECT_VERSION} SOVERSION 1 ) -set_property(TARGET ${LIB_TARGET_NAME} PROPERTY PUBLIC_HEADER ${PublicHeaders} ${JINJA2CPP_EXTRA_PUBLIC_HEADERS}) +if (JINJA2CPP_IS_MAIN_PROJECT) + set_target_properties(${LIB_TARGET_NAME} PROPERTIES + CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} + CXX_STANDARD_REQUIRED ON + ) +endif () +set_property(TARGET ${LIB_TARGET_NAME} PROPERTY PUBLIC_HEADER ${PublicHeaders} ${JINJA2CPP_EXTRA_PUBLIC_HEADERS}) + configure_file(jinja2cpp.pc.in jinja2cpp.pc @ONLY) if (JINJA2CPP_BUILD_TESTS) @@ -122,16 +134,13 @@ if (JINJA2CPP_BUILD_TESTS) target_link_libraries(jinja2cpp_tests gcov) endif () - get_target_property(TEST_CXX_STD jinja2cpp_tests CXX_STANDARD) + set_target_properties(jinja2cpp_tests PROPERTIES + CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} + CXX_STANDARD_REQUIRED ON) - string (FIND "${CURRENT_CXX_FLAGS}" "-std" TEST_FLAGS_STD_POS) - string (FIND "${TEST_CXX_STD}" "NOTFOUND" TEST_CXX_STD_NOTFOUND_POS) - - if (NOT MSVC AND TEST_FLAGS_STD_POS EQUAL -1 AND NOT (TEST_CXX_STD_NOTFOUND_POS EQUAL -1)) - set_target_properties(jinja2cpp_tests PROPERTIES - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED ON) - endif () + if (MSVC) + target_compile_options(jinja2cpp_tests PRIVATE /bigobj) + endif () add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl @@ -165,7 +174,6 @@ endmacro () Jinja2CppGetTargetIncludeDir(EXPECTED-LITE expected-lite) Jinja2CppGetTargetIncludeDir(VARIANT-LITE variant-lite) Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) -Jinja2CppGetTargetIncludeDir(VALUE-PTR-LITE value-ptr-lite) # Workaround for #14444 bug of CMake (https://gitlab.kitware.com/cmake/cmake/issues/14444) # We can't use EXPORT feature of 'install' as is due to limitation of subproject's targets installation diff --git a/cmake/public/jinja2cpp-config-deps-external.cmake.in b/cmake/public/jinja2cpp-config-deps-external.cmake.in index 1d64e430..b6c670eb 100644 --- a/cmake/public/jinja2cpp-config-deps-external.cmake.in +++ b/cmake/public/jinja2cpp-config-deps-external.cmake.in @@ -19,14 +19,7 @@ set_target_properties(optional-lite PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_OPTIONAL-LITE_INCLUDE_DIRECTORIES@" ) -# Create imported target value-ptr-lite -add_library(value-ptr-lite INTERFACE IMPORTED) - -set_target_properties(value-ptr-lite PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_VALUE-PTR-LITE_INCLUDE_DIRECTORIES@" -) - -set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite value-ptr-lite optional-lite) +set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite optional-lite) macro (Jinja2CppAddBoostDep name) if (TARGET Boost::${name}) diff --git a/include/jinja2cpp/generic_list.h b/include/jinja2cpp/generic_list.h new file mode 100644 index 00000000..1707b345 --- /dev/null +++ b/include/jinja2cpp/generic_list.h @@ -0,0 +1,104 @@ +#ifndef JINJA2_GENERIC_LIST_H +#define JINJA2_GENERIC_LIST_H + +#include + +#include +#include +#include + +namespace jinja2 +{ +class Value; + +struct IndexBasedAccessor +{ + virtual Value GetItemByIndex(int64_t idx) const = 0; + + virtual size_t GetItemsCount() const = 0; +}; + +struct ListEnumerator; +using ListEnumeratorPtr = std::unique_ptr; + +inline auto MakeEmptyListEnumeratorPtr() +{ + return ListEnumeratorPtr(nullptr, [](ListEnumerator*) {}); +} + +struct ListEnumerator +{ + virtual ~ListEnumerator() {} + + virtual void Reset() = 0; + + virtual bool MoveNext() = 0; + + virtual Value GetCurrent() const = 0; + + virtual ListEnumeratorPtr Clone() const = 0; + + virtual ListEnumeratorPtr Move() = 0; +}; + +struct ListItemAccessor +{ + virtual ~ListItemAccessor() {} + + virtual const IndexBasedAccessor* GetIndexer() const = 0; + + virtual ListEnumeratorPtr CreateEnumerator() const = 0; + + virtual nonstd::optional GetSize() const = 0; + + template + static ListEnumeratorPtr MakeEnumerator(Args&& ... args); +}; + +class GenericListIterator; + +class GenericList +{ +public: + GenericList() = default; + + GenericList(std::function accessor) + : m_accessor(std::move(accessor)) + { + } + + nonstd::optional GetSize() const + { + return m_accessor ? m_accessor()->GetSize() : nonstd::optional(); + } + + auto GetAccessor() const + { + return m_accessor ? m_accessor() : nullptr; + } + + bool IsValid() const + { + return !(!m_accessor); + } + + GenericListIterator begin() const; + + GenericListIterator end() const; + + auto cbegin() const; + + auto cend() const; + + std::function m_accessor; +}; + +template +inline ListEnumeratorPtr ListItemAccessor::MakeEnumerator(Args&& ...args) +{ + return ListEnumeratorPtr(new T(std::forward(args)...), [](ListEnumerator* e) { delete e; }); +} +} + + +#endif // JINJA2_GENERIC_LIST_H \ No newline at end of file diff --git a/include/jinja2cpp/generic_list_impl.h b/include/jinja2cpp/generic_list_impl.h new file mode 100644 index 00000000..0800acc1 --- /dev/null +++ b/include/jinja2cpp/generic_list_impl.h @@ -0,0 +1,365 @@ +#ifndef JINJA2CPP_GENERIC_LIST_IMPL_H +#define JINJA2CPP_GENERIC_LIST_IMPL_H + +#include "generic_list.h" +#include "value.h" + +#include + +namespace jinja2 +{ +namespace lists_impl +{ +template +struct InputIteratorListAccessor : ListItemAccessor +{ + mutable It1 m_begin; + mutable It2 m_end; + + struct Enumerator : public ListEnumerator + { + It1* m_cur; + It2* m_end; + bool m_justInited = true; + + Enumerator(It1* begin, It2* end) + : m_cur(begin) + , m_end(end) + {} + + void Reset() override + { + } + + bool MoveNext() override + { + if (m_justInited) + m_justInited = false; + else + ++ *m_cur; + + return (*m_cur) != (*m_end); + } + + Value GetCurrent() const override + { + return Reflect(**m_cur); + } + + ListEnumeratorPtr Clone() const override + { + auto result = MakeEnumerator(m_cur, m_end); + auto ptr = static_cast(result.get()); + ptr->m_cur = m_cur; + ptr->m_justInited = m_justInited; + return result; + } + + ListEnumeratorPtr Move() override + { + return MakeEnumerator(std::move(*this)); + } + }; + + explicit InputIteratorListAccessor(It1&& b, It2&& e) noexcept + : m_begin(std::move(b)) + , m_end(std::move(e)) + { + } + + nonstd::optional GetSize() const override + { + return nonstd::optional(); + } + + const IndexBasedAccessor* GetIndexer() const override + { + return nullptr; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + return MakeEnumerator(&m_begin, &m_end ); + } + +}; + +template +struct ForwardIteratorListAccessor : ListItemAccessor +{ + It1 m_begin; + It2 m_end; + + struct Enumerator : public ListEnumerator + { + It1 m_begin; + It1 m_cur; + It2 m_end; + bool m_justInited = true; + + Enumerator(It1 begin, It2 end) + : m_begin(begin) + , m_cur(end) + , m_end(end) + {} + + void Reset() override + { + m_justInited = true; + } + + bool MoveNext() override + { + if (m_justInited) + { + m_cur = m_begin; + m_justInited = false; + } + else + ++ m_cur; + + return m_cur != m_end; + } + + Value GetCurrent() const override + { + return Reflect(*m_cur); + } + + ListEnumeratorPtr Clone() const override + { + auto result = MakeEnumerator(m_cur, m_end); + auto ptr = static_cast(result.get()); + ptr->m_begin = m_cur; + ptr->m_cur = m_cur; + ptr->m_justInited = m_justInited; + return result; + } + + ListEnumeratorPtr Move() override + { + return MakeEnumerator(std::move(*this)); + } + }; + + explicit ForwardIteratorListAccessor(It1&& b, It2&& e) noexcept + : m_begin(std::move(b)) + , m_end(std::move(e)) + { + } + + nonstd::optional GetSize() const override + { + return nonstd::optional(); + } + + const IndexBasedAccessor* GetIndexer() const override + { + return nullptr; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + return MakeEnumerator(m_begin, m_end); + } + +}; + +template +struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor +{ + It1 m_begin; + It2 m_end; + + struct Enumerator : public ListEnumerator + { + It1 m_begin; + It1 m_cur; + It2 m_end; + bool m_justInited = true; + + Enumerator(It1 begin, It2 end) + : m_begin(begin) + , m_cur(end) + , m_end(end) + {} + + void Reset() override + { + m_justInited = true; + } + + bool MoveNext() override + { + if (m_justInited) + { + m_cur = m_begin; + m_justInited = false; + } + else + ++ m_cur; + + return m_cur != m_end; + } + + Value GetCurrent() const override + { + return Reflect(*m_cur); + } + + ListEnumeratorPtr Clone() const override + { + auto result = MakeEnumerator(m_cur, m_end); + auto ptr = static_cast(result.get()); + ptr->m_begin = m_cur; + ptr->m_cur = m_cur; + ptr->m_justInited = m_justInited; + return result; + } + + ListEnumeratorPtr Move() override + { + return MakeEnumerator(std::move(*this)); + } + }; + + explicit RandomIteratorListAccessor(It1 b, It2 e) noexcept + : m_begin(std::move(b)) + , m_end(std::move(e)) + { + } + + nonstd::optional GetSize() const override + { + return std::distance(m_begin, m_end); + } + + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + return MakeEnumerator(m_begin, m_end); + } + + + Value GetItemByIndex(int64_t idx) const override + { + auto p = m_begin; + std::advance(p, static_cast(idx)); + return Reflect(*p); + } + + size_t GetItemsCount() const override + { + return GetSize().value(); + } +}; + +using ListGenerator = std::function()>; + +class GeneratedListAccessor : public ListItemAccessor +{ +public: + class Enumerator : public ListEnumerator + { + public: + Enumerator(const ListGenerator* fn) + : m_fn(fn) + { } + + void Reset() override + { + } + + bool MoveNext() override + { + if (m_isFinished) + return false; + + auto res = (*m_fn)(); + if (!res) + return false; + + m_current = std::move(*res); + + return true; + } + + Value GetCurrent() const override { return m_current; } + + ListEnumeratorPtr Clone() const override + { + return MakeEnumerator(*this); + } + + ListEnumeratorPtr Move() override + { + return MakeEnumerator(std::move(*this)); + } + + protected: + const ListGenerator* m_fn; + Value m_current; + bool m_isFinished = false; + }; + + explicit GeneratedListAccessor(ListGenerator&& fn) : m_fn(std::move(fn)) {} + + nonstd::optional GetSize() const override + { + return nonstd::optional(); + } + const IndexBasedAccessor* GetIndexer() const override + { + return nullptr; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + return MakeEnumerator(&m_fn); + } +private: + ListGenerator m_fn; +}; + +template +auto MakeGenericList(It1&& it1, It2&& it2, std::input_iterator_tag) +{ + return GenericList([accessor = InputIteratorListAccessor(std::forward(it1), std::forward(it2))]() {return &accessor;}); +} + +template +auto MakeGenericList(It1&& it1, It2&& it2, std::random_access_iterator_tag) +{ + return GenericList([accessor = RandomIteratorListAccessor(std::forward(it1), std::forward(it2))]() {return &accessor;}); +} + +template +auto MakeGenericList(It1&& it1, It2&& it2, Category) +{ + return GenericList([accessor = ForwardIteratorListAccessor(std::forward(it1), std::forward(it2))]() {return &accessor;}); +} + +auto MakeGeneratedList(ListGenerator&& fn) +{ + return GenericList([accessor = GeneratedListAccessor(std::move(fn))]() {return &accessor;}); +} +} + +template +auto MakeGenericList(It1&& it1, It2&& it2) +{ + return lists_impl::MakeGenericList(std::forward(it1), std::forward(it2), typename std::iterator_traits::iterator_category()); +} + +auto MakeGenericList(lists_impl::ListGenerator fn) +{ + return lists_impl::MakeGeneratedList(std::move(fn)); +} + +} + +#endif //JINJA2CPP_GENERIC_LIST_IMPL_H diff --git a/include/jinja2cpp/generic_list_iterator.h b/include/jinja2cpp/generic_list_iterator.h new file mode 100644 index 00000000..bfd26de6 --- /dev/null +++ b/include/jinja2cpp/generic_list_iterator.h @@ -0,0 +1,110 @@ +#ifndef JINJA2_GENERIC_LIST_ITERATOR_H +#define JINJA2_GENERIC_LIST_ITERATOR_H + +#include "generic_list.h" +#include "value.h" +#include "value_ptr.hpp" + +namespace jinja2 +{ + +class GenericListIterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = const Value; + using difference_type = std::ptrdiff_t; + using reference = const Value&; + using pointer = const Value*; + + struct Cloner + { + ListEnumerator* operator()(const ListEnumerator &x) const + { + return x.Clone().release(); + } + + ListEnumerator* operator()(ListEnumerator &&x) const + { + return x.Move().release(); + } + }; + + using EnumeratorPtr = nonstd::value_ptr; + + GenericListIterator(ListEnumerator* e = nullptr) + : m_enumerator(e) + { + if (m_enumerator) + m_hasValue = m_enumerator->MoveNext(); + + if (m_hasValue) + m_current = std::move(m_enumerator->GetCurrent()); + } + + bool operator == (const GenericListIterator& other) const + { + if (!this->m_enumerator) + return !other.m_enumerator ? true : other == *this; + + if (!other.m_enumerator) + return !m_hasValue; + + return this->m_enumerator.get() == other.m_enumerator.get(); + } + + bool operator != (const GenericListIterator& other) const + { + return !(*this == other); + } + + reference operator *() const + { + return m_current; + } + + GenericListIterator& operator ++() + { + m_hasValue = m_enumerator->MoveNext(); + if (m_hasValue) + m_current = std::move(m_enumerator->GetCurrent()); + + return *this; + } + + GenericListIterator operator++(int) + { + GenericListIterator result(std::move(m_current)); + + this->operator++(); + return result; + } +private: + explicit GenericListIterator(Value&& val) + : m_hasValue(true) + , m_current(std::move(val)) + { + + } + +private: + const EnumeratorPtr m_enumerator; + bool m_hasValue = false; + Value m_current; +}; + +inline GenericListIterator GenericList::begin() const +{ + return m_accessor && m_accessor() ? GenericListIterator(m_accessor()->CreateEnumerator().release()) : GenericListIterator(); +} + +inline GenericListIterator GenericList::end() const +{ + return GenericListIterator(); +} + +inline auto GenericList::cbegin() const {return begin();} +inline auto GenericList::cend() const {return end();} +} + +#endif // JINJA2_GENERIC_LIST_ITERATOR_H \ No newline at end of file diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index c9739a10..426e9c49 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -105,8 +105,68 @@ using IsReflectedType = std::enable_if_t::value>; struct ContainerReflector { + template + struct Enumerator : public ListEnumerator + { + It m_begin; + It m_cur; + It m_end; + bool m_justInited = true; + + Enumerator(It begin, It end) + : m_begin(begin) + , m_cur(end) + , m_end(end) + {} + + void Reset() override + { + m_justInited = true; + } + + bool MoveNext() override + { + if (m_justInited) + { + m_cur = m_begin; + m_justInited = false; + } + else + ++ m_cur; + + return m_cur != m_end; + } + + Value GetCurrent() const override + { + return Reflect(*m_cur); + } + + ListEnumeratorPtr Clone() const override + { + auto result = std::make_unique>(m_begin, m_end); + result->m_cur = m_cur; + result->m_justInited = m_justInited; + return jinja2::ListEnumeratorPtr(result.release(), Deleter); + } + + ListEnumeratorPtr Move() override + { + auto result = std::make_unique>(m_begin, m_end); + result->m_cur = std::move(m_cur); + result->m_justInited = m_justInited; + this->m_justInited = true; + return jinja2::ListEnumeratorPtr(result.release(), Deleter); + } + + static void Deleter(ListEnumerator* e) + { + delete static_cast*>(e); + } + }; + template - struct ValueItemAccessor : ListItemAccessor + struct ValueItemAccessor : ListItemAccessor, IndexBasedAccessor { T m_value; @@ -115,20 +175,37 @@ struct ContainerReflector { } - size_t GetSize() const override + nonstd::optional GetSize() const override { return m_value.size(); } - Value GetValueByIndex(int64_t idx) const override + + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + using Enum = Enumerator; + return jinja2::ListEnumeratorPtr(new Enum(m_value.begin(), m_value.end()), Enum::Deleter); + } + + Value GetItemByIndex(int64_t idx) const override { auto p = m_value.begin(); std::advance(p, static_cast(idx)); return Reflect(*p); } + + size_t GetItemsCount() const override + { + return m_value.size(); + } }; template - struct PtrItemAccessor : ListItemAccessor + struct PtrItemAccessor : ListItemAccessor, IndexBasedAccessor { const T* m_value; @@ -136,16 +213,32 @@ struct ContainerReflector : m_value(ptr) { } - size_t GetSize() const override + nonstd::optional GetSize() const override { return m_value->size(); } - Value GetValueByIndex(int64_t idx) const override + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + using Enum = Enumerator; + return jinja2::ListEnumeratorPtr(new Enum(m_value->begin(), m_value->end()), Enum::Deleter); + } + + Value GetItemByIndex(int64_t idx) const override { auto p = m_value->begin(); - std::advance(p, idx); + std::advance(p, static_cast(idx)); return Reflect(*p); } + + size_t GetItemsCount() const override + { + return m_value->size(); + } }; template diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 57b1faeb..a335ca9f 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -1,7 +1,7 @@ #ifndef JINJA2_VALUE_H #define JINJA2_VALUE_H -#pragma once +#include "generic_list.h" #include #include @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace jinja2 { @@ -21,14 +21,6 @@ struct EmptyValue }; class Value; -struct ListItemAccessor -{ - virtual ~ListItemAccessor() {} - - virtual size_t GetSize() const = 0; - virtual Value GetValueByIndex(int64_t idx) const = 0; -}; - struct MapItemAccessor { virtual ~MapItemAccessor() {} @@ -70,35 +62,6 @@ class GenericMap std::function m_accessor; }; -class GenericList -{ -public: - GenericList() = default; - GenericList(std::function accessor) - : m_accessor(std::move(accessor)) - { - } - - size_t GetSize() const - { - return m_accessor ? m_accessor()->GetSize() : 0ULL; - } - - Value GetValueByIndex(int64_t idx) const; - - auto GetAccessor() const - { - return m_accessor(); - } - - bool IsValid() const - { - return !(!m_accessor); - } - - std::function m_accessor; -}; - using ValuesList = std::vector; using ValuesMap = std::unordered_map; struct UserCallableArgs; @@ -265,11 +228,6 @@ inline Value GenericMap::GetValueByName(const std::string& name) const return m_accessor ? m_accessor()->GetValueByName(name) : Value(); } -inline Value GenericList::GetValueByIndex(int64_t index) const -{ - return m_accessor ? m_accessor()->GetValueByIndex(index) : Value(); -} - inline Value::Value() = default; inline Value::Value(const Value& val) = default; inline Value::Value(Value&& val) noexcept diff --git a/include/jinja2cpp/value_ptr.hpp b/include/jinja2cpp/value_ptr.hpp new file mode 100644 index 00000000..fd889f46 --- /dev/null +++ b/include/jinja2cpp/value_ptr.hpp @@ -0,0 +1,1306 @@ +// +// Copyright 2017-2018 by Martin Moene +// +// https://github.com/martinmoene/value-ptr-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#ifndef NONSTD_VALUE_PTR_LITE_HPP +#define NONSTD_VALUE_PTR_LITE_HPP + +#define value_ptr_lite_MAJOR 0 +#define value_ptr_lite_MINOR 2 +#define value_ptr_lite_PATCH 1 + +#define value_ptr_lite_VERSION nsvp_STRINGIFY(value_ptr_lite_MAJOR) "." nsvp_STRINGIFY(value_ptr_lite_MINOR) "." nsvp_STRINGIFY(value_ptr_lite_PATCH) + +#define nsvp_STRINGIFY( x ) nsvp_STRINGIFY_( x ) +#define nsvp_STRINGIFY_( x ) #x + +// value-ptr-lite configuration: + +#ifndef nsvp_CONFIG_COMPARE_POINTERS +# define nsvp_CONFIG_COMPARE_POINTERS 0 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef nsvp_CONFIG_NO_EXCEPTIONS +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define nsvp_CONFIG_NO_EXCEPTIONS 0 +# else +# define nsvp_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef nsvp_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define nsvp_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define nsvp_CPLUSPLUS __cplusplus +# endif +#endif + +#define nsvp_CPP98_OR_GREATER ( nsvp_CPLUSPLUS >= 199711L ) +#define nsvp_CPP11_OR_GREATER ( nsvp_CPLUSPLUS >= 201103L ) +#define nsvp_CPP11_OR_GREATER_ ( nsvp_CPLUSPLUS >= 201103L ) +#define nsvp_CPP14_OR_GREATER ( nsvp_CPLUSPLUS >= 201402L ) +#define nsvp_CPP17_OR_GREATER ( nsvp_CPLUSPLUS >= 201703L ) +#define nsvp_CPP20_OR_GREATER ( nsvp_CPLUSPLUS >= 202000L ) + +// half-open range [lo..hi): +#define nsvp_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define nsvp_COMPILER_MSVC_VER (_MSC_VER ) +# define nsvp_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define nsvp_COMPILER_MSVC_VER 0 +# define nsvp_COMPILER_MSVC_VERSION 0 +#endif + +#define nsvp_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined(__clang__) +# define nsvp_COMPILER_CLANG_VERSION nsvp_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define nsvp_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define nsvp_COMPILER_GNUC_VERSION nsvp_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define nsvp_COMPILER_GNUC_VERSION 0 +#endif + +#if nsvp_BETWEEN( nsvp_COMPILER_MSVC_VER, 1300, 1900 ) +# pragma warning( push ) +# pragma warning( disable: 4345 ) // initialization behavior changed +#endif + +// Presence of language and library features: + +#define nsvp_HAVE( feature ) ( nsvp_HAVE_##feature ) + +#ifdef _HAS_CPP0X +# define nsvp_HAS_CPP0X _HAS_CPP0X +#else +# define nsvp_HAS_CPP0X 0 +#endif + +// Unless defined otherwise below, consider VC14 as C++11 for value_ptr-lite: + +#if nsvp_COMPILER_MSVC_VER >= 1900 +# undef nsvp_CPP11_OR_GREATER +# define nsvp_CPP11_OR_GREATER 1 +#endif + +#define nsvp_CPP11_90 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1500) +#define nsvp_CPP11_100 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1600) +#define nsvp_CPP11_110 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1700) +#define nsvp_CPP11_120 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1800) +#define nsvp_CPP11_140 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1900) +#define nsvp_CPP11_141 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1910) + +#define nsvp_CPP14_000 (nsvp_CPP14_OR_GREATER) +#define nsvp_CPP17_000 (nsvp_CPP17_OR_GREATER) + +// empty bases: + +#if nsvp_COMPILER_MSVC_VER >= 1900 +# define nsvp_DECLSPEC_EMPTY_BASES __declspec(empty_bases) +#else +# define nsvp_DECLSPEC_EMPTY_BASES +#endif + +// Presence of C++11 language features: + +#define nsvp_HAVE_CONSTEXPR_11 nsvp_CPP11_140 +#define nsvp_HAVE_INITIALIZER_LIST nsvp_CPP11_120 +#define nsvp_HAVE_IS_DEFAULT nsvp_CPP11_140 +#define nsvp_HAVE_NOEXCEPT nsvp_CPP11_140 +#define nsvp_HAVE_NULLPTR nsvp_CPP11_100 +#define nsvp_HAVE_REF_QUALIFIER nsvp_CPP11_140 + +// Presence of C++14 language features: + +#define nsvp_HAVE_CONSTEXPR_14 nsvp_CPP14_000 + +// Presence of C++17 language features: + +// no flag + +// Presence of C++ library features: + +#define nsvp_HAVE_TR1_TYPE_TRAITS (!! nsvp_COMPILER_GNUC_VERSION ) +#define nsvp_HAVE_TR1_ADD_POINTER (!! nsvp_COMPILER_GNUC_VERSION ) + +#define nsvp_HAVE_TYPE_TRAITS nsvp_CPP11_90 + +// C++ feature usage: + +#if nsvp_HAVE_CONSTEXPR_11 +# define nsvp_constexpr constexpr +#else +# define nsvp_constexpr /*constexpr*/ +#endif + +#if nsvp_HAVE_CONSTEXPR_14 +# define nsvp_constexpr14 constexpr +#else +# define nsvp_constexpr14 /*constexpr*/ +#endif + +#if nsvp_HAVE_NOEXCEPT +# define nsvp_noexcept noexcept +# define nsvp_noexcept_op noexcept +#else +# define nsvp_noexcept /*noexcept*/ +# define nsvp_noexcept_op(expr) /*noexcept(expr)*/ +#endif + +#if nsvp_HAVE_NULLPTR +# define nsvp_nullptr nullptr +#else +# define nsvp_nullptr NULL +#endif + +#if nsvp_HAVE_REF_QUALIFIER +# define nsvp_ref_qual & +# define nsvp_refref_qual && +#else +# define nsvp_ref_qual /*&*/ +# define nsvp_refref_qual /*&&*/ +#endif + +// additional includes: + +#if ! nsvp_CPP11_OR_GREATER +# include // std::swap() until C++11 +#endif + +#if nsvp_HAVE_INITIALIZER_LIST +# include +#endif + +#if nsvp_HAVE_TYPE_TRAITS +# include +#elif nsvp_HAVE_TR1_TYPE_TRAITS +# include +#endif + +// static assert: + +#if nsvp_CPP11_OR_GREATER +# define nsvp_static_assert( expr, msg ) \ + static_assert( expr, msg ) +#else +# define nsvp_static_assert( expr, msg ) \ + do { typedef int x[(expr) ? 1 : -1]; } while(0) +#endif + +// Method enabling + +#if nsvp_CPP11_OR_GREATER + +#define nsvp_REQUIRES_0(...) \ + template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > + +#define nsvp_REQUIRES_T(...) \ + , typename = typename std::enable_if< (__VA_ARGS__), nonstd::vptr::detail::enabler >::type + +#define nsvp_REQUIRES_R(R, ...) \ + typename std::enable_if< (__VA_ARGS__), R>::type + +#define nsvp_REQUIRES_A(...) \ + , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr + +#endif + +#include +#include +#include +#include + +#if ! nsvp_CONFIG_NO_EXCEPTIONS +# include +#endif + +// +// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: +// + +#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES +#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 + +// C++17 std::in_place in : + +#if nsvp_CPP17_OR_GREATER + +#include + +namespace nonstd { + +using std::in_place; +using std::in_place_type; +using std::in_place_index; +using std::in_place_t; +using std::in_place_type_t; +using std::in_place_index_t; + +#define nonstd_lite_in_place_t( T) std::in_place_t +#define nonstd_lite_in_place_type_t( T) std::in_place_type_t +#define nonstd_lite_in_place_index_t(K) std::in_place_index_t + +#define nonstd_lite_in_place( T) std::in_place_t{} +#define nonstd_lite_in_place_type( T) std::in_place_type_t{} +#define nonstd_lite_in_place_index(K) std::in_place_index_t{} + +} // namespace nonstd + +#else // nsvp_CPP17_OR_GREATER + +#include + +namespace nonstd { +namespace detail { + +template< class T > +struct in_place_type_tag {}; + +template< std::size_t K > +struct in_place_index_tag {}; + +} // namespace detail + +struct in_place_t {}; + +template< class T > +inline in_place_t in_place( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +template< class T > +inline in_place_t in_place_type( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place_index( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +// mimic templated typedef: + +#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) + +#define nonstd_lite_in_place( T) nonstd::in_place_type +#define nonstd_lite_in_place_type( T) nonstd::in_place_type +#define nonstd_lite_in_place_index(K) nonstd::in_place_index + +} // namespace nonstd + +#endif // nsvp_CPP17_OR_GREATER +#endif // nonstd_lite_HAVE_IN_PLACE_TYPES + +// +// value_ptr: +// + +namespace nonstd { namespace vptr { + +#if nsvp_CPP11_OR_GREATER + +namespace std20 { + +// type traits C++20: + +template< typename T > +struct remove_cvref +{ + typedef typename std::remove_cv< typename std::remove_reference::type >::type type; +}; + +} // namespace std20 + +#endif // nsvp_CPP11_OR_GREATER + +namespace detail { + +/*enum*/ class enabler{}; + +#if nsvp_CPP11_OR_GREATER +using std::default_delete; +#else +template< class T > +struct default_delete +{ + default_delete() nsvp_noexcept {}; + + void operator()( T * ptr ) const nsvp_noexcept + { + nsvp_static_assert( sizeof(T) > 0, "default_delete cannot delete incomplete type"); +#if nsvp_CPP11_OR_GREATER + nsvp_static_assert( ! std::is_void::value, "default_delete cannot delete incomplete type"); +#endif + delete ptr; + } +}; +#endif + +template< class T > +struct default_clone +{ +#if nsvp_CPP11_OR_GREATER + default_clone() = default; +#else + default_clone() {}; +#endif + + T * operator()( T const & x ) const + { + nsvp_static_assert( sizeof(T) > 0, "default_clone cannot clone incomplete type"); +#if nsvp_CPP11_OR_GREATER + nsvp_static_assert( ! std::is_void::value, "default_clone cannot clone incomplete type"); +#endif + return new T( x ); + } + +#if nsvp_CPP11_OR_GREATER + T * operator()( T && x ) const + { + return new T( std::move( x ) ); + } + + template< class... Args > + T * operator()( nonstd_lite_in_place_t(T), Args&&... args ) const + { + return new T( std::forward(args)...); + } + + template< class U, class... Args > + T * operator()( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) const + { + return new T( il, std::forward(args)...); + } +#endif +}; + +template +struct nsvp_DECLSPEC_EMPTY_BASES compressed_ptr : Cloner, Deleter +{ + typedef T element_type; + typedef T * pointer; + + typedef Cloner cloner_type; + typedef Deleter deleter_type; + + // Lifetime: + + ~compressed_ptr() + { + deleter_type()( ptr ); + } + + compressed_ptr() nsvp_noexcept + : ptr( nsvp_nullptr ) + {} + + explicit compressed_ptr( pointer p ) nsvp_noexcept + : ptr( p ) + {} + + compressed_ptr( compressed_ptr const & other ) + : cloner_type ( other ) + , deleter_type( other ) + , ptr( other.ptr ? cloner_type()( *other.ptr ) : nsvp_nullptr ) + {} + +#if nsvp_CPP11_OR_GREATER + compressed_ptr( compressed_ptr && other ) nsvp_noexcept + : cloner_type ( std::move( other ) ) + , deleter_type( std::move( other ) ) + , ptr( std::move( other.ptr ) ) + { + other.ptr = nullptr; + } +#endif + + explicit compressed_ptr( element_type const & value ) + : ptr( cloner_type()( value ) ) + {} + +#if nsvp_CPP11_OR_GREATER + + explicit compressed_ptr( element_type && value ) nsvp_noexcept + : ptr( cloner_type()( std::move( value ) ) ) + {} + + template< class... Args > + explicit compressed_ptr( nonstd_lite_in_place_t(T), Args&&... args ) + : ptr( cloner_type()( nonstd_lite_in_place(T), std::forward(args)...) ) + {} + + template< class U, class... Args > + explicit compressed_ptr( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) + : ptr( cloner_type()( nonstd_lite_in_place(T), il, std::forward(args)...) ) + {} + +#endif + + compressed_ptr( element_type const & value, cloner_type const & cloner ) + : cloner_type ( cloner ) + , ptr( cloner_type()( value ) ) + {} + +#if nsvp_CPP11_OR_GREATER + compressed_ptr( element_type && value, cloner_type && cloner ) nsvp_noexcept + : cloner_type ( std::move( cloner ) ) + , ptr( cloner_type()( std::move( value ) ) ) + {} +#endif + + compressed_ptr( element_type const & value, cloner_type const & cloner, deleter_type const & deleter ) + : cloner_type ( cloner ) + , deleter_type( deleter ) + , ptr( cloner_type()( value ) ) + {} + +#if nsvp_CPP11_OR_GREATER + compressed_ptr( element_type && value, cloner_type && cloner, deleter_type && deleter ) nsvp_noexcept + : cloner_type ( std::move( cloner ) ) + , deleter_type( std::move( deleter ) ) + , ptr( cloner_type()( std::move( value ) ) ) + {} +#endif + + explicit compressed_ptr( cloner_type const & cloner ) + : cloner_type( cloner ) + , ptr( nsvp_nullptr ) + {} + +#if nsvp_CPP11_OR_GREATER + explicit compressed_ptr( cloner_type && cloner ) nsvp_noexcept + : cloner_type( std::move( cloner ) ) + , ptr( nsvp_nullptr ) + {} +#endif + + explicit compressed_ptr( deleter_type const & deleter ) + : deleter_type( deleter ) + , ptr( nsvp_nullptr ) + {} + +# if nsvp_CPP11_OR_GREATER + explicit compressed_ptr( deleter_type && deleter ) nsvp_noexcept + : deleter_type( std::move( deleter ) ) + , ptr( nsvp_nullptr ) + {} +#endif + + compressed_ptr( cloner_type const & cloner, deleter_type const & deleter ) + : cloner_type ( cloner ) + , deleter_type( deleter ) + , ptr( nsvp_nullptr ) + {} + +#if nsvp_CPP11_OR_GREATER + compressed_ptr( cloner_type && cloner, deleter_type && deleter ) nsvp_noexcept + : cloner_type ( std::move( cloner ) ) + , deleter_type( std::move( deleter ) ) + , ptr( nsvp_nullptr ) + {} +#endif + + // Observers: + + pointer get() const nsvp_noexcept + { + return ptr; + } + + cloner_type & get_cloner() nsvp_noexcept + { + return *this; + } + + deleter_type & get_deleter() nsvp_noexcept + { + return *this; + } + + // Modifiers: + + pointer release() nsvp_noexcept + { + using std::swap; + pointer result = nsvp_nullptr; + swap( result, ptr ); + return result; + } + + void reset( pointer p ) nsvp_noexcept + { + get_deleter()( ptr ); + ptr = p; + } + + void reset( element_type const & v ) + { + reset( get_cloner()( v ) ); + } + +#if nsvp_CPP11_OR_GREATER + void reset( element_type && v ) + { + reset( get_cloner()( std::move( v ) ) ); + } +#endif + + void swap( compressed_ptr & other ) nsvp_noexcept + { + using std::swap; + swap( ptr, other.ptr ); + } + + pointer ptr; +}; + +} // namespace detail + +#if ! nsvp_CONFIG_NO_EXCEPTIONS + +// value_ptr access error + +class bad_value_access : public std::logic_error +{ +public: + explicit bad_value_access() + : logic_error( "bad value_ptr access" ) {} +}; + +#endif + +// class value_ptr: + +template +< + class T + , class Cloner = detail::default_clone + , class Deleter = detail::default_delete +> +class value_ptr +{ +public: + typedef T element_type; + typedef T * pointer; + typedef T & reference; + typedef T const * const_pointer; + typedef T const & const_reference; + + typedef Cloner cloner_type; + typedef Deleter deleter_type; + + // Lifetime + +#if nsvp_HAVE_IS_DEFAULT + ~value_ptr() = default; +#endif + + value_ptr() nsvp_noexcept + : ptr( cloner_type(), deleter_type() ) + {} + +#if nsvp_HAVE_NULLPTR + explicit value_ptr( std::nullptr_t ) nsvp_noexcept + : ptr( cloner_type(), deleter_type() ) + {} +#endif + + explicit value_ptr( pointer p ) nsvp_noexcept + : ptr( p ) + {} + + value_ptr( value_ptr const & other ) + : ptr( other.ptr ) + {} + +#if nsvp_CPP11_OR_GREATER + value_ptr( value_ptr && other ) nsvp_noexcept + : ptr( std::move( other.ptr ) ) + {} +#endif + + explicit value_ptr( element_type const & value ) + : ptr( value ) + {} + +#if nsvp_CPP11_OR_GREATER + + explicit value_ptr( element_type && value ) nsvp_noexcept + : ptr( std::move( value ) ) + {} + + template< class... Args + nsvp_REQUIRES_T( + std::is_constructible::value ) + > + explicit value_ptr( nonstd_lite_in_place_t(T), Args&&... args ) + : ptr( nonstd_lite_in_place(T), std::forward(args)...) + {} + + template< class U, class... Args + nsvp_REQUIRES_T( + std::is_constructible&, Args&&...>::value ) + > + explicit value_ptr( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) + : ptr( nonstd_lite_in_place(T), il, std::forward(args)...) + {} + +#endif // nsvp_CPP11_OR_GREATER + + explicit value_ptr( cloner_type const & cloner ) + : ptr( cloner ) + {} + +#if nsvp_CPP11_OR_GREATER + explicit value_ptr( cloner_type && cloner ) nsvp_noexcept + : ptr( std::move( cloner ) ) + {} +#endif + + explicit value_ptr( deleter_type const & deleter ) + : ptr( deleter ) + {} + +#if nsvp_CPP11_OR_GREATER + explicit value_ptr( deleter_type && deleter ) nsvp_noexcept + : ptr( std::move( deleter ) ) + {} +#endif + +#if nsvp_CPP11_OR_GREATER + template< class V, class ClonerOrDeleter + nsvp_REQUIRES_T( + !std::is_same::type, nonstd_lite_in_place_t(V)>::value ) + > + value_ptr( V && value, ClonerOrDeleter && cloner_or_deleter ) + : ptr( std::forward( value ), std::forward( cloner_or_deleter ) ) + {} +#else + template< class V, class ClonerOrDeleter > + value_ptr( V const & value, ClonerOrDeleter const & cloner_or_deleter ) + : ptr( value, cloner_or_deleter ) + {} +#endif + +#if nsvp_CPP11_OR_GREATER + template< class V, class C, class D + nsvp_REQUIRES_T( + !std::is_same::type, nonstd_lite_in_place_t(V)>::value ) + > + value_ptr( V && value, C && cloner, D && deleter ) + : ptr( std::forward( value ), std::forward( cloner ), std::forward( deleter ) ) + {} +#else + template< class V, class C, class D > + value_ptr( V const & value, C const & cloner, D const & deleter ) + : ptr( value, cloner, deleter ) + {} +#endif + +#if nsvp_HAVE_NULLPTR + value_ptr & operator=( std::nullptr_t ) nsvp_noexcept + { + ptr.reset( nullptr ); + return *this; + } +#endif + + value_ptr & operator=( T const & value ) + { + ptr.reset( value ); + return *this; + } + +#if nsvp_CPP11_OR_GREATER + template< class U + nsvp_REQUIRES_T( + std::is_same< typename std::decay::type, T>::value ) + > + value_ptr & operator=( U && value ) + { + ptr.reset( std::forward( value ) ); + return *this; + } +#endif + + value_ptr & operator=( value_ptr const & rhs ) + { + if ( this == &rhs ) + return *this; + + if ( rhs ) ptr.reset( *rhs ); +#if nsvp_HAVE_NULLPTR + else ptr.reset( nullptr ); +#else + else ptr.reset( pointer(0) ); +#endif + return *this; + } + +#if nsvp_CPP11_OR_GREATER + + value_ptr & operator=( value_ptr && rhs ) nsvp_noexcept + { + if ( this == &rhs ) + return *this; + + swap( rhs ); + + return *this; + } + + template< class... Args > + void emplace( Args&&... args ) + { + ptr.reset( T( std::forward(args)...) ); + } + + template< class U, class... Args > + void emplace( std::initializer_list il, Args&&... args ) + { + ptr.reset( T( il, std::forward(args)...) ); + } + +#endif // nsvp_CPP11_OR_GREATER + + // Observers: + + pointer get() const nsvp_noexcept + { + return ptr.get(); + } + + cloner_type & get_cloner() nsvp_noexcept + { + return ptr.get_cloner(); + } + + deleter_type & get_deleter() nsvp_noexcept + { + return ptr.get_deleter(); + } + + reference operator*() const + { + assert( get() != nsvp_nullptr ); return *get(); + } + + pointer operator->() const nsvp_noexcept + { + assert( get() != nsvp_nullptr ); return get(); + } + +#if nsvp_CPP11_OR_GREATER + explicit operator bool() const nsvp_noexcept + { + return has_value(); + } +#else +private: + typedef void (value_ptr::*safe_bool)() const; + void this_type_does_not_support_comparisons() const {} + +public: + operator safe_bool() const nsvp_noexcept + { + return has_value() ? &value_ptr::this_type_does_not_support_comparisons : 0; + } +#endif + + bool has_value() const nsvp_noexcept + { + return !! get(); + } + + element_type const & value() const + { +#if nsvp_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_value_access(); + } +#endif + return *get(); + } + + element_type & value() + { +#if nsvp_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_value_access(); + } +#endif + return *get(); + } + +#if nsvp_CPP11_OR_GREATER + + +#else + + template< class U > + element_type value_or( U const & v ) const + { + return has_value() ? value() : static_cast( v ); + } + +#endif // nsvp_CPP11_OR_GREATER + + // Modifiers: + + pointer release() nsvp_noexcept + { + return ptr.release(); + } + + void reset( pointer p = pointer() ) nsvp_noexcept + { + ptr.reset( p ); + } + + void swap( value_ptr & other ) nsvp_noexcept + { + ptr.swap( other.ptr ); + } + +private: + detail::compressed_ptr ptr; +}; + +// Non-member functions: + +#if nsvp_CPP11_OR_GREATER + +template< class T > +inline value_ptr< typename std::decay::type > make_value( T && v ) +{ + return value_ptr< typename std::decay::type >( std::forward( v ) ); +} + +template< class T, class... Args > +inline value_ptr make_value( Args&&... args ) +{ + return value_ptr( in_place, std::forward(args)...); +} + +template< class T, class U, class... Args > +inline value_ptr make_value( std::initializer_list il, Args&&... args ) +{ + return value_ptr( in_place, il, std::forward(args)...); +} + +#else + +template< typename T > +inline value_ptr make_value( T const & value ) +{ + return value_ptr( value ); +} + +#endif // nsvp_CPP11_OR_GREATER + +// Comparison between value_ptr-s: + +#if nsvp_CONFIG_COMPARE_POINTERS + +// compare pointers: + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator==( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return lhs.get() == rhs.get(); +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator!=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return ! ( lhs == rhs ); +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator<( + value_ptr const & lhs, + value_ptr const & rhs ) +{ +#if nsvp_CPP11_OR_GREATER + using P1 = typename value_ptr::const_pointer; + using P2 = typename value_ptr::const_pointer; + using CT = typename std::common_type::type; + return std::less()( lhs.get(), rhs.get() ); +#else + return std::less()( lhs.get(), rhs.get() ); +#endif +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator<=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return !( rhs < lhs ); +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator>( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return rhs < lhs; +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator>=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return !( lhs < rhs ); +} + +// Comparison with std::nullptr_t: + +#if nsvp_HAVE_NULLPTR + +template< class T, class D, class C > +inline bool operator==( value_ptr const & lhs, std::nullptr_t ) nsvp_noexcept +{ + return ! lhs; +} + +template< class T, class D, class C > +inline bool operator==( std::nullptr_t, value_ptr const & rhs ) nsvp_noexcept +{ + return ! rhs; +} + +template< class T, class D, class C > +inline bool operator!=( value_ptr const & lhs, std::nullptr_t ) nsvp_noexcept +{ + return static_cast( lhs ); +} + +template< class T, class D, class C > +inline bool operator!=( std::nullptr_t, value_ptr const & rhs ) nsvp_noexcept +{ + return static_cast( rhs ); +} + +template< class T, class D, class C > +inline bool operator<( value_ptr const & lhs, std::nullptr_t ) +{ + typedef typename value_ptr::const_pointer P; + return std::less

()( lhs.get(), nullptr ); +} + +template< class T, class D, class C > +inline bool operator<( std::nullptr_t, value_ptr const & rhs ) +{ + typedef typename value_ptr::const_pointer P; + return std::less

()( nullptr, rhs.get() ); +} + +template< class T, class D, class C > +inline bool operator<=( value_ptr const & lhs, std::nullptr_t ) +{ + return !( nullptr < lhs ); +} + +template< class T, class D, class C > +inline bool operator<=( std::nullptr_t, value_ptr const & rhs ) +{ + return !( rhs < nullptr ); +} + +template< class T, class D, class C > +inline bool operator>( value_ptr const & lhs, std::nullptr_t ) +{ + return nullptr < lhs; +} + +template< class T, class D, class C > +inline bool operator>( std::nullptr_t, value_ptr const & rhs ) +{ + return rhs < nullptr; +} + +template< class T, class D, class C > +inline bool operator>=( value_ptr const & lhs, std::nullptr_t ) +{ + return !( lhs < nullptr ); +} + +template< class T, class D, class C > +inline bool operator>=( std::nullptr_t, value_ptr const & rhs ) +{ + return !( nullptr < rhs ); +} + +#endif // nsvp_HAVE_NULLPTR + +#else // nsvp_CONFIG_COMPARE_POINTERS + +// compare content: + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator==( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return bool(lhs) != bool(rhs) ? false : bool(lhs) == false ? true : *lhs == *rhs; +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator!=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return ! ( lhs == rhs ); +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator<( + value_ptr const & lhs, + value_ptr const & rhs ) +{ +//#if nsvp_CPP11_OR_GREATER +// using E1 = typename value_ptr::element_type; +// using E2 = typename value_ptr::element_type; +// using CT = typename std::common_type::type; +// return std::less()( *lhs, *rhs ); +//#else +// return std::less()( *lhs, *rhs ); +//#endif + + return (!rhs) ? false : (!lhs) ? true : *lhs < *rhs; +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator<=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return !( rhs < lhs ); +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator>( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return rhs < lhs; +} + +template< + class T1, class D1, class C1, + class T2, class D2, class C2 +> +inline bool operator>=( + value_ptr const & lhs, + value_ptr const & rhs ) +{ + return !( lhs < rhs ); +} + +// compare with value: + +template< class T, class C, class D > +bool operator==( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp == value : false; +} + +template< class T, class C, class D > +bool operator==( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value == *vp : false; +} + +template< class T, class C, class D > +bool operator!=( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp != value : true; +} + +template< class T, class C, class D > +bool operator!=( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value != *vp : true; +} + +template< class T, class C, class D > +bool operator<( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp < value : true; +} + +template< class T, class C, class D > +bool operator<( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value < *vp : false; +} + +template< class T, class C, class D > +bool operator<=( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp <= value : true; +} + +template< class T, class C, class D > +bool operator<=( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value <= *vp : false; +} + +template< class T, class C, class D > +bool operator>( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp > value : false; +} + +template< class T, class C, class D > +bool operator>( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value > *vp : true; +} + +template< class T, class C, class D > +bool operator>=( value_ptr const & vp, T const & value ) +{ + return bool(vp) ? *vp >= value : false; +} + +template< class T, class C, class D > +bool operator>=( T const & value, value_ptr const & vp ) +{ + return bool(vp) ? value >= *vp : true; +} + +#endif // nsvp_CONFIG_COMPARE_POINTERS + +// swap: + +template< class T, class D, class C > +inline void swap( + value_ptr & lhs, + value_ptr & rhs ) nsvp_noexcept +{ + lhs.swap( rhs ); +} + +} // namespace vptr + +using namespace vptr; + +} // namespace nonstd + +#if nsvp_CPP11_OR_GREATER + +// Specialize the std::hash algorithm: + +namespace std +{ + +template< class T, class D, class C > +struct hash< nonstd::value_ptr > +{ + typedef nonstd::value_ptr argument_type; + typedef size_t result_type; + + result_type operator()( argument_type const & p ) const nsvp_noexcept + { + return hash()( p.get() ); + } +}; + +} // namespace std + +#endif // nsvp_CPP11_OR_GREATER + +#if nsvp_BETWEEN( nsvp_COMPILER_MSVC_VER, 1300, 1900 ) +# pragma warning( pop ) +#endif + +#endif // NONSTD_VALUE_PTR_LITE_HPP diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index a08be7dc..83c76bc8 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -60,7 +60,7 @@ InternalValue SubscriptExpression::Evaluate(RenderContext& values) for (auto idx : m_subscriptExprs) { auto subscript = idx->Evaluate(values); - auto newVal = Subscript(cur, subscript); + auto newVal = Subscript(cur, subscript, &values); if (cur.ShouldExtendLifetime()) newVal.SetParentData(cur); std::swap(newVal, cur); @@ -272,7 +272,7 @@ void CallExpression::Render(OutStream& stream, RenderContext& values) const Callable* callable = GetIf(&fnVal); if (callable == nullptr) { - fnVal = Subscript(fnVal, std::string("operator()")); + fnVal = Subscript(fnVal, std::string("operator()"), &values); callable = GetIf(&fnVal); if (callable == nullptr) { @@ -297,7 +297,7 @@ InternalValue CallExpression::CallArbitraryFn(RenderContext& values) Callable* callable = GetIf(&fnVal); if (callable == nullptr) { - fnVal = Subscript(fnVal, std::string("operator()")); + fnVal = Subscript(fnVal, std::string("operator()"), nullptr); callable = GetIf(&fnVal); if (callable == nullptr) return InternalValue(); @@ -349,41 +349,14 @@ InternalValue CallExpression::CallGlobalRange(RenderContext& values) return InternalValue(); } - class RangeGenerator : public ListAccessorImpl - { - public: - RangeGenerator(int64_t start, int64_t stop, int64_t step) - : m_start(start) - , m_stop(stop) - , m_step(step) - { - } + auto distance = stop - start; + auto items_count = distance / step; + items_count = items_count < 0 ? 0 : static_cast(items_count); - size_t GetSize() const override - { - auto distance = m_stop - m_start; - auto count = distance / m_step; - return count < 0 ? 0 : static_cast(count); - } - InternalValue GetItem(int64_t idx) const override - { - return m_start + m_step * idx; - } + return ListAdapter::CreateAdapter(items_count, [start, step](size_t idx) { + return InternalValue(static_cast(start + step * idx)); + }); - bool ShouldExtendLifetime() const override {return false;} - GenericList CreateGenericList() const override - { - return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); - } - - - private: - int64_t m_start; - int64_t m_stop; - int64_t m_step; - }; - - return ListAdapter([accessor = RangeGenerator(start, stop, step)]() -> const IListAccessor* {return &accessor;}); } InternalValue CallExpression::CallLoopCycle(RenderContext& values) @@ -500,7 +473,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo isFirstTime = false; continue; } - + prevNotFound = argsInfo[startPosArg].prevNotFound; if (prevNotFound != -1) { @@ -525,7 +498,7 @@ ParsedArguments ParseCallParamsImpl(const T& args, const CallParams& params, boo { if (argsInfo[curArg].state == Ignored) continue; - + result.args[argsInfo[curArg].info->name] = params.posParams[idx]; argsInfo[curArg].state = Positional; } diff --git a/src/filters.cpp b/src/filters.cpp index ebdfe6f0..ca8441c4 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -146,12 +146,12 @@ InternalValue Sort::Filter(const InternalValue& baseVal, RenderContext& context) BinaryExpression::CompareType compType = ConvertToBool(isCsVal) ? BinaryExpression::CaseSensitive : BinaryExpression::CaseInsensitive; - std::sort(values.begin(), values.end(), [&attrName, oper, compType](auto& val1, auto& val2) { + std::sort(values.begin(), values.end(), [&attrName, oper, compType, &context](auto& val1, auto& val2) { InternalValue cmpRes; if (IsEmpty(attrName)) cmpRes = Apply2(val1, val2, oper, compType); else - cmpRes = Apply2(Subscript(val1, attrName), Subscript(val2, attrName), oper, compType); + cmpRes = Apply2(Subscript(val1, attrName, &context), Subscript(val2, attrName, &context), oper, compType); return ConvertToBool(cmpRes); }); @@ -167,7 +167,7 @@ Attribute::Attribute(FilterParams params) InternalValue Attribute::Filter(const InternalValue& baseVal, RenderContext& context) { const auto attrNameVal = GetArgumentValue("name", context); - const auto result = Subscript(baseVal, attrNameVal); + const auto result = Subscript(baseVal, attrNameVal, &context); if (result.IsEmpty()) return GetArgumentValue("default", context); return result; @@ -302,7 +302,7 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte for (auto& item : list) { - auto attr = Subscript(item, attrName); + auto attr = Subscript(item, attrName, &context); auto p = std::find_if(groups.begin(), groups.end(), [&equalComparator, &attr](auto& i) {return equalComparator(i.grouper, attr);}); if (p == groups.end()) groups.push_back(GroupInfo{attr, {item}}); @@ -410,7 +410,7 @@ InternalValue Map::Filter(const InternalValue& baseVal, RenderContext& context) return InternalValue(); InternalValueList resultList; - resultList.reserve(list.GetSize()); + resultList.reserve(list.GetSize().value_or(0)); std::transform(list.begin(), list.end(), std::back_inserter(resultList), [filter, &context](auto& val) {return filter->Filter(val, context);}); return ListAdapter::CreateAdapter(std::move(resultList)); @@ -582,41 +582,76 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte BinaryExpression::CompareType compType = ConvertToBool(isCsVal) ? BinaryExpression::CaseSensitive : BinaryExpression::CaseInsensitive; - auto lessComparator = [&attrName, &compType](auto& val1, auto& val2) { + auto lessComparator = [&attrName, &compType, &context](auto& val1, auto& val2) { InternalValue cmpRes; if (IsEmpty(attrName)) cmpRes = Apply2(val1, val2, BinaryExpression::LogicalLt, compType); else - cmpRes = Apply2(Subscript(val1, attrName), Subscript(val2, attrName), BinaryExpression::LogicalLt, compType); + cmpRes = Apply2(Subscript(val1, attrName, &context), Subscript(val2, attrName, &context), BinaryExpression::LogicalLt, compType); return ConvertToBool(cmpRes); }; + const auto& listSize = list.GetSize(); + switch (m_mode) { case FirstItemMode: - result = list.GetSize() == 0 ? InternalValue() : list.GetValueByIndex(0); + if (listSize) + result = list.GetValueByIndex(0); + else + { + auto it = list.begin(); + if (it != list.end()) + result = *it; + } break; case LastItemMode: - result = list.GetSize() == 0 ? InternalValue() : list.GetValueByIndex(list.GetSize() - 1); + if (listSize) + result = list.GetValueByIndex(listSize.value() - 1); + else + { + auto it = list.begin(); + auto end = list.end(); + for (; it != end; ++ it) + result = *it; + } break; case LengthMode: - result = static_cast(list.GetSize()); + if (listSize) + result = static_cast(listSize.value()); + else + result = static_cast(std::distance(list.begin(), list.end())); break; case RandomMode: { std::random_device rd; std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, static_cast(list.GetSize()) - 1); - result = list.GetValueByIndex(dis(gen)); + if (listSize) + { + std::uniform_int_distribution<> dis(0, static_cast(listSize.value()) - 1); + result = list.GetValueByIndex(dis(gen)); + } + else + { + auto it = list.begin(); + auto end = list.end(); + size_t count = 0; + for (; it != end; ++ it, ++ count) + { + bool doCopy = count == 0 || std::uniform_int_distribution<>(0, count)(gen) == 0; + if (doCopy) + result = *it; + } + } break; } case MaxItemMode: { auto b = list.begin(); auto e = list.end(); - auto p = std::max_element(b, e, lessComparator); + auto p = std::max_element(list.begin(), list.end(), lessComparator); result = p != e ? *p : InternalValue(); break; } @@ -630,11 +665,26 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte } case ReverseMode: { - InternalValueList resultList(list.GetSize()); - for (std::size_t n = 0; n < list.GetSize(); ++ n) - resultList[list.GetSize() - n - 1] = list.GetValueByIndex(n); + if (listSize) + { + auto size = listSize.value(); + InternalValueList resultList(size); + for (std::size_t n = 0; n < size; ++ n) + resultList[size - n - 1] = list.GetValueByIndex(n); + result = ListAdapter::CreateAdapter(std::move(resultList)); + } + else + { + InternalValueList resultList; + auto it = list.begin(); + auto end = list.end(); + for (; it != end; ++ it) + resultList.push_back(*it); + + std::reverse(resultList.begin(), resultList.end()); + result = ListAdapter::CreateAdapter(std::move(resultList)); + } - result = ListAdapter::CreateAdapter(std::move(resultList)); break; } case SumItemsMode: @@ -674,7 +724,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte int idx = 0; for (auto& v : list) - items.push_back(Item{IsEmpty(attrName) ? v : Subscript(v, attrName), idx ++}); + items.push_back(Item{IsEmpty(attrName) ? v : Subscript(v, attrName, &context), idx ++}); std::stable_sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) { auto cmpRes = Apply2(i1.val, i2.val, BinaryExpression::LogicalLt, compType); @@ -775,13 +825,13 @@ InternalValue Tester::Filter(const InternalValue& baseVal, RenderContext& contex return InternalValue(); InternalValueList resultList; - resultList.reserve(list.GetSize()); + resultList.reserve(list.GetSize().value_or(0)); std::copy_if(list.begin(), list.end(), std::back_inserter(resultList), [this, tester, attrName, &context](auto& val) { InternalValue attrVal; bool isAttr = !IsEmpty(attrName); if (isAttr) - attrVal = Subscript(val, attrName); + attrVal = Subscript(val, attrName, &context); bool result = false; if (tester) @@ -892,45 +942,6 @@ struct ValueConverterImpl : visitors::BaseVisitor<> return result; } - template - struct StringAdapter : public ListAccessorImpl> - { - using string = std::basic_string; - StringAdapter(const string* str) - : m_str(*str) - { - } - - size_t GetSize() const override {return m_str.size();} - InternalValue GetItem(int64_t idx) const override {return InternalValue(m_str.substr(static_cast(idx), 1));} - bool ShouldExtendLifetime() const override {return false;} - GenericList CreateGenericList() const override - { - return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); - } - - const string m_str; - }; - - struct Map2ListAdapter : public ListAccessorImpl - { - Map2ListAdapter(const MapAdapter* map) - : m_values(map->GetKeys()) - { - } - - size_t GetSize() const override {return m_values.size();} - InternalValue GetItem(int64_t idx) const override {return m_values[idx];} - bool ShouldExtendLifetime() const override {return true;} - GenericList CreateGenericList() const override - { - // return m_values.Get(); - return GenericList([list = *this]() -> const ListItemAccessor* {return &list;}); - } - - const std::vector m_values; - }; - InternalValue operator()(const std::string& val) const { InternalValue result; @@ -958,7 +969,9 @@ struct ValueConverterImpl : visitors::BaseVisitor<> break; } case ValueConverter::ToListMode: - result = ListAdapter([adapter = StringAdapter(&val)]() {return &adapter;}); + result = ListAdapter::CreateAdapter(val.size(), [str=val](size_t idx) { + return InternalValue(str.substr(idx, 1)); + }); default: break; } @@ -992,7 +1005,9 @@ struct ValueConverterImpl : visitors::BaseVisitor<> break; } case ValueConverter::ToListMode: - result = ListAdapter([adapter = StringAdapter(&val)]() {return &adapter;}); + result = ListAdapter::CreateAdapter(val.size(), [str=val](size_t idx) { + return InternalValue(str.substr(idx, 1)); + }); default: break; } @@ -1010,10 +1025,14 @@ struct ValueConverterImpl : visitors::BaseVisitor<> InternalValue operator()(const MapAdapter& val) const { - if (m_params.mode == ValueConverter::ToListMode) - return ListAdapter([adapter = Map2ListAdapter(&val)]() {return &adapter;}); + if (m_params.mode != ValueConverter::ToListMode) + return InternalValue(); - return InternalValue(); + auto keys = val.GetKeys(); + auto num_keys = keys.size(); + return ListAdapter::CreateAdapter(num_keys, [values=std::move(keys)](size_t idx) { + return InternalValue(values[idx]); + }); } template @@ -1076,7 +1095,7 @@ InternalValue UserDefinedFilter::Filter(const InternalValue& baseVal, RenderCont InternalValue result; if (callable->GetType() != Callable::Type::Expression) return InternalValue(); - + return callable->GetExpressionCallable()(callParams, context); } } // filters diff --git a/src/generic_adapters.h b/src/generic_adapters.h index 513816ab..8a4b4abf 100644 --- a/src/generic_adapters.h +++ b/src/generic_adapters.h @@ -6,24 +6,158 @@ namespace jinja2 { + +template +class IndexedEnumeratorImpl : public Base +{ +public: + using ValueType = ValType; + using ThisType = IndexedEnumeratorImpl; + + IndexedEnumeratorImpl(const List* list) + : m_list(list) + , m_maxItems(list->GetSize().value()) + { } + + void Reset() override + { + m_curItem = m_invalidIndex; + } + + bool MoveNext() override + { + if (m_curItem == m_invalidIndex) + m_curItem = 0; + else + ++ m_curItem; + + return m_curItem < m_maxItems; + } + +protected: + constexpr static auto m_invalidIndex = std::numeric_limits::max(); + const List* m_list; + size_t m_curItem = m_invalidIndex; + size_t m_maxItems; +}; + + template -class ListItemAccessorImpl : public ListItemAccessor +class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAccessor { public: - Value GetValueByIndex(int64_t idx) const + using ThisType = IndexedListItemAccessorImpl; + class Enumerator : public IndexedEnumeratorImpl + { + public: + using BaseClass = IndexedEnumeratorImpl; +#if defined(_MSC_VER) + using IndexedEnumeratorImpl::IndexedEnumeratorImpl; +#else + using BaseClass::BaseClass; +#endif + + typename BaseClass::ValueType GetCurrent() const override + { + auto indexer = this->m_list->GetIndexer(); + if (!indexer) + return Value(); + + return indexer->GetItemByIndex(this->m_curItem); + } + ListEnumeratorPtr Clone() const override + { + auto result = MakeEnumerator(this->m_list); + auto base = static_cast(result.get()); + base->m_curItem = this->m_curItem; + return result; + } + + ListEnumeratorPtr Move() override + { + auto result = MakeEnumerator(this->m_list); + auto base = static_cast(result.get()); + base->m_curItem = this->m_curItem; + this->m_list = nullptr; + this->m_curItem = this->m_invalidIndex; + this->m_maxItems = 0; + return result; + } + }; + + Value GetItemByIndex(int64_t idx) const override + { + return IntValue2Value(std::move(static_cast(this)->GetItem(idx).value())); + } + + nonstd::optional GetSize() const override { - return IntValue2Value(static_cast(this)->GetItem(idx)); + return static_cast(this)->GetItemsCountImpl(); } + + size_t GetItemsCount() const override + { + return static_cast(this)->GetItemsCountImpl(); + } + + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override; + }; template -class ListAccessorImpl : public IListAccessor, public ListItemAccessorImpl +class IndexedListAccessorImpl : public IListAccessor, public IndexedListItemAccessorImpl { public: -// GenericList CreateGenericList() const override -// { -// return GenericList([accessor = this]() -> const ListItemAccessor* {return accessor;}); -// } + using ThisType = IndexedListAccessorImpl; + class Enumerator : public IndexedEnumeratorImpl + { + public: + using BaseClass = IndexedEnumeratorImpl; +#if defined(_MSC_VER) + using IndexedEnumeratorImpl::IndexedEnumeratorImpl; +#else + using BaseClass::BaseClass; +#endif + + typename BaseClass::ValueType GetCurrent() const override + { + const auto& result = this->m_list->GetItem(this->m_curItem); + if (!result) + return InternalValue(); + + return result.value(); + } + + IListAccessorEnumerator* Clone() const override + { + auto result = new Enumerator(this->m_list); + auto base = result; + base->m_curItem = this->m_curItem; + return result; + } + + IListAccessorEnumerator* Transfer() override + { + auto result = new Enumerator(std::move(*this)); + auto base = result; + base->m_curItem = this->m_curItem; + this->m_list = nullptr; + this->m_curItem = this->m_invalidIndex; + this->m_maxItems = 0; + return result; + } + }; + + nonstd::optional GetSize() const override + { + return static_cast(this)->GetItemsCountImpl(); + } + ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override; }; template @@ -42,6 +176,18 @@ class MapAccessorImpl : public IMapAccessor, public MapItemAccessorImpl public: }; +template +inline ListAccessorEnumeratorPtr IndexedListAccessorImpl::CreateListAccessorEnumerator() const +{ + return ListAccessorEnumeratorPtr(new Enumerator(this)); +} + +template +inline ListEnumeratorPtr IndexedListItemAccessorImpl::CreateEnumerator() const +{ + return MakeEnumerator(this); +} + } // jinja2 #endif // GENERIC_ADAPTERS_H diff --git a/src/internal_value.cpp b/src/internal_value.cpp index ede575f7..7b91b32a 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -6,6 +6,9 @@ namespace jinja2 { +InternalValue Value2IntValue(const Value& val); +InternalValue Value2IntValue(Value&& val); + struct SubscriptionVisitor : public visitors::BaseVisitor<> { using BaseVisitor<>::operator (); @@ -54,17 +57,32 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return InternalValue(); } - }; -InternalValue Subscript(const InternalValue& val, const InternalValue& subscript) +InternalValue Subscript(const InternalValue& val, const InternalValue& subscript, RenderContext* values) { - return Apply2(val, subscript); + static const std::string callOperName = "value()"; + auto result = Apply2(val, subscript); + + if (!values) + return result; + + auto map = GetIf(&result); + if (!map || !map->HasValue(callOperName)) + return result; + + auto callableVal = map->GetValueByName(callOperName); + auto callable = GetIf(&callableVal); + if (!callable || callable->GetKind() == Callable::Macro || callable->GetType() == Callable::Type::Statement) + return result; + + CallParams callParams; + return callable->GetExpressionCallable()(callParams, *values); } -InternalValue Subscript(const InternalValue& val, const std::string& subscript) +InternalValue Subscript(const InternalValue& val, const std::string& subscript, RenderContext* values) { - return Apply2(val, InternalValue(subscript)); + return Subscript(val, InternalValue(subscript), values); } std::string AsString(const InternalValue& val) @@ -186,16 +204,60 @@ template class Holder> class GenericListAdapter : public IListAccessor { public: + struct Enumerator : public IListAccessorEnumerator + { + ListEnumeratorPtr m_enum; + + explicit Enumerator(ListEnumeratorPtr e) + : m_enum(std::move(e)) + {} + + // Inherited via IListAccessorEnumerator + virtual void Reset() override + { + if (m_enum) + m_enum->Reset(); + } + virtual bool MoveNext() override + { + return !m_enum ? false : m_enum->MoveNext(); + } + virtual InternalValue GetCurrent() const override + { + return !m_enum ? InternalValue() : Value2IntValue(m_enum->GetCurrent()); + } + virtual IListAccessorEnumerator *Clone() const override + { + return !m_enum ? new Enumerator(MakeEmptyListEnumeratorPtr()) : new Enumerator(m_enum->Clone()); + } + virtual IListAccessorEnumerator *Transfer() override + { + return new Enumerator(std::move(m_enum)); + } + }; + template GenericListAdapter(U&& values) : m_values(std::forward(values)) {} - size_t GetSize() const override {return m_values.Get().GetSize();} - InternalValue GetItem(int64_t idx) const override + nonstd::optional GetSize() const override {return m_values.Get().GetSize();} + nonstd::optional GetItem(int64_t idx) const override { - const auto& val = m_values.Get().GetValueByIndex(idx); + const ListItemAccessor* accessor = m_values.Get().GetAccessor(); + auto indexer = accessor->GetIndexer(); + if (!indexer) + return nonstd::optional(); + + const auto& val = indexer->GetItemByIndex(idx); return visit(visitors::InputValueConvertor(true), val.data()).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} + ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override + { + const ListItemAccessor* accessor = m_values.Get().GetAccessor(); + if (!accessor) + return ListAccessorEnumeratorPtr(new Enumerator(MakeEmptyListEnumeratorPtr())); + return ListAccessorEnumeratorPtr(new Enumerator(m_values.Get().GetAccessor()->CreateEnumerator())); + } GenericList CreateGenericList() const override { // return m_values.Get(); @@ -206,14 +268,14 @@ class GenericListAdapter : public IListAccessor }; template class Holder> -class ValuesListAdapter : public ListAccessorImpl> +class ValuesListAdapter : public IndexedListAccessorImpl> { public: template ValuesListAdapter(U&& values) : m_values(std::forward(values)) {} - size_t GetSize() const override {return m_values.Get().size();} - InternalValue GetItem(int64_t idx) const override + size_t GetItemsCountImpl() const {return m_values.Get().size();} + nonstd::optional GetItem(int64_t idx) const override { const auto& val = m_values.Get()[idx]; return visit(visitors::InputValueConvertor(false), val.data()).get(); @@ -231,13 +293,13 @@ class ValuesListAdapter : public ListAccessorImpl> ListAdapter ListAdapter::CreateAdapter(InternalValueList&& values) { - class Adapter : public ListAccessorImpl + class Adapter : public IndexedListAccessorImpl { public: explicit Adapter(InternalValueList&& values) : m_values(std::move(values)) {} - size_t GetSize() const override {return m_values.size();} - InternalValue GetItem(int64_t idx) const override {return m_values[static_cast(idx)];} + size_t GetItemsCountImpl() const {return m_values.size();} + nonstd::optional GetItem(int64_t idx) const override {return m_values[static_cast(idx)];} bool ShouldExtendLifetime() const override {return false;} GenericList CreateGenericList() const override { @@ -270,35 +332,149 @@ ListAdapter ListAdapter::CreateAdapter(ValuesList&& values) return ListAdapter([accessor = ValuesListAdapter(std::move(values))]() {return &accessor;}); } -template class Holder> -class SubscriptedListAdapter : public ListAccessorImpl> +ListAdapter ListAdapter::CreateAdapter(std::function()> fn) { -public: - template - SubscriptedListAdapter(U&& values, const InternalValue& subscript) : m_values(std::forward(values)), m_subscript(subscript) {} + using GenFn = std::function()>; - size_t GetSize() const override {return m_values.Get().GetSize();} - InternalValue GetItem(int64_t idx) const override + class Adapter : public IListAccessor { - return Subscript(m_values.Get().GetValueByIndex(idx), m_subscript); - } - bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} - GenericList CreateGenericList() const override + public: + class Enumerator : public IListAccessorEnumerator + { + public: + explicit Enumerator(const GenFn* fn) + : m_fn(fn) + { } + + void Reset() override + { + } + + bool MoveNext() override + { + if (m_isFinished) + return false; + + auto res = (*m_fn)(); + if (!res) + return false; + + m_current = *res; + + return true; + } + + InternalValue GetCurrent() const override { return m_current; } + + IListAccessorEnumerator* Clone() const override + { + auto result = new Enumerator(*this); + return result; + } + + IListAccessorEnumerator* Transfer() override + { + auto result = new Enumerator(std::move(*this)); + return result; + } + + protected: + const GenFn* m_fn; + InternalValue m_current; + bool m_isFinished = false; + }; + + explicit Adapter(std::function()>&& fn) : m_fn(std::move(fn)) {} + + nonstd::optional GetSize() const override + { + return nonstd::optional(); + } + nonstd::optional GetItem(int64_t idx) const override + { + return nonstd::optional(); + } + bool ShouldExtendLifetime() const override { return false; } + ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override + { + return ListAccessorEnumeratorPtr(new Enumerator(&m_fn)); + } + + GenericList CreateGenericList() const override + { + return GenericList(); // return GenericList([adapter = *this]() -> const ListItemAccessor* {return &adapter; }); + } + private: + std::function()> m_fn; + }; + + return ListAdapter([accessor = Adapter(std::move(fn))]() {return &accessor;}); +} + +ListAdapter ListAdapter::CreateAdapter(size_t listSize, std::function fn) +{ + using GenFn = std::function; + + class Adapter : public IndexedListAccessorImpl { - return GenericList([accessor = *this]() -> const ListItemAccessor* {return &accessor;}); - } -private: - Holder m_values; - InternalValue m_subscript; -}; + public: + explicit Adapter(size_t listSize, GenFn&& fn) : m_listSize(listSize), m_fn(std::move(fn)) {} + + size_t GetItemsCountImpl() const { return m_listSize; } + nonstd::optional GetItem(int64_t idx) const override { return m_fn(static_cast(idx)); } + bool ShouldExtendLifetime() const override { return false; } + GenericList CreateGenericList() const override + { + return GenericList([adapter = *this]() -> const ListItemAccessor* {return &adapter; }); + } + private: + size_t m_listSize; + GenFn m_fn; + }; + + return ListAdapter([accessor = Adapter(listSize, std::move(fn))]() {return &accessor; }); +} + +template +auto CreateIndexedSubscribedList(Holder&& holder, const InternalValue& subscript, size_t size) +{ + return ListAdapter::CreateAdapter(size, [h = std::forward(holder), subscript](size_t idx)->InternalValue { + return Subscript(h.Get().GetValueByIndex(idx), subscript, nullptr); + }); +} + +template +auto CreateGenericSubscribedList(Holder&& holder, const InternalValue& subscript) +{ + return ListAdapter::CreateAdapter([h = std::forward(holder), e = ListAccessorEnumeratorPtr(), isFirst = true, isLast = false, subscript]() mutable { + using ResultType = nonstd::optional; + if (isFirst) + { + e = h.Get().GetEnumerator(); + isLast = !e->MoveNext(); + isFirst = false; + } + if (isLast) + return ResultType(); + + return ResultType(Subscript(e->GetCurrent(), subscript, nullptr)); + }); +} ListAdapter ListAdapter::ToSubscriptedList(const InternalValue& subscript, bool asRef) const { + auto listSize = GetSize(); if (asRef) - return ListAdapter([accessor = SubscriptedListAdapter(*this, subscript)]() {return &accessor;}); - - ListAdapter tmp(*this); - return ListAdapter([accessor = SubscriptedListAdapter(std::move(tmp), subscript)]() {return &accessor;}); + { + ByRef holder(*this); + return listSize ? CreateIndexedSubscribedList(holder, subscript, *listSize) : CreateGenericSubscribedList(holder, subscript); + } + else + { + ListAdapter tmp(*this); + BySharedVal holder(std::move(tmp)); + return listSize ? CreateIndexedSubscribedList(std::move(holder), subscript, *listSize) : CreateGenericSubscribedList(std::move(holder), subscript); + } } InternalValueList ListAdapter::ToValueList() const @@ -530,6 +706,14 @@ struct OutputValueConvertor bool m_byValue; }; +Value OptIntValue2Value(nonstd::optional val) +{ + if (val) + return Apply(val.value()); + + return Value(); +} + Value IntValue2Value(const InternalValue& val) { return Apply(val); diff --git a/src/internal_value.h b/src/internal_value.h index 44404771..7dd575e6 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -4,7 +4,6 @@ #include #include #include -// #include #include #include @@ -152,16 +151,48 @@ struct IsRecursive : std::true_type {}; template<> struct IsRecursive : std::true_type {}; +struct IListAccessorEnumerator +{ + virtual ~IListAccessorEnumerator() {} + + virtual void Reset() = 0; + + virtual bool MoveNext() = 0; + virtual InternalValue GetCurrent() const = 0; + + virtual IListAccessorEnumerator* Clone() const = 0; + virtual IListAccessorEnumerator* Transfer() = 0; + + struct Cloner + { + Cloner() = default; + + IListAccessorEnumerator* operator()(const IListAccessorEnumerator &x) const + { + return x.Clone(); + } + + IListAccessorEnumerator* operator()(IListAccessorEnumerator &&x) const + { + return x.Transfer(); + } + }; +}; + +using ListAccessorEnumeratorPtr = nonstd::value_ptr; + struct IListAccessor { virtual ~IListAccessor() {} - virtual size_t GetSize() const = 0; - virtual InternalValue GetItem(int64_t idx) const = 0; + virtual nonstd::optional GetSize() const = 0; + virtual nonstd::optional GetItem(int64_t idx) const = 0; + virtual ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const = 0; virtual GenericList CreateGenericList() const = 0; virtual bool ShouldExtendLifetime() const = 0; }; + using ListAccessorProvider = std::function; struct IMapAccessor @@ -190,11 +221,13 @@ class ListAdapter static ListAdapter CreateAdapter(const ValuesList& values); static ListAdapter CreateAdapter(GenericList&& values); static ListAdapter CreateAdapter(ValuesList&& values); + static ListAdapter CreateAdapter(std::function ()> fn); + static ListAdapter CreateAdapter(size_t listSize, std::function fn); ListAdapter& operator = (const ListAdapter&) = default; ListAdapter& operator = (ListAdapter&&) = default; - size_t GetSize() const + nonstd::optional GetSize() const { if (m_accessorProvider && m_accessorProvider()) { @@ -223,6 +256,7 @@ class ListAdapter return GenericList(); } + ListAccessorEnumeratorPtr GetEnumerator() const; class Iterator; @@ -361,15 +395,12 @@ class ListAdapter::Iterator boost::forward_traversal_tag> { public: - Iterator() - : m_current(0) - , m_list(nullptr) - {} + Iterator() = default; - explicit Iterator(const ListAdapter& list) - : m_current(0) - , m_list(&list) - , m_currentVal(list.GetSize() == 0 ? InternalValue() : list.GetValueByIndex(0)) + explicit Iterator(ListAccessorEnumeratorPtr&& iter) + : m_iterator(std::move(iter)) + , m_isFinished(!m_iterator->MoveNext()) + , m_currentVal(m_isFinished ? InternalValue() : m_iterator->GetCurrent()) {} private: @@ -377,19 +408,20 @@ class ListAdapter::Iterator void increment() { - ++ m_current; - m_currentVal = m_current == static_cast(m_list->GetSize()) ? InternalValue() : m_list->GetValueByIndex(m_current); + m_isFinished = !m_iterator->MoveNext(); + ++ m_currentIndex; + m_currentVal = m_isFinished ? InternalValue() : m_iterator->GetCurrent(); } bool equal(const Iterator& other) const { - if (m_list == nullptr) - return other.m_list == nullptr ? true : other.equal(*this); + if (!this->m_iterator) + return !other.m_iterator ? true : other.equal(*this); - if (other.m_list == nullptr) - return m_current == static_cast(m_list->GetSize()); + if (!other.m_iterator) + return this->m_isFinished; - return this->m_list == other.m_list && this->m_current == other.m_current; + return this->m_iterator.get() == other.m_iterator.get() && this->m_currentIndex == other.m_currentIndex; } const InternalValue& dereference() const @@ -397,8 +429,9 @@ class ListAdapter::Iterator return m_currentVal; } - int64_t m_current = 0; - const ListAdapter* m_list; + ListAccessorEnumeratorPtr m_iterator; + bool m_isFinished = true; + mutable uint64_t m_currentIndex = 0; mutable InternalValue m_currentVal; }; @@ -445,7 +478,11 @@ inline InternalValue ListAdapter::GetValueByIndex(int64_t idx) const { if (m_accessorProvider && m_accessorProvider()) { - return m_accessorProvider()->GetItem(idx); + const auto& val = m_accessorProvider()->GetItem(idx); + if (val) + return std::move(val.value()); + + return InternalValue(); } return InternalValue(); @@ -471,7 +508,8 @@ inline InternalValue MapAdapter::GetValueByName(const std::string& name) const return InternalValue(); } -inline ListAdapter::Iterator ListAdapter::begin() const {return Iterator(*this);} +inline ListAccessorEnumeratorPtr ListAdapter::GetEnumerator() const {return m_accessorProvider()->CreateListAccessorEnumerator();} +inline ListAdapter::Iterator ListAdapter::begin() const {return Iterator(m_accessorProvider()->CreateListAccessorEnumerator());} inline ListAdapter::Iterator ListAdapter::end() const {return Iterator();} @@ -550,12 +588,23 @@ inline bool IsEmpty(const InternalValue& val) return nonstd::get_if(&val.GetData()) != nullptr; } -InternalValue Subscript(const InternalValue& val, const InternalValue& subscript); -InternalValue Subscript(const InternalValue& val, const std::string& subscript); +class RenderContext; + +template +auto MakeDynamicProperty(Fn&& fn) +{ + return MapAdapter::CreateAdapter(InternalValueMap{ + {"value()", Callable(Callable::GlobalFunc, std::forward(fn))} + }); +} + +InternalValue Subscript(const InternalValue& val, const InternalValue& subscript, RenderContext* values); +InternalValue Subscript(const InternalValue& val, const std::string& subscript, RenderContext* values); std::string AsString(const InternalValue& val); ListAdapter ConvertToList(const InternalValue& val, bool& isConverted); ListAdapter ConvertToList(const InternalValue& val, InternalValue subscipt, bool& isConverted); Value IntValue2Value(const InternalValue& val); +Value OptIntValue2Value(nonstd::optional val); } // jinja2 diff --git a/src/statements.cpp b/src/statements.cpp index 05373022..98a51e63 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -30,15 +30,11 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende bool isSucceeded = false; auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded); if (!isSucceeded) - { return; - } auto var = parsedParams["var"]; if (!var) - { return; - } RenderLoop(var->Evaluate(context), stream, context); }); @@ -46,6 +42,10 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende bool isConverted = false; auto loopItems = ConvertToList(loopVal, InternalValue(), isConverted); + ListAdapter filteredList; + ListAdapter indexedList; + ListAccessorEnumeratorPtr enumerator; + size_t itemIdx = 0; if (!isConverted) { if (m_elseBody) @@ -54,50 +54,74 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende return; } + nonstd::optional listSize; if (m_ifExpr) { - auto& tempContext = values.EnterScope(); - InternalValueList newLoopItems; - for (auto& curValue : loopItems) + filteredList = CreateFilteredAdapter(loopItems, values); + enumerator = filteredList.GetEnumerator(); + } + else + { + enumerator = loopItems.GetEnumerator(); + listSize = loopItems.GetSize(); + } + + bool isLast = false; + auto makeIndexedList = [&enumerator, &listSize, &indexedList, &itemIdx, &isLast] + { + if (isLast) + listSize = itemIdx; + + InternalValueList items; + do { - if (m_vars.size() > 1) - { - for (auto& varName : m_vars) - tempContext[varName] = Subscript(curValue, varName); - } - else - tempContext[m_vars[0]] = curValue; + items.push_back(enumerator->GetCurrent()); + } while (enumerator->MoveNext()); - if (ConvertToBool(m_ifExpr->Evaluate(values))) - newLoopItems.push_back(curValue); - } - values.ExitScope(); + listSize = itemIdx + items.size() + 1; + indexedList = ListAdapter::CreateAdapter(std::move(items)); + enumerator = indexedList.GetEnumerator(); + isLast = !enumerator->MoveNext(); + }; - loopItems = ListAdapter::CreateAdapter(std::move(newLoopItems)); + if (listSize) + { + int64_t itemsNum = static_cast(listSize.value()); + loopVar["length"] = InternalValue(itemsNum); + } + else + { + loopVar["length"] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& params, RenderContext& context) -> InternalValue { + if (!listSize) + makeIndexedList(); + return static_cast(listSize.value()); + }); } - - int64_t itemsNum = static_cast(loopItems.GetSize()); - loopVar["length"] = InternalValue(itemsNum); bool loopRendered = false; - for (int64_t itemIdx = 0; itemIdx != itemsNum; ++ itemIdx) + isLast = !enumerator->MoveNext(); + InternalValue prevValue; + InternalValue curValue; + for (;!isLast; ++ itemIdx) { + prevValue = std::move(curValue); + curValue = enumerator->GetCurrent(); + isLast = !enumerator->MoveNext(); loopRendered = true; - loopVar["index"] = InternalValue(itemIdx + 1); - loopVar["index0"] = InternalValue(itemIdx); + loopVar["index"] = InternalValue(static_cast(itemIdx + 1)); + loopVar["index0"] = InternalValue(static_cast(itemIdx)); loopVar["first"] = InternalValue(itemIdx == 0); - loopVar["last"] = InternalValue(itemIdx == itemsNum - 1); + loopVar["last"] = isLast; if (itemIdx != 0) - loopVar["previtem"] = loopItems.GetValueByIndex(static_cast(itemIdx - 1)); - if (itemIdx != itemsNum - 1) - loopVar["nextitem"] = loopItems.GetValueByIndex(static_cast(itemIdx + 1)); + loopVar["previtem"] = prevValue; + if (!isLast) + loopVar["nextitem"] = enumerator->GetCurrent(); else loopVar.erase("nextitem"); - const auto& curValue = loopItems.GetValueByIndex(static_cast(itemIdx)); if (m_vars.size() > 1) { for (auto& varName : m_vars) - context[varName] = Subscript(curValue, varName); + context[varName] = Subscript(curValue, varName, &values); } else context[m_vars[0]] = curValue; @@ -111,6 +135,36 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende values.ExitScope(); } +ListAdapter ForStatement::CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const +{ + return ListAdapter::CreateAdapter([e = loopItems.GetEnumerator(), this, &values]() { + using ResultType = nonstd::optional; + + auto& tempContext = values.EnterScope(); + for (bool finish = !e->MoveNext(); !finish; finish = !e->MoveNext()) + { + auto curValue = e->GetCurrent(); + if (m_vars.size() > 1) + { + for (auto& varName : m_vars) + tempContext[varName] = Subscript(curValue, varName, &values); + } else + { + tempContext[m_vars[0]] = curValue; + } + + if (ConvertToBool(m_ifExpr->Evaluate(values))) + { + values.ExitScope(); + return ResultType(std::move(curValue)); + } + } + values.ExitScope(); + + return ResultType(); + }); +} + void IfStatement::Render(OutStream& os, RenderContext& values) { InternalValue val = m_expr->Evaluate(values); @@ -155,7 +209,7 @@ void SetStatement::Render(OutStream&, RenderContext& values) else { for (auto& name : m_fields) - values.GetCurrentScope()[name] = Subscript(val, name); + values.GetCurrentScope()[name] = Subscript(val, name, &values); } } } diff --git a/src/statements.h b/src/statements.h index 273a220f..83ffcfec 100644 --- a/src/statements.h +++ b/src/statements.h @@ -56,6 +56,7 @@ class ForStatement : public Statement private: void RenderLoop(const InternalValue& val, OutStream& os, RenderContext& values); + ListAdapter CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const; private: std::vector m_vars; @@ -64,6 +65,7 @@ class ForStatement : public Statement bool m_isRecursive; RendererPtr m_mainBody; RendererPtr m_elseBody; + }; class ElseBranchStatement; diff --git a/src/value_visitors.h b/src/value_visitors.h index 75d01548..2fa64fc3 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -739,12 +739,12 @@ struct BooleanEvaluator : BaseVisitor bool operator() (const MapAdapter& val) const { - return val.GetSize() != 0; + return val.GetSize() != 0ULL; } bool operator() (const ListAdapter& val) const { - return val.GetSize() != 0; + return val.GetSize() != 0ULL; } bool operator() (const EmptyValue&) const diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index dee021b6..a83c2dc1 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -1,10 +1,13 @@ -#include -#include - #include "test_tools.h" #include "jinja2cpp/template.h" #include "jinja2cpp/reflected_value.h" +#include "jinja2cpp/generic_list_impl.h" + +#include +#include +#include +#include using namespace jinja2; @@ -372,7 +375,6 @@ b[1] = image[1]; EXPECT_EQ(expectedResult, result); } - TEST(ForLoopTest, RecursiveLoop) { std::string source = R"( @@ -409,3 +411,108 @@ root1 -> child1_1 -> child1_2 -> child1_3 -> root2 -> child2_1 -> child2_2 -> ch EXPECT_EQ(expectedResult, result); } +TEST(ForLoopTest, GenericListTest_Generator) +{ + std::string source = R"( +{{ input[0] | pprint }} +{% for i in input %}>{{ i }}<{% endfor %} +{% for i in input %}>{{ i }}<{% else %}{% endfor %} +)"; + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + ValuesMap params = { + {"input", jinja2::MakeGenericList([cur = 10]() mutable -> nonstd::optional { + if (cur > 90) + return nonstd::optional(); + + auto tmp = cur; + cur += 10; + return Value(tmp); + }) } + }; + + std::string result = tpl.RenderAsString(params).value(); + std::cout << "[" << result << "]" << std::endl; + std::string expectedResult = R"DELIM( +none +>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; + + EXPECT_EQ(expectedResult, result); +} + +TEST(ForLoopTest, GenericListTest_InputIterator) +{ + std::string source = R"( +{{ input[0] | pprint }} +{% for i in input %}>{{ i }}<{% endfor %} +{% for i in input %}>{{ i }}<{% else %}{% endfor %} +)"; + std::string sampleStr("10 20 30 40 50 60 70 80 90"); + std::istringstream is(sampleStr); + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + ValuesMap params = { + {"input", jinja2::MakeGenericList(std::istream_iterator(is), std::istream_iterator()) } + }; + + std::string result = tpl.RenderAsString(params).value(); + std::cout << "[" << result << "]" << std::endl; + std::string expectedResult = R"DELIM( +none +>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; + + EXPECT_EQ(expectedResult, result); +} + +TEST(ForLoopTest, GenericListTest_ForwardIterator) +{ + std::string source = R"( +{{ input[0] | pprint }} +{% for i in input %}>{{ i }}<{% endfor %} +{% for i in input %}>{{ i }}<{% else %}{% endfor %} +)"; + std::forward_list sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90}; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + ValuesMap params = { + {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) } + }; + + std::string result = tpl.RenderAsString(params).value(); + std::cout << "[" << result << "]" << std::endl; + std::string expectedResult = R"DELIM( +none +>10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; + + EXPECT_EQ(expectedResult, result); +} + +TEST(ForLoopTest, GenericListTest_RandomIterator) +{ + std::string source = R"( +{{ input[0] | pprint }} +{% for i in input %}>{{ i }}<{% endfor %} +{% for i in input %}>{{ i }}<{% else %}{% endfor %} +)"; + std::array sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90}; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + ValuesMap params = { + {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) } + }; + + std::string result = tpl.RenderAsString(params).value(); + std::cout << "[" << result << "]" << std::endl; + std::string expectedResult = R"DELIM( +10 +>10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; + + EXPECT_EQ(expectedResult, result); +} diff --git a/test/includes_test.cpp b/test/includes_test.cpp index 82b7bb7d..3f30eb7d 100644 --- a/test/includes_test.cpp +++ b/test/includes_test.cpp @@ -2,6 +2,7 @@ #include #include "test_tools.h" +#include // Test cases are taken from the pandor/Jinja2 tests @@ -75,8 +76,8 @@ TEST_F(IncludeTest, TestMissingIncludesError1) 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()); + EXPECT_EQ(1ull, filesList->GetSize().value()); + EXPECT_EQ("missing", (*filesList->begin()).asString()); } TEST_F(IncludeTest, TestMissingIncludesError2) @@ -95,9 +96,10 @@ TEST_F(IncludeTest, TestMissingIncludesError2) 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()); + EXPECT_EQ(2ull, filesList->GetSize().value()); + auto params_iter = filesList->begin(); + EXPECT_EQ("missing", (*params_iter++).asString()); + EXPECT_EQ("missing2", (*params_iter++).asString()); } TEST_F(IncludeTest, TestContextIncludeWithOverrides) diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index 623ed695..bc791c93 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -5,6 +5,7 @@ #include "jinja2cpp/template.h" #include "jinja2cpp/user_callable.h" +#include "jinja2cpp/generic_list_iterator.h" #include "test_tools.h" using namespace jinja2; @@ -79,6 +80,7 @@ TEST(UserCallableTest, SimpleUserCallableWithParams2) {{ test(str2='World!', str1='Hello') }} {{ test(str2='World!') }} {{ test('Hello') }} +{{ test2(['H', 'e', 'l', 'l', 'o']) }} )"; Template tpl; @@ -97,6 +99,17 @@ TEST(UserCallableTest, SimpleUserCallableWithParams2) }, ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} ); + params["test2"] = MakeCallable( + [](const GenericList& list) { + std::ostringstream os; + + for(auto& v : list) + os << v.asString(); + + return os.str(); + }, + ArgInfo{"list"} + ); std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; @@ -105,6 +118,7 @@ Hello World! Hello World! World! Hello default +Hello )"; EXPECT_EQ(expectedResult, result); } @@ -179,7 +193,10 @@ TEST_P(UserCallableParamConvertTest, Test) params["DoubleFn"] = MakeCallable([](double val) {return val;}, ArgInfo{"val"}); params["StringFn"] = MakeCallable([](const std::string& val) {return val;}, ArgInfo{"val"}); params["WStringFn"] = MakeCallable([](const std::wstring& val) {return val;}, ArgInfo{"val"}); - params["GListFn"] = MakeCallable([](const GenericList& val) {return val;}, ArgInfo{"val"}); + params["GListFn"] = MakeCallable([](const GenericList& val) + { + return val; + }, ArgInfo{"val"}); params["GMapFn"] = MakeCallable([](const GenericMap& val) {return val;}, ArgInfo{"val"}); params["VarArgsFn"] = MakeCallable([](const ValuesList& val) { return val; diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 0145c686..bd2a8f6f 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -71,7 +71,7 @@ endif () if (NOT DEFINED JINJA2_PUBLIC_LIBS_INT) - set (JINJA2CPP_PUBLIC_LIBS expected-lite variant-lite value-ptr-lite optional-lite) + set (JINJA2CPP_PUBLIC_LIBS expected-lite variant-lite optional-lite) else () set (JINJA2CPP_PUBLIC_LIBS ${JINJA2_PUBLIC_LIBS_INT}) endif () diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index 5cd58d60..ffe8deb8 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -9,14 +9,9 @@ update_submodule(nonstd/optional-lite) add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) add_library(optional-lite ALIAS optional-lite) -update_submodule(nonstd/value-ptr-lite) -add_subdirectory(thirdparty/nonstd/value-ptr-lite EXCLUDE_FROM_ALL) -add_library(value-ptr-lite ALIAS value-ptr-lite) - install (FILES thirdparty/nonstd/expected-lite/include/nonstd/expected.hpp thirdparty/nonstd/variant-lite/include/nonstd/variant.hpp thirdparty/nonstd/optional-lite/include/nonstd/optional.hpp - thirdparty/nonstd/value-ptr-lite/include/nonstd/value_ptr.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd) \ No newline at end of file diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite index 4c4da907..4ed80e7c 160000 --- a/thirdparty/nonstd/optional-lite +++ b/thirdparty/nonstd/optional-lite @@ -1 +1 @@ -Subproject commit 4c4da9074ac4c779bb81332fd82292edd8a6089d +Subproject commit 4ed80e7c1c2c78b61dee468dda3089c1467f1000 diff --git a/thirdparty/nonstd/value-ptr-lite b/thirdparty/nonstd/value-ptr-lite deleted file mode 160000 index cb7f9ff0..00000000 --- a/thirdparty/nonstd/value-ptr-lite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cb7f9ff07efa9905bdba28d15d09bf0764833b0f diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index 7aea1cd3..6bb6a693 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -3,8 +3,7 @@ message(STATUS "'conan-build' dependencies mode selected for Jinja2Cpp. All depe find_package(expected-lite) find_package(variant-lite) find_package(optional-lite) -find_package(value-ptr-lite) find_package(boost) set (JINJA2_PRIVATE_LIBS_INT boost::boost) -set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite value-ptr-lite::value-ptr-lite optional-lite::optional-lite) +set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite optional-lite::optional-lite) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake index fc2cba7e..ea69d32b 100644 --- a/thirdparty/thirdparty-external.cmake +++ b/thirdparty/thirdparty-external.cmake @@ -37,9 +37,8 @@ endmacro () find_hdr_package(expected-lite nonstd/expected.hpp) find_hdr_package(variant-lite nonstd/variant.hpp) find_hdr_package(optional-lite nonstd/optional.hpp) -find_hdr_package(value-ptr-lite nonstd/value_ptr.hpp) -install(TARGETS expected-lite variant-lite optional-lite value-ptr-lite +install(TARGETS expected-lite variant-lite optional-lite EXPORT InstallTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} From c6b8bfed801d8ce239f5df4faa7361264c8e386f Mon Sep 17 00:00:00 2001 From: MeanSquaredError <35379301+MeanSquaredError@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:58:03 +0300 Subject: [PATCH 093/206] Fix a buffer overrun in ParserTraits::GetAsString (second pull request) (#125) * Remove trailing garbage characters in return values. Add error checking for calls to std::wcsrtombs * Fix buffer overrun in ParserTraits::GetAsString by replacing its string conversion code with a call to StringConverter::DoConvert --- src/helpers.h | 42 ++++++++++++++++++++++++------------------ src/template_parser.h | 12 ++---------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/helpers.h b/src/helpers.h index b814be62..999ed65b 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -64,23 +64,26 @@ struct StringConverter static std::string DoConvert(const std::wstring& from) { std::mbstate_t state = std::mbstate_t(); - auto src = from.data(); + auto srcPtr = from.data(); + std::size_t srcSize = from.size(); + std::size_t destBytes = 0; + #ifndef _MSC_VER - std::size_t len = 1 + std::wcsrtombs(nullptr, &src, 0, &state); + destBytes = std::wcsrtombs(nullptr, &srcPtr, srcSize, &state); + if (destBytes == (size_t)-1) + return std::string(); #else - std::size_t len = 0; - auto err = wcsrtombs_s(&len, nullptr, 0, &src, 0, &state); + auto err = wcsrtombs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); if (err != 0) return std::string(); - ++ len; #endif std::string result; - result.resize(len); - src = from.data(); + result.resize(destBytes); #ifndef _MSC_VER - std::wcsrtombs(&result[0], &src, from.size(), &state); + std::wcsrtombs(&result[0], &srcPtr, srcSize, &state); #else - wcsrtombs_s(&len, &result[0], len, &src, from.size(), &state); + wcsrtombs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); + result.resize(destBytes-1); #endif return result; } @@ -92,23 +95,26 @@ struct StringConverter static std::wstring DoConvert(const std::string& from) { std::mbstate_t state = std::mbstate_t(); - auto src = from.data(); + auto srcPtr = from.data(); + std::size_t srcSize = from.size(); + std::size_t destBytes = 0; + #ifndef _MSC_VER - std::size_t len = 1 + std::mbsrtowcs(NULL, &src, 0, &state); + destBytes = std::mbsrtowcs(nullptr, &srcPtr, srcSize, &state); + if (destBytes == (size_t)-1) + return std::wstring(); #else - std::size_t len = 0; - auto err = mbsrtowcs_s(&len, NULL, 0, &src, 0, &state); + auto err = mbsrtowcs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); if (err != 0) return std::wstring(); - ++len; #endif std::wstring result; - result.resize(len); - src = from.data(); + result.resize(destBytes); #ifndef _MSC_VER - std::mbsrtowcs(&result[0], &src, result.size(), &state); + std::mbsrtowcs(&result[0], &srcPtr, srcSize, &state); #else - mbsrtowcs_s(&len, &result[0], len, &src, result.size(), &state); + mbsrtowcs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); + result.resize(destBytes-1); #endif return result; } diff --git a/src/template_parser.h b/src/template_parser.h index c2841634..512d468c 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -136,16 +136,8 @@ struct ParserTraits : public ParserTraitsBase<> } static std::string GetAsString(const std::wstring& str, CharRange range) { - auto tmpStr = str.substr(range.startOffset, range.size()); - std::string result; - result.resize(tmpStr.size(), '\0'); -#ifdef _MSC_VER - size_t dummy = 0; - wcstombs_s(&dummy, &result[0], result.size(), tmpStr.c_str(), tmpStr.size()); -#else - wcstombs(&result[0], tmpStr.c_str(), result.size()); -#endif - return result; + auto srcStr = str.substr(range.startOffset, range.size()); + return detail::StringConverter::DoConvert(srcStr); } static InternalValue RangeToNum(const std::wstring& /*str*/, CharRange /*range*/, Token::Type /*hint*/) { From ed5e86e53fda532b01aa03e3fc778777899815d3 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Wed, 7 Aug 2019 03:04:37 +0300 Subject: [PATCH 094/206] Add sanitized build (#126) * Add basic support for sanitized build + small rework of coverage instrumented build * remove debug prints * pull request fixes --- .travis.yml | 15 ++- CMakeLists.txt | 126 +++++++++++++++++------- cmake/coverage.cmake | 24 +++++ cmake/sanitizer.address+undefined.cmake | 1 + cmake/sanitizer.cmake | 41 ++++++++ cmake/sanitizer.memory.cmake | 1 + thirdparty/CMakeLists.txt | 4 +- 7 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 cmake/coverage.cmake create mode 100644 cmake/sanitizer.address+undefined.cmake create mode 100644 cmake/sanitizer.cmake create mode 100644 cmake/sanitizer.memory.cmake diff --git a/.travis.yml b/.travis.yml index eec5124d..55b9f350 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,6 +76,17 @@ matrix: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] packages: ['cmake', 'clang-6.0', 'g++-7'] + - os: linux + compiler: clang + env: + COMPILER=clang++-6.0 + EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 + SANITIZE_BUILD=address+undefined + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] + packages: ['cmake', 'clang-6.0', 'g++-7'] + before_install: - date -u - uname -a @@ -86,9 +97,11 @@ install: script: - export BUILD_TARGET="all" + - export CMAKE_OPTS="-DCMAKE_VERBOSE_MAKEFILE=ON" - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi - if [[ "${BUILD_CONFIG}" == "" ]]; then export BUILD_CONFIG="Release"; fi - - if [[ "${COLLECT_COVERAGE}" != "" ]]; then export BUILD_CONFIG="Debug" && export CMAKE_OPTS="-DCOVERAGE_ENABLED=TRUE"; fi + - if [[ "${COLLECT_COVERAGE}" != "" ]]; then export BUILD_CONFIG="Debug" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_COVERAGE=ON"; fi + - if [[ "${SANITIZE_BUILD}" != "" ]]; then export BUILD_CONFIG="RelWithDebInfo" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_SANITIZERS=${SANITIZE_BUILD}"; fi - $CXX --version - mkdir -p build && cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a05d488..67b9a705 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,13 +26,31 @@ endif () include(CMakePackageConfigHelpers) include(GNUInstallDirs) -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED) +if (JINJA2CPP_WITH_COVERAGE) message (STATUS "This is DEBUG build with enabled Code Coverage") set (CMAKE_BUILD_TYPE Debug) - include(code_coverage) - setup_target_for_coverage(build_coverage jinja2cpp_tests coverage) + set (JINJA2CPP_COVERAGE_TARGET "jinja2cpp_build_coverage") + include(coverage) + add_coverage_target("${JINJA2CPP_COVERAGE_TARGET}") +endif () + +set(JINJA2CPP_SANITIZERS address+undefined memory) +set(JINJA2CPP_WITH_SANITIZERS none CACHE STRING "Build with sanitizer") +set_property(CACHE JINJA2CPP_WITH_SANITIZERS PROPERTY STRINGS ${JINJA2CPP_SANITIZERS}) + +if(NOT ${JINJA2CPP_WITH_SANITIZERS} STREQUAL "none") + message (STATUS "Build with sanitizers enabled: ${JINJA2CPP_WITH_SANITIZERS}") + set(_chosen_san) + list(FIND JINJA2CPP_SANITIZERS ${JINJA2CPP_WITH_SANITIZERS} _chosen_san) + if (${_chosen_san} EQUAL -1) + message(FATAL_ERROR "Wrong sanitizer type has been chosen, must be one of ${JINJA2CPP_SANITIZERS}") + endif() + + include("sanitizer.${JINJA2CPP_WITH_SANITIZERS}") + set (JINJA2CPP_SANITIZE_TARGET "jinja2cpp_build_sanitizers") + add_sanitizer_target(${JINJA2CPP_SANITIZE_TARGET}) endif() if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") @@ -86,13 +104,40 @@ add_library(${LIB_TARGET_NAME} ${LIB_LINK_TYPE} string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") +set(JINJA2CPP_EXTRA_LIBS "" CACHE STRING "You can pass some libs that could used during link stage") +set(JINJA2CPP_PUBLIC_LIBS "${JINJA2CPP_EXTRA_LIBS}") +separate_arguments(JINJA2CPP_PUBLIC_LIBS) +if (JINJA2CPP_WITH_COVERAGE) + target_compile_options( + ${JINJA2CPP_COVERAGE_TARGET} + INTERFACE + -g -O0 + ) + list(APPEND JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_COVERAGE_TARGET}) +endif() +if (NOT JINJA2CPP_WITH_SANITIZERS STREQUAL "none") + target_compile_options( + ${JINJA2CPP_SANITIZE_TARGET} + INTERFACE + -g -O2 + ) + list(APPEND JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_SANITIZE_TARGET}) +endif() + +set(JINJA2CPP_PRIVATE_LIBS "${JINJA2CPP_PRIVATE_LIBS}") include(thirdparty/CMakeLists.txt) -target_link_libraries(${LIB_TARGET_NAME} PUBLIC ${JINJA2CPP_PUBLIC_LIBS} PRIVATE ${JINJA2CPP_PRIVATE_LIBS}) +target_link_libraries( + ${LIB_TARGET_NAME} + PUBLIC + ${JINJA2CPP_PUBLIC_LIBS} + PRIVATE + ${JINJA2CPP_PRIVATE_LIBS} +) target_include_directories(${LIB_TARGET_NAME} PUBLIC - $ + $ $) if(JINJA2CPP_STRICT_WARNINGS) @@ -102,16 +147,13 @@ else () target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() endif() -if (COVERAGE_ENABLED AND NOT MSVC) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -g PUBLIC -O0 --coverage -fprofile-arcs -ftest-coverage) -endif () target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) set_target_properties(${LIB_TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 - ) +) if (JINJA2CPP_IS_MAIN_PROJECT) set_target_properties(${LIB_TARGET_NAME} PROPERTIES @@ -130,9 +172,6 @@ if (JINJA2CPP_BUILD_TESTS) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) target_link_libraries(jinja2cpp_tests gtest gtest_main ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS}) - if (COVERAGE_ENABLED) - target_link_libraries(jinja2cpp_tests gcov) - endif () set_target_properties(jinja2cpp_tests PROPERTIES CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} @@ -147,7 +186,7 @@ if (JINJA2CPP_BUILD_TESTS) COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data ${CMAKE_CURRENT_BINARY_DIR}/test_data MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data/simple_template1.j2tpl COMMENT "Copy test data to the destination dir" - ) + ) add_custom_target(CopyTestData ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl @@ -158,8 +197,8 @@ if (JINJA2CPP_BUILD_TESTS) add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests) endif () -set (JINJA2CPP_INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/${LIB_TARGET_NAME}) -set (JINJA2CPP_TMP_CONFIG_PATH cmake/config) +set (JINJA2CPP_INSTALL_CONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/${LIB_TARGET_NAME}") +set (JINJA2CPP_TMP_CONFIG_PATH "cmake/config") macro (Jinja2CppGetTargetIncludeDir infix target) @@ -170,7 +209,7 @@ macro (Jinja2CppGetTargetIncludeDir infix target) get_target_property(${_J2CPP_VAR_NAME} ${target} INTERFACE_INCLUDE_DIRECTORIES) endif () endmacro () - + Jinja2CppGetTargetIncludeDir(EXPECTED-LITE expected-lite) Jinja2CppGetTargetIncludeDir(VARIANT-LITE variant-lite) Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) @@ -179,36 +218,51 @@ Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) # We can't use EXPORT feature of 'install' as is due to limitation of subproject's targets installation # So jinja2cpp-config.cmake should be written manually -install(TARGETS ${LIB_TARGET_NAME} - EXPORT InstallTargets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jinja2cpp - ) - -install (FILES ${CMAKE_BINARY_DIR}/jinja2cpp.pc - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) - -install(EXPORT InstallTargets - FILE jinja2cpp-cfg.cmake - DESTINATION ${JINJA2CPP_INSTALL_CONFIG_DIR}) - +install( + TARGETS + ${LIB_TARGET_NAME} + EXPORT + InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jinja2cpp +) + +install( + FILES + ${CMAKE_BINARY_DIR}/jinja2cpp.pc + DESTINATION + ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig +) + +install( + EXPORT + InstallTargets + FILE + jinja2cpp-cfg.cmake + DESTINATION + ${JINJA2CPP_INSTALL_CONFIG_DIR} +) + configure_package_config_file( cmake/public/jinja2cpp-config.cmake.in ${JINJA2CPP_TMP_CONFIG_PATH}/jinja2cpp-config.cmake INSTALL_DESTINATION ${JINJA2CPP_TMP_CONFIG_PATH} NO_CHECK_REQUIRED_COMPONENTS_MACRO - ) +) configure_package_config_file( cmake/public/jinja2cpp-config-deps-${JINJA2CPP_DEPS_MODE}.cmake.in ${JINJA2CPP_TMP_CONFIG_PATH}/jinja2cpp-config-deps.cmake INSTALL_DESTINATION ${JINJA2CPP_TMP_CONFIG_PATH} NO_CHECK_REQUIRED_COMPONENTS_MACRO - ) +) -install (FILES +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${JINJA2CPP_TMP_CONFIG_PATH}/${LIB_TARGET_NAME}-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${JINJA2CPP_TMP_CONFIG_PATH}/${LIB_TARGET_NAME}-config-deps.cmake - DESTINATION ${JINJA2CPP_INSTALL_CONFIG_DIR}) + DESTINATION + ${JINJA2CPP_INSTALL_CONFIG_DIR} +) diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake new file mode 100644 index 00000000..0aabecbf --- /dev/null +++ b/cmake/coverage.cmake @@ -0,0 +1,24 @@ +if ((NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") AND (NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")) + message(WARNING "coverage build is not supported on such compiler ${CMAKE_CXX_COMPILER_ID}") + set(JINJA2CPP_WITH_COVERAGE OFF) + return() +endif() + +function(add_coverage_target _TARGET) + if (NOT TARGET ${_TARGET}) + add_library(${_TARGET} INTERFACE) + endif() + target_compile_options( + ${_TARGET} + INTERFACE + -fprofile-arcs -ftest-coverage + ) + target_link_libraries(${_TARGET} INTERFACE gcov) + + install( + TARGETS + ${_TARGET} + EXPORT + InstallTargets + ) +endfunction() diff --git a/cmake/sanitizer.address+undefined.cmake b/cmake/sanitizer.address+undefined.cmake new file mode 100644 index 00000000..91e3a703 --- /dev/null +++ b/cmake/sanitizer.address+undefined.cmake @@ -0,0 +1 @@ +include(sanitizer) diff --git a/cmake/sanitizer.cmake b/cmake/sanitizer.cmake new file mode 100644 index 00000000..36e4bc37 --- /dev/null +++ b/cmake/sanitizer.cmake @@ -0,0 +1,41 @@ +#if ((NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") AND (NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")) +# message(WARNING "sanitized build is not supported using this compiler ${CMAKE_CXX_COMPILER_ID}") +# set(JINJA2CPP_WITH_SANITIZERS OFF) +# return() +#endif() + +set(_BASE_SANITIZER_FLAGS "-fno-omit-frame-pointer -fno-optimize-sibling-calls") +separate_arguments(_BASE_SANITIZER_FLAGS) +set(_BASE_ENABLE_SANITIZER_FLAGS) +if(JINJA2CPP_WITH_SANITIZERS STREQUAL address+undefined) + set(_BASE_ENABLE_SANITIZER_FLAGS "-fsanitize=address,undefined") +endif() + +if(JINJA2CPP_WITH_SANITIZERS STREQUAL memory) + set(_BASE_ENABLE_SANITIZER_FLAGS "-fsanitize=memory") +endif() + +function(add_sanitizer_target _TARGET) + if (NOT TARGET ${_TARGET}) + add_library(${_TARGET} INTERFACE) + endif() + target_compile_options( + ${_TARGET} + INTERFACE + ${_BASE_SANITIZER_FLAGS} ${_BASE_ENABLE_SANITIZER_FLAGS} + ) + target_link_libraries( + ${_TARGET} + INTERFACE + ${_BASE_ENABLE_SANITIZER_FLAGS} + ) + + install( + TARGETS + ${_TARGET} + EXPORT + InstallTargets + ) +endfunction() + + diff --git a/cmake/sanitizer.memory.cmake b/cmake/sanitizer.memory.cmake new file mode 100644 index 00000000..91e3a703 --- /dev/null +++ b/cmake/sanitizer.memory.cmake @@ -0,0 +1 @@ +include(sanitizer) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index bd2a8f6f..fbdc852a 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -64,14 +64,14 @@ if(JINJA2CPP_BUILD_TESTS) endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) - set (JINJA2CPP_PRIVATE_LIBS boost_variant boost_filesystem boost_algorithm) + set (JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} boost_variant boost_filesystem boost_algorithm) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () if (NOT DEFINED JINJA2_PUBLIC_LIBS_INT) - set (JINJA2CPP_PUBLIC_LIBS expected-lite variant-lite optional-lite) + set (JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_PUBLIC_LIBS} expected-lite variant-lite optional-lite) else () set (JINJA2CPP_PUBLIC_LIBS ${JINJA2_PUBLIC_LIBS_INT}) endif () From fc66464812a73596620003ad26ca9b7849d5bd1f Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 7 Aug 2019 23:44:17 +0300 Subject: [PATCH 095/206] string_view support, fmtlib usage and string/wstring support (#127) * Add new submodules * Use {fmt} instead of iostreams and other optimizations * Fix install dependency on fmtlib * Fix CXX standard setting and appveyor build scripts * Disable perf tests for deubg configuration * Fix C++ standard management in build scripts * Fix build with 'external' dependencies mode * Implement objects pool for internal values * - Use nonstd::string_view wherever it's possible - Use robin_hood hash table instead of default * Use robin hood hashmap library for values storage Full support of string_view as a parameter type Full and transparent support of char/wchar_t within template engine * Fix gcc/clang build * Fix gcc/clang build * Fix build * Update readme * Fix review issues, fix external build and installation --- .gitmodules | 9 + CMakeLists.txt | 26 +- README.md | 16 +- appveyor.yml | 2 +- .../jinja2cpp-config-deps-external.cmake.in | 9 +- include/jinja2cpp/filesystem_handler.h | 10 +- include/jinja2cpp/string_helpers.h | 204 +++++++++ include/jinja2cpp/user_callable.h | 86 +++- include/jinja2cpp/value.h | 45 +- src/error_info.cpp | 8 +- src/expression_evaluator.cpp | 22 +- src/expression_evaluator.h | 7 + src/filesystem_handler.cpp | 27 +- src/filters.cpp | 171 ++++--- src/helpers.h | 87 +--- src/internal_value.cpp | 138 ++++-- src/internal_value.h | 70 ++- src/render_context.h | 5 +- src/statements.cpp | 59 ++- src/string_converter_filter.cpp | 65 ++- src/template.cpp | 27 +- src/template_impl.h | 56 ++- src/template_parser.h | 31 +- src/testers.cpp | 5 + src/value_visitors.h | 255 +++++++++-- test/basic_tests.cpp | 116 ++--- test/errors_test.cpp | 135 +++++- test/expressions_test.cpp | 86 ++-- test/filters_test.cpp | 76 +--- test/forloop_test.cpp | 427 ++++++------------ test/macro_test.cpp | 254 ++++------- test/perf_test.cpp | 84 +++- test/set_test.cpp | 205 ++++----- test/test_tools.h | 115 ++++- test/testers_test.cpp | 8 +- test/user_callable_test.cpp | 200 ++++---- thirdparty/CMakeLists.txt | 4 +- thirdparty/fmtlib | 1 + thirdparty/internal_deps.cmake | 32 +- thirdparty/nonstd/string-view-lite | 1 + thirdparty/robin-hood-hashing | 1 + thirdparty/thirdparty-conan-build.cmake | 6 +- thirdparty/thirdparty-external.cmake | 19 +- 43 files changed, 1962 insertions(+), 1248 deletions(-) create mode 100644 include/jinja2cpp/string_helpers.h create mode 160000 thirdparty/fmtlib create mode 160000 thirdparty/nonstd/string-view-lite create mode 160000 thirdparty/robin-hood-hashing diff --git a/.gitmodules b/.gitmodules index 9b953978..1c61dcb5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,12 @@ [submodule "thirdparty/nonstd/optional-lite"] path = thirdparty/nonstd/optional-lite url = https://github.com/martinmoene/optional-lite.git +[submodule "thirdparty/nonstd/string-view-lite"] + path = thirdparty/nonstd/string-view-lite + url = https://github.com/martinmoene/string-view-lite.git +[submodule "thirdparty/fmtlib"] + path = thirdparty/fmtlib + url = https://github.com/fmtlib/fmt.git +[submodule "thirdparty/robin-hood-hashing"] + path = thirdparty/robin-hood-hashing + url = https://github.com/martinus/robin-hood-hashing.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 67b9a705..d249d925 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,18 @@ if (NOT JINJA2CPP_DEPS_MODE) set (JINJA2CPP_DEPS_MODE "internal") endif () -if (JINJA2CPP_IS_MAIN_PROJECT) +if (JINJA2CPP_IS_MAIN_PROJECT OR NOT CMAKE_CXX_STANDARD) set (JINJA2CPP_CXX_STANDARD 14 CACHE STRING "Jinja2Cpp C++ standard to build with. C++14 is default") - if (NOT JINJA2CPP_CXX_STANDARD) - set (JINJA2CPP_CXX_STANDARD 14) - endif () +endif () + +if (NOT JINJA2CPP_CXX_STANDARD) + set (JINJA2CPP_CXX_STANDARD ${CMAKE_CXX_STANDARD}) +endif () + +if (JINJA2CPP_CXX_STANDARD LESS 14) + message(FATAL_ERROR "Jinja2Cpp is required C++14 or greater standard set. Currently selected standard: ${JINJA2CPP_CXX_STANDARD}") +else() + message(STATUS "Jinja2Cpp C++ standard: ${JINJA2CPP_CXX_STANDARD}") endif () include(CMakePackageConfigHelpers) @@ -155,12 +162,10 @@ set_target_properties(${LIB_TARGET_NAME} PROPERTIES SOVERSION 1 ) -if (JINJA2CPP_IS_MAIN_PROJECT) - set_target_properties(${LIB_TARGET_NAME} PROPERTIES - CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} - CXX_STANDARD_REQUIRED ON - ) -endif () +set_target_properties(${LIB_TARGET_NAME} PROPERTIES + CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} + CXX_STANDARD_REQUIRED ON + ) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY PUBLIC_HEADER ${PublicHeaders} ${JINJA2CPP_EXTRA_PUBLIC_HEADERS}) @@ -213,6 +218,7 @@ endmacro () Jinja2CppGetTargetIncludeDir(EXPECTED-LITE expected-lite) Jinja2CppGetTargetIncludeDir(VARIANT-LITE variant-lite) Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) +Jinja2CppGetTargetIncludeDir(STRING-VIEW-LITE string-view-lite) # Workaround for #14444 bug of CMake (https://gitlab.kitware.com/cmake/cmake/issues/14444) # We can't use EXPORT feature of 'install' as is due to limitation of subproject's targets installation diff --git a/README.md b/README.md index acce8a02..3d8196d9 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ C++ implementation of Jinja2 Python template engine. This library was originally Main features of Jinja2C++: - Easy-to-use public interface. Just load templates and render them. - Conformance to [Jinja2 specification](http://jinja.pocoo.org/docs/2.10/) -- Partial support for both narrow- and wide-character strings both for templates and parameters. +- Full support of 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. -- Control statements (set, for, if, do, with). +- Control statements (`set`, `for`, `if`, `do`, `with`). - Templates extention, including and importing - Macros - Rich error reporting. @@ -118,8 +118,10 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f - Linux gcc 6.0 - Linux gcc 7.0 - Linux clang 5.0 +- Linux clang 6.0 - Microsoft Visual Studio 2015 x86, x64 - Microsoft Visual Studio 2017 x86, x64 +- Microsoft Visual Studio 2019 x86, x64 ## Build and install Jinja2Cpp has several external dependencies: @@ -128,6 +130,9 @@ Jinja2Cpp has several external dependencies: - `nonstd::variant-lite` [https://github.com/martinmoene/variant-lite](https://github.com/martinmoene/variant-lite) - `nonstd::value-ptr-lite` [https://github.com/martinmoene/value-ptr-lite](https://github.com/martinmoene/value-ptr-lite) - `nonstd::optional-lite` [https://github.com/martinmoene/optional-lite](https://github.com/martinmoene/optional-lite) +- `nonstd::string-view-lite` [https://github.com/martinmoene/string-view-lite](https://github.com/martinmoene/string-view-lite) +- `fmtlib::fmt` [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt) +- `robin-hood-hashing` [https://github.com/martinus/robin-hood-hashing](https://github.com/martinus/robin-hood-hashing) In simpliest case to compile Jinja2Cpp you need: @@ -230,7 +235,12 @@ In case of C++17 standard enabled for your project you should define `variant_CO ## Acknowledgments Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. -Thanks to @martinmoene for perfectly implemented xxx-lite libraries. +Thanks to @martinmoene for the perfectly implemented xxx-lite libraries. + +Thanks to @vitaut for the amazing text formatting library + +Thanks to @martinus for the fast hash maps implementation + ## Changelog diff --git a/appveyor.yml b/appveyor.yml index c1ab8b53..71e272bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -49,7 +49,7 @@ init: build_script: - mkdir -p build && cd build - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%configuration% -DMSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=external-boost - - cmake --build . --target all + - cmake --build . --target all --config Release test_script: - ctest -C Release -V diff --git a/cmake/public/jinja2cpp-config-deps-external.cmake.in b/cmake/public/jinja2cpp-config-deps-external.cmake.in index b6c670eb..9a189890 100644 --- a/cmake/public/jinja2cpp-config-deps-external.cmake.in +++ b/cmake/public/jinja2cpp-config-deps-external.cmake.in @@ -19,7 +19,14 @@ set_target_properties(optional-lite PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_OPTIONAL-LITE_INCLUDE_DIRECTORIES@" ) -set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite optional-lite) +# Create imported target string-view-lite +add_library(string-view-lite INTERFACE IMPORTED) + +set_target_properties(string-view-lite PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_STRING-VIEW-LITE_INCLUDE_DIRECTORIES@" +) + +set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite optional-lite string-view-lite) macro (Jinja2CppAddBoostDep name) if (TARGET Boost::${name}) diff --git a/include/jinja2cpp/filesystem_handler.h b/include/jinja2cpp/filesystem_handler.h index 19fbb05f..2879ff4f 100644 --- a/include/jinja2cpp/filesystem_handler.h +++ b/include/jinja2cpp/filesystem_handler.h @@ -2,6 +2,7 @@ #define JINJA2CPP_FILESYSTEM_HANDLER_H #include +#include #include #include @@ -37,8 +38,13 @@ class MemoryFileSystem : public IFilesystemHandler WCharFileStreamPtr OpenWStream(const std::string& name) const override; private: - using FileContent = nonstd::variant; - std::unordered_map m_filesMap; + // using FileContent = nonstd::variant; + struct FileContent + { + nonstd::optional narrowContent; + nonstd::optional wideContent; + }; + mutable std::unordered_map m_filesMap; }; class RealFileSystem : public IFilesystemHandler diff --git a/include/jinja2cpp/string_helpers.h b/include/jinja2cpp/string_helpers.h new file mode 100644 index 00000000..99ded498 --- /dev/null +++ b/include/jinja2cpp/string_helpers.h @@ -0,0 +1,204 @@ +#ifndef JINJA2_STRING_HELPERS_H +#define JINJA2_STRING_HELPERS_H + +#include +#include "value.h" + +#include +#include +#include + +namespace jinja2 +{ +namespace detail +{ + template + struct StringConverter; + + template + struct StringConverter + { + static Src DoConvert(const nonstd::basic_string_view& from) + { + return Src(from.begin(), from.end()); + } + }; + + template<> + struct StringConverter + { + static std::string DoConvert(const nonstd::wstring_view& from) + { + std::mbstate_t state = std::mbstate_t(); + auto srcPtr = from.data(); + std::size_t srcSize = from.size(); + std::size_t destBytes = 0; + +#ifndef _MSC_VER + destBytes = std::wcsrtombs(nullptr, &srcPtr, srcSize, &state); + if (destBytes == static_cast(-1)) + return std::string(); +#else + auto err = wcsrtombs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); + if (err != 0) + return std::string(); +#endif + std::string result; +#ifndef _MSC_VER + result.resize(destBytes + 1); + auto converted = std::wcsrtombs(&result[0], &srcPtr, srcSize, &state); + if (converted == static_cast(-1)) + return std::string(); + result.resize(converted); +#else + result.resize(destBytes); + wcsrtombs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); + result.resize(destBytes - 1); +#endif + return result; + } + }; + + template<> + struct StringConverter + { + static std::wstring DoConvert(const nonstd::string_view& from) + { + std::mbstate_t state = std::mbstate_t(); + auto srcPtr = from.data(); + std::size_t srcSize = from.size(); + std::size_t destBytes = 0; + +#ifndef _MSC_VER + destBytes = std::mbsrtowcs(nullptr, &srcPtr, srcSize, &state); + if (destBytes == static_cast(-1)) + return std::wstring(); +#else + auto err = mbsrtowcs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); + if (err != 0) + return std::wstring(); +#endif + std::wstring result; +#ifndef _MSC_VER + result.resize(destBytes + 1); + srcPtr = from.data(); + auto converted = std::mbsrtowcs(&result[0], &srcPtr, srcSize, &state); + if (converted == static_cast(-1)) + return std::wstring(); + result.resize(converted); +#else + result.resize(destBytes); + mbsrtowcs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); + result.resize(destBytes - 1); +#endif + return result; + } + }; + + template + struct StringConverter, T> : public StringConverter, T> {}; + +} // detail + +template +Dst ConvertString(Src&& from) +{ + using src_t = std::decay_t; + return detail::StringConverter>::DoConvert(nonstd::basic_string_view(from)); +} + +inline const std::string AsString(const std::string& str) +{ + return str; +} + +inline std::string AsString(const std::wstring& str) +{ + return ConvertString(str); +} + +inline std::string AsString(const nonstd::string_view& str) +{ + return std::string(str.begin(), str.end()); +} + +inline std::string AsString(const nonstd::wstring_view& str) +{ + return ConvertString(str); +} + +inline const std::wstring AsWString(const std::wstring& str) +{ + return str; +} + +inline std::wstring AsWString(const std::string& str) +{ + return ConvertString(str); +} + +inline std::wstring AsWString(const nonstd::wstring_view& str) +{ + return std::wstring(str.begin(), str.end()); +} + +inline std::wstring AsWString(const nonstd::string_view& str) +{ + return ConvertString(str); +} + +namespace detail +{ +struct StringGetter +{ + template + std::string operator()(const std::basic_string& str) const + { + return AsString(str); + } + template + std::string operator()(const nonstd::basic_string_view& str) const + { + return AsString(str); + } + + template + std::string operator()(T&&) const + { + return std::string(); + } +}; + +struct WStringGetter +{ + template + std::wstring operator()(const std::basic_string& str) const + { + return AsWString(str); + } + template + std::wstring operator()(const nonstd::basic_string_view& str) const + { + return AsWString(str); + } + + template + std::wstring operator()(T&&) const + { + return std::wstring(); + } +}; +} + +inline std::string AsString(const Value& val) +{ + return nonstd::visit(detail::StringGetter(), val.data()); +} + +inline std::wstring AsWString(const Value& val) +{ + return nonstd::visit(detail::WStringGetter(), val.data()); +} +} + +#endif // JINJA2_STRING_HELPERS_H \ No newline at end of file diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h index 72d9121e..bc307631 100644 --- a/include/jinja2cpp/user_callable.h +++ b/include/jinja2cpp/user_callable.h @@ -2,6 +2,8 @@ #define USER_CALLABLE_H #include "value.h" +#include "string_helpers.h" +#include #include @@ -15,6 +17,86 @@ struct CanBeCalled : std::false_type {}; template struct CanBeCalled::value>::type> : std::true_type {}; +template +struct ArgPromoter +{ + ArgPromoter(const T* val) : m_ptr(val) {} + + operator T() const { return *m_ptr; } + + const T* m_ptr; +}; + +template<> +struct ArgPromoter +{ +public: + ArgPromoter(const EmptyValue*) {} + + template + operator T() { return T(); } +}; + +template +struct ArgPromoter> +{ + using string = std::basic_string; + using string_view = nonstd::basic_string_view; + using other_string = std::conditional_t::value, std::wstring, std::string>; + using other_string_view = std::conditional_t::value, nonstd::wstring_view, nonstd::string_view>; + + ArgPromoter(const string* str) : m_ptr(str) {} + + operator const string&() const { return *m_ptr; } + operator string () const { return *m_ptr; } + operator string_view () const { return *m_ptr; } + operator other_string () const + { + return ConvertString(*m_ptr); + } + operator other_string_view () const + { + m_convertedStr = ConvertString(*m_ptr); + return m_convertedStr.value(); + } + + const string* m_ptr; + mutable nonstd::optional m_convertedStr; +}; + +template +struct ArgPromoter> +{ + using string = std::basic_string; + using string_view = nonstd::basic_string_view; + using other_string = std::conditional_t::value, std::wstring, std::string>; + using other_string_view = std::conditional_t::value, nonstd::wstring_view, nonstd::string_view>; + + ArgPromoter(const string_view* str) : m_ptr(str) {} + + operator const string_view& () const { return *m_ptr; } + operator string_view () const { return *m_ptr; } + operator string () const { return string(m_ptr->begin(), m_ptr->end()); } + operator other_string () const + { + return ConvertString(*m_ptr); + } + operator other_string_view () const + { + m_convertedStr = ConvertString(*m_ptr); + return m_convertedStr.value(); + } + + const string_view* m_ptr; + mutable nonstd::optional m_convertedStr; +}; + +template +auto Promote(Arg&& arg) +{ + return ArgPromoter>(&arg); +} + template struct UCInvoker { @@ -25,7 +107,7 @@ struct UCInvoker struct FuncTester { template - static auto TestFn(F&& f) -> decltype(Value(f(std::declval()...))); + static auto TestFn(F&& f) -> decltype(Value(f(Promote(std::declval())...))); static auto TestFn(...) -> char; using result_type = decltype(TestFn(std::declval())); @@ -39,7 +121,7 @@ struct UCInvoker template auto operator()(Args&& ... args) const -> std::enable_if_t>::value, Value> { - return Value(fn(args...)); + return Value(fn(Promote(args)...)); } template diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index a335ca9f..4beca729 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -2,15 +2,17 @@ #define JINJA2_VALUE_H #include "generic_list.h" +#include "value_ptr.hpp" + +#include +#include +#include #include #include #include #include #include -#include -#include -#include namespace jinja2 { @@ -74,7 +76,22 @@ using RecWrapper = nonstd::value_ptr; class Value { public: - using ValueData = nonstd::variant, RecWrapper, GenericList, GenericMap, RecWrapper>; + using ValueData = nonstd::variant< + EmptyValue, + bool, + std::string, + std::wstring, + nonstd::string_view, + nonstd::wstring_view, + int64_t, + double, + RecWrapper, + RecWrapper, + GenericList, + GenericMap, + RecWrapper + >; + template struct AnyOf : public std::false_type {}; @@ -89,7 +106,7 @@ class Value Value& operator =(const Value&); Value& operator =(Value&&) noexcept; template - Value(T&& val, typename std::enable_if::value>::type* = nullptr) + Value(T&& val, typename std::enable_if::value>::type* = nullptr) : m_data(std::forward(val)) { } @@ -102,6 +119,11 @@ class Value : m_data(std::string(val)) { } + template + Value(wchar_t (&val)[N]) + : m_data(std::wstring(val)) + { + } Value(int val) : m_data(static_cast(val)) { @@ -142,6 +164,19 @@ class Value return nonstd::get(m_data); } + bool isWString() const + { + return nonstd::get_if(&m_data) != nullptr; + } + auto& asWString() + { + return nonstd::get(m_data); + } + auto& asWString() const + { + return nonstd::get(m_data); + } + bool isList() const { return nonstd::get_if>(&m_data) != nullptr || nonstd::get_if(&m_data) != nullptr; diff --git a/src/error_info.cpp b/src/error_info.cpp index b19bace0..2c29834d 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -26,6 +26,12 @@ struct ValueRenderer os << ConvertString>(val); } + template + void operator() (const nonstd::basic_string_view& val) const + { + os << ConvertString>(val); + } + void operator() (const ValuesList& vals) const { os << '{'; @@ -76,7 +82,7 @@ struct ValueRenderer } - void operator() (const UserCallable& val) const + void operator() (const UserCallable& /*val*/) const { } diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index 83c76bc8..af4c0497 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -182,7 +182,7 @@ InternalValue DictCreator::Evaluate(RenderContext& context) result[e.first] = e.second->Evaluate(context); } - return MapAdapter::CreateAdapter(std::move(result));; + return CreateMapAdapter(std::move(result));; } ExpressionFilter::ExpressionFilter(const std::string& filterName, CallParams params) @@ -237,18 +237,7 @@ InternalValue DictionaryCreator::Evaluate(RenderContext& context) InternalValue CallExpression::Evaluate(RenderContext& values) { - enum - { - InvalidFn = -1, - RangeFn = 1, - LoopCycleFn = 2 - }; - - auto& scope = values.EnterScope(); - scope["range"] = InternalValue(static_cast(RangeFn)); - scope["loop"] = MapAdapter::CreateAdapter(InternalValueMap{{"cycle", InternalValue(static_cast(LoopCycleFn))}}); auto fn = m_valueRef->Evaluate(values); - values.ExitScope(); auto fnId = ConvertToInt(fn, InvalidFn); @@ -262,8 +251,6 @@ InternalValue CallExpression::Evaluate(RenderContext& values) default: return CallArbitraryFn(values); } - - return InternalValue(); } void CallExpression::Render(OutStream& stream, RenderContext& values) @@ -372,6 +359,13 @@ InternalValue CallExpression::CallLoopCycle(RenderContext& values) return m_params.posParams[idx]->Evaluate(values); } + +void SetupGlobals(InternalValueMap& globalParams) +{ + globalParams["range"] = InternalValue(static_cast(RangeFn)); + // globalParams["loop"] = MapAdapter::CreateAdapter(InternalValueMap{{"cycle", InternalValue(static_cast(LoopCycleFn))}}); +} + namespace helpers { enum ArgState diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index 8d35dac9..38573ffd 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -10,6 +10,13 @@ namespace jinja2 { +enum +{ + InvalidFn = -1, + RangeFn = 1, + LoopCycleFn = 2 +}; + class ExpressionEvaluatorBase { public: diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index 9cf1c087..4f90e572 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -36,12 +37,12 @@ struct FileContentConverter void MemoryFileSystem::AddFile(std::string fileName, std::string fileContent) { - m_filesMap[std::move(fileName)] = FileContent(std::move(fileContent)); + m_filesMap[std::move(fileName)] = FileContent{std::move(fileContent), {}}; } void MemoryFileSystem::AddFile(std::string fileName, std::wstring fileContent) { - m_filesMap[std::move(fileName)] = FileContent(std::move(fileContent)); + m_filesMap[std::move(fileName)] = FileContent{ {}, std::move(fileContent) }; } CharFileStreamPtr MemoryFileSystem::OpenStream(const std::string& name) const @@ -51,8 +52,15 @@ CharFileStreamPtr MemoryFileSystem::OpenStream(const std::string& name) const if (p == m_filesMap.end()) return result; - TargetFileStream targetStream(&result); - visit(FileContentConverter(), p->second, targetStream); + auto& content = p->second; + + if (!content.narrowContent && !content.wideContent) + return result; + + if (!content.narrowContent) + content.narrowContent = ConvertString(content.wideContent.value()); + + result.reset(new std::istringstream(content.narrowContent.value())); return result; } @@ -64,8 +72,15 @@ WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const if (p == m_filesMap.end()) return result; - TargetFileStream targetStream(&result); - visit(FileContentConverter(), p->second, targetStream); + auto& content = p->second; + + if (!content.narrowContent && !content.wideContent) + return result; + + if (!content.wideContent) + content.wideContent = ConvertString(content.narrowContent.value()); + + result.reset(new std::wistringstream(content.wideContent.value())); return result; } diff --git a/src/filters.cpp b/src/filters.cpp index ca8441c4..4bcb1eca 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -314,7 +314,7 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte for (auto& g : groups) { InternalValueMap groupItem{{"grouper", std::move(g.grouper)}, {"list", ListAdapter::CreateAdapter(std::move(g.items))}}; - result.push_back(MapAdapter::CreateAdapter(std::move(groupItem))); + result.push_back(CreateMapAdapter(std::move(groupItem))); } return ListAdapter::CreateAdapter(std::move(result)); @@ -416,7 +416,7 @@ InternalValue Map::Filter(const InternalValue& baseVal, RenderContext& context) return ListAdapter::CreateAdapter(std::move(resultList)); } -struct PrettyPrinter : visitors::BaseVisitor +struct PrettyPrinter : visitors::BaseVisitor { using BaseVisitor::operator(); @@ -424,11 +424,12 @@ struct PrettyPrinter : visitors::BaseVisitor : m_context(context) {} - InternalValue operator()(const ListAdapter& list) const + std::string operator()(const ListAdapter& list) const { - std::ostringstream os; + std::string str; + auto os = std::back_inserter(str); - os << "["; + fmt::format_to(os, "["); bool isFirst = true; for (auto& v : list) @@ -436,18 +437,20 @@ struct PrettyPrinter : visitors::BaseVisitor if (isFirst) isFirst = false; else - os << ", "; - os << AsString(Apply(v, m_context)); + fmt::format_to(os, ", "); + fmt::format_to(os, "{}", Apply(v, m_context)); } - os << "]"; + fmt::format_to(os, "]"); - return InternalValue(os.str()); + return str; } - InternalValue operator()(const MapAdapter& map) const + std::string operator()(const MapAdapter& map) const { - std::ostringstream os; - os << "{"; + std::string str; + auto os = std::back_inserter(str); + + fmt::format_to(os, "{{"); const auto& keys = map.GetKeys(); @@ -457,64 +460,76 @@ struct PrettyPrinter : visitors::BaseVisitor if (isFirst) isFirst = false; else - os << ", "; + fmt::format_to(os, ", "); - os << "'" << k << "': "; - os << AsString(Apply(map.GetValueByName(k), m_context)); + fmt::format_to(os, "'{}': ", k); + fmt::format_to(os, "{}", Apply(map.GetValueByName(k), m_context)); } - os << "}"; + fmt::format_to(os, "}}"); - return InternalValue(os.str()); + return str; } - InternalValue operator() (const KeyValuePair& kwPair) const + std::string operator() (const KeyValuePair& kwPair) const { - std::ostringstream os; + std::string str; + auto os = std::back_inserter(str); + + fmt::format_to(os, "'{}': ", kwPair.key); + fmt::format_to(os, "{}", Apply(kwPair.value, m_context)); + + return str; + } - os << "'" << kwPair.key << "': "; - os << AsString(Apply(kwPair.value, m_context)); + std::string operator()(const std::string& str) const + { + return fmt::format("'{}'", str); + } - return InternalValue(os.str()); + std::string operator()(const nonstd::string_view& str) const + { + return fmt::format("'{}'", fmt::basic_string_view(str.data(), str.size())); } - InternalValue operator()(const std::string& str) const + std::string operator()(const std::wstring& str) const { - return "'"s + str + "'"s; + return fmt::format("'{}'", ConvertString(str)); } - InternalValue operator()(const std::wstring&) const + std::string operator()(const nonstd::wstring_view& str) const { - return "''"s; + return fmt::format("'{}'", ConvertString(str)); } - InternalValue operator()(bool val) const + std::string operator()(bool val) const { return val ? "true"s : "false"s; } - InternalValue operator()(EmptyValue) const + std::string operator()(EmptyValue) const { return "none"s; } - InternalValue operator()(const Callable&) const + std::string operator()(const Callable&) const { return ""s; } - InternalValue operator()(double val) const + std::string operator()(double val) const { - std::ostringstream os; - os << val; - return InternalValue(os.str()); + std::string str; + auto os = std::back_inserter(str); + + fmt::format_to(os, "{:.8g}", val); + + return str; } - InternalValue operator()(int64_t val) const + std::string operator()(int64_t val) const { - std::ostringstream os; - os << val; - return InternalValue(os.str()); + return fmt::format("{}", val); } const RenderContext* m_context; @@ -851,16 +866,16 @@ ValueConverter::ValueConverter(FilterParams params, ValueConverter::Mode mode) switch (mode) { case ToFloatMode: - ParseParams({{"default", false}}, params); + ParseParams({{"default"s, false}}, params); break; case ToIntMode: - ParseParams({{"default", false}, {"base", false, static_cast(10)}}, params); + ParseParams({{"default"s, false}, {"base"s, false, static_cast(10)}}, params); break; case ToListMode: case AbsMode: break; case RoundMode: - ParseParams({{"precision", false}, {"method", false, "common"s}}, params); + ParseParams({{"precision"s, false}, {"method"s, false, "common"s}}, params); break; } @@ -942,30 +957,65 @@ struct ValueConverterImpl : visitors::BaseVisitor<> return result; } - InternalValue operator()(const std::string& val) const + static double ConvertToDouble(const char* buff, bool& isConverted) + { + char* endBuff = nullptr; + double dblVal = strtod(buff, &endBuff); + isConverted = *endBuff == 0; + return dblVal; + } + + static double ConvertToDouble(const wchar_t* buff, bool& isConverted) + { + wchar_t* endBuff = nullptr; + double dblVal = wcstod(buff, &endBuff); + isConverted = *endBuff == 0; + return dblVal; + } + + static long long ConvertToInt(const char* buff, int base, bool& isConverted) + { + char* endBuff = nullptr; + long long intVal = strtoll(buff, &endBuff, base); + isConverted = *endBuff == 0; + return intVal; + } + + static long long ConvertToInt(const wchar_t* buff, int base, bool& isConverted) + { + wchar_t* endBuff = nullptr; + long long intVal = wcstoll(buff, &endBuff, base); + isConverted = *endBuff == 0; + return intVal; + } + + template + InternalValue operator()(const std::basic_string& val) const { InternalValue result; switch (m_params.mode) { case ValueConverter::ToFloatMode: { - char* endBuff = nullptr; - double dblVal = strtod(val.c_str(), &endBuff); - if (*endBuff != 0) + bool converted = false; + double dblVal = ConvertToDouble(val.c_str(), converted); + + if (!converted) result = m_params.defValule; else - result = static_cast(dblVal); + result = dblVal; break; } case ValueConverter::ToIntMode: { - char* endBuff = nullptr; int base = static_cast(GetAs(m_params.base)); - int64_t dblVal = strtoll(val.c_str(), &endBuff, base); - if (*endBuff != 0) + bool converted = false; + long long intVal = ConvertToInt(val.c_str(), base, converted); + + if (!converted) result = m_params.defValule; else - result = static_cast(dblVal); + result = static_cast(intVal); break; } case ValueConverter::ToListMode: @@ -979,16 +1029,20 @@ struct ValueConverterImpl : visitors::BaseVisitor<> return result; } - InternalValue operator()(const std::wstring& val) const + + template + InternalValue operator()(const nonstd::basic_string_view& val) const { InternalValue result; switch (m_params.mode) { case ValueConverter::ToFloatMode: { - wchar_t* endBuff = nullptr; - double dblVal = wcstod(val.c_str(), &endBuff); - if (*endBuff != 0) + bool converted = false; + std::basic_string str(val.begin(), val.end()); + double dblVal = ConvertToDouble(str.c_str(), converted); + + if (!converted) result = m_params.defValule; else result = static_cast(dblVal); @@ -996,12 +1050,15 @@ struct ValueConverterImpl : visitors::BaseVisitor<> } case ValueConverter::ToIntMode: { - wchar_t* endBuff = nullptr; - int64_t dblVal = wcstoll(val.c_str(), &endBuff, static_cast(GetAs(m_params.base))); - if (*endBuff != 0) + int base = static_cast(GetAs(m_params.base)); + bool converted = false; + std::basic_string str(val.begin(), val.end()); + long long intVal = ConvertToInt(str.c_str(), base, converted); + + if (!converted) result = m_params.defValule; else - result = static_cast(dblVal); + result = static_cast(intVal); break; } case ValueConverter::ToListMode: diff --git a/src/helpers.h b/src/helpers.h index 999ed65b..115e457f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -1,6 +1,9 @@ #ifndef HELPERS_H #define HELPERS_H +#include +#include + #include #include #include @@ -44,90 +47,6 @@ struct MultiStringLiteral #define UNIVERSAL_STR(Str) MultiStringLiteral{Str, L##Str} -namespace detail -{ -template -struct StringConverter; - -template -struct StringConverter -{ - static Src DoConvert(const Src& from) - { - return from; - } -}; - -template<> -struct StringConverter -{ - static std::string DoConvert(const std::wstring& from) - { - std::mbstate_t state = std::mbstate_t(); - auto srcPtr = from.data(); - std::size_t srcSize = from.size(); - std::size_t destBytes = 0; - -#ifndef _MSC_VER - destBytes = std::wcsrtombs(nullptr, &srcPtr, srcSize, &state); - if (destBytes == (size_t)-1) - return std::string(); -#else - auto err = wcsrtombs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); - if (err != 0) - return std::string(); -#endif - std::string result; - result.resize(destBytes); -#ifndef _MSC_VER - std::wcsrtombs(&result[0], &srcPtr, srcSize, &state); -#else - wcsrtombs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); - result.resize(destBytes-1); -#endif - return result; - } -}; - -template<> -struct StringConverter -{ - static std::wstring DoConvert(const std::string& from) - { - std::mbstate_t state = std::mbstate_t(); - auto srcPtr = from.data(); - std::size_t srcSize = from.size(); - std::size_t destBytes = 0; - -#ifndef _MSC_VER - destBytes = std::mbsrtowcs(nullptr, &srcPtr, srcSize, &state); - if (destBytes == (size_t)-1) - return std::wstring(); -#else - auto err = mbsrtowcs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state); - if (err != 0) - return std::wstring(); -#endif - std::wstring result; - result.resize(destBytes); -#ifndef _MSC_VER - std::mbsrtowcs(&result[0], &srcPtr, srcSize, &state); -#else - mbsrtowcs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state); - result.resize(destBytes-1); -#endif - return result; - } -}; - -} // detail - -template -Dst ConvertString(Src&& from) -{ - return detail::StringConverter, std::decay_t>::DoConvert(std::forward(from)); -} - //! CompileEscapes replaces escape characters by their meanings. /** * @param[in] s Characters sequence with zero or more escape characters. diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 7b91b32a..f8e6db19 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -1,4 +1,5 @@ #include "internal_value.h" +#include "helpers.h" #include "value_visitors.h" #include "expression_evaluator.h" #include "generic_adapters.h" @@ -13,9 +14,20 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> { using BaseVisitor<>::operator (); - InternalValue operator() (const MapAdapter& values, const std::string& field) const + template + InternalValue operator() (const MapAdapter& values, const std::basic_string& fieldName) const + { + auto field = ConvertString(fieldName); + if (!values.HasValue(field)) + return InternalValue(); + + return values.GetValueByName(field); + } + + template + InternalValue operator() (const MapAdapter& values, const nonstd::basic_string_view& fieldName) const { - // std::cout << "operator() (const MapAdapter& values, const std::string& field)" << ": values.size() = " << values.GetSize() << ", field = " << field << std::endl; + auto field = ConvertString(fieldName); if (!values.HasValue(field)) return InternalValue(); @@ -24,20 +36,29 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> InternalValue operator() (const ListAdapter& values, int64_t index) const { - // std::cout << "operator() (const ListAdapter& values, int64_t index)" << ": values.size() = " << values.GetSize() << ", index = " << index << std::endl; if (index < 0 || static_cast(index) >= values.GetSize()) return InternalValue(); return values.GetValueByIndex(index); } - InternalValue operator() (const MapAdapter& values, int64_t index) const + InternalValue operator() (const MapAdapter& /*values*/, int64_t /*index*/) const { return InternalValue(); } template InternalValue operator() (const std::basic_string& str, int64_t index) const + { + if (index < 0 || static_cast(index) >= str.size()) + return InternalValue(); + + std::basic_string resultStr(1, str[static_cast(index)]); + return TargetString(std::move(resultStr)); + } + + template + InternalValue operator() (const nonstd::basic_string_view& str, int64_t index) const { // std::cout << "operator() (const std::basic_string& str, int64_t index)" << ": index = " << index << std::endl; if (index < 0 || static_cast(index) >= str.size()) @@ -47,7 +68,19 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return TargetString(std::move(result)); } - InternalValue operator() (const KeyValuePair& values, const std::string& field) const + template + InternalValue operator() (const KeyValuePair& values, const std::basic_string& fieldName) const + { + return SubscriptKvPair(values, ConvertString(fieldName)); + } + + template + InternalValue operator() (const KeyValuePair& values, const nonstd::basic_string_view& fieldName) const + { + return SubscriptKvPair(values, ConvertString(fieldName)); + } + + InternalValue SubscriptKvPair(const KeyValuePair& values, const std::string& field) const { // std::cout << "operator() (const KeyValuePair& values, const std::string& field)" << ": field = " << field << std::endl; if (field == "key") @@ -85,20 +118,31 @@ InternalValue Subscript(const InternalValue& val, const std::string& subscript, return Subscript(val, InternalValue(subscript), values); } -std::string AsString(const InternalValue& val) +struct StringGetter : public visitors::BaseVisitor { - auto* str = GetIf(&val); - auto* tstr = GetIf(&val); - if (str != nullptr) - return *str; - else /* if (tstr != nullptr) */ + using BaseVisitor::operator (); + + std::string operator()(const std::string& str) const { - str = GetIf(tstr); - if (str != nullptr) - return *str; + return str; } + std::string operator()(const nonstd::string_view& str) const + { + return std::string(str.begin(), str.end()); + } + std::string operator()(const std::wstring& str) const + { + return ConvertString(str); + } + std::string operator()(const nonstd::wstring_view& str) const + { + return ConvertString(str); + } +}; - return std::string(); +std::string AsString(const InternalValue& val) +{ + return Apply(val); } struct ListConverter : public visitors::BaseVisitor> @@ -153,7 +197,7 @@ template class ByRef { public: - ByRef(const T& val) + explicit ByRef(const T& val) : m_val(&val) {} @@ -168,12 +212,10 @@ template class ByVal { public: - ByVal(T&& val) + explicit ByVal(T&& val) : m_val(std::move(val)) {} - ~ByVal() - { - } + ~ByVal() = default; const T& Get() const {return m_val;} T& Get() {return m_val;} @@ -186,12 +228,10 @@ template class BySharedVal { public: - BySharedVal(T&& val) + explicit BySharedVal(T&& val) : m_val(std::make_shared(std::move(val))) {} - ~BySharedVal() - { - } + ~BySharedVal() = default; const T& Get() const {return *m_val;} T& Get() {return *m_val;} @@ -213,24 +253,24 @@ class GenericListAdapter : public IListAccessor {} // Inherited via IListAccessorEnumerator - virtual void Reset() override + void Reset() override { if (m_enum) m_enum->Reset(); } - virtual bool MoveNext() override + bool MoveNext() override { return !m_enum ? false : m_enum->MoveNext(); } - virtual InternalValue GetCurrent() const override + InternalValue GetCurrent() const override { return !m_enum ? InternalValue() : Value2IntValue(m_enum->GetCurrent()); } - virtual IListAccessorEnumerator *Clone() const override + IListAccessorEnumerator *Clone() const override { return !m_enum ? new Enumerator(MakeEmptyListEnumeratorPtr()) : new Enumerator(m_enum->Clone()); } - virtual IListAccessorEnumerator *Transfer() override + IListAccessorEnumerator *Transfer() override { return new Enumerator(std::move(m_enum)); } @@ -248,7 +288,7 @@ class GenericListAdapter : public IListAccessor return nonstd::optional(); const auto& val = indexer->GetItemByIndex(idx); - return visit(visitors::InputValueConvertor(true), val.data()).get(); + return visit(visitors::InputValueConvertor(true, true), val.data()).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override @@ -278,7 +318,7 @@ class ValuesListAdapter : public IndexedListAccessorImpl GetItem(int64_t idx) const override { const auto& val = m_values.Get()[idx]; - return visit(visitors::InputValueConvertor(false), val.data()).get(); + return visit(visitors::InputValueConvertor(false, true), val.data()).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} GenericList CreateGenericList() const override @@ -390,7 +430,7 @@ ListAdapter ListAdapter::CreateAdapter(std::function(); } - nonstd::optional GetItem(int64_t idx) const override + nonstd::optional GetItem(int64_t /*idx*/) const override { return nonstd::optional(); } @@ -535,7 +575,7 @@ class InternalValueMapAdapter : public MapAccessorImpl> Holder m_values; }; - -MapAdapter MapAdapter::CreateAdapter(InternalValueMap&& values) +MapAdapter CreateMapAdapter(InternalValueMap&& values) { return MapAdapter([accessor = InternalValueMapAdapter(std::move(values))]() mutable {return &accessor;}); } -MapAdapter MapAdapter::CreateAdapter(const InternalValueMap* values) + +MapAdapter CreateMapAdapter(const InternalValueMap* values) { return MapAdapter([accessor = InternalValueMapAdapter(*values)]() mutable {return &accessor;}); } -MapAdapter MapAdapter::CreateAdapter(const GenericMap& values) + +MapAdapter CreateMapAdapter(const GenericMap& values) { return MapAdapter([accessor = GenericMapAdapter(values)]() mutable {return &accessor;}); } -MapAdapter MapAdapter::CreateAdapter(GenericMap&& values) + +MapAdapter CreateMapAdapter(GenericMap&& values) { return MapAdapter([accessor = GenericMapAdapter(std::move(values))]() mutable {return &accessor;}); } -MapAdapter MapAdapter::CreateAdapter(const ValuesMap& values) + +MapAdapter CreateMapAdapter(const ValuesMap& values) { return MapAdapter([accessor = ValuesMapAdapter(values)]() mutable {return &accessor;}); } -MapAdapter MapAdapter::CreateAdapter(ValuesMap&& values) + +MapAdapter CreateMapAdapter(ValuesMap&& values) { return MapAdapter([accessor = ValuesMapAdapter(std::move(values))]() mutable {return &accessor;}); } @@ -677,9 +721,19 @@ struct OutputValueConvertor return str.get(); } } + result_t operator()(const TargetStringView& str) const + { + switch (str.index()) + { + case 0: + return str.get(); + default: + return str.get(); + } + } result_t operator()(const KeyValuePair& pair) const { - return ValuesMap{{"key", IntValue2Value(pair.key)}, {"value", IntValue2Value(pair.value)}}; + return ValuesMap{{"key", Value(pair.key)}, {"value", IntValue2Value(pair.value)}}; } result_t operator()(const Callable&) const {return result_t();} result_t operator()(const UserCallable&) const {return result_t();} diff --git a/src/internal_value.h b/src/internal_value.h index 7dd575e6..b9a9351d 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -2,10 +2,24 @@ #define INTERNAL_VALUE_H #include -#include + #include -#include #include +#include + +#include + +#if defined(_MSC_VER) && _MSC_VER <= 1900 // robin_hood hash map doesn't compatible with MSVC 14.0 +#include +#else +#include +#endif + + +#include +#include + +#include namespace jinja2 { @@ -83,6 +97,7 @@ auto MakeWrapped(T&& val) using ValueRef = ReferenceWrapper; using TargetString = nonstd::variant; +using TargetStringView = nonstd::variant; class ListAdapter; class MapAdapter; @@ -94,10 +109,22 @@ struct KeyValuePair; class RendererBase; class InternalValue; -using InternalValueData = nonstd::variant, RecursiveWrapper, std::shared_ptr>; +using InternalValueData = nonstd::variant< + EmptyValue, + bool, + std::string, + TargetString, + TargetStringView, + int64_t, + double, + ValueRef, + ListAdapter, + MapAdapter, + RecursiveWrapper, + RecursiveWrapper, + std::shared_ptr>; using InternalValueRef = ReferenceWrapper; -using InternalValueMap = std::unordered_map; using InternalValueList = std::vector; template @@ -270,16 +297,9 @@ class ListAdapter class MapAdapter { public: - MapAdapter() {} + MapAdapter() = default; explicit MapAdapter(MapAccessorProvider prov) : m_accessorProvider(std::move(prov)) {} - static MapAdapter CreateAdapter(InternalValueMap&& values); - static MapAdapter CreateAdapter(const InternalValueMap* values); - static MapAdapter CreateAdapter(const GenericMap& values); - static MapAdapter CreateAdapter(GenericMap&& values); - static MapAdapter CreateAdapter(const ValuesMap& values); - static MapAdapter CreateAdapter(ValuesMap&& values); - size_t GetSize() const { if (m_accessorProvider && m_accessorProvider()) @@ -313,7 +333,7 @@ class MapAdapter { if (m_accessorProvider && m_accessorProvider()) { - return m_accessorProvider()->SetValue(name, val); + return m_accessorProvider()->SetValue(std::move(name), val); } return false; @@ -435,6 +455,20 @@ class ListAdapter::Iterator mutable InternalValue m_currentVal; }; +#if defined(_MSC_VER) && _MSC_VER <= 1900 // robin_hood hash map doesn't compatible with MSVC 14.0 +typedef std::unordered_map InternalValueMap; +#else +typedef robin_hood::unordered_map InternalValueMap; +#endif + + +MapAdapter CreateMapAdapter(InternalValueMap&& values); +MapAdapter CreateMapAdapter(const InternalValueMap* values); +MapAdapter CreateMapAdapter(const GenericMap& values); +MapAdapter CreateMapAdapter(GenericMap&& values); +MapAdapter CreateMapAdapter(const ValuesMap& values); +MapAdapter CreateMapAdapter(ValuesMap&& values); + template inline auto ValueGetter::GetPtr(const InternalValue* val) { @@ -585,7 +619,7 @@ class Callable inline bool IsEmpty(const InternalValue& val) { - return nonstd::get_if(&val.GetData()) != nullptr; + return val.IsEmpty() || nonstd::get_if(&val.GetData()) != nullptr; } class RenderContext; @@ -593,11 +627,17 @@ class RenderContext; template auto MakeDynamicProperty(Fn&& fn) { - return MapAdapter::CreateAdapter(InternalValueMap{ + return CreateMapAdapter(InternalValueMap{ {"value()", Callable(Callable::GlobalFunc, std::forward(fn))} }); } +template +auto sv_to_string(const nonstd::basic_string_view& sv) +{ + return std::basic_string(sv.begin(), sv.end()); +} + InternalValue Subscript(const InternalValue& val, const InternalValue& subscript, RenderContext* values); InternalValue Subscript(const InternalValue& val, const std::string& subscript, RenderContext* values); std::string AsString(const InternalValue& val); diff --git a/src/render_context.h b/src/render_context.h index 9f986c7d..f7e5f368 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -7,6 +7,7 @@ #include #include +#include namespace jinja2 { @@ -35,7 +36,7 @@ class RenderContext m_externalScope = &extValues; m_globalScope = &globalValues; EnterScope(); - (*m_currentScope)["self"] = MapAdapter::CreateAdapter(InternalValueMap()); + (*m_currentScope)["self"] = CreateMapAdapter(InternalValueMap()); } InternalValueMap& EnterScope() @@ -120,7 +121,7 @@ class RenderContext const InternalValueMap* m_externalScope; const InternalValueMap* m_globalScope; InternalValueMap m_emptyScope; - std::list m_scopes; + std::deque m_scopes; IRendererCallback* m_rendererCallback; const InternalValueMap* m_boundScope = nullptr; }; diff --git a/src/statements.cpp b/src/statements.cpp index 98a51e63..cede4667 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -6,7 +6,9 @@ #include #include +#include +using namespace std::string_literals; namespace jinja2 { @@ -23,10 +25,10 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende auto& context = values.EnterScope(); InternalValueMap loopVar; - context["loop"] = MapAdapter::CreateAdapter(&loopVar); + context["loop"s] = CreateMapAdapter(&loopVar); if (m_isRecursive) { - loopVar["operator()"] = Callable(Callable::GlobalFunc, [this](const CallParams& params, OutStream& stream, RenderContext& context) { + loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this](const CallParams& params, OutStream& stream, RenderContext& context) { bool isSucceeded = false; auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded); if (!isSucceeded) @@ -87,11 +89,11 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende if (listSize) { int64_t itemsNum = static_cast(listSize.value()); - loopVar["length"] = InternalValue(itemsNum); + loopVar["length"s] = InternalValue(itemsNum); } else { - loopVar["length"] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& params, RenderContext& context) -> InternalValue { + loopVar["length"s] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& /*params*/, RenderContext& /*context*/) -> InternalValue { if (!listSize) makeIndexedList(); return static_cast(listSize.value()); @@ -101,22 +103,33 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende isLast = !enumerator->MoveNext(); InternalValue prevValue; InternalValue curValue; + InternalValue nextValue; + loopVar["cycle"s] = static_cast(LoopCycleFn); for (;!isLast; ++ itemIdx) { prevValue = std::move(curValue); - curValue = enumerator->GetCurrent(); - isLast = !enumerator->MoveNext(); - loopRendered = true; - loopVar["index"] = InternalValue(static_cast(itemIdx + 1)); - loopVar["index0"] = InternalValue(static_cast(itemIdx)); - loopVar["first"] = InternalValue(itemIdx == 0); - loopVar["last"] = isLast; if (itemIdx != 0) - loopVar["previtem"] = prevValue; + { + std::swap(curValue, nextValue); + loopVar["previtem"s] = prevValue; + } + else + curValue = enumerator->GetCurrent(); + + isLast = !enumerator->MoveNext(); if (!isLast) - loopVar["nextitem"] = enumerator->GetCurrent(); + { + nextValue = enumerator->GetCurrent(); + loopVar["nextitem"s] = nextValue; + } else - loopVar.erase("nextitem"); + loopVar.erase("nextitem"s); + + loopRendered = true; + loopVar["index"s] = static_cast(itemIdx + 1); + loopVar["index0"s] = static_cast(itemIdx); + loopVar["first"s] = itemIdx == 0; + loopVar["last"s] = isLast; if (m_vars.size() > 1) { @@ -485,7 +498,7 @@ class ImportedMacroRenderer : public RendererBase , m_withContext(withContext) {} - void Render(OutStream& os, RenderContext& values) override + void Render(OutStream& /*os*/, RenderContext& /*values*/) override { } @@ -516,7 +529,7 @@ class ImportedMacroRenderer : public RendererBase bool m_withContext; }; -void ImportStatement::Render(OutStream& os, RenderContext& values) +void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) { auto name = m_nameExpr->Evaluate(values); @@ -593,7 +606,7 @@ ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedSc } if (m_namespace) - values.GetCurrentScope()[m_namespace.value()] = MapAdapter::CreateAdapter(std::move(importedNs)); + values.GetCurrentScope()[m_namespace.value()] = CreateMapAdapter(std::move(importedNs)); } void MacroStatement::PrepareMacroParams(RenderContext& values) @@ -635,12 +648,12 @@ void MacroStatement::InvokeMacroRenderer(const CallParams& callParams, OutStream for (auto& a : callArgs) scope[a.first] = std::move(a.second); - scope["kwargs"] = MapAdapter::CreateAdapter(std::move(kwArgs)); - scope["varargs"] = ListAdapter::CreateAdapter(std::move(varArgs)); + scope["kwargs"s] = CreateMapAdapter(std::move(kwArgs)); + scope["varargs"s] = ListAdapter::CreateAdapter(std::move(varArgs)); - scope["name"] = static_cast(m_name); - scope["arguments"] = ListAdapter::CreateAdapter(std::move(arguments)); - scope["defaults"] = ListAdapter::CreateAdapter(std::move(defaults)); + scope["name"s] = static_cast(m_name); + scope["arguments"s] = ListAdapter::CreateAdapter(std::move(arguments)); + scope["defaults"s] = ListAdapter::CreateAdapter(std::move(defaults)); m_mainBody->Render(stream, context); @@ -704,7 +717,7 @@ void MacroCallStatement::SetupMacroScope(InternalValueMap&) } -void DoStatement::Render(OutStream& os, RenderContext& values) +void DoStatement::Render(OutStream& /*os*/, RenderContext& values) { m_expr->Evaluate(values); } diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index da3852f1..cb9dca6e 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -19,12 +19,12 @@ namespace filters { template -struct StringEncoder : public visitors::BaseVisitor<> +struct StringEncoder : public visitors::BaseVisitor { using BaseVisitor::operator(); template - InternalValue operator() (const std::basic_string& str) const + TargetString operator() (const std::basic_string& str) const { std::basic_string result; @@ -33,7 +33,20 @@ struct StringEncoder : public visitors::BaseVisitor<> static_cast(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);}); } - return InternalValue(result); + return TargetString(std::move(result)); + } + + template + TargetString operator() (const nonstd::basic_string_view& str) const + { + std::basic_string result; + + for (auto& ch : str) + { + static_cast(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);}); + } + + return TargetString(std::move(result)); } template @@ -168,7 +181,7 @@ StringConverter::StringConverter(FilterParams params, StringConverter::Mode mode InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContext& context) { - InternalValue result; + TargetString result; auto isAlpha = ba::is_alpha(); auto isAlNum = ba::is_alnum(); @@ -176,9 +189,10 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex switch (m_mode) { case TrimMode: - result = ApplyStringConverter(baseVal, [](auto str) -> InternalValue { + result = ApplyStringConverter(baseVal, [](auto strView) -> TargetString { + auto str = sv_to_string(strView); ba::trim_all(str); - return InternalValue(str); + return TargetString(str); }); break; case TitleMode: @@ -206,8 +220,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex } isDelim = !isAlNum(ch); }); - result = InternalValue(wc); - break; + return InternalValue(wc); } case UpperMode: result = ApplyStringConverter(baseVal, [&isAlpha](auto ch, auto&& fn) mutable { @@ -226,11 +239,14 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex }); break; case ReplaceMode: - result = ApplyStringConverter(baseVal, [this, &context](auto str) -> InternalValue { - 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); + result = ApplyStringConverter(baseVal, [this, &context](auto srcStr) -> TargetString { + std::decay_t emptyStrView; + using CharT = typename decltype(emptyStrView)::value_type; + std::basic_string emptyStr; + auto oldStr = GetAsSameString(srcStr, this->GetArgumentValue("old", context)).value_or(emptyStr); + auto newStr = GetAsSameString(srcStr, this->GetArgumentValue("new", context)).value_or(emptyStr); auto count = ConvertToInt(this->GetArgumentValue("count", context)); + auto str = sv_to_string(srcStr); if (count == 0) ba::replace_all(str, oldStr, newStr); else @@ -238,18 +254,22 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex for (int64_t n = 0; n < count; ++ n) ba::replace_first(str, oldStr, newStr); } - return InternalValue(str); + return str; }); break; case TruncateMode: - result = ApplyStringConverter(baseVal, [this, &context, &isAlNum](auto str) -> InternalValue { - std::decay_t emptyStr; + result = ApplyStringConverter(baseVal, [this, &context, &isAlNum](auto srcStr) -> TargetString { + std::decay_t emptyStrView; + using CharT = typename decltype(emptyStrView)::value_type; + std::basic_string emptyStr; auto length = ConvertToInt(this->GetArgumentValue("length", context)); auto killWords = ConvertToBool(this->GetArgumentValue("killwords", context)); - auto end = GetAsSameString(str, this->GetArgumentValue("end", context)); + auto end = GetAsSameString(srcStr, this->GetArgumentValue("end", context)); auto leeway = ConvertToInt(this->GetArgumentValue("leeway", context), 5); - if (static_cast(str.size()) <= length) - return InternalValue(str); + if (static_cast(srcStr.size()) <= length) + return sv_to_string(srcStr); + + auto str = sv_to_string(srcStr); if (killWords) { @@ -258,7 +278,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex str.erase(str.begin() + length, str.end()); str += end.value_or(emptyStr); } - return InternalValue(str); + return str; } auto p = str.begin() + length; @@ -266,7 +286,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex { for (; leeway != 0 && p != str.end() && isAlNum(*p); -- leeway, ++ p); if (p == str.end()) - return InternalValue(str); + return TargetString(str); } if (isAlNum(*p)) @@ -277,7 +297,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex ba::trim_right(str); str += end.value_or(emptyStr); - return InternalValue(str); + return TargetString(std::move(str)); }); break; case UrlEncodeMode: @@ -286,7 +306,8 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex default: break; } - return result; + + return std::move(result); } } diff --git a/src/template.cpp b/src/template.cpp index c2ea0f36..d4817074 100644 --- a/src/template.cpp +++ b/src/template.cpp @@ -1,6 +1,8 @@ #include "jinja2cpp/template.h" #include "template_impl.h" +#include + #include #include @@ -63,18 +65,20 @@ Result Template::LoadFromFile(const std::string& fileName) Result Template::Render(std::ostream& os, const jinja2::ValuesMap& params) { - auto result = GetImpl(m_impl)->Render(os, params); + std::string buffer; + auto result = GetImpl(m_impl)->Render(buffer, params); + + if(result) + os.write(buffer.data(), buffer.size()); return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); } Result Template::RenderAsString(const jinja2::ValuesMap& params) { - std::string outStr; - outStr.reserve(10000); - std::ostringstream os(outStr); - auto result = Render(os, params); - return result ? os.str() : Result(result.get_unexpected()); + std::string buffer; + auto result = GetImpl(m_impl)->Render(buffer, params); + return !result ? Result(std::move(buffer)) : Result(nonstd::make_unexpected(std::move(result.get())));; } TemplateW::TemplateW(TemplateEnv* env) @@ -127,15 +131,18 @@ ResultW TemplateW::LoadFromFile(const std::string& fileName) ResultW TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params) { - auto result = GetImpl(m_impl)->Render(os, params); + std::wstring buffer; + auto result = GetImpl(m_impl)->Render(buffer, params); + if (result) + os.write(buffer.data(), buffer.size()); return !result ? ResultW() : ResultW(nonstd::make_unexpected(std::move(result.get()))); } ResultW TemplateW::RenderAsString(const jinja2::ValuesMap& params) { - std::wostringstream os; - auto result = GetImpl(m_impl)->Render(os, params); + std::wstring buffer; + auto result = GetImpl(m_impl)->Render(buffer, params); - return !result ? os.str() : ResultW(nonstd::make_unexpected(std::move(result.get()))); + return !result ? buffer : ResultW(nonstd::make_unexpected(std::move(result.get()))); } } // jinga2 diff --git a/src/template_impl.h b/src/template_impl.h index 91a241a3..5ffe65f2 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -16,6 +16,8 @@ namespace jinja2 { +extern void SetupGlobals(InternalValueMap& globalParams); + class ITemplateImpl { public: @@ -48,14 +50,14 @@ template class GenericStreamWriter : public OutStream::StreamWriter { public: - explicit GenericStreamWriter(std::basic_ostream& os) + explicit GenericStreamWriter(std::basic_string& os) : m_os(os) {} // StreamWriter interface void WriteBuffer(const void* ptr, size_t length) override { - m_os.write(reinterpret_cast(ptr), length); + m_os.append(reinterpret_cast(ptr), length); } void WriteValue(const InternalValue& val) override { @@ -63,7 +65,7 @@ class GenericStreamWriter : public OutStream::StreamWriter } private: - std::basic_ostream& m_os; + std::basic_string& m_os; }; template @@ -82,15 +84,40 @@ class StringStreamWriter : public OutStream::StreamWriter } void WriteValue(const InternalValue& val) override { - std::basic_ostringstream os; - Apply>(val, os); - (*m_targetStr) += os.str(); + Apply>(val, *m_targetStr); } private: std::basic_string* m_targetStr; }; +template +struct ErrorConverter; + +template +struct ErrorConverter, ErrorInfoTpl> +{ + static ErrorInfoTpl Convert(const ErrorInfoTpl& srcError) + { + typename ErrorInfoTpl::Data errorData; + errorData.code = srcError.GetCode(); + errorData.srcLoc = srcError.GetErrorLocation(); + errorData.locationDescr = ConvertString>(srcError.GetLocationDescr()); + errorData.extraParams = srcError.GetExtraParams(); + + return ErrorInfoTpl(errorData); + } +}; + +template +struct ErrorConverter, ErrorInfoTpl> +{ + static const ErrorInfoTpl& Convert(const ErrorInfoTpl& srcError) + { + return srcError; + } +}; + template class TemplateImpl : public ITemplateImpl { @@ -121,7 +148,7 @@ class TemplateImpl : public ITemplateImpl return boost::optional>(); } - boost::optional> Render(std::basic_ostream& os, const ValuesMap& params) + boost::optional> Render(std::basic_string& os, const ValuesMap& params) { boost::optional> normalResult; @@ -145,7 +172,7 @@ class TemplateImpl : public ITemplateImpl for (auto& ip : params) { auto valRef = &ip.second.data(); - auto newParam = visit(visitors::InputValueConvertor(), *valRef); + auto newParam = visit(visitors::InputValueConvertor(false, true), *valRef); if (!newParam) intParams[ip.first] = ValueRef(static_cast(*valRef)); else @@ -160,6 +187,7 @@ class TemplateImpl : public ITemplateImpl } convertFn(params); + SetupGlobals(intParams); RendererCallback callback(this); RenderContext context(intParams, extParams, &callback); @@ -167,9 +195,13 @@ class TemplateImpl : public ITemplateImpl OutStream outStream([writer = GenericStreamWriter(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); m_renderer->Render(outStream, context); } - catch (const ErrorInfoTpl& error) + catch (const ErrorInfoTpl& error) + { + return ErrorConverter, ErrorInfoTpl>::Convert(error); + } + catch (const ErrorInfoTpl& error) { - return error; + return ErrorConverter, ErrorInfoTpl>::Convert(error); } catch (const std::exception& ex) { @@ -248,9 +280,9 @@ class TemplateImpl : public ITemplateImpl TargetString GetAsTargetString(const InternalValue& val) override { - std::basic_ostringstream os; + std::basic_string os; Apply>(val, os); - return TargetString(os.str()); + return TargetString(std::move(os)); } OutStream GetStreamOnString(TargetString& str) override diff --git a/src/template_parser.h b/src/template_parser.h index 512d468c..2ca94fe7 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -139,9 +139,30 @@ struct ParserTraits : public ParserTraitsBase<> auto srcStr = str.substr(range.startOffset, range.size()); return detail::StringConverter::DoConvert(srcStr); } - static InternalValue RangeToNum(const std::wstring& /*str*/, CharRange /*range*/, Token::Type /*hint*/) + static InternalValue RangeToNum(const std::wstring& str, CharRange range, Token::Type hint) { - return InternalValue(); + wchar_t buff[std::max(std::numeric_limits::max_digits10, std::numeric_limits::max_digits10) * 2 + 1]; + std::copy(str.data() + range.startOffset, str.data() + range.endOffset, buff); + buff[range.size()] = 0; + InternalValue result; + if (hint == Token::IntegerNum) + { + result = static_cast(wcstoll(buff, nullptr, 0)); + } + else + { + wchar_t* endBuff = nullptr; + int64_t val = wcstoll(buff, &endBuff, 10); + if ((errno == ERANGE) || *endBuff) + { + endBuff = nullptr; + double dblVal = wcstod(buff, nullptr); + result = static_cast(dblVal); + } + else + result = static_cast(val); + } + return result; } }; @@ -215,14 +236,12 @@ class StatementsParser ParseResult ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token); + ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); private: Settings m_settings; TemplateEnv* m_env; - - ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token); - - ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); }; template diff --git a/src/testers.cpp b/src/testers.cpp index 7394a188..51b5f8d8 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -163,6 +163,11 @@ struct ValueKindGetter : visitors::BaseVisitor { return ValueKind::String; } + template + ValueKind operator()(const nonstd::basic_string_view&) const + { + return ValueKind::String; + } ValueKind operator()(int64_t) const { return ValueKind::Integer; diff --git a/src/value_visitors.h b/src/value_visitors.h index 2fa64fc3..1d33542d 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -2,10 +2,12 @@ #define VALUE_VISITORS_H #include "expression_evaluator.h" +#include "helpers.h" #include "jinja2cpp/value.h" #include #include +#include #include #include @@ -60,12 +62,15 @@ auto ApplyUnwrapped(const InternalValueData& val, Fn&& fn) { auto valueRef = GetIf(&val); auto targetString = GetIf(&val); + auto targetSV = GetIf(&val); // auto internalValueRef = GetIf(&val); if (valueRef != nullptr) return fn(valueRef->get().data()); else if (targetString != nullptr) return fn(*targetString); + else if (targetSV != nullptr) + return fn(*targetSV); // else if (internalValueRef != nullptr) // return fn(internalValueRef->get()); @@ -141,15 +146,21 @@ struct BaseVisitor template struct ValueRendererBase { - ValueRendererBase(std::basic_ostream& os) + ValueRendererBase(std::basic_string& os) : m_os(&os) { } template - void operator()(const T& val) const + void operator()(const T& val) const; + void operator()(double val) const; + void operator()(const nonstd::basic_string_view& val) const { - (*m_os) << val; + m_os->append(val.begin(), val.end()); + } + void operator()(const std::basic_string& val) const + { + m_os->append(val.begin(), val.end()); } void operator()(const EmptyValue&) const {} @@ -161,34 +172,64 @@ struct ValueRendererBase void operator()(const ListAdapter&) const {} void operator()(const ValueRef&) const {} void operator()(const TargetString&) const {} + void operator()(const TargetStringView&) const {} void operator()(const KeyValuePair&) const {} void operator()(const Callable&) const {} void operator()(const UserCallable&) const {} - void operator()(const RendererBase*) const {} + void operator()(const std::shared_ptr) const {} template void operator()(const boost::recursive_wrapper&) const {} template void operator()(const RecWrapper&) const {} - std::basic_ostream* m_os; + auto GetOs() const { return std::back_inserter(*m_os); } + + std::basic_string* m_os; }; +template<> +template +void ValueRendererBase::operator()(const T& val) const +{ + fmt::format_to(GetOs(), "{}", val); +} + +template<> +template +void ValueRendererBase::operator()(const T& val) const +{ + fmt::format_to(GetOs(), L"{}", val); +} + +template<> +inline void ValueRendererBase::operator()(double val) const +{ + fmt::format_to(GetOs(), "{:.8g}", val); +} + +template<> +inline void ValueRendererBase::operator()(double val) const +{ + fmt::format_to(GetOs(), L"{:.8g}", val); +} + struct InputValueConvertor { using result_t = boost::optional; - InputValueConvertor(bool byValue = false) + InputValueConvertor(bool byValue, bool allowStringRef) : m_byValue(byValue) + , m_allowStringRef(allowStringRef) { } template result_t operator() (const std::basic_string& val) const { - // if (m_byValue) - return result_t(InternalValue(TargetString(val))); + if (m_allowStringRef) + return result_t(TargetStringView(nonstd::basic_string_view(val))); - // return result_t(); + return result_t(TargetString(val)); } result_t operator() (const ValuesList& vals) const @@ -228,10 +269,10 @@ struct InputValueConvertor if (m_byValue) { ValuesMap newVals(vals); - return result_t(InternalValue(MapAdapter::CreateAdapter(std::move(newVals)))); + return result_t(CreateMapAdapter(std::move(newVals))); } - return result_t(InternalValue(MapAdapter::CreateAdapter(vals))); + return result_t(CreateMapAdapter(vals)); } result_t operator() (const GenericMap& vals) const @@ -239,10 +280,10 @@ struct InputValueConvertor if (m_byValue) { GenericMap newVals(vals); - return result_t(InternalValue(MapAdapter::CreateAdapter(std::move(newVals)))); + return result_t(CreateMapAdapter(std::move(newVals))); } - return result_t(InternalValue(MapAdapter::CreateAdapter(vals))); + return result_t(CreateMapAdapter(vals)); } result_t operator() (const UserCallable& val) const @@ -271,6 +312,7 @@ struct InputValueConvertor static result_t ConvertUserCallable(const UserCallable& val); bool m_byValue; + bool m_allowStringRef; }; @@ -280,34 +322,47 @@ struct ValueRenderer; template<> struct ValueRenderer : ValueRendererBase { - ValueRenderer(std::ostream& os) + ValueRenderer(std::string& os) : ValueRendererBase::ValueRendererBase(os) { } using ValueRendererBase::operator (); - void operator()(const std::wstring&) const {} + void operator()(const std::wstring& str) const + { + (*m_os) += ConvertString(str); + } + void operator()(const nonstd::wstring_view& str) const + { + (*m_os) += ConvertString(str); + } void operator() (bool val) const { - (*m_os) << (val ? "true" : "false"); + m_os->append(val ? "true" : "false"); } }; template<> struct ValueRenderer : ValueRendererBase { - ValueRenderer(std::wostream& os) + ValueRenderer(std::wstring& os) : ValueRendererBase::ValueRendererBase(os) { } using ValueRendererBase::operator (); - void operator()(const std::string&) const + void operator()(const std::string& str) const { + (*m_os) += ConvertString(str); + } + void operator()(const nonstd::string_view& str) const + { + (*m_os) += ConvertString(str); } void operator() (bool val) const { - (*m_os) << (val ? L"true" : L"false"); + // fmt::format_to(GetOs(), L"{}", (const wchar_t*)(val ? "true" : "false")); + m_os->append(val ? L"true" : L"false"); } }; @@ -419,6 +474,22 @@ struct UnaryOperation : BaseVisitor return result; } + template + InternalValue operator() (const nonstd::basic_string_view& val) const + { + InternalValue result; + switch (m_oper) + { + case jinja2::UnaryExpression::LogicalNot: + result = val.empty(); + break; + default: + break; + } + + return result; + } + InternalValue operator() (const EmptyValue&) const { InternalValue result; @@ -555,25 +626,83 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (double left, int64_t right) const + InternalValue operator() (int64_t left, double right) const { return this->operator ()(static_cast(left), static_cast(right)); } - InternalValue operator() (int64_t left, double right) const + InternalValue operator() (double left, int64_t right) const { return this->operator ()(static_cast(left), static_cast(right)); } template - InternalValue operator() (const std::basic_string& left, const std::basic_string& right) const + InternalValue operator() (const std::basic_string &left, const std::basic_string &right) const + { + return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(right)); + } + + template + std::enable_if_t::value, InternalValue> operator() (const std::basic_string& left, const std::basic_string& right) const + { + auto rightStr = ConvertString>(right); + return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(rightStr)); + } + + template + InternalValue operator() (const nonstd::basic_string_view &left, const std::basic_string &right) const + { + return ProcessStrings(left, nonstd::basic_string_view(right)); + } + + template + std::enable_if_t::value, InternalValue> operator() (const nonstd::basic_string_view& left, const std::basic_string& right) const + { + auto rightStr = ConvertString>(right); + return ProcessStrings(left, nonstd::basic_string_view(rightStr)); + } + + template + InternalValue operator() (const std::basic_string &left, const nonstd::basic_string_view &right) const + { + return ProcessStrings(nonstd::basic_string_view(left), right); + } + + template + std::enable_if_t::value, InternalValue> operator() (const std::basic_string& left, const nonstd::basic_string_view& right) const + { + auto rightStr = ConvertString>(right); + return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(rightStr)); + } + + template + InternalValue operator() (const nonstd::basic_string_view &left, const nonstd::basic_string_view &right) const + { + return ProcessStrings(left, right); + } + + template + std::enable_if_t::value, InternalValue> operator() (const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const { + auto rightStr = ConvertString>(right); + return ProcessStrings(left, nonstd::basic_string_view(rightStr)); + } + + template + InternalValue ProcessStrings(const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const + { + using string = std::basic_string; InternalValue result; + switch (m_oper) { case jinja2::BinaryExpression::Plus: - result = left + right; + { + auto str = string(left.begin(), left.end()); + str.append(right.begin(), right.end()); + result = std::move(str); break; + } case jinja2::BinaryExpression::LogicalEq: result = m_compType == BinaryExpression::CaseSensitive ? left == right : boost::iequals(left, right); break; @@ -737,6 +866,12 @@ struct BooleanEvaluator : BaseVisitor return !str.empty(); } + template + bool operator()(const nonstd::basic_string_view& str) const + { + return !str.empty(); + } + bool operator() (const MapAdapter& val) const { return val.GetSize() != 0ULL; @@ -784,31 +919,66 @@ using IntegerEvaluator = NumberEvaluator; using DoubleEvaluator = NumberEvaluator; -struct StringJoiner : BaseVisitor<> +struct StringJoiner : BaseVisitor { using BaseVisitor::operator (); - InternalValue operator() (EmptyValue, const std::string& str) const + template + TargetString operator() (EmptyValue, const std::basic_string& str) const { return str; } - InternalValue operator() (const std::string& left, const std::string& right) const + template + TargetString operator() (EmptyValue, const nonstd::basic_string_view& str) const + { + return std::basic_string(str.begin(), str.end()); + } + + template + TargetString operator() (const std::basic_string& left, const std::basic_string& right) const { return left + right; } + + template + std::enable_if_t::value, TargetString> operator() (const std::basic_string& left, const std::basic_string& right) const + { + return left + ConvertString>(right); + } + + template + TargetString operator() (std::basic_string left, const nonstd::basic_string_view& right) const + { + left.append(right.begin(), right.end()); + return std::move(left); + } + + template + std::enable_if_t::value, TargetString> operator() (std::basic_string left, const nonstd::basic_string_view& right) const + { + auto r = ConvertString>(right); + left.append(right.begin(), right.end()); + return std::move(left); + } }; template -struct StringConverterImpl : public BaseVisitor()(std::declval()))> +struct StringConverterImpl : public BaseVisitor()(std::declval()))> { - using R = decltype(std::declval()(std::string())); + using R = decltype(std::declval()(nonstd::string_view())); using BaseVisitor::operator (); StringConverterImpl(const Fn& fn) : m_fn(fn) {} template R operator()(const std::basic_string& str) const + { + return m_fn(nonstd::basic_string_view(str)); + } + + template + R operator()(const nonstd::basic_string_view& str) const { return m_fn(str); } @@ -820,6 +990,9 @@ template struct SameStringGetter : public visitors::BaseVisitor>> { using ResultString = std::basic_string; + using OtherString = std::conditional_t::value, std::wstring, std::string>; + using ResultStringView = nonstd::basic_string_view; + using OtherStringView = std::conditional_t::value, nonstd::wstring_view, nonstd::string_view>; using Result = nonstd::expected; using BaseVisitor::operator (); @@ -827,6 +1000,21 @@ struct SameStringGetter : public visitors::BaseVisitor(str)); + } + + Result operator()(const OtherStringView& str) const + { + return nonstd::make_unexpected(ConvertString(str)); + } }; } // visitors @@ -863,6 +1051,17 @@ auto GetAsSameString(const std::basic_string&, const InternalValue& val) return Result(); } +template +auto GetAsSameString(const nonstd::basic_string_view&, const InternalValue& val) +{ + using Result = nonstd::optional>; + auto result = Apply>(val); + if (!result) + return Result(result.error()); + + return Result(); +} + } // jinja2 #endif // VALUE_VISITORS_H diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index b19f954b..f7d28f4c 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -4,99 +4,48 @@ #include "gtest/gtest.h" #include "jinja2cpp/template.h" +#include "test_tools.h" using namespace jinja2; -TEST(BasicTests, PlainSingleLineTemplateProcessing) -{ - std::string source = "Hello World from Parser!"; +using BasicMultiStrTest = BasicTemplateRenderer; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); -} -TEST(BasicTests, PlainSingleLineTemplateProcessing_Wide) +MULTISTR_TEST(BasicMultiStrTest, PlainSingleLineTemplateProcessing, R"(Hello World from Parser!)", R"(Hello World from Parser!)") { - std::wstring source = L"Hello World from Parser!"; - - TemplateW tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); } -TEST(BasicTests, PlainMultiLineTemplateProcessing) +MULTISTR_TEST(BasicMultiStrTest, PlainMultiLineTemplateProcessing, R"(Hello World +from Parser!)", R"(Hello World +from Parser!)") { - std::string source = R"(Hello World -from Parser!)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); } -TEST(BasicTests, InlineCommentsSkip) +MULTISTR_TEST(BasicMultiStrTest, InlineCommentsSkip, "Hello World {#Comment to skip #}from Parser!", "Hello World from Parser!") { - std::string source = "Hello World {#Comment to skip #}from Parser!"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); } -TEST(BasicTests, SingleLineCommentsSkip) -{ - std::string source = R"(Hello World +MULTISTR_TEST(BasicMultiStrTest, SingleLineCommentsSkip, +R"(Hello World {#Comment to skip #} -from Parser!)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); +from Parser!)", +R"(Hello World +from Parser!)") +{ } -TEST(BasicTests, MultiLineCommentsSkip) -{ - std::string source = R"(Hello World +MULTISTR_TEST(BasicMultiStrTest, MultiLineCommentsSkip, +R"(Hello World {#Comment to skip #} -from Parser!)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); +from Parser!)", +R"(Hello World +from Parser!)") +{ } -TEST(BasicTests, CommentedOutCodeSkip) -{ - std::string source = R"(Hello World +MULTISTR_TEST(BasicMultiStrTest, CommentedOutCodeSkip, +R"(Hello World {#Comment to {{for}} {{endfor}} @@ -104,16 +53,10 @@ skip #} {#Comment to {% skip #} -from Parser!)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - 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()); +from Parser!)", +R"(Hello World +from Parser!)") +{ } TEST(BasicTests, StripLSpaces_1) @@ -457,13 +400,6 @@ from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } -TEST(BasicTests, LiteralWithEscapeCharacters) +MULTISTR_TEST(BasicMultiStrTest, LiteralWithEscapeCharacters, R"({{ 'Hello\t\nWorld\n\twith\nescape\tcharacters!' }})", "Hello\t\nWorld\n\twith\nescape\tcharacters!") { - Template tpl; - ASSERT_TRUE(tpl.Load(R"({{ 'Hello\t\nWorld\n\twith\nescape\tcharacters!' }})")); - - const auto result = tpl.RenderAsString({}).value(); - std::cout << result << std::endl; - const std::string expectedResult = "Hello\t\nWorld\n\twith\nescape\tcharacters!"; - EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } \ No newline at end of file diff --git a/test/errors_test.cpp b/test/errors_test.cpp index d88aa32e..4faabc76 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -11,13 +11,6 @@ struct ErrorsGenericExtensionTestTag; using ErrorsGenericTest = InputOutputPairTest; using ErrorsGenericExtensionsTest = InputOutputPairTest; -std::string ErrorToString(const jinja2::ErrorInfo& error) -{ - std::ostringstream errorDescr; - errorDescr << error; - return errorDescr.str(); -} - TEST_F(TemplateEnvFixture, EnvironmentAbsentErrorsTest) { Template tpl1; @@ -42,6 +35,30 @@ TEST_F(TemplateEnvFixture, EnvironmentAbsentErrorsTest) EXPECT_EQ("noname.j2tpl:1:4: error: Template environment doesn't set\n{% import 'module' %}\n---^-------", ErrorToString(parseResult.error())); } +TEST_F(TemplateEnvFixture, EnvironmentAbsentErrorsTest_Wide) +{ + TemplateW tpl1; + auto parseResult = tpl1.Load(L"{% extends 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:4: error: Template environment doesn't set\n{% extends 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load(L"{% include 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:4: error: Template environment doesn't set\n{% include 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load(L"{% from 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:4: error: Template environment doesn't set\n{% from 'module' %}\n---^-------", ErrorToString(parseResult.error())); + + parseResult = tpl1.Load(L"{% import 'module' %}"); + ASSERT_FALSE(parseResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:4: error: Template environment doesn't set\n{% import 'module' %}\n---^-------", ErrorToString(parseResult.error())); +} + TEST_F(TemplateEnvFixture, RenderErrorsTest) { Template tpl1; @@ -68,6 +85,32 @@ TEST_F(TemplateEnvFixture, RenderErrorsTest) EXPECT_EQ("noname.j2tpl:1:1: error: Invalid template name: 10\n", ErrorToString(renderResult.error())); } +TEST_F(TemplateEnvFixture, RenderErrorsTest_Wide) +{ + TemplateW tpl1; + auto renderResult = tpl1.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L":1:1: error: Template not parsed\n", ErrorToString(renderResult.error())); + + TemplateW tpl2; + tpl2.Load(LR"({{ foo() }})"); + renderResult = tpl2.RenderAsString({ {"foo", MakeCallable([]() -> Value {throw std::runtime_error("Bang!"); })} }); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:1: error: Unexpected exception occurred during template processing. Exception: Bang!\n", ErrorToString(renderResult.error())); + + TemplateW tpl3(&m_env); + auto parseResult = tpl3.Load(L"{% import name as name %}"); + EXPECT_TRUE(parseResult.has_value()); + if (!parseResult) + std::wcout << parseResult.error() << std::endl; + renderResult = tpl3.RenderAsString({ {"name", 10} }); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"noname.j2tpl:1:1: error: Invalid template name: 10\n", ErrorToString(renderResult.error())); +} + TEST_F(TemplateEnvFixture, ErrorPropagationTest) { AddFile("module", "{% for %}"); @@ -104,6 +147,42 @@ TEST_F(TemplateEnvFixture, ErrorPropagationTest) EXPECT_EQ("module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); } +TEST_F(TemplateEnvFixture, ErrorPropagationTest_Wide) +{ + AddFile("module", "{% for %}"); + TemplateW tpl1(&m_env); + auto parseResult = tpl1.Load(L"{% extends 'module' %}"); + ASSERT_TRUE(parseResult.has_value()); + auto renderResult = tpl1.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + TemplateW tpl2(&m_env); + parseResult = tpl2.Load(L"{% include 'module' %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl2.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + TemplateW tpl3(&m_env); + parseResult = tpl3.Load(L"{% from 'module' import name %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl3.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); + + TemplateW tpl4(&m_env); + parseResult = tpl4.Load(L"{% import 'module' as module %}"); + ASSERT_TRUE(parseResult.has_value()); + renderResult = tpl4.RenderAsString({}); + ASSERT_FALSE(renderResult.has_value()); + + EXPECT_EQ(L"module:1:8: error: Identifier expected\n{% for %}\n ---^-------", ErrorToString(renderResult.error())); +} + TEST_P(ErrorsGenericTest, Test) { auto& testParam = GetParam(); @@ -114,14 +193,28 @@ TEST_P(ErrorsGenericTest, Test) auto parseResult = tpl.Load(source); ASSERT_FALSE(parseResult.has_value()); - std::ostringstream errorDescr; - errorDescr << parseResult.error(); - std::string result = errorDescr.str(); + auto result = ErrorToString(parseResult.error()); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); } +TEST_P(ErrorsGenericTest, Test_Wide) +{ + auto& testParam = GetParam(); + std::string source = testParam.tpl; + + TemplateEnv env; + TemplateW tpl(&env); + auto parseResult = tpl.Load(jinja2::ConvertString(source)); + ASSERT_FALSE(parseResult.has_value()); + + auto result = ErrorToString(parseResult.error()); + std::wcout << result << std::endl; + std::wstring expectedResult = jinja2::ConvertString(testParam.result); + EXPECT_EQ(expectedResult, result); +} + TEST_P(ErrorsGenericExtensionsTest, Test) { auto& testParam = GetParam(); @@ -134,14 +227,30 @@ TEST_P(ErrorsGenericExtensionsTest, Test) auto parseResult = tpl.Load(source); ASSERT_FALSE(parseResult.has_value()); - std::ostringstream errorDescr; - errorDescr << parseResult.error(); - std::string result = errorDescr.str(); + auto result = ErrorToString(parseResult.error()); std::cout << result << std::endl; std::string expectedResult = testParam.result; EXPECT_EQ(expectedResult, result); } +TEST_P(ErrorsGenericExtensionsTest, Test_Wide) +{ + auto& testParam = GetParam(); + std::string source = testParam.tpl; + + TemplateEnv env; + env.GetSettings().extensions.Do = true; + + TemplateW tpl(&env); + auto parseResult = tpl.Load(jinja2::ConvertString(source)); + ASSERT_FALSE(parseResult.has_value()); + + auto result = ErrorToString(parseResult.error()); + std::wcout << result << std::endl; + std::wstring expectedResult = jinja2::ConvertString(testParam.result); + EXPECT_EQ(expectedResult, result); +} + INSTANTIATE_TEST_CASE_P(BasicTest, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{{}}", "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"}, diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 2b6c5128..f57e4d15 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -8,9 +8,10 @@ using namespace jinja2; -TEST(ExpressionsTest, BinaryMathOperations) -{ - std::string source = R"( +using ExpressionsMultiStrTest = BasicTemplateRenderer; + +MULTISTR_TEST(ExpressionsMultiStrTest, BinaryMathOperations, +R"( {{ 1 + 10 }} {{ 1 - 10}} {{ 0.1 + 1 }} @@ -23,67 +24,66 @@ TEST(ExpressionsTest, BinaryMathOperations) {{ 10 ** -2 }} {{ 10/10 + 2*5 }} {{ 'Hello' + " " + 'World ' + stringValue }} -{{ 'Hello' ~ " " ~ 123 ~ ' ' ~ 1.234 ~ " " ~ true ~ " " ~ intValue ~ " " ~ false ~ ' ' ~ 'World ' ~ stringValue }} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - {"intValue", 3}, - {"doubleValue", 12.123f}, - {"stringValue", "rain"}, - {"boolFalseValue", false}, - {"boolTrueValue", true}, - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +{{ 'Hello' + " " + 'World ' + wstringValue }} +{{ stringValue + ' ' + wstringValue }} +{{ wstringValue + ' ' + stringValue }} +{{ wstringValue + ' ' + wstringValue }} +{{ stringValue + ' ' + stringValue }} +{{ 'Hello' ~ " " ~ 123 ~ ' ' ~ 1.234 ~ " " ~ true ~ " " ~ intValue ~ " " ~ false ~ ' ' ~ 'World ' ~ stringValue ~ ' ' ~ wstringValue}} +)", +//----------- +R"( 11 -9 1.1 -10.4 10 -2.33333 +2.3333333 2 1 81 0.01 11 Hello World rain -Hello 123 1.234 true 3 false World rain -)"; - - EXPECT_STREQ(expectedResult.c_str(), result.c_str()); -} - -TEST(ExpressionsTest, IfExpression) +Hello World rain +rain rain +rain rain +rain rain +rain rain +Hello 123 1.234 true 3 false World rain rain +)") { - std::string source = R"( -{{ intValue if intValue is eq(3) }} -{{ stringValue if intValue < 3 else doubleValue }} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { + params = { {"intValue", 3}, {"doubleValue", 12.123f}, {"stringValue", "rain"}, + {"wstringValue", std::wstring(L"rain")}, {"boolFalseValue", false}, {"boolTrueValue", true}, }; +} - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +MULTISTR_TEST(ExpressionsMultiStrTest, IfExpression, +R"( +{{ intValue if intValue is eq(3) }} +{{ stringValue if intValue < 3 else doubleValue }} +{{ wstringValue if intValue == 3 else doubleValue }} +)", +//----------- +R"( 3 12.123 -)"; - - EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +rain +)") +{ + params = { + {"intValue", 3}, + {"doubleValue", 12.123f}, + {"stringValue", "rain"}, + {"wstringValue", std::wstring(L"rain")}, + {"boolFalseValue", false}, + {"boolTrueValue", true}, + }; } TEST(ExpressionTest, DoStatement) diff --git a/test/filters_test.cpp b/test/filters_test.cpp index aea45c52..37742215 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -19,13 +19,7 @@ TEST_P(ListIteratorTest, Test) auto& testParam = GetParam(); std::string source = "{% for i in " + testParam.tpl + " %}{{i}}{{', ' if not loop.last}}{% endfor %}"; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - std::string result = tpl.RenderAsString(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result); } TEST_P(FilterGroupByTest, Test) @@ -61,64 +55,38 @@ TEST_P(FilterGroupByTest, Test) {% endfor %} {% endfor %})"; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result, params); } -TEST(FilterGenericTestSingle, ApplyMacroTest) -{ - std::string source = R"( +using FilterGenericTestSingle = BasicTemplateRenderer; + +MULTISTR_TEST(FilterGenericTestSingle, ApplyMacroTest, +R"( {% macro test(str) %}{{ str | upper }}{% endmacro %} {{ 'Hello World!' | applymacro(macro='test') }} {{ ['str1', 'str2', 'str3'] | map('applymacro', macro='test') | join(', ') }} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------------- +R"( HELLO WORLD! STR1, STR2, STR3 -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest) -{ - std::string source = R"( +MULTISTR_TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest, + R"( {% macro joiner(list, delim) %}{{ list | map('applymacro', macro='caller') | join(delim) }}{% endmacro %} {% call(item) joiner(['str1', 'str2', 'str3'], '->') %}{{item | upper}}{% endcall %} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------- +R"( STR1->STR2->STR3 -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } INSTANTIATE_TEST_CASE_P(StringJoin, FilterGenericTest, ::testing::Values( @@ -387,12 +355,12 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"}, InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"}, InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, - InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': '']"} + InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': 'test string 0']"} )); INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values( InputOutputPair{"'Hello World' | urlencode", "Hello+World"}, - InputOutputPair{"'Hello World\xD0\x9C\xD0\xBA' | urlencode", "Hello+World%D0%9C%D0%BA"}, + // InputOutputPair{"'Hello World\xD0\x9C\xD0\xBA' | urlencode", "Hello+World%D0%9C%D0%BA"}, InputOutputPair{"'! # $ & ( ) * + , / : ; = ? @ [ ] %' | urlencode", "%21+%23+%24+%26+%28+%29+%2A+%2B+%2C+%2F+%3A+%3B+%3D+%3F+%40+%5B+%5D+%25"} )); diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index a83c2dc1..eba02da3 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -11,100 +11,76 @@ using namespace jinja2; -TEST(ForLoopTest, IntegersLoop) -{ - std::string source = R"( +using ForLoopTest = BasicTemplateRenderer; + +MULTISTR_TEST(ForLoopTest, IntegersLoop, + R"( {% for i in its %} a[{{i}}] = image[{{i}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - {"its", ValuesList{0, 1, 2} } - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------- +R"( a[0] = image[0]; a[1] = image[1]; a[2] = image[2]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = { + {"its", ValuesList{0, 1, 2} } + }; } -TEST(ForLoopTest, InlineIntegersLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, InlineIntegersLoop, +R"( {% for i in (0, 1, 2) %} a[{{i}}] = image[{{i}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//--------- +R"( a[0] = image[0]; a[1] = image[1]; a[2] = image[2]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(ForLoopTest, EmptyLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, EmptyLoop, +R"( {% for i in ints %} a[{{i}}] = image[{{i}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { +)", +//--------- +R"( +)" +) +{ + params = { {"ints", ValuesList()} }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -)"; - EXPECT_EQ(expectedResult, result); } -TEST(ForLoopTest, ReflectedIntegersLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, ReflectedIntegersLoop, +R"( {% for i in its %} a[{{i}}] = image[{{i}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - {"its", Reflect(std::vector{0, 1, 2} ) } - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//---------- +R"( a[0] = image[0]; a[1] = image[1]; a[2] = image[2]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = { + {"its", Reflect(std::vector{0, 1, 2} ) } + }; } struct RangeForLoopTesstTag {}; @@ -115,16 +91,7 @@ TEST_P(RangeForLoopTest, IntegersRangeLoop) auto& testParam = GetParam(); std::string source = "{% for i in " + testParam.tpl + " %}{{i}},{% endfor %}"; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result, {}); } INSTANTIATE_TEST_CASE_P(RangeParamResolving, RangeForLoopTest, ::testing::Values( @@ -145,87 +112,62 @@ INSTANTIATE_TEST_CASE_P(RangeParamResolving, RangeForLoopTest, ::testing::Values InputOutputPair{"range(start=0, 8, step=2)", "0,2,4,6,"} )); -TEST(ForLoopTest, LoopCycleLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopCycleLoop, +R"( {% for i in range(5) %} a[{{i}}] = image[{{loop.cycle(2, 4, 6)}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------- +R"( a[0] = image[2]; a[1] = image[4]; a[2] = image[6]; a[3] = image[2]; a[4] = image[4]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(ForLoopTest, LoopCycle2Loop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopCycle2Loop, +R"( {% for i in range(5) %} a[{{i}}] = image[{{loop.cycle("a", "b", "c")}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------- +R"( a[0] = image[a]; a[1] = image[b]; a[2] = image[c]; a[3] = image[a]; a[4] = image[b]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(ForLoopTest, LoopWithIf) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopWithIf, +R"( {% for i in range(10) if i is even %} a[{{i}}] = image[{{i}}]; {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//--------- +R"( a[0] = image[0]; a[2] = image[2]; a[4] = image[4]; a[6] = image[6]; a[8] = image[8]; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(ForLoopTest, LoopWithElse) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopWithElse, +R"( {% for i in idx%} a[{{i}}] = image[{{i}}]; {% else %} @@ -236,92 +178,68 @@ a[{{i}}] = image[{{i}}]; {% else %} No indexes given {% endfor %} -)"; - - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------- +R"( No indexes given No indexes given -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(ForLoopTest, LoopVariableWithIf) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopVariableWithIf, +R"( {% for i in its if i is even%} {{i}} length={{loop.length}}, index={{loop.index}}, index0={{loop.index0}}, first={{loop.first}}, last={{loop.last}}, previtem={{loop.previtem}}, nextitem={{loop.nextitem}}; {% endfor %} -)"; - - Template mytemplate; - ASSERT_TRUE(mytemplate.Load(source)); - - ValuesMap params = { - {"its", ValuesList{0, 1, 2, 3, 4} } - }; - - std::string result = mytemplate.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------- +R"( 0 length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=2; 2 length=3, index=2, index0=1, first=false, last=false, previtem=0, nextitem=4; 4 length=3, index=3, index0=2, first=false, last=true, previtem=2, nextitem=; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = { + {"its", ValuesList{0, 1, 2, 3, 4} } + }; } -TEST(ForLoopTest, LoopVariable) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, LoopVariable, +R"( {% for i in its %} length={{loop.length}}, index={{loop.index}}, index0={{loop.index0}}, first={{loop.first}}, last={{loop.last}}, previtem={{loop.previtem}}, nextitem={{loop.nextitem}}; {% endfor %} -)"; - - Template mytemplate; - ASSERT_TRUE(mytemplate.Load(source)); - - ValuesMap params = { - {"its", ValuesList{0, 1, 2} } - }; - - std::string result = mytemplate.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------------- +R"( length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=1; length=3, index=2, index0=1, first=false, last=false, previtem=0, nextitem=2; length=3, index=3, index0=2, first=false, last=true, previtem=1, nextitem=; -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = { + {"its", ValuesList{0, 1, 2} } + }; } -TEST(ForLoopTest, SimpleContainerLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, SimpleContainerLoop, +R"( {% for i,name in images %} a[{{i}}] = "{{name}}_{{loop.index0}}"; {% endfor %} -)"; - - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { +)", +//--------- +R"( +a[1] = "image1_0"; +a[2] = "image2_1"; +a[3] = "image3_2"; +)") +{ + params = { {"images", ValuesList{ ValuesMap{{"i", Value(1)}, {"name", "image1"}}, ValuesMap{{"i", Value(2)}, {"name", "image2"}}, @@ -329,38 +247,17 @@ a[{{i}}] = "{{name}}_{{loop.index0}}"; } } }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -a[1] = "image1_0"; -a[2] = "image2_1"; -a[3] = "image3_2"; -)"; - - EXPECT_EQ(expectedResult, result); } -TEST(ForLoopTest, SimpleNestedLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, SimpleNestedLoop, +R"( {% for i in outers %}a[{{i}}] = image[{{i}}]; {% for j in inners %}b[{{j}}] = image[{{j}}]; {% endfor %} {% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - {"outers", ValuesList{0, 1, 2} }, - {"inners", ValuesList{0, 1}} - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << "[" << result << "]" << std::endl; - std::string expectedResult = R"DELIM( +)", +//----------- +R"( a[0] = image[0]; b[0] = image[0]; b[1] = image[1]; @@ -370,14 +267,16 @@ b[1] = image[1]; a[2] = image[2]; b[0] = image[0]; b[1] = image[1]; -)DELIM"; - - EXPECT_EQ(expectedResult, result); +)") +{ + params = { + {"outers", ValuesList{0, 1, 2} }, + {"inners", ValuesList{0, 1}} + }; } -TEST(ForLoopTest, RecursiveLoop) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, RecursiveLoop, +R"( {%set items=[ {'name'='root1', 'children'=[ {'name'='child1_1'}, @@ -396,32 +295,27 @@ TEST(ForLoopTest, RecursiveLoop) ]} ] %} {% for i in items recursive %}{{i.name}} -> {{loop(i.children)}}{% endfor %} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap 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"; - - EXPECT_EQ(expectedResult, result); +)", +//--------- +R"( +root1 -> child1_1 -> child1_2 -> child1_3 -> root2 -> child2_1 -> child2_2 -> child2_3 -> root3 -> child3_1 -> child3_2 -> child3_3 -> )" +) +{ } -TEST(ForLoopTest, GenericListTest_Generator) -{ - std::string source = R"( +MULTISTR_TEST(ForLoopTest, GenericListTest_Generator, +R"( {{ input[0] | pprint }} {% for i in input %}>{{ i }}<{% endfor %} {% for i in input %}>{{ i }}<{% else %}{% endfor %} -)"; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { +)", +//---------- +R"( +none +>10<>20<>30<>40<>50<>60<>70<>80<>90<)" +) +{ + params = { {"input", jinja2::MakeGenericList([cur = 10]() mutable -> nonstd::optional { if (cur > 90) return nonstd::optional(); @@ -431,17 +325,11 @@ TEST(ForLoopTest, GenericListTest_Generator) return Value(tmp); }) } }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << "[" << result << "]" << std::endl; - std::string expectedResult = R"DELIM( -none ->10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; - - EXPECT_EQ(expectedResult, result); } -TEST(ForLoopTest, GenericListTest_InputIterator) +using ForLoopTestSingle = SubstitutionTestBase; + +TEST_F(ForLoopTestSingle, GenericListTest_InputIterator) { std::string source = R"( {{ input[0] | pprint }} @@ -451,23 +339,18 @@ TEST(ForLoopTest, GenericListTest_InputIterator) std::string sampleStr("10 20 30 40 50 60 70 80 90"); std::istringstream is(sampleStr); - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - ValuesMap params = { {"input", jinja2::MakeGenericList(std::istream_iterator(is), std::istream_iterator()) } }; - std::string result = tpl.RenderAsString(params).value(); - std::cout << "[" << result << "]" << std::endl; - std::string expectedResult = R"DELIM( + std::string expectedResult = R"( none ->10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; +>10<>20<>30<>40<>50<>60<>70<>80<>90<)"; - EXPECT_EQ(expectedResult, result); + BasicTemplateRenderer::ExecuteTest(source, expectedResult, params, "Narrow version"); } -TEST(ForLoopTest, GenericListTest_ForwardIterator) +TEST_F(ForLoopTestSingle, GenericListTest_ForwardIterator) { std::string source = R"( {{ input[0] | pprint }} @@ -476,23 +359,18 @@ TEST(ForLoopTest, GenericListTest_ForwardIterator) )"; std::forward_list sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90}; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - ValuesMap params = { {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) } }; - std::string result = tpl.RenderAsString(params).value(); - std::cout << "[" << result << "]" << std::endl; - std::string expectedResult = R"DELIM( + std::string expectedResult = R"( none ->10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; +>10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)"; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, expectedResult, params); } -TEST(ForLoopTest, GenericListTest_RandomIterator) +TEST_F(ForLoopTestSingle, GenericListTest_RandomIterator) { std::string source = R"( {{ input[0] | pprint }} @@ -501,18 +379,13 @@ TEST(ForLoopTest, GenericListTest_RandomIterator) )"; std::array sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90}; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - ValuesMap params = { {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) } }; - std::string result = tpl.RenderAsString(params).value(); - std::cout << "[" << result << "]" << std::endl; - std::string expectedResult = R"DELIM( + std::string expectedResult = R"( 10 ->10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)DELIM"; +>10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)"; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, expectedResult, params); } diff --git a/test/macro_test.cpp b/test/macro_test.cpp index 02e10468..a2a6c5fc 100644 --- a/test/macro_test.cpp +++ b/test/macro_test.cpp @@ -8,93 +8,64 @@ using namespace jinja2; -TEST(MacroTest, SimpleMacro) -{ - std::string source = R"( +using MacroTest = BasicTemplateRenderer; + +MULTISTR_TEST(MacroTest, SimpleMacro, +R"( {% macro test %} Hello World! {% endmacro %} {{ test() }}{{ test() }} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------------- +R"( Hello World! Hello World! -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, OneParamMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, OneParamMacro, +R"( {% macro test(param) %} -->{{ param }}<-- {% endmacro %} {{ test('Hello') }}{{ test(param='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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------- +R"( -->Hello<-- -->World!<-- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, OneDefaultParamMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, OneDefaultParamMacro, +R"( {% macro test(param='Hello') %} -->{{ param }}<-- {% endmacro %} {{ test() }}{{ test('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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------------- +R"( -->Hello<-- -->World!<-- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, ClosureMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, ClosureMacro, +R"( {% macro test1(param) %}-->{{ param('Hello World') }}<--{% endmacro %} {% macro test(param1) %} {% set var='Some Value' %} @@ -104,32 +75,22 @@ TEST(MacroTest, ClosureMacro) -->{{ test1(inner2) }}<-- {% endmacro %} {{ test() }}{{ test('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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------- +R"( -->-->Some Value -> Hello World<--<-- -->-->HELLO WORLD<--<-- -->-->Some ValueWorld! -> Hello World<--<-- -->-->HELLO WORLD<--<-- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, MacroVariables) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, MacroVariables, +R"( {% macro test(param1='Hello', param2, param3='World') %} name: {{ name }} arguments: {{ arguments | pprint }} @@ -138,111 +99,71 @@ varargs: {{ varargs | pprint }} kwargs: {{ kwargs | pprint }} {% endmacro %} {{ test(1, 2, param3=3, 4, extraValue=5, 6) }} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------- +R"( name: test arguments: ['param1', 'param2', 'param3'] defaults: ['Hello', none, 'World'] varargs: [4, 6] kwargs: {'extraValue': 5} -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, SimpleCallMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, SimpleCallMacro, +R"( {% macro test %} Hello World! -> {{ caller() }} <- {% endmacro %} {% call test %}Message from caller{% endcall %} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//----------------- +R"( Hello World! -> Message from caller <- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, CallWithParamsAndSimpleMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, CallWithParamsAndSimpleMacro, +R"( {% macro test %} -> {{ caller('Hello World' | upper) }} <- {% endmacro %} {% call(message) test %}{{ message }}{% endcall %} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------------ +R"( -> HELLO WORLD <- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, CallWithParamsAndMacro) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, CallWithParamsAndMacro, +R"( {% macro test(msg) %} {{ msg }} >>> -> {{ caller([msg]) }} <--> {{ caller([msg], 'upper') }} <- {% endmacro %} {% call(message, fName='lower') test('Hello World') %}{{ message | map(fName) | first }}{% endcall %} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------------- +R"( Hello World >>> -> hello world <--> HELLO WORLD <- -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } -TEST(MacroTest, MacroCallVariables) -{ - std::string source = R"( +MULTISTR_TEST(MacroTest, MacroCallVariables, +R"( {% macro invoke() %}{{ caller(1, 2, param3=3, 4, extraValue=5, 6) }}{% endmacro %} {% call (param1='Hello', param2, param3='World') invoke %} name: {{ name }} @@ -251,25 +172,16 @@ defaults: {{ defaults | pprint }} varargs: {{ varargs | pprint }} kwargs: {{ kwargs | pprint }} {% endcall %} -)"; - - 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(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------------- +R"( name: $call$ arguments: ['param1', 'param2', 'param3'] defaults: ['Hello', none, 'World'] varargs: [4, 6] kwargs: {'extraValue': 5} -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ + params = PrepareTestData(); } diff --git a/test/perf_test.cpp b/test/perf_test.cpp index 843906ee..92afbb4e 100644 --- a/test/perf_test.cpp +++ b/test/perf_test.cpp @@ -9,7 +9,11 @@ using namespace jinja2; constexpr int Iterations = 10000; +#if !defined(NDEBUG) || defined(_DEBUG) #define PerfTests DISABLED_PerfTests +#else +#define PerfTests PerfTests +#endif TEST(PerfTests, PlainText) { @@ -20,7 +24,13 @@ TEST(PerfTests, PlainText) jinja2::ValuesMap params; - std::cout << tpl.RenderAsString(params).value() << std::endl; + auto renderResult = tpl.RenderAsString(params); + if (!renderResult) + { + std::cout << "Render error: " << renderResult.error() << std::endl; + ASSERT_FALSE(true); + } + std::cout << renderResult.value() << std::endl; std::string result; for (int n = 0; n < Iterations * 100; ++ n) result = tpl.RenderAsString(params).value(); @@ -106,10 +116,16 @@ TEST(PerfTests, ForLoopText) jinja2::ValuesMap params = {}; - std::cout << tpl.RenderAsString(params).value() << std::endl; + auto renderResult = tpl.RenderAsString(params); + if (!renderResult) + { + std::cout << "Render error: " << renderResult.error() << std::endl; + ASSERT_FALSE(true); + } + std::cout << renderResult.value() << std::endl; std::string result; for (int n = 0; n < Iterations * 20; ++ n) - result = tpl.RenderAsString(params).value(); + tpl.RenderAsString(params); std::cout << result << std::endl; } @@ -164,3 +180,65 @@ TEST(PerfTests, ForLoopIfText) std::cout << result << std::endl; } + +TEST(PerfTests, DISABLED_TestMatsuhiko) +{ + std::string source = R"( + + + + {{ page_title }} + + +

+

{{ page_title }}

+
+ +
+ + {% for row in table %} + + {% for cell in row|list %} + + {% endfor %} + + {% endfor %} +
{{ cell }}
+
+ + +)"; + + Template tpl; + auto parseRes = tpl.Load(source); + EXPECT_TRUE(parseRes.has_value()); + if (!parseRes) + { + std::cout << parseRes.error() << std::endl; + return; + } + + jinja2::ValuesMap params = {}; + params["page_title"] = "mitsuhiko's benchmark"; + // jinja2::ValuesMap dictEntry = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}, {"f", 6}, {"g",7}, {"h", 8}, {"i", 9}, {"j", 10}}; + jinja2::ValuesList dictEntry = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}; + jinja2::ValuesList table; + for (int n = 0; n < 1000; ++ n) + table.push_back(jinja2::Value(dictEntry)); + params["table"] = std::move(table); + +// std::cout << tpl.RenderAsString(params).value() << std::endl; + std::string result; + for (int n = 0; n < 5000; ++ n) + tpl.RenderAsString(params).value(); + +// std::cout << result << std::endl; +} diff --git a/test/set_test.cpp b/test/set_test.cpp index eeadebaf..5de376b8 100644 --- a/test/set_test.cpp +++ b/test/set_test.cpp @@ -9,183 +9,155 @@ using namespace jinja2; -TEST(SetTest, SimpleSetTest) -{ - std::string source = R"( +using SetTest = BasicTemplateRenderer; + +MULTISTR_TEST(SetTest, SimpleSetTest, +R"( {% set val = intValue %} localVal: {{val}} paramsVal: {{intValue}} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { +)", +//------------ +R"( +localVal: 3 +paramsVal: 3 +)" +) +{ + params = { {"intValue", 3}, {"doubleValue", 12.123f}, {"stringValue", "rain"}, {"boolFalseValue", false}, {"boolTrueValue", true}, }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -localVal: 3 -paramsVal: 3 -)"; - EXPECT_EQ(expectedResult, result); } -TEST(SetTest, Tuple1AssignmentTest) -{ - std::string source = R"( +MULTISTR_TEST(SetTest, Tuple1AssignmentTest, +R"( {% set firstName, lastName = emploee %} firtsName: {{firstName}} lastName: {{lastName}} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { +)", +//-------------- +R"( +firtsName: John +lastName: Dow +)") +{ + params = { {"emploee", ValuesMap{ {"firstName", "John"}, {"lastName", "Dow"} }}, }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -firtsName: John -lastName: Dow -)"; - EXPECT_EQ(expectedResult, result); } -TEST(SetTest, Tuple2AssignmentTest) -{ - std::string source = R"( +MULTISTR_TEST(SetTest, Tuple2AssignmentTest, +R"( {% set tuple = ("Hello", "World") %} hello: {{tuple[0]}} world: {{tuple[1]}} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//------------ +R"( hello: Hello world: World -)"; - EXPECT_EQ(expectedResult, result); +)") +{ } -TEST(SetTest, Tuple3AssignmentTest) -{ - std::string source = R"( +MULTISTR_TEST(SetTest, Tuple3AssignmentTest, +R"( {% set tuple = ["Hello", "World"] %} hello: {{tuple[0]}} world: {{tuple[1]}} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +R"( hello: Hello world: World -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -TEST(SetTest, Tuple4AssignmentTest) -{ - std::string source = R"( +MULTISTR_TEST(SetTest, Tuple4AssignmentTest, +R"( {% set dict = {'hello' = "Hello", 'world' = "World"} %} hello: {{dict.hello}} world: {{dict.world}} -)"; - - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - ValuesMap params = { - }; - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( +)", +//-------- +R"( hello: Hello world: World -)"; - EXPECT_EQ(expectedResult, result); +)" +) +{ } -using WithTest = TemplateEnvFixture; +using WithTest = BasicTemplateRenderer; -TEST_F(WithTest, SimpleTest) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, SimpleTest, +R"( {% with inner = 42 %} {{ inner }} {%- endwith %} -)", {}); - EXPECT_EQ("\n42", result); +)", +//---------- +"\n42" +) +{ } -TEST_F(WithTest, MultiVarsTest) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, MultiVarsTest, +R"( {% with inner1 = 42, inner2 = 'Hello World' %} {{ inner1 }} {{ inner2 }} {%- endwith %} -)", {}); - EXPECT_EQ("\n42\nHello World", result); +)", +//---------- +"\n42\nHello World" +) +{ } -TEST_F(WithTest, ScopeTest1) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, ScopeTest1, +R"( {{ outer }} {% with inner = 42, outer = 'Hello World' %} {{ inner }} {{ outer }} {%- endwith %} {{ outer }} -)", {{"outer", "World Hello"}}); - EXPECT_EQ("\nWorld Hello\n42\nHello WorldWorld Hello\n", result); +)", +//--------------- +"\nWorld Hello\n42\nHello WorldWorld Hello\n" +) +{ + params = {{"outer", "World Hello"}}; } -TEST_F(WithTest, ScopeTest2) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, ScopeTest2, +R"( {{ outer }} {% with outer = 'Hello World', inner = outer %} {{ inner }} {{ outer }} {%- endwith %} {{ outer }} -)", {{"outer", "World Hello"}}); - EXPECT_EQ("\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n", result); +)", +//-------------- +"\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n" +) +{ + params = {{"outer", "World Hello"}}; } -TEST_F(WithTest, ScopeTest3) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, ScopeTest3, +R"( {{ outer }} {% with outer = 'Hello World' %} {% set inner = outer %} @@ -193,13 +165,16 @@ TEST_F(WithTest, ScopeTest3) {{ outer }} {%- endwith %} {{ outer }} -)", {{"outer", "World Hello"}}); - EXPECT_EQ("\nWorld Hello\nHello World\nHello WorldWorld Hello\n", result); +)", +//-------------- +"\nWorld Hello\nHello World\nHello WorldWorld Hello\n" +) +{ + params = {{"outer", "World Hello"}}; } -TEST_F(WithTest, ScopeTest4) -{ - auto result = Render(R"( +MULTISTR_TEST(WithTest, ScopeTest4, +R"( {% with inner1 = 42 %} {% set inner2 = outer %} {{ inner1 }} @@ -207,6 +182,10 @@ TEST_F(WithTest, ScopeTest4) {%- endwith %} >> {{ inner1 }} << >> {{ inner2 }} << -)", {{"outer", "World Hello"}}); - EXPECT_EQ("\n42\nWorld Hello>> <<\n>> <<\n", result); +)", +//--------------- +"\n42\nWorld Hello>> <<\n>> <<\n" +) +{ + params = {{"outer", "World Hello"}}; } diff --git a/test/test_tools.h b/test/test_tools.h index d50069ee..061579ff 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -7,6 +7,7 @@ #include #include #include +#include "../src/helpers.h" struct InputOutputPair { @@ -20,11 +21,6 @@ struct InputOutputPair } }; -template> -class InputOutputPairTest : public Base -{ -}; - struct TestInnerStruct { ~TestInnerStruct() {isAlive = false;} @@ -109,27 +105,90 @@ inline jinja2::ValuesMap PrepareTestData() }; } -class SubstitutionTestBase : public ::testing::TestWithParam +inline std::string ErrorToString(const jinja2::ErrorInfo& error) { -protected: - void PerformTest(const InputOutputPair& testParam) - { - std::string source = "{{ " + testParam.tpl + " }}"; + std::ostringstream errorDescr; + errorDescr << error; + return errorDescr.str(); +} + +inline std::wstring ErrorToString(const jinja2::ErrorInfoW& error) +{ + std::wostringstream errorDescr; + errorDescr << error; + return errorDescr.str(); +} - jinja2::Template tpl; +inline void StringToConsole(const std::string& str) +{ + std::cout << str << std::endl; +} + +inline void StringToConsole(const std::wstring& str) +{ + std::wcout << str << std::endl; +} + +class BasicTemplateRenderer : public ::testing::Test +{ +public: + template + static void ExecuteTest(const std::basic_string& source, const std::basic_string& expectedResult, const jinja2::ValuesMap& params, const char* version = "") + { + TemplateT tpl; auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); + EXPECT_TRUE(parseRes.has_value()) << version; if (!parseRes) { - std::cout << parseRes.error() << std::endl; + StringToConsole(ErrorToString(parseRes.error())); return; } - std::string result = tpl.RenderAsString(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + auto renderRes = tpl.RenderAsString(params); + EXPECT_TRUE(renderRes.has_value()) << version; + if (!renderRes) + { + StringToConsole(ErrorToString(renderRes.error())); + return; + } + auto result = renderRes.value(); + StringToConsole(result); + EXPECT_EQ(expectedResult, result) << version; + } + + template + void PerformTest(const std::basic_string& source, const std::basic_string& expectedResult, void (*paramsGetter)(jinja2::ValuesMap&)) + { + jinja2::ValuesMap params; + paramsGetter(params); + + ExecuteTest(source, expectedResult, params); + } +}; + +class SubstitutionTestBase : public ::testing::TestWithParam +{ +protected: + void PerformNarrowTest(const InputOutputPair& testParam) + { + BasicTemplateRenderer::ExecuteTest("{{ " + testParam.tpl + " }}", testParam.result, PrepareTestData(), "Narrow version"); + } + + void PerformWideTest(const InputOutputPair& testParam) + { + BasicTemplateRenderer::ExecuteTest(L"{{ " + jinja2::ConvertString(testParam.tpl) + L" }}", jinja2::ConvertString(testParam.result), PrepareTestData(), "Wide version"); } + + void PerformBothTests(const std::string& tpl, const std::string result, const jinja2::ValuesMap& params = PrepareTestData()) + { + BasicTemplateRenderer::ExecuteTest(tpl, result, params, "Narrow version"); + BasicTemplateRenderer::ExecuteTest(jinja2::ConvertString(tpl), jinja2::ConvertString(result), params, "Wide version"); + } +}; + +template +class InputOutputPairTest : public Base +{ }; class TemplateEnvFixture : public ::testing::Test @@ -180,16 +239,34 @@ class TemplateEnvFixture : public ::testing::Test jinja2::TemplateEnv m_env; }; + +#define MULTISTR_TEST_IMPL(Fixture, TestName, StringT, TemplateT, Tpl, Result, ParamsGetter) \ +TEST_F(Fixture, TestName) \ +{ \ + PerformTest(StringT(Tpl), StringT(Result), ParamsGetter); \ +} + +#define MULTISTR_TEST(Fixture, TestName, Tpl, Result) \ +void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params);\ +MULTISTR_TEST_IMPL(Fixture, TestName##_Narrow, std::string, Template, Tpl, Result, Fixture##_##TestName##_Params_Getter) \ +MULTISTR_TEST_IMPL(Fixture, TestName##_Wide, std::wstring, TemplateW, L##Tpl, L##Result, Fixture##_##TestName##_Params_Getter) \ +void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params) + struct SubstitutionGenericTestTag; using SubstitutionGenericTest = InputOutputPairTest; #define SUBSTITUION_TEST_P(TestName) \ struct TestName##Tag; \ using TestName = InputOutputPairTest;\ -TEST_P(TestName, Test) \ +TEST_P(TestName, Test##_Narrow) \ +{ \ + auto& testParam = GetParam(); \ + PerformNarrowTest(testParam); \ +} \ +TEST_P(TestName, Test##_Wide) \ { \ auto& testParam = GetParam(); \ - PerformTest(testParam); \ + PerformWideTest(testParam); \ } namespace jinja2 diff --git a/test/testers_test.cpp b/test/testers_test.cpp index 19dc6758..7aa74c13 100644 --- a/test/testers_test.cpp +++ b/test/testers_test.cpp @@ -15,13 +15,7 @@ TEST_P(TestersGenericTest, Test) auto& testParam = GetParam(); std::string source = "{{ 'true' if " + testParam.tpl + " else 'false' }}"; - Template tpl; - ASSERT_TRUE(tpl.Load(source)); - - std::string result = tpl.RenderAsString(PrepareTestData()).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result); } INSTANTIATE_TEST_CASE_P(EqTest, TestersGenericTest, ::testing::Values( diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index bc791c93..179f68ac 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -10,120 +10,110 @@ using namespace jinja2; -TEST(UserCallableTest, SimpleUserCallable) -{ - std::string source = R"( +using UserCallableTest = BasicTemplateRenderer; + +MULTISTR_TEST(UserCallableTest, SimpleUserCallable, +R"( {{ test() }} {{ test() }} -)"; - - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - +{{ test_wide() }} +{{ test_wide() }} +)", +//------------ +R"( +Hello World! +Hello World! +Hello World! +Hello World! +)" +) +{ jinja2::UserCallable uc; uc.callable = [](auto&)->jinja2::Value {return "Hello World!";}; - jinja2::ValuesMap params; + jinja2::UserCallable ucWide; + ucWide.callable = [](auto&)->jinja2::Value {return std::wstring(L"Hello World!"); }; params["test"] = std::move(uc); - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -Hello World! -Hello World! -)"; - EXPECT_EQ(expectedResult, result); + params["test_wide"] = std::move(ucWide); } -TEST(UserCallableTest, SimpleUserCallableWithParams1) -{ - std::string source = R"( +MULTISTR_TEST(UserCallableTest, SimpleUserCallableWithParams1, +R"( {{ test('Hello', 'World!') }} {{ test(str2='World!', str1='Hello') }} -)"; - - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - +)", +//------------- +R"( +Hello World! +Hello World! +)" +) +{ jinja2::UserCallable uc; uc.callable = [](auto& params)->jinja2::Value { - return params["str1"].asString() + " " + params["str2"].asString(); + auto str1 = params["str1"]; + auto str2 = params["str2"]; + + if (str1.isString()) + return str1.asString() + " " + str2.asString(); + + return str1.asWString() + L" " + str2.asWString(); }; uc.argsInfo = {{"str1", true}, {"str2", true}}; - jinja2::ValuesMap params; params["test"] = std::move(uc); - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -Hello World! -Hello World! -)"; - EXPECT_EQ(expectedResult, result); } -TEST(UserCallableTest, SimpleUserCallableWithParams2) -{ - std::string source = R"( +MULTISTR_TEST(UserCallableTest, SimpleUserCallableWithParams2, +R"( {{ test('Hello', 'World!') }} {{ test(str2='World!', str1='Hello') }} {{ test(str2='World!') }} {{ test('Hello') }} +{{ test_w('Hello', 'World!') }} +{{ test_w(str2='World!', str1='Hello') }} +{{ test_w(str2='World!') }} +{{ test_w('Hello') }} {{ test2(['H', 'e', 'l', 'l', 'o']) }} -)"; - - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - - jinja2::ValuesMap params; +)", +//------------- +R"( +Hello World! +Hello World! + World! +Hello default +Hello World! +Hello World! + World! +Hello default +Hello +)" +) +{ params["test"] = MakeCallable( [](const std::string& str1, const std::string& str2) { return str1 + " " + str2; }, ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} ); + params["test_w"] = MakeCallable( + [](const std::wstring& str1, const std::wstring& str2) { + return str1 + L" " + str2; + }, + ArgInfo{ "str1" }, ArgInfo{ "str2", false, "default" } + ); params["test2"] = MakeCallable( [](const GenericList& list) { std::ostringstream os; for(auto& v : list) - os << v.asString(); + os << AsString(v); return os.str(); }, ArgInfo{"list"} ); - - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = R"( -Hello World! -Hello World! - World! -Hello default -Hello -)"; - EXPECT_EQ(expectedResult, result); } -TEST(UserCallableTest, ReflectedCallable) +TEST(UserCallableTestSingle, ReflectedCallable) { std::string source = R"( {% set callable = reflected.basicCallable %}{{ callable() }} @@ -176,15 +166,6 @@ TEST_P(UserCallableParamConvertTest, Test) auto& testParam = GetParam(); std::string source = "{{" + testParam.tpl + " | pprint }}"; - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - jinja2::ValuesMap params = PrepareTestData(); params["BoolFn"] = MakeCallable([](bool val) {return val;}, ArgInfo{"val"}); @@ -193,7 +174,9 @@ TEST_P(UserCallableParamConvertTest, Test) params["DoubleFn"] = MakeCallable([](double val) {return val;}, ArgInfo{"val"}); params["StringFn"] = MakeCallable([](const std::string& val) {return val;}, ArgInfo{"val"}); params["WStringFn"] = MakeCallable([](const std::wstring& val) {return val;}, ArgInfo{"val"}); - params["GListFn"] = MakeCallable([](const GenericList& val) + params["StringViewFn"] = MakeCallable([](const nonstd::string_view& val) {return std::string(val.begin(), val.end()); }, ArgInfo{ "val" }); + params["WStringViewFn"] = MakeCallable([](const nonstd::wstring_view& val) {return std::wstring(val.begin(), val.end()); }, ArgInfo{ "val" }); + params["GListFn"] = MakeCallable([](const GenericList& val) { return val; }, ArgInfo{"val"}); @@ -205,10 +188,7 @@ TEST_P(UserCallableParamConvertTest, Test) return val; }, ArgInfo{"**kwargs"}); - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result, params); } struct UserCallableFilterTestTag; @@ -219,15 +199,6 @@ TEST_P(UserCallableFilterTest, Test) auto& testParam = GetParam(); std::string source = "{{ " + testParam.tpl + " }}"; - Template tpl; - auto parseRes = tpl.Load(source); - EXPECT_TRUE(parseRes.has_value()); - if (!parseRes) - { - std::cout << parseRes.error() << std::endl; - return; - } - jinja2::ValuesMap params = PrepareTestData(); params["surround"] = MakeCallable( @@ -245,7 +216,7 @@ TEST_P(UserCallableFilterTest, Test) isFirst = false; else os << delim; - os << v.asString(); + os << AsString(v); } return os.str(); @@ -254,10 +225,7 @@ TEST_P(UserCallableFilterTest, Test) return testValue == pattern; }, ArgInfo{"testVal"}, ArgInfo{"pattern"}); - std::string result = tpl.RenderAsString(params).value(); - std::cout << result << std::endl; - std::string expectedResult = testParam.result; - EXPECT_EQ(expectedResult, result); + PerformBothTests(source, testParam.result, params); } INSTANTIATE_TEST_CASE_P(BoolParamConvert, UserCallableParamConvertTest, ::testing::Values( @@ -300,7 +268,29 @@ INSTANTIATE_TEST_CASE_P(VarKwArgsParamsConvert, UserCallableParamConvertTest, :: INSTANTIATE_TEST_CASE_P(StringParamConvert, UserCallableParamConvertTest, ::testing::Values( InputOutputPair{"StringFn()", "''"}, InputOutputPair{"StringFn('Hello World')", "'Hello World'"}, - InputOutputPair{"StringFn(stringValue)", "'rain'"} + InputOutputPair{"StringFn(stringValue)", "'rain'"}, + InputOutputPair{"StringFn(wstringValue)", "' hello world '"} + )); + +INSTANTIATE_TEST_CASE_P(WStringParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"WStringFn()", "''"}, + InputOutputPair{"WStringFn('Hello World')", "'Hello World'"}, + InputOutputPair{"WStringFn(stringValue)", "'rain'"}, + InputOutputPair{"WStringFn(wstringValue)", "' hello world '"} + )); + +INSTANTIATE_TEST_CASE_P(StringViewParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"StringViewFn()", "''"}, + InputOutputPair{"StringViewFn('Hello World')", "'Hello World'"}, + InputOutputPair{"StringViewFn(stringValue)", "'rain'"}, + InputOutputPair{"StringViewFn(wstringValue)", "' hello world '"} + )); + +INSTANTIATE_TEST_CASE_P(WStringViewParamConvert, UserCallableParamConvertTest, ::testing::Values( + InputOutputPair{"WStringViewFn()", "''"}, + InputOutputPair{"WStringViewFn('Hello World')", "'Hello World'"}, + InputOutputPair{"WStringViewFn(stringValue)", "'rain'"}, + InputOutputPair{"WStringViewFn(wstringValue)", "' hello world '"} )); INSTANTIATE_TEST_CASE_P(ListParamConvert, UserCallableParamConvertTest, ::testing::Values( @@ -337,7 +327,7 @@ INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], " - "'wstrValue': '']"}, + "'wstrValue': 'test string 0']"}, InputOutputPair{"GMapFn(reflectedVal.innerStruct) | dictsort", "['strValue': 'Hello World!']"} )); diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fbdc852a..5fbadd22 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -64,14 +64,14 @@ if(JINJA2CPP_BUILD_TESTS) endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) - set (JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} boost_variant boost_filesystem boost_algorithm) + set (JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} boost_variant boost_filesystem boost_algorithm fmt rh_lib) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () if (NOT DEFINED JINJA2_PUBLIC_LIBS_INT) - set (JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_PUBLIC_LIBS} expected-lite variant-lite optional-lite) + set (JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_PUBLIC_LIBS} expected-lite variant-lite optional-lite string-view-lite) else () set (JINJA2CPP_PUBLIC_LIBS ${JINJA2_PUBLIC_LIBS_INT}) endif () diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib new file mode 160000 index 00000000..037b84f2 --- /dev/null +++ b/thirdparty/fmtlib @@ -0,0 +1 @@ +Subproject commit 037b84f21465dbca971bfa67646ab34c872d7e6a diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index ffe8deb8..aa9564e9 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -9,9 +9,39 @@ update_submodule(nonstd/optional-lite) add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) add_library(optional-lite ALIAS optional-lite) +update_submodule(nonstd/string-view-lite) +add_subdirectory(thirdparty/nonstd/string-view-lite EXCLUDE_FROM_ALL) +add_library(string-view-lite ALIAS string-view-lite) + +update_submodule(fmtlib) +set (FMT_INSTALL ON CACHE BOOL "" FORCE) +add_subdirectory(thirdparty/fmtlib EXCLUDE_FROM_ALL) +add_library(fmt ALIAS fmt-header-only) + +update_submodule(robin-hood-hashing) +add_library(rh_lib INTERFACE) +target_include_directories(rh_lib + INTERFACE + $ + $) +# set_target_properties(rh_lib PROPERTIES +# INTERFACE_INCLUDE_DIRECTORIES +# BLA-BLA-BLA +# "$" +# "$" +# ) + install (FILES thirdparty/nonstd/expected-lite/include/nonstd/expected.hpp thirdparty/nonstd/variant-lite/include/nonstd/variant.hpp thirdparty/nonstd/optional-lite/include/nonstd/optional.hpp + thirdparty/nonstd/string-view-lite/include/nonstd/string_view.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd) - \ No newline at end of file + +install(TARGETS rh_lib + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + ) + diff --git a/thirdparty/nonstd/string-view-lite b/thirdparty/nonstd/string-view-lite new file mode 160000 index 00000000..932318ad --- /dev/null +++ b/thirdparty/nonstd/string-view-lite @@ -0,0 +1 @@ +Subproject commit 932318ad6330345b5cf4730b313ccee56343638d diff --git a/thirdparty/robin-hood-hashing b/thirdparty/robin-hood-hashing new file mode 160000 index 00000000..d6079c33 --- /dev/null +++ b/thirdparty/robin-hood-hashing @@ -0,0 +1 @@ +Subproject commit d6079c3306a472e5b876a13a0663abf440716f70 diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index 6bb6a693..e27b7ba0 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -3,7 +3,9 @@ message(STATUS "'conan-build' dependencies mode selected for Jinja2Cpp. All depe find_package(expected-lite) find_package(variant-lite) find_package(optional-lite) +find_package(string-view-lite) find_package(boost) +find_package(fmtlib) -set (JINJA2_PRIVATE_LIBS_INT boost::boost) -set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite optional-lite::optional-lite) +set (JINJA2_PRIVATE_LIBS_INT boost::boost fmt::fmt) +set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite optional-lite::optional-lite string-view-lite::string-view-lite) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake index ea69d32b..b40b0c5f 100644 --- a/thirdparty/thirdparty-external.cmake +++ b/thirdparty/thirdparty-external.cmake @@ -20,7 +20,7 @@ macro (FindHeaderOnlyLib HDR_PATH TARGET_NAME) endmacro () macro (find_hdr_package PKG_NAME HDR_PATH) - find_package(${PKG_NAME} QUIET) + find_package(${PKG_NAME}) if(NOT ${PKG_NAME}_FOUND) FindHeaderOnlyLib(${HDR_PATH} ${PKG_NAME}) endif () @@ -37,8 +37,16 @@ endmacro () find_hdr_package(expected-lite nonstd/expected.hpp) find_hdr_package(variant-lite nonstd/variant.hpp) find_hdr_package(optional-lite nonstd/optional.hpp) +find_hdr_package(string-view-lite nonstd/string_view.hpp) +find_hdr_package(fmt-header-only fmt/format.h) +find_hdr_package(rh_lib robin_hood.h) -install(TARGETS expected-lite variant-lite optional-lite +if (TARGET fmt-header-only) + target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) + add_library(fmt ALIAS fmt-header-only) +endif () + +install(TARGETS expected-lite variant-lite optional-lite string-view-lite EXPORT InstallTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -46,4 +54,11 @@ install(TARGETS expected-lite variant-lite optional-lite PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd ) +install(TARGETS fmt-header-only rh_lib + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + ) + include (./thirdparty/external_boost_deps.cmake) From 8d50ae8492580a6ebaaabcc102bc11010bd9eb8f Mon Sep 17 00:00:00 2001 From: Eugene Palchukovsky Date: Fri, 16 Aug 2019 23:20:08 +0300 Subject: [PATCH 096/206] Supports statement "filter". Resolves #44. (#129) * Supports statement "filter". Resolves #44. * Removed unused assigned and variable. * Adds unit-test for statement "filter" with a chain of parameterized filters. --- src/expression_parser.h | 2 +- src/statements.cpp | 11 +++++- src/statements.h | 20 ++++++++++ src/template_parser.cpp | 44 +++++++++++++++++++++- src/template_parser.h | 7 ++-- test/{set_test.cpp => statements_tets.cpp} | 32 ++++++++++++++++ 6 files changed, 110 insertions(+), 6 deletions(-) rename test/{set_test.cpp => statements_tets.cpp} (79%) diff --git a/src/expression_parser.h b/src/expression_parser.h index 5273e9c7..90411e10 100644 --- a/src/expression_parser.h +++ b/src/expression_parser.h @@ -21,6 +21,7 @@ class ExpressionParser ParseResult Parse(LexScanner& lexer); ParseResult> ParseFullExpression(LexScanner& lexer, bool includeIfPart = true); ParseResult ParseCallParams(LexScanner& lexer); + ParseResult> ParseFilterExpression(LexScanner& lexer); private: ParseResult> ParseLogicalNot(LexScanner& lexer); ParseResult> ParseLogicalOr(LexScanner& lexer); @@ -37,7 +38,6 @@ class ExpressionParser ParseResult> ParseTuple(LexScanner& lexer); ParseResult> ParseCall(LexScanner& lexer, ExpressionEvaluatorPtr valueRef); ParseResult> ParseSubscript(LexScanner& lexer, ExpressionEvaluatorPtr valueRef); - ParseResult> ParseFilterExpression(LexScanner& lexer); ParseResult> ParseIfExpression(LexScanner& lexer); private: diff --git a/src/statements.cpp b/src/statements.cpp index cede4667..70126a19 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -5,7 +5,6 @@ #include -#include #include using namespace std::string_literals; @@ -734,4 +733,14 @@ void WithStatement::Render(OutStream& os, RenderContext& values) innerValues.ExitScope(); } + +void FilterStatement::Render(OutStream& os, RenderContext& values) +{ + TargetString arg; + auto argStream = values.GetRendererCallback()->GetStreamOnString(arg); + auto innerValues = values.Clone(true); + m_body->Render(argStream, innerValues); + const auto result = m_expr->Evaluate(arg, values); + os.WriteValue(result); +} } // jinja2 diff --git a/src/statements.h b/src/statements.h index 83ffcfec..7aeacd41 100644 --- a/src/statements.h +++ b/src/statements.h @@ -358,6 +358,26 @@ class WithStatement : public Statement std::vector>> m_scopeVars; RendererPtr m_mainBody; }; + +class FilterStatement : public Statement +{ +public: + VISITABLE_STATEMENT(); + + explicit FilterStatement(ExpressionEvaluatorPtr expr) + : m_expr(std::move(expr)) {} + + void SetBody(RendererPtr renderer) + { + m_body = std::move(renderer); + } + + void Render(OutStream &, RenderContext &) override; + +private: + ExpressionEvaluatorPtr m_expr; + RendererPtr m_body; +}; } // jinja2 diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 37a6a1a5..eed5bd19 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -1,5 +1,5 @@ #include "template_parser.h" -#include +#include namespace jinja2 { @@ -74,7 +74,11 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme result = ParseEndWith(lexer, statementsInfo, tok); break; case Keyword::Filter: + result = ParseFilter(lexer, statementsInfo, tok); + break; case Keyword::EndFilter: + result = ParseEndFilter(lexer, statementsInfo, tok); + break; case Keyword::EndSet: return MakeParseError(ErrorCode::YetUnsupported, tok); default: @@ -891,4 +895,42 @@ StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& /*lexer return ParseResult(); } +StatementsParser::ParseResult StatementsParser::ParseFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + ExpressionParser exprParser(m_settings); + auto filterExpr = exprParser.ParseFilterExpression(lexer); + if (!filterExpr) + { + return filterExpr.get_unexpected(); + } + + auto renderer = std::make_shared(*filterExpr); + auto statementInfo = StatementInfo::Create( + StatementInfo::FilterStatement, stmtTok); + statementInfo.renderer = std::move(renderer); + statementsInfo.push_back(std::move(statementInfo)); + + return {}; +} + +StatementsParser::ParseResult StatementsParser::ParseEndFilter(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) +{ + if (statementsInfo.size() <= 1) + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + + auto info = statementsInfo.back(); + if (info.type != StatementInfo::FilterStatement) + { + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + } + + statementsInfo.pop_back(); + auto &renderer = *boost::polymorphic_downcast(info.renderer.get()); + renderer.SetBody(info.compositions[0]); + + statementsInfo.back().currentComposition->AddRenderer(info.renderer); + + return {}; +} + } diff --git a/src/template_parser.h b/src/template_parser.h index 2ca94fe7..ae8fce27 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -6,7 +6,6 @@ #include "lexer.h" #include "lexertk.h" #include "error_handling.h" -#include "expression_evaluator.h" #include "expression_parser.h" #include "statements.h" #include "helpers.h" @@ -20,7 +19,6 @@ #include #include #include -#include #include #include @@ -180,7 +178,8 @@ struct StatementInfo ParentBlockStatement, MacroStatement, MacroCallStatement, - WithStatement + WithStatement, + FilterStatement }; using ComposedPtr = std::shared_ptr; @@ -238,6 +237,8 @@ class StatementsParser ParseResult ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token); ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); + ParseResult ParseEndFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); private: Settings m_settings; diff --git a/test/set_test.cpp b/test/statements_tets.cpp similarity index 79% rename from test/set_test.cpp rename to test/statements_tets.cpp index 5de376b8..f78a4815 100644 --- a/test/set_test.cpp +++ b/test/statements_tets.cpp @@ -189,3 +189,35 @@ R"( { params = {{"outer", "World Hello"}}; } + +TEST(FilterStatement, General) +{ + const std::string source = R"( +{% filter upper %} + This text becomes uppercase +{% endfilter %} +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("\n THIS TEXT BECOMES UPPERCASE\n", result.c_str()); +} + +TEST(FilterStatement, ChainAndParams) +{ + const std::string source = R"( +{% filter list | sort(reverse=true) | unique | join("+") %} +11222333445556677890 +{% endfilter %} +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("\n9+8+7+6+5+4+3+2+1+0+\n", result.c_str()); +} From 6d4a1895223a2da867f2914c036f3b11f05c0b3e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 18 Aug 2019 21:37:27 +0300 Subject: [PATCH 097/206] Add built-in reflection for nlohmann and rapid json libraries. Resolves #78 (#130) * Add reflection for nlohmann json library ( https://github.com/nlohmann/json ) * Add extra tests for nlohmann json reflectors * Implement reflection for RapidJson library ( https://github.com/Tencent/rapidjson ) * Fix build, add extra tests --- .gitmodules | 6 + CMakeLists.txt | 5 +- README.md | 2 +- include/jinja2cpp/binding/nlohmann_json.h | 168 ++++++++++++++++++++ include/jinja2cpp/binding/rapid_json.h | 155 +++++++++++++++++++ include/jinja2cpp/reflected_value.h | 161 ++++++++++++-------- test/nlohmann_json_binding_test.cpp | 177 ++++++++++++++++++++++ test/rapid_json_binding_test.cpp | 137 +++++++++++++++++ test/test_tools.h | 18 +-- thirdparty/internal_deps.cmake | 17 +++ thirdparty/json/nlohmann | 1 + thirdparty/json/rapid | 1 + 12 files changed, 770 insertions(+), 78 deletions(-) create mode 100644 include/jinja2cpp/binding/nlohmann_json.h create mode 100644 include/jinja2cpp/binding/rapid_json.h create mode 100644 test/nlohmann_json_binding_test.cpp create mode 100644 test/rapid_json_binding_test.cpp create mode 160000 thirdparty/json/nlohmann create mode 160000 thirdparty/json/rapid diff --git a/.gitmodules b/.gitmodules index 1c61dcb5..ab5dd7dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,9 @@ [submodule "thirdparty/robin-hood-hashing"] path = thirdparty/robin-hood-hashing url = https://github.com/martinus/robin-hood-hashing.git +[submodule "thirdparty/json/nlohmann"] + path = thirdparty/json/nlohmann + url = https://github.com/nlohmann/json.git +[submodule "thirdparty/json/rapid"] + path = thirdparty/json/rapid + url = https://github.com/Tencent/rapidjson.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d249d925..d2081c2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,7 +149,7 @@ target_include_directories(${LIB_TARGET_NAME} if(JINJA2CPP_STRICT_WARNINGS) if(NOT MSVC) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-unused-command-line-argument) else () target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() @@ -176,7 +176,8 @@ if (JINJA2CPP_BUILD_TESTS) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) - target_link_libraries(jinja2cpp_tests gtest gtest_main ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS}) + target_link_libraries(jinja2cpp_tests gtest gtest_main nlohmann_json ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS} ) + target_include_directories(jinja2cpp_tests PRIVATE ${RapidJSON_INCLUDE_DIR}) set_target_properties(jinja2cpp_tests PROPERTIES CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} diff --git a/README.md b/README.md index 3d8196d9..b1b412d6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Main features of Jinja2C++: - Easy-to-use public interface. Just load templates and render them. - Conformance to [Jinja2 specification](http://jinja.pocoo.org/docs/2.10/) - Full support of narrow- and wide-character strings both for templates and parameters. -- Built-in reflection for C++ types. +- Built-in reflection for the common C++ types, nlohmann and rapid JSON libraries. - Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions. - Control statements (`set`, `for`, `if`, `do`, `with`). - Templates extention, including and importing diff --git a/include/jinja2cpp/binding/nlohmann_json.h b/include/jinja2cpp/binding/nlohmann_json.h new file mode 100644 index 00000000..9a244faa --- /dev/null +++ b/include/jinja2cpp/binding/nlohmann_json.h @@ -0,0 +1,168 @@ +#ifndef JINJA2CPP_BINDING_NLOHMANN_JSON_H +#define JINJA2CPP_BINDING_NLOHMANN_JSON_H + +#include + +#include + +namespace jinja2 +{ +namespace detail +{ + +class NLohmannJsonObjectAccessor : public MapItemAccessor, public ReflectedDataHolder +{ +public: + using ReflectedDataHolder::ReflectedDataHolder; + ~NLohmannJsonObjectAccessor() override = default; + + size_t GetSize() const override + { + auto j = this->GetValue(); + return j ? j->size() : 0ULL; + } + + bool HasValue(const std::string& name) const override + { + auto j = this->GetValue(); + return j ? j->contains(name) : false; + } + + Value GetValueByName(const std::string& name) const override + { + auto j = this->GetValue(); + if (!j || !j->contains(name)) + return Value(); + + return Reflect(&(*j)[name]); + } + + std::vector GetKeys() const override + { + auto j = this->GetValue(); + if (!j) + return {}; + + std::vector result; + for (auto& item : j->items()) + { + result.emplace_back(item.key()); + } + return result; + } +}; + + +struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, ReflectedDataHolder +{ + using ReflectedDataHolder::ReflectedDataHolder; + + nonstd::optional GetSize() const override + { + auto j = this->GetValue(); + return j ? j->size() : nonstd::optional(); + } + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + using Enum = Enumerator; + auto j = this->GetValue(); + if (!j) + jinja2::ListEnumeratorPtr(nullptr, Enum::Deleter); + + return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end()), Enum::Deleter); + } + + Value GetItemByIndex(int64_t idx) const override + { + auto j = this->GetValue(); + if (!j) + return Value(); + + return Reflect((*j)[idx]); + } + + size_t GetItemsCount() const override + { + auto sz = this->GetSize(); + return sz.value_or(0ULL); + } +}; + +template<> +struct Reflector +{ + static Value Create(nlohmann::json val) + { + Value result; + switch (val.type()) + { + case nlohmann::detail::value_t::null: + break; + case nlohmann::detail::value_t::object: + result = GenericMap([accessor = NLohmannJsonObjectAccessor(std::move(val))]() { return &accessor; }); + break; + case nlohmann::detail::value_t::array: + result = GenericList([accessor = NLohmannJsonArrayAccessor(std::move(val))]() { return &accessor; }); + break; + case nlohmann::detail::value_t::string: + result = val.get(); + break; + case nlohmann::detail::value_t::boolean: + result = val.get(); + break; + case nlohmann::detail::value_t::number_integer: + case nlohmann::detail::value_t::number_unsigned: + result = val.get(); + break; + case nlohmann::detail::value_t::number_float: + result = val.get(); + break; + case nlohmann::detail::value_t::discarded: + break; + } + return result; + } + + static Value CreateFromPtr(const nlohmann::json *val) + { + Value result; + switch (val->type()) + { + case nlohmann::detail::value_t::null: + break; + case nlohmann::detail::value_t::object: + result = GenericMap([accessor = NLohmannJsonObjectAccessor(val)]() { return &accessor; }); + break; + case nlohmann::detail::value_t::array: + result = GenericList([accessor = NLohmannJsonArrayAccessor(val)]() {return &accessor;}); + break; + case nlohmann::detail::value_t::string: + result = val->get(); + break; + case nlohmann::detail::value_t::boolean: + result = val->get(); + break; + case nlohmann::detail::value_t::number_integer: + case nlohmann::detail::value_t::number_unsigned: + result = val->get(); + break; + case nlohmann::detail::value_t::number_float: + result = val->get(); + break; + case nlohmann::detail::value_t::discarded: + break; + } + return result; + } + +}; + +} // namespace detail +} // namespace jinja2 + +#endif // JINJA2CPP_BINDING_NLOHMANN_JSON_H \ No newline at end of file diff --git a/include/jinja2cpp/binding/rapid_json.h b/include/jinja2cpp/binding/rapid_json.h new file mode 100644 index 00000000..26fde15d --- /dev/null +++ b/include/jinja2cpp/binding/rapid_json.h @@ -0,0 +1,155 @@ +#ifndef JINJA2CPP_BINDING_RAPID_JSON_H +#define JINJA2CPP_BINDING_RAPID_JSON_H + +#include +#include + +#include + +namespace jinja2 +{ +namespace detail +{ + +template +class RapidJsonObjectAccessor : public MapItemAccessor, public ReflectedDataHolder +{ +public: + using ReflectedDataHolder::ReflectedDataHolder; + ~RapidJsonObjectAccessor() override = default; + + size_t GetSize() const override + { + auto j = this->GetValue(); + return j ? j->MemberCount() : 0ULL; + } + + bool HasValue(const std::string& name) const override + { + auto j = this->GetValue(); + return j ? j->HasMember(name.c_str()) : false; + } + + Value GetValueByName(const std::string& name) const override + { + auto j = this->GetValue(); + if (!j || !j->HasMember(name.c_str())) + return Value(); + + return Reflect(&(*j)[name.c_str()]); + } + + std::vector GetKeys() const override + { + auto j = this->GetValue(); + if (!j) + return {}; + + std::vector result; + // for (auto& item : j->()) + for (auto it = j->MemberBegin(); it != j->MemberEnd(); ++ it) + { + result.emplace_back(it->name.GetString()); + } + return result; + } +}; + + +struct RapidJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, ReflectedDataHolder +{ + using ReflectedDataHolder::ReflectedDataHolder; + + nonstd::optional GetSize() const override + { + auto j = this->GetValue(); + return j ? j->Size() : nonstd::optional(); + } + const IndexBasedAccessor* GetIndexer() const override + { + return this; + } + + ListEnumeratorPtr CreateEnumerator() const override + { + using Enum = Enumerator; + auto j = this->GetValue(); + if (!j) + jinja2::ListEnumeratorPtr(nullptr, Enum::Deleter); + + return jinja2::ListEnumeratorPtr(new Enum(j->Begin(), j->End()), Enum::Deleter); + } + + Value GetItemByIndex(int64_t idx) const override + { + auto j = this->GetValue(); + if (!j) + return Value(); + + return Reflect((*j)[idx]); + } + + size_t GetItemsCount() const override + { + auto sz = this->GetSize(); + return sz.value_or(0ULL); + } +}; + +template<> +struct Reflector +{ + static Value CreateFromPtr(const rapidjson::Value *val) + { + Value result; + switch (val->GetType()) + { + case rapidjson::kNullType: + break; + case rapidjson::kFalseType: + result = Value(false); + break; + case rapidjson::kTrueType: + result = Value(true); + break; + case rapidjson::kObjectType: + result = GenericMap([accessor = RapidJsonObjectAccessor(val)]() { return &accessor; }); + break; + case rapidjson::kArrayType: + result = GenericList([accessor = RapidJsonArrayAccessor(val)]() { return &accessor; }); + break; + case rapidjson::kStringType: + result = std::string(val->GetString(), val->GetStringLength()); + break; + case rapidjson::kNumberType: + if (val->IsInt64() || val->IsUint64()) + result = val->GetInt64(); + else if (val->IsInt() || val->IsUint()) + result = val->GetInt(); + else + result = val->GetDouble(); + break; + } + return result; + } + +}; + +template<> +struct Reflector +{ + static Value Create(const rapidjson::Document& val) + { + return GenericMap([accessor = RapidJsonObjectAccessor(&val)]() { return &accessor; }); + } + + static Value CreateFromPtr(const rapidjson::Document *val) + { + return GenericMap([accessor = RapidJsonObjectAccessor(val)]() { return &accessor; }); + } + +}; +} // namespace detail +} // namespace jinja2 + +#endif // JINJA2CPP_BINDING_RAPID_JSON_H diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 426e9c49..1b08697c 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -71,20 +71,20 @@ class ReflectedMapImplBase : public MapItemAccessor } }; +template +class ReflectedDataHolder; + template -class ReflectedMapImpl : public ReflectedMapImplBase> +class ReflectedDataHolder { public: - ReflectedMapImpl(T val) : m_value(val) {} - ReflectedMapImpl(const T* val) : m_valuePtr(val) {} + explicit ReflectedDataHolder(T val) : m_value(std::move(val)) {} + explicit ReflectedDataHolder(const T* val) : m_valuePtr(val) {} - static auto GetAccessors() {return TypeReflection::GetAccessors();} - template - Value GetField(Fn&& accessor) const +protected: + const T* GetValue() const { - if (!m_valuePtr && !m_value) - return Value(); - return accessor(m_valuePtr ? *m_valuePtr : m_value.value()); + return m_valuePtr ? m_valuePtr : (m_value ? &m_value.value() : nullptr); } private: @@ -92,6 +92,39 @@ class ReflectedMapImpl : public ReflectedMapImplBase> const T* m_valuePtr = nullptr; }; +template +class ReflectedDataHolder +{ +public: + explicit ReflectedDataHolder(const T* val) : m_valuePtr(val) {} + +protected: + const T* GetValue() const + { + return m_valuePtr; + } + +private: + const T* m_valuePtr = nullptr; +}; + +template +class ReflectedMapImpl : public ReflectedMapImplBase>, public ReflectedDataHolder +{ +public: + using ReflectedDataHolder::ReflectedDataHolder; + + static auto GetAccessors() {return TypeReflection::GetAccessors();} + template + Value GetField(Fn&& accessor) const + { + auto v = this->GetValue(); + if (!v) + return Value(); + return accessor(*v); + } +}; + namespace detail { template @@ -100,71 +133,68 @@ struct Reflector; template using IsReflectedType = std::enable_if_t::value>; -// using IsReflectedType = std::enable_if_t::GetAccessors())::key_type, std::string>::value>; -// using IsReflectedType = typename Type2Void::GetAccessors())>::key_type>::type; - -struct ContainerReflector +template +struct Enumerator : public ListEnumerator { - template - struct Enumerator : public ListEnumerator - { - It m_begin; - It m_cur; - It m_end; - bool m_justInited = true; + It m_begin; + It m_cur; + It m_end; + bool m_justInited = true; - Enumerator(It begin, It end) - : m_begin(begin) - , m_cur(end) - , m_end(end) - {} + Enumerator(It begin, It end) + : m_begin(begin) + , m_cur(end) + , m_end(end) + {} - void Reset() override - { - m_justInited = true; - } + void Reset() override + { + m_justInited = true; + } - bool MoveNext() override + bool MoveNext() override + { + if (m_justInited) { - if (m_justInited) - { - m_cur = m_begin; - m_justInited = false; - } - else - ++ m_cur; - - return m_cur != m_end; + m_cur = m_begin; + m_justInited = false; } + else + ++ m_cur; - Value GetCurrent() const override - { - return Reflect(*m_cur); - } + return m_cur != m_end; + } - ListEnumeratorPtr Clone() const override - { - auto result = std::make_unique>(m_begin, m_end); - result->m_cur = m_cur; - result->m_justInited = m_justInited; - return jinja2::ListEnumeratorPtr(result.release(), Deleter); - } + Value GetCurrent() const override + { + return Reflect(*m_cur); + } - ListEnumeratorPtr Move() override - { - auto result = std::make_unique>(m_begin, m_end); - result->m_cur = std::move(m_cur); - result->m_justInited = m_justInited; - this->m_justInited = true; - return jinja2::ListEnumeratorPtr(result.release(), Deleter); - } + ListEnumeratorPtr Clone() const override + { + auto result = std::make_unique>(m_begin, m_end); + result->m_cur = m_cur; + result->m_justInited = m_justInited; + return jinja2::ListEnumeratorPtr(result.release(), Deleter); + } - static void Deleter(ListEnumerator* e) - { - delete static_cast*>(e); - } - }; + ListEnumeratorPtr Move() override + { + auto result = std::make_unique>(m_begin, m_end); + result->m_cur = std::move(m_cur); + result->m_justInited = m_justInited; + this->m_justInited = true; + return jinja2::ListEnumeratorPtr(result.release(), Deleter); + } + static void Deleter(ListEnumerator* e) + { + delete static_cast*>(e); + } +}; + +struct ContainerReflector +{ template struct ValueItemAccessor : ListItemAccessor, IndexBasedAccessor { @@ -209,7 +239,7 @@ struct ContainerReflector { const T* m_value; - PtrItemAccessor(const T* ptr) + explicit PtrItemAccessor(const T* ptr) : m_value(ptr) { } @@ -244,7 +274,7 @@ struct ContainerReflector template static Value CreateFromValue(T&& cont) { - return GenericList([accessor = ValueItemAccessor(std::move(cont))]() {return &accessor;}); + return GenericList([accessor = ValueItemAccessor(std::forward(cont))]() {return &accessor;}); } template @@ -446,7 +476,6 @@ template Value Reflect(T&& val) { return detail::Reflector::Create(std::forward(val)); - // return Value(ReflectedMap([accessor = ReflectedMapImpl(std::forward(val))]() -> const ReflectedMap::ItemAccessor* {return &accessor;})); } } // jinja2 diff --git a/test/nlohmann_json_binding_test.cpp b/test/nlohmann_json_binding_test.cpp new file mode 100644 index 00000000..82227711 --- /dev/null +++ b/test/nlohmann_json_binding_test.cpp @@ -0,0 +1,177 @@ +#include +#include + +#include "gtest/gtest.h" + +#include +#include +#include "test_tools.h" + +using NlohmannJsonTest = BasicTemplateRenderer; + +MULTISTR_TEST(NlohmannJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") +{ + nlohmann::json values = { + {"message", "Hello World from Parser!"} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(NlohmannJsonTest, BasicTypesReflection, R"( +{{ json.bool | pprint }} +{{ json.small_int | pprint }} +{{ json.big_int | pprint }} +{{ json.double | pprint }} +{{ json.string | pprint }} +)", +R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +)") +{ + nlohmann::json values = { + {"bool", true}, + {"small_int", 100500}, + {"big_int", 100500100500100LL}, + {"double", 100.5}, + {"string", "Hello World!"}, + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(NlohmannJsonTest, BasicValuesReflection, R"( +{{ bool_val | pprint }} +{{ small_int_val | pprint }} +{{ big_int_val | pprint }} +{{ double_val | pprint }} +{{ string_val | pprint }} +{{ array_val | pprint }} +{{ object_val | pprint }} +{{ empty_val | pprint }} +)", + R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +[1, 2, 3, 4] +{'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} +none +)") +{ + nlohmann::json values = { + {"bool", true}, + {"small_int", 100500}, + {"big_int", 100500100500100LL}, + {"double", 100.5}, + {"string", "Hello World!"}, + {"array", {1, 2, 3, 4}}, + {"object", { + {"message", "Hello World from Parser!"}, + {"message2", "Hello World from Parser-123!"} + }}}; + + auto boolVal = values["bool"]; + auto smallIntVal = values["small_int"]; + auto bigIntVal = values["big_int"]; + auto doubleVal = values["double"]; + auto stringVal = values["string"]; + auto arrayVal = values["array"]; + auto objectVal = values["object"]; + nlohmann::json emptyVal; + + params["bool_val"] = jinja2::Reflect(std::move(boolVal)); + params["small_int_val"] = jinja2::Reflect(std::move(smallIntVal)); + params["big_int_val"] = jinja2::Reflect(std::move(bigIntVal)); + params["double_val"] = jinja2::Reflect(std::move(doubleVal)); + params["string_val"] = jinja2::Reflect(std::move(stringVal)); + params["array_val"] = jinja2::Reflect(std::move(arrayVal)); + params["object_val"] = jinja2::Reflect(std::move(objectVal)); + params["empty_val"] = jinja2::Reflect(std::move(emptyVal)); +} + +MULTISTR_TEST(NlohmannJsonTest, SubobjectReflection, +R"( +{{ json.object.message }} +{{ json.object.message3 }} +{{ json.object | list | join(', ') }} +)", +R"( +Hello World from Parser! + +message, message2 +)") +{ + nlohmann::json values = { + {"object", { + {"message", "Hello World from Parser!"}, + {"message2", "Hello World from Parser-123!"} + }} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(NlohmannJsonTest, ArrayReflection, +R"( +{{ json.array | sort | pprint }} +{{ json.array | length }} +{{ json.array | first }} +{{ json.array | last }} +{{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} +)", +R"( +[1, 2, 3, 4, 5, 6, 7, 8, 9] +9 +9 +1 +1-3-5 +)") +{ + nlohmann::json values = { + {"array", {9, 8, 7, 6, 5, 4, 3, 2, 1}} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(NlohmannJsonTest, ParsedTypesReflection, R"( +{{ json.bool | pprint }} +{{ json.small_int | pprint }} +{{ json.big_int | pprint }} +{{ json.double | pprint }} +{{ json.string | pprint }} +{{ json.object.message | pprint }} +{{ json.array | sort | pprint }} +)", + R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +'Hello World from Parser!' +[1, 2, 3, 4, 5, 6, 7, 8, 9] +)") +{ + nlohmann::json values = nlohmann::json::parse(R"( +{ + "big_int": 100500100500100, + "bool": true, + "double": 100.5, + "small_int": 100500, + "string": "Hello World!", + "object": {"message": "Hello World from Parser!"}, + "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] +} +)");; + + params["json"] = jinja2::Reflect(std::move(values)); +} + diff --git a/test/rapid_json_binding_test.cpp b/test/rapid_json_binding_test.cpp new file mode 100644 index 00000000..189f3e72 --- /dev/null +++ b/test/rapid_json_binding_test.cpp @@ -0,0 +1,137 @@ +#include +#include + +#include "gtest/gtest.h" + +#include +#include +#include "test_tools.h" + +class RapidJsonTest : public BasicTemplateRenderer +{ +public: + const auto& GetJson() const {return m_json;} + const auto& GetEmptyVal() const {return m_emptyVal;} +protected: + void SetUp() override + { + const char *json = R"( +{ + "message": "Hello World from Parser!", + "big_int": 100500100500100, + "bool_true": true, + "bool_false": false, + "double": 100.5, + "small_int": 100500, + "string": "Hello World!", + "object": {"message": "Hello World from Parser!", "message2": "Hello World from Parser-123!"}, + "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] +} +)"; + m_json.Parse(json); + } +protected: + rapidjson::Document m_json; + rapidjson::Value m_emptyVal; +}; + +MULTISTR_TEST(RapidJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") +{ + params["json"] = jinja2::Reflect(test.GetJson()); +} + +MULTISTR_TEST(RapidJsonTest, BasicTypesReflection, R"( +{{ json.bool_true | pprint }} +{{ json.bool_false | pprint }} +{{ json.small_int | pprint }} +{{ json.big_int | pprint }} +{{ json.double | pprint }} +{{ json.string | pprint }} +)", +R"( +true +false +100500 +100500100500100 +100.5 +'Hello World!' +)") +{ + params["json"] = jinja2::Reflect(test.GetJson()); +} + +MULTISTR_TEST(RapidJsonTest, BasicValuesReflection, R"( +{{ bool_val | pprint }} +{{ small_int_val | pprint }} +{{ big_int_val | pprint }} +{{ double_val | pprint }} +{{ string_val | pprint }} +{{ array_val | pprint }} +{{ object_val | pprint }} +{{ empty_val | pprint }} +)", + R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +[9, 8, 7, 6, 5, 4, 3, 2, 1] +{'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} +none +)") +{ + auto& values = test.GetJson(); + + auto& boolVal = values["bool_true"]; + auto& smallIntVal = values["small_int"]; + auto& bigIntVal = values["big_int"]; + auto& doubleVal = values["double"]; + auto& stringVal = values["string"]; + auto& arrayVal = values["array"]; + auto& objectVal = values["object"]; + auto& emptyVal = test.GetEmptyVal(); + + params["bool_val"] = jinja2::Reflect(boolVal); + params["small_int_val"] = jinja2::Reflect(smallIntVal); + params["big_int_val"] = jinja2::Reflect(bigIntVal); + params["double_val"] = jinja2::Reflect(doubleVal); + params["string_val"] = jinja2::Reflect(stringVal); + params["array_val"] = jinja2::Reflect(arrayVal); + params["object_val"] = jinja2::Reflect(objectVal); + params["empty_val"] = jinja2::Reflect(emptyVal); +} + +MULTISTR_TEST(RapidJsonTest, SubobjectReflection, +R"( +{{ json.object.message }} +{{ json.object.message3 }} +{{ json.object | list | join(', ') }} +)", +R"( +Hello World from Parser! + +message, message2 +)") +{ + params["json"] = jinja2::Reflect(test.GetJson()); +} + +MULTISTR_TEST(RapidJsonTest, ArrayReflection, +R"( +{{ json.array | sort | pprint }} +{{ json.array | length }} +{{ json.array | first }} +{{ json.array | last }} +{{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} +)", +R"( +[1, 2, 3, 4, 5, 6, 7, 8, 9] +9 +9 +1 +1-3-5 +)") +{ + params["json"] = jinja2::Reflect(test.GetJson()); +} diff --git a/test/test_tools.h b/test/test_tools.h index 061579ff..5db11922 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -157,11 +157,8 @@ class BasicTemplateRenderer : public ::testing::Test } template - void PerformTest(const std::basic_string& source, const std::basic_string& expectedResult, void (*paramsGetter)(jinja2::ValuesMap&)) + void PerformTest(const std::basic_string& source, const std::basic_string& expectedResult, const jinja2::ValuesMap& params) { - jinja2::ValuesMap params; - paramsGetter(params); - ExecuteTest(source, expectedResult, params); } }; @@ -243,14 +240,17 @@ class TemplateEnvFixture : public ::testing::Test #define MULTISTR_TEST_IMPL(Fixture, TestName, StringT, TemplateT, Tpl, Result, ParamsGetter) \ TEST_F(Fixture, TestName) \ { \ - PerformTest(StringT(Tpl), StringT(Result), ParamsGetter); \ + jinja2::ValuesMap params; \ + ParamsGetter(params, *this); \ + \ + PerformTest(StringT(Tpl), StringT(Result), params); \ } #define MULTISTR_TEST(Fixture, TestName, Tpl, Result) \ -void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params);\ -MULTISTR_TEST_IMPL(Fixture, TestName##_Narrow, std::string, Template, Tpl, Result, Fixture##_##TestName##_Params_Getter) \ -MULTISTR_TEST_IMPL(Fixture, TestName##_Wide, std::wstring, TemplateW, L##Tpl, L##Result, Fixture##_##TestName##_Params_Getter) \ -void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params) +void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test);\ +MULTISTR_TEST_IMPL(Fixture, TestName##_Narrow, std::string, jinja2::Template, Tpl, Result, Fixture##_##TestName##_Params_Getter) \ +MULTISTR_TEST_IMPL(Fixture, TestName##_Wide, std::wstring, jinja2::TemplateW, L##Tpl, L##Result, Fixture##_##TestName##_Params_Getter) \ +void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test) struct SubstitutionGenericTestTag; using SubstitutionGenericTest = InputOutputPairTest; diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index aa9564e9..5dd4ceb0 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -18,6 +18,23 @@ set (FMT_INSTALL ON CACHE BOOL "" FORCE) add_subdirectory(thirdparty/fmtlib EXCLUDE_FROM_ALL) add_library(fmt ALIAS fmt-header-only) +if (JINJA2CPP_BUILD_TESTS) + update_submodule(json/nlohmann) + set (JSON_BuildTests OFF CACHE BOOL "" FORCE) + set (JSON_Install OFF CACHE BOOL "" FORCE) + set (JSON_MultipleHeaders ON CACHE BOOL "" FORCE) + add_subdirectory(thirdparty/json/nlohmann EXCLUDE_FROM_ALL) + + update_submodule(json/rapid) + set (RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) + set (RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set (RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set (RAPIDJSON_BUILD_THIRDPARTY_GTEST OFF CACHE BOOL "" FORCE) + set (RAPIDJSON_ENABLE_INSTRUMENTATION_OPT OFF CACHE BOOL "" FORCE) + add_subdirectory(thirdparty/json/rapid EXCLUDE_FROM_ALL) + find_package(RapidJSON) +endif() + update_submodule(robin-hood-hashing) add_library(rh_lib INTERFACE) target_include_directories(rh_lib diff --git a/thirdparty/json/nlohmann b/thirdparty/json/nlohmann new file mode 160000 index 00000000..a015b78e --- /dev/null +++ b/thirdparty/json/nlohmann @@ -0,0 +1 @@ +Subproject commit a015b78e81c859b88840cb0cd4001ce1fe5e7865 diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid new file mode 160000 index 00000000..d87b698d --- /dev/null +++ b/thirdparty/json/rapid @@ -0,0 +1 @@ +Subproject commit d87b698d0fcc10a5f632ecbc80a9cb2a8fa094a5 From f826cecd7da5db27a804dd8a8ca23b30aed91da8 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 18 Aug 2019 21:46:20 +0300 Subject: [PATCH 098/206] [skip ci] Update Readme.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1b412d6..f5578e47 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Main features of Jinja2C++: - Full support of narrow- and wide-character strings both for templates and parameters. - Built-in reflection for the common C++ types, nlohmann and rapid JSON libraries. - Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions. -- Control statements (`set`, `for`, `if`, `do`, `with`). +- Control statements (`set`, `for`, `if`, `filter`, `do`, `with`). - Templates extention, including and importing - Macros - Rich error reporting. @@ -103,6 +103,7 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - 'include' statement - 'import'/'from' statements - 'set' statement +- 'filter' statement - 'extends'/'block' statements - 'macro'/'call' statements - 'with' statement From 2b26969035d3b418a6ed8c2d61c5d341aa829aef Mon Sep 17 00:00:00 2001 From: Eugene Palchukovsky Date: Sun, 25 Aug 2019 09:56:54 +0300 Subject: [PATCH 099/206] Adds block statement "set". Resolves #45. (#131) * Adds block statement "set". Resolves #45. * Fixes minor typos. * Fixes compiling error with VS 2015. --- src/internal_value.cpp | 6 ++++ src/statements.cpp | 53 ++++++++++++++++++++-------- src/statements.h | 65 ++++++++++++++++++++++++++++++---- src/template_parser.cpp | 67 +++++++++++++++++++++++++---------- test/errors_test.cpp | 4 +-- test/statements_tets.cpp | 75 +++++++++++++++++++++++++++++++++++++--- 6 files changed, 225 insertions(+), 45 deletions(-) diff --git a/src/internal_value.cpp b/src/internal_value.cpp index f8e6db19..4c184253 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -34,6 +34,12 @@ struct SubscriptionVisitor : public visitors::BaseVisitor<> return values.GetValueByName(field); } + template + InternalValue operator() (std::basic_string value, const std::basic_string& /*fieldName*/) const + { + return TargetString(std::move(value)); + } + InternalValue operator() (const ListAdapter& values, int64_t index) const { if (index < 0 || static_cast(index) >= values.GetSize()) diff --git a/src/statements.cpp b/src/statements.cpp index 70126a19..aa595cae 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -211,19 +211,44 @@ void ElseBranchStatement::Render(OutStream& os, RenderContext& values) m_mainBody->Render(os, values); } -void SetStatement::Render(OutStream&, RenderContext& values) -{ - if (m_expr) - { - InternalValue val = m_expr->Evaluate(values); - if (m_fields.size() == 1) - values.GetCurrentScope()[m_fields[0]] = val; - else - { - for (auto& name : m_fields) - values.GetCurrentScope()[name] = Subscript(val, name, &values); - } - } +void SetStatement::AssignBody(InternalValue body, RenderContext& values) +{ + auto &scope = values.GetCurrentScope(); + if (m_fields.size() == 1) + scope[m_fields.front()] = std::move(body); + else + { + for (const auto& name : m_fields) + scope[name] = Subscript(body, name, &values); + } +} + +void SetLineStatement::Render(OutStream&, RenderContext& values) +{ + if (!m_expr) + return; + AssignBody(m_expr->Evaluate(values), values); +} + +InternalValue SetBlockStatement::RenderBody(RenderContext& values) +{ + TargetString result; + auto stream = values.GetRendererCallback()->GetStreamOnString(result); + auto innerValues = values.Clone(true); + m_body->Render(stream, innerValues); + return result; +} + +void SetRawBlockStatement::Render(OutStream&, RenderContext& values) +{ + AssignBody(RenderBody(values), values); +} + +void SetFilteredBlockStatement::Render(OutStream&, RenderContext& values) +{ + if (!m_expr) + return; + AssignBody(m_expr->Evaluate(RenderBody(values), values), values); } class BlocksRenderer : public RendererBase @@ -740,7 +765,7 @@ void FilterStatement::Render(OutStream& os, RenderContext& values) auto argStream = values.GetRendererCallback()->GetStreamOnString(arg); auto innerValues = values.Clone(true); m_body->Render(argStream, innerValues); - const auto result = m_expr->Evaluate(arg, values); + const auto result = m_expr->Evaluate(std::move(arg), values); os.WriteValue(result); } } // jinja2 diff --git a/src/statements.h b/src/statements.h index 7aeacd41..eaeba8c9 100644 --- a/src/statements.h +++ b/src/statements.h @@ -124,22 +124,75 @@ class ElseBranchStatement : public Statement class SetStatement : public Statement { public: - VISITABLE_STATEMENT(); - SetStatement(std::vector fields) : m_fields(std::move(fields)) { } - void SetAssignmentExpr(ExpressionEvaluatorPtr<> expr) +protected: + void AssignBody(InternalValue, RenderContext&); + +private: + const std::vector m_fields; +}; + +class SetLineStatement final : public SetStatement +{ +public: + VISITABLE_STATEMENT(); + + SetLineStatement(std::vector fields, ExpressionEvaluatorPtr<> expr) + : SetStatement(std::move(fields)), m_expr(std::move(expr)) { - m_expr = std::move(expr); } + void Render(OutStream& os, RenderContext& values) override; private: - std::vector m_fields; - ExpressionEvaluatorPtr<> m_expr; + const ExpressionEvaluatorPtr<> m_expr; +}; + +class SetBlockStatement : public SetStatement +{ +public: + using SetStatement::SetStatement; + + void SetBody(RendererPtr renderer) + { + m_body = std::move(renderer); + } + +protected: + InternalValue RenderBody(RenderContext&); + +private: + RendererPtr m_body; +}; + +class SetRawBlockStatement final : public SetBlockStatement +{ +public: + VISITABLE_STATEMENT(); + + using SetBlockStatement::SetBlockStatement; + + void Render(OutStream&, RenderContext&) override; +}; + +class SetFilteredBlockStatement final : public SetBlockStatement +{ +public: + VISITABLE_STATEMENT(); + + explicit SetFilteredBlockStatement(std::vector fields, ExpressionEvaluatorPtr expr) + : SetBlockStatement(std::move(fields)), m_expr(std::move(expr)) + { + } + + void Render(OutStream&, RenderContext&) override; + +private: + const ExpressionEvaluatorPtr m_expr; }; class ParentBlockStatement : public Statement diff --git a/src/template_parser.cpp b/src/template_parser.cpp index eed5bd19..7ad31310 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -32,6 +32,9 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme case Keyword::Set: result = ParseSet(lexer, statementsInfo, tok); break; + case Keyword::EndSet: + result = ParseEndSet(lexer, statementsInfo, tok); + break; case Keyword::Block: result = ParseBlock(lexer, statementsInfo, tok); break; @@ -79,8 +82,6 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme case Keyword::EndFilter: result = ParseEndFilter(lexer, statementsInfo, tok); break; - case Keyword::EndSet: - return MakeParseError(ErrorCode::YetUnsupported, tok); default: return MakeParseError(ErrorCode::UnexpectedToken, tok); } @@ -320,30 +321,60 @@ StatementsParser::ParseResult StatementsParser::ParseSet(LexScanner& lexer, Stat if (vars.empty()) return MakeParseError(ErrorCode::ExpectedIdentifier, lexer.PeekNextToken()); - auto operTok = lexer.NextToken(); - ExpressionEvaluatorPtr<> valueExpr; - if (operTok == '=') + ExpressionParser exprParser(m_settings); + if (lexer.EatIfEqual('=')) { - ExpressionParser exprParser(m_settings); - auto expr = exprParser.ParseFullExpression(lexer); + const auto expr = exprParser.ParseFullExpression(lexer); if (!expr) return expr.get_unexpected(); - valueExpr = *expr; + statementsInfo.back().currentComposition->AddRenderer( + std::make_shared(std::move(vars), *expr)); + } + else if (lexer.EatIfEqual('|')) + { + const auto expr = exprParser.ParseFilterExpression(lexer); + if (!expr) + return expr.get_unexpected(); + auto statementInfo = StatementInfo::Create( + StatementInfo::SetStatement, stmtTok); + statementInfo.renderer = std::make_shared( + std::move(vars), *expr); + statementsInfo.push_back(std::move(statementInfo)); } else - return MakeParseError(ErrorCode::YetUnsupported, operTok, {stmtTok}); // TODO: Add handling of the block assignments - - auto renderer = std::make_shared(vars); - renderer->SetAssignmentExpr(valueExpr); - statementsInfo.back().currentComposition->AddRenderer(renderer); + { + auto operTok = lexer.NextToken(); + if (lexer.NextToken() != Token::Eof) + return MakeParseError(ErrorCode::YetUnsupported, operTok, {std::move(stmtTok)}); + auto statementInfo = StatementInfo::Create( + StatementInfo::SetStatement, stmtTok); + statementInfo.renderer = std::make_shared( + std::move(vars)); + statementsInfo.push_back(std::move(statementInfo)); + } - return ParseResult(); + return {}; } -StatementsParser::ParseResult StatementsParser::ParseEndSet(LexScanner& /*lexer*/, StatementInfoList& /*statementsInfo*/ +StatementsParser::ParseResult StatementsParser::ParseEndSet(LexScanner& + , StatementInfoList& statementsInfo , const Token& stmtTok) { - return MakeParseError(ErrorCode::YetUnsupported, stmtTok); + if (statementsInfo.size() <= 1) + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + + const auto info = statementsInfo.back(); + if (info.type != StatementInfo::SetStatement) + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + + auto &renderer = *boost::polymorphic_downcast( + info.renderer.get()); + renderer.SetBody(info.compositions[0]); + + statementsInfo.pop_back(); + statementsInfo.back().currentComposition->AddRenderer(info.renderer); + + return {}; } StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, StatementInfoList& statementsInfo @@ -915,10 +946,10 @@ StatementsParser::ParseResult StatementsParser::ParseFilter(LexScanner& lexer, S StatementsParser::ParseResult StatementsParser::ParseEndFilter(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok) { - if (statementsInfo.size() <= 1) + if (statementsInfo.size() <= 1) return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); - auto info = statementsInfo.back(); + const auto info = statementsInfo.back(); if (info.type != StatementInfo::FilterStatement) { return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); diff --git a/test/errors_test.cpp b/test/errors_test.cpp index 4faabc76..513832e0 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -380,14 +380,12 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_1, ErrorsGenericTest, ::testing::Values( "noname.j2tpl:2:4: error: Unexpected statement: 'endfor'\n{% endfor %}\n---^-------"}, InputOutputPair{"{% set %}", "noname.j2tpl:1:8: error: Identifier expected\n{% set %}\n ---^-------"}, - InputOutputPair{"{% set id%}", - "noname.j2tpl:1:10: error: This feature has not been supported yet\n{% set id%}\n ---^-------"}, InputOutputPair{"{% set 10%}", "noname.j2tpl:1:8: error: Identifier expected\n{% set 10%}\n ---^-------"}, InputOutputPair{"{% set i = {key] %}", "noname.j2tpl:1:13: error: String expected\n{% set i = {key] %}\n ---^-------"}, InputOutputPair{"{% set id=10%}\n{% endset %}", - "noname.j2tpl:2:4: error: This feature has not been supported yet\n{% endset %}\n---^-------"}, + "noname.j2tpl:2:4: error: Unexpected statement: 'endset'\n{% endset %}\n---^-------"}, InputOutputPair{"{% extends %}", "noname.j2tpl:1:12: error: Unexpected token '<>'. Expected: '<>', '<>'\n{% extends %}\n ---^-------"}, InputOutputPair{"{% extends 10 %}", diff --git a/test/statements_tets.cpp b/test/statements_tets.cpp index f78a4815..f5e14ce6 100644 --- a/test/statements_tets.cpp +++ b/test/statements_tets.cpp @@ -202,14 +202,13 @@ TEST(FilterStatement, General) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - std::cout << result << std::endl; EXPECT_STREQ("\n THIS TEXT BECOMES UPPERCASE\n", result.c_str()); } TEST(FilterStatement, ChainAndParams) { const std::string source = R"( -{% filter list | sort(reverse=true) | unique | join("+") %} +{% filter trim | list | sort(reverse=true) | unique | join("+") %} 11222333445556677890 {% endfilter %} )"; @@ -218,6 +217,74 @@ TEST(FilterStatement, ChainAndParams) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - std::cout << result << std::endl; - EXPECT_STREQ("\n9+8+7+6+5+4+3+2+1+0+\n", result.c_str()); + EXPECT_STREQ("\n9+8+7+6+5+4+3+2+1+0", result.c_str()); } + +TEST(SetBlockStatement, OneVar) +{ + const std::string source = R"( +{% set foo %} +11222333445556677890 +{% endset %} +|{{foo}}| +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + EXPECT_STREQ("\n|11222333445556677890\n|\n", result.c_str()); +} + +TEST(SetBlockStatement, MoreVars) +{ + const std::string source = R"( +{% set foo1,foo2,foo3,foo4,foo5 %} +11222333445556677890 +{% endset %} +|{{foo1}}| +|{{foo2}}| +|{{foo5}}| +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + EXPECT_STREQ("\n|11222333445556677890\n|\n|11222333445556677890\n|\n|11222333445556677890\n|\n", result.c_str()); +} + +TEST(SetBlockStatement, OneVarFiltered) +{ + const std::string source = R"( +{% set foo | trim | list | sort(reverse=true) | unique | join("+") %} +11222333445556677890 +{% endset %} +|{{foo}}| +)"; + + Template tpl; + const auto load = tpl.Load(source); + ASSERT_TRUE(load) << load.error(); + + const auto result = tpl.RenderAsString({}).value(); + EXPECT_STREQ("\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); +} + +TEST(SetBlockStatement, MoreVarsFiltered) +{ + const std::string source = R"( +{% set foo1,foo2,foo3,foo4,foo5 | trim | list | sort(reverse=true) | unique | join("+") %} +11222333445556677890 +{% endset %} +|{{foo1}}| +|{{foo2}}| +|{{foo5}}| +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + EXPECT_STREQ("\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); +} \ No newline at end of file From 3dc7857fdb06443c994de9737ad48a1dd97c4ece Mon Sep 17 00:00:00 2001 From: wbn Date: Thu, 26 Sep 2019 10:49:06 -0700 Subject: [PATCH 100/206] adding CapitalMode to StringConverter::Filter (#139) --- README.md | 2 +- src/string_converter_filter.cpp | 15 +++++++++++++++ test/filters_test.cpp | 10 ++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f5578e47..27080daf 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ More detailed examples and features describtion can be found in the documentatio ## Current Jinja2 support Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Jinja2Cpp is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: - expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. -- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode**) +- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize**) - big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) - limited number of functions (**range**, **loop.cycle**) - 'if' statement (with 'elif' and 'else' branches) diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index cb9dca6e..36d1237f 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -303,6 +303,21 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex case UrlEncodeMode: result = Apply(baseVal); break; + case CapitalMode: + result = ApplyStringConverter(baseVal, [isFirstChar = true, &isAlpha](auto ch, auto&& fn) mutable { + if (isAlpha(ch)) + { + if (isFirstChar) + fn(std::toupper(ch, std::locale())); + else + fn(std::tolower(ch, std::locale())); + } + else + fn(ch); + + isFirstChar = false; + }); + break; default: break; } diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 37742215..d19779f6 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -466,3 +466,13 @@ INSTANTIATE_TEST_CASE_P(Truncate, FilterGenericTest, ::testing::Values( InputOutputPair{"'VeryVeryVeryLongWord' | truncate(16) | pprint", "'VeryVeryVeryLongWord'"}, InputOutputPair{"'foo bar baz qux' | truncate(6, end=' >>', leeway=0) | pprint", "'foo >>'"} )); + +INSTANTIATE_TEST_CASE_P(Capitalize, FilterGenericTest, ::testing::Values( + InputOutputPair{"'String' | capitalize | pprint", "'String'"}, + InputOutputPair{"'string' | capitalize | pprint", "'String'"}, + InputOutputPair{"'1234string' | capitalize | pprint", "'1234string'"}, + InputOutputPair{"'1234String' | capitalize | pprint", "'1234string'"}, + InputOutputPair{"'Hello World' | capitalize | pprint", "'Hello world'"}, + InputOutputPair{"' Hello World' | capitalize | pprint", "' hello world'"}, + InputOutputPair{"'Hello123OOO, World!' | capitalize | pprint", "'Hello123ooo, world!'"} + )); From ac607d2fcfdb50971f14a5f2250dd420256205e9 Mon Sep 17 00:00:00 2001 From: morenol <13-10934@usb.ve> Date: Fri, 27 Sep 2019 01:47:09 -0400 Subject: [PATCH 101/206] Add escape filter (#144) --- src/string_converter_filter.cpp | 25 +++++++++++++++++++++++++ test/filters_test.cpp | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/src/string_converter_filter.cpp b/src/string_converter_filter.cpp index 36d1237f..9e51f45c 100644 --- a/src/string_converter_filter.cpp +++ b/src/string_converter_filter.cpp @@ -318,6 +318,31 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex isFirstChar = false; }); break; + case EscapeHtmlMode: + result = ApplyStringConverter(baseVal, [](auto ch, auto&& fn) mutable { + switch(ch) + { + case '<': + fn('&', 'l', 't', ';'); + break; + case '>': + fn('&', 'g', 't', ';'); + break; + case '&': + fn('&', 'a', 'm', 'p', ';'); + break; + case '\'': + fn('&', '#', '3', '9', ';'); + break; + case '\"': + fn('&', '#', '3', '4', ';'); + break; + default: + fn(ch); + break; + } + }); + break; default: break; } diff --git a/test/filters_test.cpp b/test/filters_test.cpp index d19779f6..4375f069 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -476,3 +476,9 @@ INSTANTIATE_TEST_CASE_P(Capitalize, FilterGenericTest, ::testing::Values( InputOutputPair{"' Hello World' | capitalize | pprint", "' hello world'"}, InputOutputPair{"'Hello123OOO, World!' | capitalize | pprint", "'Hello123ooo, world!'"} )); + +INSTANTIATE_TEST_CASE_P(Escape, FilterGenericTest, ::testing::Values( + InputOutputPair{"'' | escape | pprint", "''"}, + InputOutputPair{"'abcd&> Date: Tue, 1 Oct 2019 16:02:42 +0300 Subject: [PATCH 102/206] Release 1.0 preparation (#147) * Test appveyor clang/MinGW builds * Test appveyor MinGW/clang builds * Test appveyor MinGW/clang builds * Test appveyor.yml MinGW/clang builds * Test appveyor MinGW/clang builds * Test appveyor MinGW/clang builds * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang builds * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang test * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor MinGW/clang build * Test appveyor clang build * Test appveyor clang build * Test appveyor clang build * Update appveyor.yml * Update appveyor.yml * [skip ci] Update Readme.md * [skip ci] Fix conan_build dependency management build scenario * - Add documentation - Fix conan build issues connected with type of MSVC runtime - Fix build issue with some versions of boost ( #132 ) * Fix missed variable reference * [skip ci] Update conan.io badge * Debug failed appveyor tests * Debug failed appveyor builds * Debug failed appveyor builds * Improve MSVC C++ runtime type handling * Return build configurations to appveyor.yml * Debug appveyor builds * Debug appveyor build * Debug appveyor builds (3) * Debug build (4) * Debug build (5) * - Fix crash in broken `extends`/`block` statements parsing - Fix scope control in `include` and `for` statements - Add `loop.depth` and `loop.depth0` variables * Implement template cache inside jinja2::TemplateEnv * Implement context passing to the user-defined callables, fix warnings * Remove 'robin hood hashing' submodule * Fix build: remove robin hood hashing lib * Fix macro call in expression context * Add `ToString` method to the `ErrorInfoTpl` class * Improve reflection and add more doxygen comments * Remove unnecessary typedef in error_info.cpp * Fix buggy format string * Add osx images to Travis CI * Improve setup of travis osx environments * Add reference documentation * Add clang 7 and 8 to the travis CI * Add gcc 8 and 9 to travis CI * Force use nonstd::optional and nonstd::string_view with C++17 enabled * Enable warning for gcc 8 and 9 * Fix travis build * Fix travis gcc 8/9 build with C++17 enabled * Fix error in travis config * Fix travis config errors --- .clang-format | 36 + .gitmodules | 3 - .travis.yml | 342 ++-- CMakeLists.txt | 41 +- README.md | 27 +- appveyor.yml | 40 +- include/jinja2cpp/binding/nlohmann_json.h | 6 - include/jinja2cpp/binding/rapid_json.h | 6 - include/jinja2cpp/error_info.h | 122 +- include/jinja2cpp/filesystem_handler.h | 110 +- include/jinja2cpp/generic_list.h | 195 +- include/jinja2cpp/generic_list_impl.h | 33 +- include/jinja2cpp/generic_list_iterator.h | 13 +- include/jinja2cpp/reflected_value.h | 93 +- include/jinja2cpp/string_helpers.h | 109 +- include/jinja2cpp/template.h | 181 +- include/jinja2cpp/template_env.h | 176 +- include/jinja2cpp/user_callable.h | 44 +- include/jinja2cpp/value.h | 344 +++- src/error_info.cpp | 189 +- src/expression_evaluator.cpp | 2 +- src/filesystem_handler.cpp | 37 +- src/filters.cpp | 2 +- src/generic_adapters.h | 5 - src/helpers.h | 3 +- src/internal_value.cpp | 30 +- src/internal_value.h | 2 +- src/render_context.h | 10 + src/robin_hood.h | 2019 +++++++++++++++++++++ src/statements.cpp | 26 +- src/statements.h | 3 +- src/template_env.cpp | 72 +- src/template_parser.cpp | 6 +- src/template_parser.h | 27 +- test/errors_test.cpp | 5 + test/expressions_test.cpp | 25 +- test/filesystem_handler_test.cpp | 167 ++ test/filters_test.cpp | 11 +- test/forloop_test.cpp | 22 +- test/includes_test.cpp | 26 +- test/macro_test.cpp | 14 + test/test_tools.h | 48 +- test/user_callable_test.cpp | 13 +- thirdparty/CMakeLists.txt | 6 +- thirdparty/internal_deps.cmake | 21 - thirdparty/robin-hood-hashing | 1 - thirdparty/thirdparty-conan-build.cmake | 4 +- thirdparty/thirdparty-external.cmake | 3 +- 48 files changed, 4249 insertions(+), 471 deletions(-) create mode 100644 .clang-format create mode 100644 src/robin_hood.h delete mode 160000 thirdparty/robin-hood-hashing diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..2e4bd643 --- /dev/null +++ b/.clang-format @@ -0,0 +1,36 @@ +BasedOnStyle: Mozilla +IndentWidth: 4 +ColumnLimit: 160 +BreakBeforeBraces: Custom +BraceWrapping: + AfterControlStatement: true + AfterClass: true + AfterNamespace: true + AfterFunction: true + BeforeCatch: true + BeforeElse: true + AfterStruct: true +AccessModifierOffset: -4 +BinPackParameters: false +AlwaysBreakAfterReturnType: None +AlwaysBreakAfterDefinitionReturnType: None +AllowAllParametersOfDeclarationOnNextLine: false +ConstructorInitializerIndentWidth: 4 +NamespaceIndentation: None +PointerAlignment: Left +Standard: Cpp11 +UseTab: Never +AlignAfterOpenBracket: Align +PenaltyReturnTypeOnItsOwnLine: 0 +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^((<|")(shared)\/)' + Priority: 4 + - Regex: '^((<|")(components)\/)' + Priority: 3 + - Regex: '^(<.*\.(h|hpp|hxx)>)' + Priority: 2 + - Regex: '^".*' + Priority: 1 + - Regex: '^(<[\w]*>)' + Priority: 5 diff --git a/.gitmodules b/.gitmodules index ab5dd7dc..44f7f09b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,9 +20,6 @@ [submodule "thirdparty/fmtlib"] path = thirdparty/fmtlib url = https://github.com/fmtlib/fmt.git -[submodule "thirdparty/robin-hood-hashing"] - path = thirdparty/robin-hood-hashing - url = https://github.com/martinus/robin-hood-hashing.git [submodule "thirdparty/json/nlohmann"] path = thirdparty/json/nlohmann url = https://github.com/nlohmann/json.git diff --git a/.travis.yml b/.travis.yml index 55b9f350..c6690502 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,120 +1,248 @@ +--- +after_success: + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then echo \"Uploading code coverate report\" ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --directory . --capture --output-file coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --remove coverage.info '/usr/*' --output-file coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --list coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then bash <(curl -s https://codecov.io/bash) -t \"225d6d7a-2b71-4dbe-bf87-fbf75eb7c119\" || echo \"Codecov did not collect coverage reports\"; fi" +before_install: + - "date -u" + - "uname -a" + - "if [[ \"${SYSTEM_BOOST_PACKAGE}\" != \"\" ]]; then sudo add-apt-repository -y ppa:samuel-bachmann/boost && sudo apt-get update -qq; fi" +dist: xenial +install: + - "if [[ \"${SYSTEM_BOOST_PACKAGE}\" != \"\" ]]; then sudo apt-get install libboost1.60-all-dev; fi" language: cpp - -dist: trusty -sudo: required - -matrix: - include: - - os: linux +matrix: + include: + - + addons: + apt: + packages: + - cmake + - g++-5 + sources: + - ubuntu-toolchain-r-test compiler: gcc env: COMPILER=g++-5 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-5'] - - - os: linux - compiler: gcc - env: - COMPILER=g++-6 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-6'] - - - os: linux + os: linux + - + addons: + apt: + packages: + - cmake + - g++-6 + sources: + - ubuntu-toolchain-r-test compiler: gcc - env: - COMPILER=g++-6 - COLLECT_COVERAGE=1 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-6', 'lcov'] - - - os: linux + env: COMPILER=g++-6 + os: linux + - + addons: + apt: + packages: + - cmake + - g++-7 + sources: + - ubuntu-toolchain-r-test compiler: gcc env: COMPILER=g++-7 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-7'] - - - os: linux + os: linux + - + addons: + apt: + packages: + - cmake + - g++-7 + sources: + - ubuntu-toolchain-r-test + compiler: gcc + env: "COMPILER=g++-7 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" + os: linux + - + addons: + apt: + packages: + - cmake + - g++-8 + sources: + - ubuntu-toolchain-r-test + compiler: gcc + env: "COMPILER=g++-8 EXTRA_FLAGS=-DJINJA2CPP_STRICT_WARNINGS=OFF" + os: linux + - + addons: + apt: + packages: + - cmake + - g++-8 + sources: + - ubuntu-toolchain-r-test compiler: gcc - env: - COMPILER=g++-7 - EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['cmake', 'g++-7'] - - - os: linux + env: "COMPILER=g++-8 EXTRA_FLAGS='-DJINJA2CPP_CXX_STANDARD=17 -DJINJA2CPP_STRICT_WARNINGS=OFF'" + os: linux + - + addons: + apt: + packages: + - cmake + - g++-9 + sources: + - ubuntu-toolchain-r-test + compiler: gcc + env: "COMPILER=g++-9 EXTRA_FLAGS=-DJINJA2CPP_STRICT_WARNINGS=OFF" + os: linux + - + addons: + apt: + packages: + - cmake + - g++-9 + sources: + - ubuntu-toolchain-r-test + compiler: gcc + env: "COMPILER=g++-9 EXTRA_FLAGS='-DJINJA2CPP_CXX_STANDARD=17 -DJINJA2CPP_STRICT_WARNINGS=OFF'" + os: linux + - + addons: + apt: + packages: + - cmake + - clang-5.0 + - g++-7 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-5.0 compiler: clang env: COMPILER=clang++-5.0 - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] - packages: ['cmake', 'clang-5.0', 'g++-7'] - - - os: linux + os: linux + - + addons: + apt: + packages: + - cmake + - clang-6.0 + - g++-7 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-6.0 compiler: clang env: COMPILER=clang++-6.0 - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['cmake', 'clang-6.0', 'g++-7'] - - - os: linux + os: linux + - + addons: + apt: + packages: + - cmake + - clang-6.0 + - g++-7 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-6.0 + compiler: clang + env: "COMPILER=clang++-6.0 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" + os: linux + - + addons: + apt: + packages: + - cmake + - clang-7 + - g++-7 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-7 + compiler: clang + env: COMPILER=clang++-7 + os: linux + - + addons: + apt: + packages: + - cmake + - clang-7 + - g++-7 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-7 compiler: clang - env: - COMPILER=clang++-6.0 - EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['cmake', 'clang-6.0', 'g++-7'] - - - os: linux + env: "COMPILER=clang++-7 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" + os: linux + - + addons: + apt: + packages: + - cmake + - clang-8 + - g++-8 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-8 compiler: clang - env: - COMPILER=clang++-6.0 - EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 - SANITIZE_BUILD=address+undefined - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['cmake', 'clang-6.0', 'g++-7'] - -before_install: - - date -u - - uname -a - - if [[ "${SYSTEM_BOOST_PACKAGE}" != "" ]]; then sudo add-apt-repository -y ppa:samuel-bachmann/boost && sudo apt-get update -qq; fi - -install: - - if [[ "${SYSTEM_BOOST_PACKAGE}" != "" ]]; then sudo apt-get install libboost1.60-all-dev; fi - -script: - - export BUILD_TARGET="all" - - export CMAKE_OPTS="-DCMAKE_VERBOSE_MAKEFILE=ON" - - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi - - if [[ "${BUILD_CONFIG}" == "" ]]; then export BUILD_CONFIG="Release"; fi - - if [[ "${COLLECT_COVERAGE}" != "" ]]; then export BUILD_CONFIG="Debug" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_COVERAGE=ON"; fi - - if [[ "${SANITIZE_BUILD}" != "" ]]; then export BUILD_CONFIG="RelWithDebInfo" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_SANITIZERS=${SANITIZE_BUILD}"; fi - - $CXX --version - - - mkdir -p build && cd build - - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - - ctest -C $BUILD_CONFIG -V - - -after_success: - # Creating report - - echo "Uploading code coverate report" - - lcov --directory . --capture --output-file coverage.info # capture coverage info - - lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter out system - - lcov --list coverage.info #debug info - # Uploading report to CodeCov - - bash <(curl -s https://codecov.io/bash) -t "225d6d7a-2b71-4dbe-bf87-fbf75eb7c119" || echo "Codecov did not collect coverage reports" - - fi + env: COMPILER=clang++-8 + os: linux + - + addons: + apt: + packages: + - cmake + - clang-8 + - g++-8 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-8 + compiler: clang + env: "COMPILER=clang++-8 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" + os: linux + - + compiler: clang + env: COMPILER='clang++' + os: osx + osx_image: xcode9 + - + compiler: clang + env: COMPILER='clang++' + os: osx + osx_image: xcode10 + - + compiler: clang + env: COMPILER='clang++' + os: osx + osx_image: xcode11 + - + addons: + apt: + packages: + - cmake + - g++-6 + - lcov + sources: + - ubuntu-toolchain-r-test + compiler: gcc + env: "COMPILER=g++-6 COLLECT_COVERAGE=1" + os: linux + - + addons: + apt: + packages: + - cmake + - clang-8 + - g++-8 + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-8 + compiler: clang + env: "COMPILER=clang++-8 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 SANITIZE_BUILD=address+undefined" + os: linux +script: + - "export BUILD_TARGET=\"all\"" + - "export CMAKE_OPTS=\"-DCMAKE_VERBOSE_MAKEFILE=OFF\"" + - "if [[ \"${COMPILER}\" != \"\" ]]; then export CXX=${COMPILER}; fi" + - "if [[ \"${BUILD_CONFIG}\" == \"\" ]]; then export BUILD_CONFIG=\"Release\"; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then export BUILD_CONFIG=\"Debug\" && export CMAKE_OPTS=\"${CMAKE_OPTS} -DJINJA2CPP_WITH_COVERAGE=ON\"; fi" + - "if [[ \"${SANITIZE_BUILD}\" != \"\" ]]; then export BUILD_CONFIG=\"RelWithDebInfo\" && export CMAKE_OPTS=\"${CMAKE_OPTS} -DJINJA2CPP_WITH_SANITIZERS=${SANITIZE_BUILD}\"; fi" + - "$CXX --version" + - "mkdir -p build && cd build" + - "cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4" + - "ctest -C $BUILD_CONFIG -V" +sudo: required diff --git a/CMakeLists.txt b/CMakeLists.txt index d2081c2b..ba03ad4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0.1) -project(Jinja2Cpp VERSION 0.9.3) +project(Jinja2Cpp VERSION 1.0.0) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) @@ -18,6 +18,7 @@ endif () if (JINJA2CPP_IS_MAIN_PROJECT OR NOT CMAKE_CXX_STANDARD) set (JINJA2CPP_CXX_STANDARD 14 CACHE STRING "Jinja2Cpp C++ standard to build with. C++14 is default") + set (CMAKE_CXX_STANDARD ${JINJA2CPP_CXX_STANDARD}) endif () if (NOT JINJA2CPP_CXX_STANDARD) @@ -61,27 +62,41 @@ if(NOT ${JINJA2CPP_WITH_SANITIZERS} STREQUAL "none") endif() if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") - if (NOT UNIX) - set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj") + if (NOT UNIX) + if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") + set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj -O1") + else () + add_definitions(/D_CRT_SECURE_NO_WARNINGS) + endif () endif () else () set (COMMON_MSVC_OPTS "/wd4503 /bigobj") add_definitions(/DBOOST_ALL_NO_LIB) # MSVC - if (CMAKE_BUILD_TYPE MATCHES "Debug" AND MSVC_RUNTIME_TYPE) - set (MSVC_RUNTIME_TYPE "${MSVC_RUNTIME_TYPE}d") + if (CMAKE_BUILD_TYPE MATCHES "Debug" AND JINJA2CPP_MSVC_RUNTIME_TYPE) + set (JINJA2CPP_MSVC_RUNTIME_TYPE "${JINJA2CPP_MSVC_RUNTIME_TYPE}d") endif () - if (CMAKE_BUILD_TYPE MATCHES "Debug") - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") + if (JINJA2CPP_DEPS_MODE MATCHES "conan-build" OR CMAKE_BUILD_TYPE STREQUAL "") + if (NOT JINJA2CPP_MSVC_RUNTIME_TYPE STREQUAL "") + set (MSVC_RUNTIME_DEBUG ${JINJA2CPP_MSVC_RUNTIME_TYPE}) + set (MSVC_RUNTIME_RELEASE ${JINJA2CPP_MSVC_RUNTIME_TYPE}) + endif () + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MSVC_RUNTIME_DEBUG} ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_RELEASE} ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${MSVC_RUNTIME_RELEASE} ${COMMON_MSVC_OPTS}") + set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/PROFILE") + set (Boost_USE_DEBUG_RUNTIME OFF) + elseif (CMAKE_BUILD_TYPE MATCHES "Debug") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${JINJA2CPP_MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") set (Boost_USE_DEBUG_RUNTIME ON) else () - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") - set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${JINJA2CPP_MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${JINJA2CPP_MSVC_RUNTIME_TYPE} ${COMMON_MSVC_OPTS}") set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/PROFILE") set (Boost_USE_DEBUG_RUNTIME OFF) endif () - + message (STATUS "Selected MSVC runtime type for Jinja2C++ library: '${JINJA2CPP_MSVC_RUNTIME_TYPE}'") endif() @@ -149,13 +164,15 @@ target_include_directories(${LIB_TARGET_NAME} if(JINJA2CPP_STRICT_WARNINGS) if(NOT MSVC) - target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-unused-command-line-argument) + if (UNIX) + target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Werror -Wno-unused-command-line-argument) + endif () else () target_compile_options(${LIB_TARGET_NAME} PRIVATE /W4) endif() endif() -target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD) +target_compile_definitions(${LIB_TARGET_NAME} PUBLIC variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_NONSTD optional_CONFIG_SELECT_OPTIONAL=optional_OPTIONAL_NONSTD BOOST_SYSTEM_NO_DEPRECATED BOOST_ERROR_CODE_HEADER_ONLY) set_target_properties(${LIB_TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} diff --git a/README.md b/README.md index 27080daf..082908ee 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/jinja2cpp/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) -[![conan.io](https://api.bintray.com/packages/manu343726/conan-packages/jinja2cpp%3AManu343726/images/download.svg) ](https://bintray.com/manu343726/conan-packages/jinja2cpp%3AManu343726/_latestVersion) +[![conan.io](https://api.bintray.com/packages/flexferrum/conan-packages/jinja2cpp:flexferrum/images/download.svg?version=1.0.0:testing) ](https://bintray.com/flexferrum/conan-packages/jinja2cpp:flexferrum/1.0.0:testing/link) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) C++ implementation of Jinja2 Python template engine. This library was originally inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. @@ -102,7 +102,7 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, - 'for' statement (with 'else' branch and 'if' part support) - 'include' statement - 'import'/'from' statements -- 'set' statement +- 'set' statement (both line and block) - 'filter' statement - 'extends'/'block' statements - 'macro'/'call' statements @@ -114,15 +114,24 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.dev/docs/j2_compatibility.html). ## Supported compilers -Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled feature): +Compilation of Jinja2Cpp tested on the following compilers (with C++14 and C++17 enabled features): - Linux gcc 5.0 - Linux gcc 6.0 - Linux gcc 7.0 - Linux clang 5.0 - Linux clang 6.0 +- Linux clang 7 +- Linux clang 8 +- MacOS X-Code 9 +- MacOS X-Code 10 +- MacOS X-Code 11 (C++14 in default build, C++17 with externally-provided boost) - Microsoft Visual Studio 2015 x86, x64 - Microsoft Visual Studio 2017 x86, x64 - Microsoft Visual Studio 2019 x86, x64 +- MinGW gcc compiler 7.3 +- MinGW gcc compiler 8.1 + +**Note:** Support of gcc version >= 9.x or clang version >= 8.0 depends on version of Boost library provided. ## Build and install Jinja2Cpp has several external dependencies: @@ -234,13 +243,17 @@ You can define (via -D command line CMake option) the following build flags: In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD` macro in the build settings. ## Acknowledgments -Thanks to @manu343726 for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. +Thanks to **@manu343726** for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. + +Thanks to **@martinmoene** for the perfectly implemented xxx-lite libraries. + +Thanks to **@vitaut** for the amazing text formatting library -Thanks to @martinmoene for the perfectly implemented xxx-lite libraries. +Thanks to **@martinus** for the fast hash maps implementation -Thanks to @vitaut for the amazing text formatting library +Thanks to **@palchukovsky** for the great contribution into codebase -Thanks to @martinus for the fast hash maps implementation +Thanks to **@rmorozov** for stanitized builds setup ## Changelog diff --git a/appveyor.yml b/appveyor.yml index 71e272bd..b91a00f7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.9.1.{build} +version: 1.0.0.{build} os: - Visual Studio 2015 @@ -13,7 +13,26 @@ configuration: - Release environment: + BOOST_ROOT: C:\Libraries\boost_1_65_1 + GENERATOR: "\"NMake Makefiles\"" + DEPS_MODE: external-boost + matrix: +# - BUILD_PLATFORM: clang +# MSVC_RUNTIME_TYPE: +# GENERATOR: "\"Unix Makefiles\"" +# DEPS_MODE: internal +# EXTRA_CMAKE_ARGS: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM=mingw32-make.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DJINJA2CPP_STRICT_WARNINGS=OFF + - BUILD_PLATFORM: MinGW7 + MSVC_RUNTIME_TYPE: + GENERATOR: "\"Unix Makefiles\"" + DEPS_MODE: internal + EXTRA_CMAKE_ARGS: -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++.exe -DCMAKE_MAKE_PROGRAM=mingw32-make.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DJINJA2CPP_STRICT_WARNINGS=OFF + - BUILD_PLATFORM: MinGW8 + MSVC_RUNTIME_TYPE: + GENERATOR: "\"Unix Makefiles\"" + DEPS_MODE: internal + EXTRA_CMAKE_ARGS: -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++.exe -DCMAKE_MAKE_PROGRAM=mingw32-make.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DJINJA2CPP_STRICT_WARNINGS=OFF - BUILD_PLATFORM: x64 MSVC_RUNTIME_TYPE: /MD - BUILD_PLATFORM: x64 @@ -30,17 +49,32 @@ environment: matrix: fast_finish: false exclude: + - os: Visual Studio 2015 + BUILD_PLATFORM: MinGW7 + - os: Visual Studio 2015 + BUILD_PLATFORM: MinGW8 + - os: Visual Studio 2015 + BUILD_PLATFORM: clang - platform: Win32 BUILD_PLATFORM: x64 + - platform: Win32 + BUILD_PLATFORM: MinGW7 + - platform: Win32 + BUILD_PLATFORM: MinGW8 + - platform: Win32 + BUILD_PLATFORM: clang - platform: x64 BUILD_PLATFORM: x86 + init: - - set BOOST_ROOT=C:\Libraries\boost_1_65_1 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.1;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" set PATH=%BOOST_ROOT%\lib64-msvc-14.1;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="MinGW7" set PATH=C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="MinGW8" set PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="clang" set PATH=%BOOST_ROOT%\lib64-msvc-14.1;C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64\bin;C:\Libraries\llvm-5.0.0\bin;%PATH% && call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.0;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 @@ -48,7 +82,7 @@ init: build_script: - mkdir -p build && cd build - - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%configuration% -DMSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=external-boost + - cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%configuration% -DJINJA2CPP_MSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=%DEPS_MODE% %EXTRA_CMAKE_ARGS% - cmake --build . --target all --config Release test_script: diff --git a/include/jinja2cpp/binding/nlohmann_json.h b/include/jinja2cpp/binding/nlohmann_json.h index 9a244faa..676835ca 100644 --- a/include/jinja2cpp/binding/nlohmann_json.h +++ b/include/jinja2cpp/binding/nlohmann_json.h @@ -85,12 +85,6 @@ struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, Reflect return Reflect((*j)[idx]); } - - size_t GetItemsCount() const override - { - auto sz = this->GetSize(); - return sz.value_or(0ULL); - } }; template<> diff --git a/include/jinja2cpp/binding/rapid_json.h b/include/jinja2cpp/binding/rapid_json.h index 26fde15d..d45c736e 100644 --- a/include/jinja2cpp/binding/rapid_json.h +++ b/include/jinja2cpp/binding/rapid_json.h @@ -88,12 +88,6 @@ struct RapidJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, ReflectedD return Reflect((*j)[idx]); } - - size_t GetItemsCount() const override - { - auto sz = this->GetSize(); - return sz.value_or(0ULL); - } }; template<> diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 98a84633..222302bf 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -9,44 +9,68 @@ namespace jinja2 { +/*! + * \brief Type of the error + */ enum class ErrorCode { - Unspecified = 0, - UnexpectedException = 1, - YetUnsupported, - FileNotFound, - ExpectedStringLiteral = 1001, - ExpectedIdentifier, - ExpectedSquareBracket, - ExpectedRoundBracket, - ExpectedCurlyBracket, - ExpectedToken, - ExpectedExpression, - ExpectedEndOfStatement, - UnexpectedToken, - UnexpectedStatement, - UnexpectedCommentBegin, - UnexpectedCommentEnd, - UnexpectedExprBegin, - UnexpectedExprEnd, - UnexpectedStmtBegin, - UnexpectedStmtEnd, - TemplateNotFound, - TemplateNotParsed, - InvalidValueType, - InvalidTemplateName, - ExtensionDisabled, - TemplateEnvAbsent, + Unspecified = 0, //!< Error is unspecified + UnexpectedException = 1, //!< Generic exception occurred during template parsing or execution. ExtraParams[0] contains `what()` string of the exception + YetUnsupported, //!< Feature of the jinja2 specification which yet not supported + FileNotFound, //!< Requested file was not found. ExtraParams[0] contains name of the file + ExtensionDisabled, //!< Particular jinja2 extension disabled in the settings + TemplateEnvAbsent, //!< Template uses `extend`, `import`, `from` or `include` features but it's loaded without the template environment set + TemplateNotFound, //!< Template with the specified name was not found. ExtraParams[0] contains name of the file + TemplateNotParsed, //!< Template was not parsed + InvalidValueType, //!< Invalid type of the value in the particular context + InvalidTemplateName, //!< Invalid name of the template. ExtraParams[0] contains the name + ExpectedStringLiteral = 1001, //!< String literal expected + ExpectedIdentifier, //!< Identifier expected + ExpectedSquareBracket, //!< ']' expected + ExpectedRoundBracket, //!< ')' expected + ExpectedCurlyBracket, //!< '}' expected + ExpectedToken, //!< Specific token(s) expected. ExtraParams[0] contains the actual token, rest of ExtraParams contain set of expected tokens + ExpectedExpression, //!< Expression expected + ExpectedEndOfStatement, //!< End of statement expected. ExtraParams[0] contains the expected end of statement tag + UnexpectedToken, //!< Unexpected token. ExtraParams[0] contains the invalid token + UnexpectedStatement, //!< Unexpected statement. ExtraParams[0] contains the invalid statement tag + UnexpectedCommentBegin, //!< Unexpected comment block begin (`{#`) + UnexpectedCommentEnd, //!< Unexpected comment block end (`#}`) + UnexpectedExprBegin, //!< Unexpected expression block begin (`{{`) + UnexpectedExprEnd, //!< Unexpected expression block end (`}}`) + UnexpectedStmtBegin, //!< Unexpected statement block begin (`{%`) + UnexpectedStmtEnd, //!< Unexpected statment block end (`%}`) }; +/*! + * \brief Information about the source location of the error + */ struct SourceLocation { + //! Name of the file std::string fileName; + //! Line number (1-based) unsigned line = 0; + //! Column number (1-based) unsigned col = 0; }; template +/*! + * \brief Detailed information about the parse-time or render-time error + * + * If template parsing or rendering fails the detailed error information is provided. Exact specialization of ErrorInfoTpl is an object which contains + * this information. Type of specialization depends on type of the template object: \ref ErrorInfo for \ref Template and \ref ErrorInfoW for \ref TemplateW. + * + * Detailed information about an error contains: + * - Error code + * - Error location + * - Other locations related to the error + * - Description of the location + * - Extra parameters of the error + * + * @tparam CharT Character type which was used in template parser + */ class ErrorInfoTpl { public: @@ -59,30 +83,26 @@ class ErrorInfoTpl std::basic_string locationDescr; }; + //! Default constructor ErrorInfoTpl() = default; - ErrorInfoTpl(Data data) + //! Initializing constructor from error description + explicit ErrorInfoTpl(Data data) : m_errorData(std::move(data)) {} - ~ErrorInfoTpl() noexcept - { - static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_constructible>::value, "Should be nothrow-moveable"); - static_assert(std::is_nothrow_move_assignable>::value, "Should be nothrow-moveable"); - } + //! Copy constructor ErrorInfoTpl(const ErrorInfoTpl&) = default; + //! Move constructor ErrorInfoTpl(ErrorInfoTpl&& val) noexcept : m_errorData(std::move(val.m_errorData)) { } + //! Destructor + ~ErrorInfoTpl() noexcept = default; + //! Copy-assignment operator ErrorInfoTpl& operator =(const ErrorInfoTpl&) = default; + //! Move-assignment operator ErrorInfoTpl& operator =(ErrorInfoTpl&& val) noexcept { if (this == &val) @@ -97,31 +117,55 @@ class ErrorInfoTpl return *this; } + //! Return code of the error ErrorCode GetCode() const { return m_errorData.code; } + //! Return error location in the template file auto& GetErrorLocation() const { return m_errorData.srcLoc; } + //! Return locations, related to the main error location auto& GetRelatedLocations() const { return m_errorData.relatedLocs; } + /*! + * \brief Return location description + * + * Return string of two lines. First line is contents of the line with error. Second highlight the exact position within line. For instance: + * ``` + * {% for i in range(10) endfor%} + * ---^------- + * ``` + * + * @return Location description + */ const std::basic_string& GetLocationDescr() const { return m_errorData.locationDescr; } + /*! + * \brief Return extra params of the error + * + * Extra params is a additional details assiciated with the error. For instance, name of the file which wasn't opened + * + * @return Vector with extra error params + */ auto& GetExtraParams() const { return m_errorData.extraParams; } + //! Convert error to the detailed string representation + std::basic_string ToString() const; + private: Data m_errorData; }; diff --git a/include/jinja2cpp/filesystem_handler.h b/include/jinja2cpp/filesystem_handler.h index 2879ff4f..355326e9 100644 --- a/include/jinja2cpp/filesystem_handler.h +++ b/include/jinja2cpp/filesystem_handler.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace jinja2 { @@ -17,28 +18,88 @@ using FileStreamPtr = std::unique_ptr, void (*)(std::b using CharFileStreamPtr = FileStreamPtr; using WCharFileStreamPtr = FileStreamPtr; +/*! + * \brief Generic interface to filesystem handlers (loaders) + * + * This interface should be implemented in order to provide custom file system handler. Interface provides most-common methods which are called by + * the template environment to load the particular template. `OpenStream` methods return the unique pointer to the generic `istream` object implementation. + * So, the exact type (ex. `ifstream`, `istringstream` etc.) of input stream is unspecified. In order to delete stream object correctly returned pointer + * provide the custom deleter which should properly delete the stream object. + */ class IFilesystemHandler { public: + //! Destructor virtual ~IFilesystemHandler() = default; + /*! + * \brief Method is called to open the file with the specified name in 'narrow-char' mode. + * + * Method should return unique pointer to the std::istream object with custom deleter (\ref CharFileStreamPtr) . Deleter should properly delete pointee + * stream object. + * + * @param name Name of the file to open + * @return Opened stream object or empty pointer in case of any error + */ virtual CharFileStreamPtr OpenStream(const std::string& name) const = 0; + /*! + * \brief Method is called to open the file with the specified name in 'wide-char' mode. + * + * Method should return unique pointer to the std::wistream object with custom deleter (\ref WCharFileStreamPtr) . Deleter should properly delete pointee + * stream object. + * + * @param name Name of the file to open + * @return Opened stream object or empty pointer in case of any error + */ virtual WCharFileStreamPtr OpenWStream(const std::string& name) const = 0; + /*! + * \brief Method is called to obtain the modification date of the specified file (if applicable) + * + * If the underlaying filesystem supports retrival of the last modification date of the file this method should return this date when called. In other + * case it should return the empty optional object. Main purpose of this method is to help templates loader to determine the necessity of cached template + * reload + * + * @param name Name of the file to get the last modification date + * @return Last modification date (if applicable) or empty optional object otherwise + */ + virtual nonstd::optional GetLastModificationDate(const std::string& name) const = 0; }; using FilesystemHandlerPtr = std::shared_ptr; +/*! + * \brief Filesystem handler for files stored in memory + * + * This filesystem handler implements the simple dictionary object which maps name of the file to it's content. New files can be added by \ref AddFile + * methods. Content of the files automatically converted to narrow/wide strings representation if necessary. + */ class MemoryFileSystem : public IFilesystemHandler { public: + /*! + * \brief Add new narrow-char "file" to the filesystem handler + * + * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file + * + * @param fileName Name of the file to add + * @param fileContent Content of the file to add + */ void AddFile(std::string fileName, std::string fileContent); + /*! + * \brief Add new wide-char "file" to the filesystem handler + * + * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file + * + * @param fileName Name of the file to add + * @param fileContent Content of the file to add + */ void AddFile(std::string fileName, std::wstring fileContent); CharFileStreamPtr OpenStream(const std::string& name) const override; WCharFileStreamPtr OpenWStream(const std::string& name) const override; + nonstd::optional GetLastModificationDate(const std::string& name) const override; private: - // using FileContent = nonstd::variant; struct FileContent { nonstd::optional narrowContent; @@ -47,18 +108,63 @@ class MemoryFileSystem : public IFilesystemHandler mutable std::unordered_map m_filesMap; }; +/*! + * \brief Filesystem handler for files stored on the filesystem + * + * This filesystem handler is an interface to the real file system. Root directory for file name mapping provided as a constructor argument. Each name (path) of + * the file to open is appended to the root directory path and then passed to the stream open methods. + */ class RealFileSystem : public IFilesystemHandler { public: - RealFileSystem(std::string rootFolder = "."); + /*! + * \brief Initializing/default constructor + * + * @param rootFolder Path to the root folder. This path is used as a root for every opened file + */ + explicit RealFileSystem(std::string rootFolder = "."); + /*! + * \brief Reset path to the root folder to the new value + * + * @param newRoot New path to the root folder + */ void SetRootFolder(std::string newRoot) { m_rootFolder = std::move(newRoot); } + /*! + * \brief Get path to the current root folder + * + * @return + */ + const std::string& GetRootFolder() const + { + return m_rootFolder; + } + /*! + * \brief Get full path to the specified file. + * + * Appends specified name of the file to the current root folder and returns it + * + * @param name Name of the file to get path to + * @return Full path to the file + */ + std::string GetFullFilePath(const std::string& name) const; + CharFileStreamPtr OpenStream(const std::string& name) const override; WCharFileStreamPtr OpenWStream(const std::string& name) const override; + /*! + * \brief Open the specified file as a binary stream + * + * Opens the specified file in the binary mode (instead of text). + * + * @param name Name of the file to get the last modification date + * @return Last modification date (if applicable) or empty optional object otherwise + */ + CharFileStreamPtr OpenByteStream(const std::string& name) const; + nonstd::optional GetLastModificationDate(const std::string& name) const override; private: std::string m_rootFolder; diff --git a/include/jinja2cpp/generic_list.h b/include/jinja2cpp/generic_list.h index 1707b345..802e0edd 100644 --- a/include/jinja2cpp/generic_list.h +++ b/include/jinja2cpp/generic_list.h @@ -11,11 +11,21 @@ namespace jinja2 { class Value; +/*! + * \brief Interface for accessing items in list by the indexes + * + * This interface should provided by the particular list implementation in case of support index-based access to the items. + */ struct IndexBasedAccessor { + /*! + * \brief This method is called to get the item by the specified index + * + * @param idx Index of item to get + * + * @return requested item + */ virtual Value GetItemByIndex(int64_t idx) const = 0; - - virtual size_t GetItemsCount() const = 0; }; struct ListEnumerator; @@ -26,68 +36,225 @@ inline auto MakeEmptyListEnumeratorPtr() return ListEnumeratorPtr(nullptr, [](ListEnumerator*) {}); } +/*! + * \brief Generic list enumerator interface + * + * This interface should be implemented by the lists of any type. Interface is used to enumerate the list contents item by item. + * + * Implementation notes: Initial state of the enumerator should be "before the first item". So, the first call of \ref MoveNext method either moves the + * enumerator to the first element end returns `true` or moves enumerator to the end and returns `false` in case of empty list. Each call of \ref GetCurrent + * method should return the current enumerable item. + */ struct ListEnumerator { - virtual ~ListEnumerator() {} - + //! Destructor + virtual ~ListEnumerator() = default; + + /*! + * \brief Method is called to reset enumerator to the initial state ('before the first element') if applicable. + * + * For the sequences which allow multi-pass iteration this method should reset enumerator to the initial state. For the single-pass sequences method + * can do nothing. + */ virtual void Reset() = 0; + /*! + * \brief Method is called to move the enumerator to the next item (if any) + * + * @return `true` if enumerator successfully moved and `false` if enumerator reaches the end of the sequence. + */ virtual bool MoveNext() = 0; + /*! + * \brief Method is called to get the current item value. Can be called multiply times + * + * @return Value of the item if the current item is valid item and empty value otherwise + */ virtual Value GetCurrent() const = 0; + /*! + * \brief Method is called to make a deep **copy** of the current enumerator state if possible + * + * @return New enumerator object with copy of the current enumerator state or empty pointer if copying is not applicable to the enumerator + */ virtual ListEnumeratorPtr Clone() const = 0; + /*! + * \brief Method is called to transfer current enumerator state to the new object + * + * State of the enumerator after successful creation of the new object is unspecified by there is a guarantee that there will no calls to the 'moved' + * enumerator. Destruction of the moved enumerator shouldn't affect the newly created object. + * + * @return New enumerator object which holds and owns the current enumerator state + */ virtual ListEnumeratorPtr Move() = 0; }; +/*! + * \brief Generic list implementation interface + * + * Every list implementation should implement this interface for providing access to the items of the list. There are several types of lists and implementation + * notes for every of them: + * - **Single-pass sequences** . For instance, input-stream iterators, generator-based sequences. This type of generic lists should provide no size and no indexer. Enumerator of such sequence supports should be moveable but non-copyable and non-resetable. + * - **Forward sequences** . For instance, single-linked lists. This type of lists should provide no size and indexer. Enumerator should be moveable, copyable and resetable. + * - **Bidirectional sequences**. Have got the same implementation requirements as forward sequences. + * - **Random-access sequences**. Such as arrays or vectors. Should provide valid size (number of stored items), valid indexer implementation and moveable, + * copyable and resetable enumerator. + * + * It's assumed that indexer interface is a part of list implementation. + */ struct ListItemAccessor { - virtual ~ListItemAccessor() {} - + virtual ~ListItemAccessor() = default; + + /*! + * \brief Called to get pointer to indexer interface implementation (if applicable) + * + * See implementation notes for the interface. This method should return pointer to the valid indexer interface if (and only if) list implementation + * supports random access to the items. + * + * Method can be called several times from the different threads. + * + * @return Pointer to the indexer interface implementation or null if indexing isn't supported for the list + */ virtual const IndexBasedAccessor* GetIndexer() const = 0; + /*! + * \brief Called to get enumerator of the particular list + * + * See implementation notes for the interface. This method should return unique pointer (with custom deleter) to the ListEnumerator interface implementation. Enumerator implementation should follow the requirements for the particular list type implementation. + * + * Method can be called several times from the different threads. + * + * @return Pointer to the enumerator of the list + */ virtual ListEnumeratorPtr CreateEnumerator() const = 0; + /*! + * \brief Called to get size of the list if applicable. + * + * See implementation notes for the interface. This method should return valid (non-empty) size only for random-access sequences. In other cases this + * method should return empty optional. + * + * Method can be called several times from the different threads. + * + * @return Non-empty optional with the valid size of the list or empty optional in case of non-random sequence implementation + */ virtual nonstd::optional GetSize() const = 0; - template - static ListEnumeratorPtr MakeEnumerator(Args&& ... args); + /*! + * \brief Helper factory method of particular enumerator implementation + * + * @tparam T Type of enumerator to create + * @tparam Args Type of enumerator construct args + * @param args Actual enumerator constructor args + * @return Unique pointer to the enumerator + */ + template + static ListEnumeratorPtr MakeEnumerator(Args&&... args); }; +namespace detail +{ class GenericListIterator; +} +/*! + * \brief Facade class for generic lists + * + * This class holds the implementation of particular generic list interface and provides friendly access to it's method. Also this class is used to hold + * the particular list. Pointer to the generic list interface implementation is held inside std::function object which provides access to the pointer to the interface. + * + * You can use \ref MakeGenericList method to create instances of the GenericList: + * ``` + * std::array sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90}; + * + * ValuesMap params = { + * {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) } + * }; + * ``` + */ class GenericList { public: + //! Default constructor GenericList() = default; - GenericList(std::function accessor) + /*! + * \brief Initializing constructor + * + * This constructor is only one way to create the valid GenericList object. `accessor` is a functional object which provides access to the \ref ListItemAccessor + * interface. The most common way of GenericList creation is to initialize it with lambda which simultaniously holds and and provide access to the + * generic list implementation: + * + * ``` + * auto MakeGeneratedList(ListGenerator&& fn) + * { + * return GenericList([accessor = GeneratedListAccessor(std::move(fn))]() {return &accessor;}); + * } + * ``` + * + * @param accessor Functional object which provides access to the particular generic list implementation + */ + explicit GenericList(std::function accessor) : m_accessor(std::move(accessor)) { } + /*! + * \brief Get size of the list + * + * @return Actual size of the generic list or empty optional object if not applicable + */ nonstd::optional GetSize() const { return m_accessor ? m_accessor()->GetSize() : nonstd::optional(); } + /*! + * \brief Get pointer to the list accessor interface implementation + * + * @return Pointer to the list accessor interface or nullptr in case of non-initialized GenericList object + */ auto GetAccessor() const { return m_accessor ? m_accessor() : nullptr; } + /*! + * \brief Check the GenericList object state + * + * @return true if GenericList object is valid (initialize) or false otherwize + */ bool IsValid() const { return !(!m_accessor); } - GenericListIterator begin() const; - - GenericListIterator end() const; - + /*! + * \brief Get interator to the first element of the list + * + * @return Iterator to the first element of the generic list or iterator equal to the `end()` if list is empty or not initialized + */ + detail::GenericListIterator begin() const; + /*! + * \brief Get the end iterator + * + * @return 'end' iterator of the generic list + */ + detail::GenericListIterator end() const; + + /*! + * \brief Get interator to the first element of the list + * + * @return Iterator to the first element of the generic list or iterator equal to the `end()` if list is empty or not initialized + */ auto cbegin() const; - + /*! + * \brief Get the end iterator + * + * @return 'end' iterator of the generic list + */ auto cend() const; std::function m_accessor; diff --git a/include/jinja2cpp/generic_list_impl.h b/include/jinja2cpp/generic_list_impl.h index 0800acc1..76e70853 100644 --- a/include/jinja2cpp/generic_list_impl.h +++ b/include/jinja2cpp/generic_list_impl.h @@ -252,10 +252,6 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor return Reflect(*p); } - size_t GetItemsCount() const override - { - return GetSize().value(); - } }; using ListGenerator = std::function()>; @@ -349,12 +345,41 @@ auto MakeGeneratedList(ListGenerator&& fn) } } +/*! + * \brief Create instance of the GenericList from the pair of iterators + * + * @tparam It1 Type of the first (begin) iterator + * @tparam It2 Type of the second (end) iterator + * @param it1 First (begin) iterator + * @param it2 Second (end) iterator + * @return Instance of GenericList object for the provided pair of iterators + */ template auto MakeGenericList(It1&& it1, It2&& it2) { return lists_impl::MakeGenericList(std::forward(it1), std::forward(it2), typename std::iterator_traits::iterator_category()); } +/*! + * \brief Create instance of the GenericList from the generator method (generator-based generic list) + * + * List generator method should follow the function signature: nonstd::optional() . Non-empty optional returned from the generator means that generated + * list isn't empty yet. The first returned empty optional object means the end of the generated sequence. For instance: + * ``` + * jinja2::MakeGenericList([cur = 10]() mutable -> nonstd::optional { + * if (cur > 90) + * return nonstd::optional(); + * + * auto tmp = cur; + * cur += 10; + * return Value(tmp); +}); + * ``` + * This generator produces the following list: `[10, 20, 30, 40, 50, 60, 70, 80, 90]` + * + * @param fn Generator of the items sequences + * @return Instance of GenericList object for the provided generation method + */ auto MakeGenericList(lists_impl::ListGenerator fn) { return lists_impl::MakeGeneratedList(std::move(fn)); diff --git a/include/jinja2cpp/generic_list_iterator.h b/include/jinja2cpp/generic_list_iterator.h index bfd26de6..eb11482f 100644 --- a/include/jinja2cpp/generic_list_iterator.h +++ b/include/jinja2cpp/generic_list_iterator.h @@ -7,7 +7,8 @@ namespace jinja2 { - +namespace detail +{ class GenericListIterator { public: @@ -93,14 +94,16 @@ class GenericListIterator Value m_current; }; -inline GenericListIterator GenericList::begin() const +} + +inline detail::GenericListIterator GenericList::begin() const { - return m_accessor && m_accessor() ? GenericListIterator(m_accessor()->CreateEnumerator().release()) : GenericListIterator(); + return m_accessor && m_accessor() ? detail::GenericListIterator(m_accessor()->CreateEnumerator().release()) : detail::GenericListIterator(); } -inline GenericListIterator GenericList::end() const +inline detail::GenericListIterator GenericList::end() const { - return GenericListIterator(); + return detail::GenericListIterator(); } inline auto GenericList::cbegin() const {return begin();} diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 1b08697c..253ddf4c 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -15,6 +15,50 @@ namespace jinja2 { +/*! + * \brief Reflect the arbitrary C++ type to the Jinja2C++ engine + * + * Generic method which reflects arbitrary C++ type to the Jinja2C++ template engine. The way of reflection depends on the actual reflected type and could be + * - Reflect as an exact value if the type is basic type (such as `char`, `int`, `double`, `std::string` etc.) + * - Reflect as a GenericList/GenericMap for standard containers respectively + * - Reflect as a GenericMap for the user types + * Also pointers/shared pointers to the types could be reflected. + * + * Reflected value takes ownership on the object, passed to the `Reflect` method by r-value reference or value. Actually, such object is moved. For const + * references or pointers reflected value holds the pointer to the reflected object. So, it's necessary to be sure that life time of the reflected object is + * longer than it's usage within the template. + * + * In order to reflect custom (user) the \ref jinja2::TypeReflection template should be specialized in the following way: + * ```c++ + * struct jinja2::TypeReflection : jinja2::TypeReflected + * { + * using FieldAccessor = typename jinja2::TypeReflected::FieldAccessor; + * static auto& GetAccessors() + * { + * static std::unordered_map accessors = { + * {"intValue", [](const TestStruct& obj) {assert(obj.isAlive); return jinja2::Reflect(obj.intValue);}}, + * {"intEvenValue", [](const TestStruct& obj) -> Value + * { + * assert(obj.isAlive); + * if (obj.intValue % 2) + * return {}; + * return {obj.intValue}; + * }}, + * }; + * + * return accessors; + * } + * }; + * + * `TestStruct` here is a type which should be reflected. Specialization of the \ref TypeReflection template should derived from \ref TypeReflected template + * and define only one method: `GetAccessors`. This method returns the unordered map object which maps field name (as a string) to the corresponded field + * accessor. And field accessor here is a lambda object which takes the reflected object reference and returns the value of the field from it. + * + * @tparam T Type of value to reflect + * @param val Value to reflect + * + * @return jinja2::Value which contains the reflected value or the empty one + */ template Value Reflect(T&& val); @@ -39,6 +83,7 @@ struct TypeReflection : TypeReflectedImpl { }; +#ifndef JINJA2CPP_NO_DOXYGEN template class ReflectedMapImplBase : public MapItemAccessor { @@ -227,11 +272,6 @@ struct ContainerReflector std::advance(p, static_cast(idx)); return Reflect(*p); } - - size_t GetItemsCount() const override - { - return m_value.size(); - } }; template @@ -264,11 +304,6 @@ struct ContainerReflector std::advance(p, static_cast(idx)); return Reflect(*p); } - - size_t GetItemsCount() const override - { - return m_value->size(); - } }; template @@ -398,10 +433,6 @@ struct Reflector { return Reflector::CreateFromPtr(val); } -// static auto CreateFromPtr(const T*const val) -// { -// return Reflector::CreateFromPtr(val); -// } }; template @@ -422,19 +453,24 @@ struct Reflector> } }; -template<> -struct Reflector +template +struct Reflector> { - static auto Create(std::string str) - { + static auto Create(std::basic_string str) { return Value(std::move(str)); } - static auto CreateFromPtr(const std::string* str) - { + static auto CreateFromPtr(const std::basic_string* str) { return Value(*str); } }; +template +struct Reflector> +{ + static auto Create(nonstd::basic_string_view str) { return Value(std::move(str)); } + static auto CreateFromPtr(const nonstd::basic_string_view* str) { return Value(*str); } +}; + template<> struct Reflector { @@ -448,6 +484,20 @@ struct Reflector } }; +template<> +struct Reflector +{ + static auto Create(double val) { return Value(val); } + static auto CreateFromPtr(const float* val) { return Value(static_cast(*val)); } +}; + +template<> +struct Reflector +{ + static auto Create(double val) { return Value(val); } + static auto CreateFromPtr(const double* val) { return Value(*val); } +}; + #define JINJA2_INT_REFLECTOR(Type) \ template<> \ struct Reflector \ @@ -462,6 +512,8 @@ struct Reflector \ } \ } +JINJA2_INT_REFLECTOR(char); +JINJA2_INT_REFLECTOR(wchar_t); JINJA2_INT_REFLECTOR(int8_t); JINJA2_INT_REFLECTOR(uint8_t); JINJA2_INT_REFLECTOR(int16_t); @@ -471,6 +523,7 @@ JINJA2_INT_REFLECTOR(uint32_t); JINJA2_INT_REFLECTOR(int64_t); JINJA2_INT_REFLECTOR(uint64_t); } // detail +#endif template Value Reflect(T&& val) diff --git a/include/jinja2cpp/string_helpers.h b/include/jinja2cpp/string_helpers.h index 99ded498..eec0caa6 100644 --- a/include/jinja2cpp/string_helpers.h +++ b/include/jinja2cpp/string_helpers.h @@ -100,6 +100,18 @@ namespace detail } // detail +/*! + * \brief Convert string objects from one representation or another + * + * Converts string or string views to string objects with possible char conversion (char -> wchar_t or wchar_t -> char). + * This function should be used when exact type of string is needed. + * + * @tparam Dst Destination string type. Mandatory. Can be std::string or std::wstring + * @tparam Src Source string type. Auto detected. Can be either std::basic_string or nonstd::string_view + * + * @param from Source string object which should be converted + * @return Destination string object of the specified type + */ template Dst ConvertString(Src&& from) { @@ -107,41 +119,102 @@ Dst ConvertString(Src&& from) return detail::StringConverter>::DoConvert(nonstd::basic_string_view(from)); } +/*! + * \brief Gets std::string from std::string + * + * Helper method for use in template context which gets std::string from the other possible string objects (std::string in this case) + * + * @param str Source string + * @return Copy of the source string + */ inline const std::string AsString(const std::string& str) { return str; } - +/*! + * \brief Gets std::string from std::wstring + * + * Helper method for use in template context which gets std::string from the other possible string objects (std::wstring in this case) + * Conversion wchar_t -> char is performing + * + * @param str Source string + * @return Converted source string + */ inline std::string AsString(const std::wstring& str) { return ConvertString(str); } - +/*! + * \brief Gets std::string from nonstd::string_view + * + * Helper method for use in template context which gets std::string from the other possible string objects (nonstd::string_view in this case) + * + * @param str Source string + * @return Copy of the source string + */ inline std::string AsString(const nonstd::string_view& str) { return std::string(str.begin(), str.end()); } - +/*! + * \brief Gets std::string from nonstd::wstring_view + * + * Helper method for use in template context which gets std::string from the other possible string objects (nonstd::wstring_view in this case) + * Conversion wchar_t -> char is performing + * + * @param str Source string + * @return Converted source string + */ inline std::string AsString(const nonstd::wstring_view& str) { return ConvertString(str); } - +/*! + * \brief Gets std::wstring from std::wstring + * + * Helper method for use in template context which gets std::wstring from the other possible string objects (std::wstring in this case) + * + * @param str Source string + * @return Copy of the source string + */ inline const std::wstring AsWString(const std::wstring& str) { return str; } - +/*! + * \brief Gets std::wstring from std::string + * + * Helper method for use in template context which gets std::wstring from the other possible string objects (std::string in this case) + * Conversion char -> wchar_t is performing + * + * @param str Source string + * @return Converted source string + */ inline std::wstring AsWString(const std::string& str) { return ConvertString(str); } - +/*! + * \brief Gets std::wstring from nonstd::wstring_view + * + * Helper method for use in template context which gets std::wstring from the other possible string objects (nonstd::wstring_view in this case) + * + * @param str Source string + * @return Copy of the source string + */ inline std::wstring AsWString(const nonstd::wstring_view& str) { return std::wstring(str.begin(), str.end()); } - +/*! + * \brief Gets std::wstring from nonstd::string_view + * + * Helper method for use in template context which gets std::wstring from the other possible string objects (nonstd::string_view in this case) + * Conversion char -> wchar_t is performing + * + * @param str Source string + * @return Converted source string + */ inline std::wstring AsWString(const nonstd::string_view& str) { return ConvertString(str); @@ -189,12 +262,30 @@ struct WStringGetter } }; } - +/*! + * \brief Gets std::string from the arbitrary \ref Value + * + * Helper method for use in template context which gets std::string from the other possible string objects (Value in this case). + * Conversion wchar_t -> char is performing if needed. In case of non-string object actually stored in the \ref Value + * empty string is returned. + * + * @param val Source string + * @return Extracted or empty string + */ inline std::string AsString(const Value& val) { return nonstd::visit(detail::StringGetter(), val.data()); } - +/*! + * \brief Gets std::wstring from the arbitrary \ref Value + * + * Helper method for use in template context which gets std::wstring from the other possible string objects (Value in this case). + * Conversion char -> wchar_t is performing if needed. In case of non-string object actually stored in the \ref Value + * empty string is returned. + * + * @param val Source string + * @return Extracted or empty string + */ inline std::wstring AsWString(const Value& val) { return nonstd::visit(detail::WStringGetter(), val.data()); diff --git a/include/jinja2cpp/template.h b/include/jinja2cpp/template.h index 467f6ed6..f8ae6079 100644 --- a/include/jinja2cpp/template.h +++ b/include/jinja2cpp/template.h @@ -20,19 +20,109 @@ using Result = nonstd::expected; template using ResultW = nonstd::expected; +/*! + * \brief Template object which is used to render narrow char templates + * + * This class is a main class for rendering narrow char templates. It can be used independently or together with + * \ref TemplateEnv. In the second case it's possible to use templates inheritance and extension. + * + * Basic usage of Template class: + * ```c++ + * std::string source = "Hello World from Parser!"; + * + * jinja2::Template tpl; + * tpl.Load(source); + * std::string result = tpl.RenderAsString(ValuesMap{}).value(); + * ``` + */ class Template { public: + /*! + * \brief Default constructor + */ Template() : Template(nullptr) {} + /*! + * \brief Initializing constructor + * + * Creates instance of the template with the specified template environment object + * + * @param env Template environment object which created template should refer to + */ explicit Template(TemplateEnv* env); + /*! + * Destructor + */ ~Template(); + /*! + * \brief Load template from the zero-terminated narrow char string + * + * Takes specified narrow char string and parses it as a Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param tpl Zero-terminated narrow char string with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ Result Load(const char* tpl, std::string tplName = std::string()); + /*! + * \brief Load template from the std::string + * + * Takes specified std::string object and parses it as a Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param str std::string object with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ Result Load(const std::string& str, std::string tplName = std::string()); + /*! + * \brief Load template from the stream + * + * Takes specified stream object and parses it as a source of Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param stream Stream object with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ Result Load(std::istream& stream, std::string tplName = std::string()); + /*! + * \brief Load template from the specified file + * + * Loads file with the specified name and parses it as a source of Jinja2 template. In case of error returns + * detailed diagnostic + * + * @param fileName Name of the file to load + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ Result LoadFromFile(const std::string& fileName); + /*! + * \brief Render previously loaded template to the narrow char stream + * + * Renders previously loaded template to the specified narrow char stream and specified set of params. + * + * @param os Stream to render template to + * @param params Set of params which should be passed to the template engine and can be used within the template + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ Result Render(std::ostream& os, const ValuesMap& params); + /*! + * \brief Render previously loaded template to the narrow char string + * + * Renders previously loaded template as a narrow char string and with specified set of params. + * + * @param params Set of params which should be passed to the template engine and can be used within the template + * + * @return Either rendered string or instance of \ref ErrorInfoTpl as an error + */ Result RenderAsString(const ValuesMap& params); private: @@ -40,20 +130,109 @@ class Template friend class TemplateImpl; }; - +/*! + * \brief Template object which is used to render wide char templates + * + * This class is a main class for rendering wide char templates. It can be used independently or together with + * \ref TemplateEnv. In the second case it's possible to use templates inheritance and extension. + * + * Basic usage of Template class: + * ```c++ + * std::string source = "Hello World from Parser!"; + * + * jinja2::Template tpl; + * tpl.Load(source); + * std::string result = tpl.RenderAsString(ValuesMap{}).value(); + * ``` +*/ class TemplateW { public: + /*! + * \brief Default constructor + */ TemplateW() : TemplateW(nullptr) {} + /*! + * \brief Initializing constructor + * + * Creates instance of the template with the specified template environment object + * + * @param env Template environment object which created template should refer to + */ explicit TemplateW(TemplateEnv* env); + /*! + * Destructor + */ ~TemplateW(); + /*! + * \brief Load template from the zero-terminated wide char string + * + * Takes specified wide char string and parses it as a Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param tpl Zero-terminated wide char string with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ ResultW Load(const wchar_t* tpl, std::string tplName = std::string()); + /*! + * \brief Load template from the std::wstring + * + * Takes specified std::wstring object and parses it as a Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param str std::wstring object with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ ResultW Load(const std::wstring& str, std::string tplName = std::string()); + /*! + * \brief Load template from the stream + * + * Takes specified stream object and parses it as a source of Jinja2 template. In case of error returns detailed + * diagnostic + * + * @param stream Stream object with template description + * @param tplName Optional name of the template (for the error reporting purposes) + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ ResultW Load(std::wistream& stream, std::string tplName = std::string()); + /*! + * \brief Load template from the specified file + * + * Loads file with the specified name and parses it as a source of Jinja2 template. In case of error returns + * detailed diagnostic + * + * @param fileName Name of the file to load + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ ResultW LoadFromFile(const std::string& fileName); + /*! + * \brief Render previously loaded template to the wide char stream + * + * Renders previously loaded template to the specified wide char stream and specified set of params. + * + * @param os Stream to render template to + * @param params Set of params which should be passed to the template engine and can be used within the template + * + * @return Either noting or instance of \ref ErrorInfoTpl as an error + */ ResultW Render(std::wostream& os, const ValuesMap& params); + /*! + * \brief Render previously loaded template to the wide char string + * + * Renders previously loaded template as a wide char string and with specified set of params. + * + * @param params Set of params which should be passed to the template engine and can be used within the template + * + * @return Either rendered string or instance of \ref ErrorInfoTpl as an error + */ ResultW RenderAsString(const ValuesMap& params); private: diff --git a/include/jinja2cpp/template_env.h b/include/jinja2cpp/template_env.h index f8355432..c324a669 100644 --- a/include/jinja2cpp/template_env.h +++ b/include/jinja2cpp/template_env.h @@ -15,82 +15,228 @@ namespace jinja2 class IErrorHandler; class IFilesystemHandler; +//! Compatibility mode for jinja2c++ engine enum class Jinja2CompatMode { - None, - Vesrsion_2_10, + None, //!< Default mode + Vesrsion_2_10, //!< Compatibility with Jinja2 v.2.10 specification }; +//! Global template environment settings struct Settings { + /// Extensions set which should be supported struct Extensions { - bool Do = false; + bool Do = false; //!< Enable use of `do` statement }; + //! Enables use of line statements (yet not supported) bool useLineStatements = false; + //! Enables blocks trimming the same way as it does python Jinja2 engine bool trimBlocks = false; + //! Enables blocks stripping (from the left) the same way as it does python Jinja2 engine bool lstripBlocks = false; + //! Templates cache size + int cacheSize = 400; + //! If auto_reload is set to true (default) every time a template is requested the loader checks if the source changed and if yes, it will reload the template + bool autoReload = true; + //! Extensions set enabled for templates Extensions extensions; + //! Controls Jinja2 compatibility mode Jinja2CompatMode jinja2CompatMode = Jinja2CompatMode::None; }; +/*! + * \brief Global template environment which controls behaviour of the different \ref Template instances + * + * This class is used for fine tuning of the templates behaviour and for state sharing between them. With this class + * it's possible to control template loading, provide template sources, set global variables, use template inheritance + * and inclusion. + * + * It's possible to load templates from the environment via \ref LoadTemplate or \ref LoadTemplateW methods + * or to pass instance of the environment directly to the \ref Template via constructor. + */ class TemplateEnv { public: - void SetErrorHandler(IErrorHandler* h) - { - m_errorHandler = h; - } - auto GetErrorHandler() const - { - return m_errorHandler; - } - + using TimePoint = std::chrono::system_clock::time_point; + using TimeStamp = std::chrono::steady_clock::time_point; + + /*! + * \brief Returns global settings for the environment + * + * @return Constant reference to the global settings + */ const Settings& GetSettings() const {return m_settings;} + /*! + * \brief Returns global settings for the environment available for modification + * + * @return Reference to the global settings + */ Settings& GetSettings() {return m_settings;} + + /*! + * \brief Replace global settings for the environment with the new ones + * + * @param setts New settings + */ void SetSettings(const Settings& setts) {m_settings = setts;} + /*! + * \brief Add pointer to file system handler with the specified prefix + * + * Adds filesystem handler which provides access to the external source of templates. With added handlers it's + * possible to load templates from the `import`, `extends` and `include` jinja2 tags. Prefix is used for + * distinguish one templates source from another. \ref LoadTemplate or \ref LoadTemplateW methods use + * handlers to load templates with the specified name. + * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously. + * + * Basic usage: + * ```c++ + * jinja2::TemplateEnv env; + * + * auto fs = std::make_shared(); + * env.AddFilesystemHandler(std::string(), fs); + * fs->AddFile("base.j2tpl", "Hello World!"); + * ``` + * + * @param prefix Optional prefix of the handler's filesystem. Prefix is a part of the file name and passed to the handler's \ref IFilesystemHandler::OpenStream method + * @param h Shared pointer to the handler + */ void AddFilesystemHandler(std::string prefix, FilesystemHandlerPtr h) { - m_filesystemHandlers.push_back(FsHandler{std::move(prefix), h}); + m_filesystemHandlers.push_back(FsHandler{std::move(prefix), std::move(h)}); } + /*! + * \brief Add reference to file system handler with the specified prefix + * + * Adds filesystem handler which provides access to the external source of templates. With added handlers it's + * possible to load templates from the `import`, `extends` and `include` jinja2 tags. Prefix is used for + * distinguish one templates source from another. \ref LoadTemplate or \ref LoadTemplateW methods use + * handlers to load templates with the specified name. + * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously. + * + * Basic usage: + * ```c++ + * jinja2::TemplateEnv env; + * + * MemoryFileSystem fs; + * env.AddFilesystemHandler(std::string(), fs); + * fs.AddFile("base.j2tpl", "Hello World!"); + * ``` + * + * @param prefix Optional prefix of the handler's filesystem. Prefix is a part of the file name and passed to the handler's \ref IFilesystemHandler::OpenStream method + * @param h Reference to the handler. It's assumed that lifetime of the handler is controlled externally + */ void AddFilesystemHandler(std::string prefix, IFilesystemHandler& h) { m_filesystemHandlers.push_back(FsHandler{std::move(prefix), std::shared_ptr(&h, [](auto*) {})}); } + /*! + * \brief Load narrow char template with the specified name via registered file handlers + * + * In case of specified file present in any of the registered handlers, template is loaded and parsed. If any + * error occurred during the loading or parsing detailed diagnostic will be returned. + * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously. + * + * @param fileName Template name to load + * + * @return Either loaded template or load/parse error. See \ref ErrorInfoTpl + */ nonstd::expected LoadTemplate(std::string fileName); + /*! + * \brief Load wide char template with the specified name via registered file handlers + * + * In case of specified file present in any of the registered handlers, template is loaded and parsed. If any + * error occurred during the loading or parsing detailed diagnostic will be returned. + * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously. + * + * @param fileName Template name to load + * + * @return Either loaded template or load/parse error. See \ref ErrorInfoTpl + */ nonstd::expected LoadTemplateW(std::string fileName); + /*! + * \brief Add global variable to the environment + * + * Adds global variable which can be referred in any template which is loaded within this environment object. + * Method is thread-safe. + * + * @param name Name of the variable + * @param val Value of the variable + */ void AddGlobal(std::string name, Value val) { std::unique_lock l(m_guard); m_globalValues[std::move(name)] = std::move(val); } - + /*! + * \brief Remove global variable from the environment + * + * Removes global variable from the environment. + * Method is thread-safe. + * + * @param name Name of the variable + */ void RemoveGlobal(const std::string& name) { std::unique_lock l(m_guard); m_globalValues.erase(name); } + /*! + * \brief Call the specified function with the current set of global variables under the internal lock + * + * Main purpose of this method is to help external code to enumerate global variables thread-safely. Provided functional object is called under the + * internal lock with the current set of global variables as an argument. + * + * @tparam Fn Type of the functional object to call + * @param fn Functional object to call + */ template void ApplyGlobals(Fn&& fn) { std::shared_lock l(m_guard); fn(m_globalValues); } + +private: + template + auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache); + + private: - IErrorHandler* m_errorHandler; struct FsHandler { std::string prefix; FilesystemHandlerPtr handler; }; + + struct BaseTemplateInfo + { + nonstd::optional lastModification; + TimeStamp lastAccessTime; + FilesystemHandlerPtr handler; + }; + + struct TemplateCacheEntry : public BaseTemplateInfo + { + Template tpl; + }; + + struct TemplateWCacheEntry : public BaseTemplateInfo + { + TemplateW tpl; + }; + std::vector m_filesystemHandlers; Settings m_settings; ValuesMap m_globalValues; std::shared_timed_mutex m_guard; + std::unordered_map m_templateCache; + std::unordered_map m_templateWCache; }; } // jinja2 diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h index bc307631..2f9bb37e 100644 --- a/include/jinja2cpp/user_callable.h +++ b/include/jinja2cpp/user_callable.h @@ -9,6 +9,7 @@ namespace jinja2 { +#ifndef JINJA2CPP_NO_DOXYGEN namespace detail { template @@ -142,6 +143,8 @@ inline const Value& GetParamValue(const UserCallableParams& params, const ArgInf return params.extraKwArgs; else if (info.paramName == "*args") return params.extraPosArgs; + else if (info.paramName == "*context") + return params.context; return info.defValue; } @@ -183,7 +186,39 @@ Value InvokeUserCallable(Fn&& fn, const UserCallableParams& params, ArgDescr&& . return nonstd::visit(ParamUnwrapper>(&invoker), GetParamValue(params, ad).data()...); } } // detail - +#endif // JINJA2CPP_NO_DOXYGEN + +/*! + * \brief Create user-defined callable from the specified function + * + * Creates instance of the UserCallable object which invokes specified function (f) with parsed params according + * to the description (ad). For instance: + * ```c++ + * MakeCallable( + * [](const std::string& str1, const std::string& str2) { + * return str1 + " " + str2; + * }, + * ArgInfo{"str1"}, ArgInfo{"str2", false, "default"} + * ); + * ``` + * In this sample lambda function with two string params will be invoked from the jinja2 template and provided with + * the specified params. Each param is described by \ref ArgInfo structure. Result of the function will be converted + * and passed back to the jinja2 template. + * + * In case the function should accept extra positional args or extra named args this params should be described the + * following name. + * - Extra positional args. \ref ArgInfo should describe this param with name `*args`. Param of the function should + * has \ref ValuesList type + * - Extra named args. \ref ArgInfo should describe this param with name `**kwargs`. Param of the function should + * has \ref ValuesMap type + * - Current template context. \ref ArgInfo should describe this param with name `*context`. Param of the function should + * has \ref GenericMap type + * + * \param f Function which should be called + * \param ad Function param descriptors + * + * \returns Instance of the properly initialized \ref UserCallable structure + */ template auto MakeCallable(Fn&& f, ArgDescr&& ... ad) { @@ -195,6 +230,13 @@ auto MakeCallable(Fn&& f, ArgDescr&& ... ad) }; } +/*! + * \brief Create user-callable from the function with no params. + * + * \param f Function which should be called + * + * \returns Instance of the properly initialized \ref UserCallable structure + */ template auto MakeCallable(Fn&& f) { diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 4beca729..86626c08 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -16,6 +16,7 @@ namespace jinja2 { +//! Empty value container struct EmptyValue { template @@ -23,44 +24,117 @@ struct EmptyValue }; class Value; +/*! + * \brief Interface to the generic dictionary type which maps string to some value + */ struct MapItemAccessor { - virtual ~MapItemAccessor() {} + //! Destructor + virtual ~MapItemAccessor() = default; + + //! Method is called to obtain number of items in the dictionary. Maximum possible size_t value means non-calculable size virtual size_t GetSize() const = 0; + + /*! + * \brief Method is called to check presence of the item in the dictionary + * + * @param name Name of the item + * + * @return true if item is present and false otherwise. + */ virtual bool HasValue(const std::string& name) const = 0; + /*! + * \brief Method is called for retrieving the value by specified name + * + * @param name Name of the value to retrieve + * + * @return Requestd value or empty \ref Value if item is absent + */ virtual Value GetValueByName(const std::string& name) const = 0; + /*! + * \brief Method is called for retrieving collection of keys in the dictionary + * + * @return Collection of keys if any. Ordering of keys is unspecified. + */ virtual std::vector GetKeys() const = 0; }; +/*! + * \brief Helper class for accessing maps specified by the \ref MapItemAccessor interface + * + * In the \ref Value type can be stored either ValuesMap instance or GenericMap instance. ValuesMap is a simple + * dictionary object based on std::unordered_map. Rather than GenericMap is a more robust object which can provide + * access to the different types of dictionary entities. GenericMap takes the \ref MapItemAccessor interface instance + * and uses it to access particular items in the dictionaries. + */ class GenericMap { public: + //! Default constructor GenericMap() = default; - GenericMap(std::function accessor) + /*! + * \brief Initializing constructor + * + * The only one way to get valid non-empty GeneridMap is to construct it with the specified \ref MapItemAccessor + * implementation provider. This provider is a functional object which returns pointer to the interface instance. + * + * @param accessor Functional object which returns pointer to the \ref MapItemAccessor interface + */ + explicit GenericMap(std::function accessor) : m_accessor(std::move(accessor)) { } + /*! + * \brief Check the presence the specific item in the dictionary + * + * @param name Name of the the item + * + * @return true of item is present and false otherwise + */ bool HasValue(const std::string& name) const { return m_accessor ? m_accessor()->HasValue(name) : false; } + /*! + * \brief Get specific item from the dictionary + * + * @param name Name of the item to get + * + * @return Value of the item or empty \ref Value if no item + */ Value GetValueByName(const std::string& name) const; + /*! + * \brief Get size of the dictionary + * + * @return Size of the dictionary + */ size_t GetSize() const { return m_accessor ? m_accessor()->GetSize() : 0; } + /*! + * \brief Get collection of keys from the dictionary + * + * @return Collection of the keys or empty collection if no keys + */ auto GetKeys() const { return m_accessor ? m_accessor()->GetKeys() : std::vector(); } + /*! + * \brief Get the underlying access interface to the dictionary + * + * @return Pointer to the underlying interface or nullptr if no + */ auto GetAccessor() const { return m_accessor(); } +private: std::function m_accessor; }; @@ -73,6 +147,30 @@ struct UserCallable; template using RecWrapper = nonstd::value_ptr; +/*! + * \brief Generic value class + * + * Variant-based class which is used for passing values to and from Jinja2C++ template engine. This class store the + * following types of values: + * + * - EmptyValue. In this case instance of this class threated as 'empty' + * - Boolean value. + * - String value. + * - Wide string value + * - String view value (nonstd::string_view) + * - Wide string view value (nonstd::wstring_view) + * - integer (int64_t) value + * - floating point (double) value + * - Simple list of other values (\ref ValuesList) + * - Simple map of other values (\ref ValuesMap) + * - Generic list of other values (\ref GenericList) + * - Generic map of other values (\ref GenericMap) + * - User-defined callable (\ref UserCallable) + * + * Exact value can be accessed via nonstd::visit method applied to the result of the Value::data() call or any of + * asXXX method (ex. \ref Value::asString). In case of string retrieval it's better to use \ref AsString or \ref + * AsWString functions. Thay hide all nececcary transformations between various types of strings (or string views). + */ class Value { public: @@ -98,125 +196,295 @@ class Value template struct AnyOf : public std::integral_constant, H>::value || AnyOf::value> {}; + //! Default constructor Value(); + //! Copy constructor Value(const Value& val); + //! Move constructor Value(Value&& val) noexcept; + //! Desctructor ~Value(); + //! Assignment operator Value& operator =(const Value&); + //! Move assignment operator Value& operator =(Value&&) noexcept; + /*! + * \brief Generic initializing constructor + * + * Creates \ref Value from the arbitrary type which is compatible with types listed in \ref Value::ValueData + * + * @tparam T Type of value to create \ref Value instance from + * @param val Value which should be used to initialize \ref Value instance + */ template Value(T&& val, typename std::enable_if::value>::type* = nullptr) : m_data(std::forward(val)) { } + /*! + * \brief Initializing constructor from pointer to the null-terminated narrow string + * + * @param val Null-terminated string which should be used to initialize \ref Value instance + */ Value(const char* val) : m_data(std::string(val)) { } + /*! + * \brief Initializing constructor from pointer to the null-terminated wide string + * + * @param val Null-terminated string which should be used to initialize \ref Value instance + */ + Value(const wchar_t* val) + : m_data(std::wstring(val)) + { + } + /*! + * \brief Initializing constructor from the narrow string literal + * + * @param val String literal which should be used to initialize \ref Value instance + */ template Value(char (&val)[N]) : m_data(std::string(val)) { } + /*! + * \brief Initializing constructor from the wide string literal + * + * @param val String literal which should be used to initialize \ref Value instance + */ template Value(wchar_t (&val)[N]) : m_data(std::wstring(val)) { } + /*! + * \brief Initializing constructor from the int value + * + * @param val Integer value which should be used to initialize \ref Value instance + */ Value(int val) : m_data(static_cast(val)) { } + /*! + * \brief Initializing constructor from the \ref ValuesList + * + * @param list List of values which should be used to initialize \ref Value instance + */ Value(const ValuesList& list) : m_data(RecWrapper(list)) { } + /*! + * \brief Initializing constructor from the \ref ValuesMap + * + * @param map Map of values which should be used to initialize \ref Value instance + */ Value(const ValuesMap& map) : m_data(RecWrapper(map)) { } + /*! + * \brief Initializing constructor from the \ref UserCallable + * + * @param callable UserCallable which should be used to initialize \ref Value instance + */ Value(const UserCallable& callable); + /*! + * \brief Initializing move constructor from the \ref ValuesList + * + * @param list List of values which should be used to initialize \ref Value instance + */ Value(ValuesList&& list) noexcept : m_data(RecWrapper(std::move(list))) { } + /*! + * \brief Initializing move constructor from the \ref ValuesMap + * + * @param map Map of values which should be used to initialize \ref Value instance + */ Value(ValuesMap&& map) noexcept : m_data(RecWrapper(std::move(map))) { } + /*! + * \brief Initializing move constructor from the \ref UserCallable + * + * @param callable UserCallable which should be used to initialize \ref Value instance + */ Value(UserCallable&& callable); + /*! + * \brief Get the non-mutable stored data object + * + * Returns the stored data object in order to get the typed value from it. For instance: + * ```c++ + * inline std::string AsString(const jinja2::Value& val) + * { + * return nonstd::visit(StringGetter(), val.data()); + * } + * ``` + * + * @return Non-mutable stored data object + */ const ValueData& data() const {return m_data;} - + /*! + * \brief Get the mutable stored data object + * + * Returns the stored data object in order to get the typed value from it. For instance: + * ```c++ + * inline std::string AsString(Value& val) + * { + * return nonstd::visit(StringGetter(), val.data()); + * } + * ``` + * + * @return Mutable stored data object + */ ValueData& data() {return m_data;} + //! Test Value for containing std::string object bool isString() const { return nonstd::get_if(&m_data) != nullptr; } + /*! + * \brief Returns mutable containing std::string object + * + * Returns containing std::string object. Appropriate exception is thrown in case non-string containing value + * + * @return Mutable containing std::string object + */ auto& asString() { return nonstd::get(m_data); } + /*! + * \brief Returns non-mutable containing std::string object + * + * Returns containing std::string object. Appropriate exception is thrown in case of non-string containing value + * + * @return Non-mutable containing std::string object + */ auto& asString() const { return nonstd::get(m_data); } + //! Test Value for containing std::wstring object bool isWString() const { return nonstd::get_if(&m_data) != nullptr; } + /*! + * \brief Returns mutable containing std::wstring object + * + * Returns containing std::wstring object. Appropriate exception is thrown in case of non-wstring containing value + * + * @return Mutable containing std::wstring object + */ auto& asWString() { return nonstd::get(m_data); } + /*! + * \brief Returns non-mutable containing std::wstring object + * + * Returns containing std::wstring object. Appropriate exception is thrown in case of non-wstring containing value + * + * @return Non-mutable containing std::wstring object + */ auto& asWString() const { return nonstd::get(m_data); } + //! Test Value for containing jinja2::ValuesList object bool isList() const { return nonstd::get_if>(&m_data) != nullptr || nonstd::get_if(&m_data) != nullptr; } + /*! + * \brief Returns mutable containing jinja2::ValuesList object + * + * Returns containing jinja2::ValuesList object. Appropriate exception is thrown in case of non-Valueslist containing value + * + * @return Mutable containing jinja2::ValuesList object + */ auto& asList() { return *nonstd::get>(m_data).get(); } + /*! + * \brief Returns non-mutable containing jinja2::ValuesList object + * + * Returns containing jinja2::ValuesList object. Appropriate exception is thrown in case of non-Valueslist containing value + * + * @return Non-mutable containing jinja2::ValuesList object + */ auto& asList() const { return *nonstd::get>(m_data).get(); } + //! Test Value for containing jinja2::ValuesMap object bool isMap() const { return nonstd::get_if>(&m_data) != nullptr || nonstd::get_if(&m_data) != nullptr; } + /*! + * \brief Returns mutable containing jinja2::ValuesMap object + * + * Returns containing jinja2::ValuesMap object. Appropriate exception is thrown in case of non-ValuesMap containing value + * + * @return Mutable containing jinja2::ValuesMap object + */ auto& asMap() { return *nonstd::get>(m_data).get(); } + /*! + * \brief Returns non-mutable containing jinja2::ValuesMap object + * + * Returns containing jinja2::ValuesMap object. Appropriate exception is thrown in case of non-ValuesMap containing value + * + * @return Non-mutable containing jinja2::ValuesMap object + */ auto& asMap() const { return *nonstd::get>(m_data).get(); } + //! Test Value for emptyness bool isEmpty() const { return nonstd::get_if(&m_data) != nullptr; } - Value subscript(const Value& index) const; - private: ValueData m_data; }; + +/*! + * \brief Information about user-callable parameters passed from Jinja2 call context + * + * This structure prepared by the Jinja2C++ engine and filled by information about call parameters gathered from the + * call context. See documentation for \ref UserCallable for detailed information + * + */ struct UserCallableParams { + //! Values of parameters mapped according to \ref UserCallable::argsInfo ValuesMap args; + //! Values of extra positional args got from the call expression Value extraPosArgs; + //! Values of extra named args got from the call expression Value extraKwArgs; + //! Context object which provides access to the current variables set of the template + Value context; bool paramsParsed = false; Value operator[](const std::string& paramName) const @@ -229,22 +497,70 @@ struct UserCallableParams } }; +/*! + * \brief Information about one argument of the user-defined callable + * + * This structure is used as a description of the user-callable argument. Information from this structure is used + * by the Jinja2C++ engine to map actual call parameters to the expected ones by the user-defined callable. + */ struct ArgInfo { - std::string paramName; - bool isMandatory; - Value defValue; - - ArgInfo(std::string name, bool isMandat = false, Value defVal = Value()) - : paramName(std::move(name)) - , isMandatory(isMandat) - , defValue(std::move(defVal)) - {} + //! Name of the argument + std::string paramName; + //! Mandatory flag + bool isMandatory; + //! Default value for the argument + Value defValue; + + ArgInfo(std::string name, bool isMandat = false, Value defVal = Value()) + : paramName(std::move(name)) + , isMandatory(isMandat) + , defValue(std::move(defVal)) {} }; +/*! + * \brief User-callable descriptor + * + * This descriptor is used for description of the user-defined callables passed to the Jinja2C++ engine. Information + * from this descriptor is used by the engine to properly parse and prepare of the call parameters and pass it to the + * user-callable. For instance, such kind of user-defined callable passed as a parameter: + * ```c++ + * jinja2::UserCallable uc; + * uc.callable = [](auto& params)->jinja2::Value { + * auto str1 = params["str1"]; + * auto str2 = params["str2"]; + * + * if (str1.isString()) + * return str1.asString() + " " + str2.asString(); + * + * return str1.asWString() + L" " + str2.asWString(); + * }; + * uc.argsInfo = {{"str1", true}, {"str2", true}}; + * params["test"] = std::move(uc); + * ``` + * This declaration defines user-defined callable which takes two named parameters: `str1` and `str2`. Further, it's + * possible to call this user-defined callable from the Jinja2 template this way: + * ```jinja2 + * {{ test('Hello', 'World!') }} + * ``` + * or: + * ``` + * {{ test(str2='World!', str1='Hello') }} + * ``` + * Jinja2C++ engine maps actual call parameters according the information from \ref UserCallable::argsInfo field and + * pass them as a \ref UserCallableParams structure. Every named param (explicitly defined in the call or it's default value) + * passed throught \ref UserCallableParams::args field. Every extra positional param mentoined in call passed as \ref + * UserCallableParams::extraPosArgs. Every extra named param mentoined in call passed as \ref + * UserCallableParams::extraKwArgs. + * + * If any of argument, marked as `mandatory` in the \ref UserCallable::argsInfo field is missed in the point of the + * user-defined call the call is failed. + */ struct UserCallable { + //! Functional object which is actually handle the call std::function callable; + //! Information about arguments of the user-defined callable std::vector argsInfo; }; diff --git a/src/error_info.cpp b/src/error_info.cpp index 2c29834d..ede3367f 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -1,159 +1,165 @@ -#include #include "helpers.h" -namespace jinja2 -{ +#include +#include namespace { -template +template struct ValueRenderer { - std::basic_ostream& os; + using CharT = typename FmtCtx::char_type; + FmtCtx* ctx; - ValueRenderer(std::basic_ostream& osRef) : os(osRef) {} - - void operator() (bool val) const - { - os << (val ? UNIVERSAL_STR("True") : UNIVERSAL_STR("False")); - } - void operator() (const EmptyValue&) const + explicit ValueRenderer(FmtCtx* c) + : ctx(c) { } + + void operator()(bool val) const { fmt::format_to(ctx->out(), (val ? UNIVERSAL_STR("True") : UNIVERSAL_STR("False")).GetValue()); } + void operator()(const jinja2::EmptyValue&) const { fmt::format_to(ctx->out(), UNIVERSAL_STR("").GetValue()); } template - void operator() (const std::basic_string& val) const + void operator()(const std::basic_string& val) const { - os << ConvertString>(val); + fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue(), jinja2::ConvertString>(val)); } template - void operator() (const nonstd::basic_string_view& val) const + void operator()(const nonstd::basic_string_view& val) const { - os << ConvertString>(val); + fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue(), jinja2::ConvertString>(val)); } - void operator() (const ValuesList& vals) const + void operator()(const jinja2::ValuesList& vals) const { - os << '{'; + fmt::format_to(ctx->out(), UNIVERSAL_STR("{{").GetValue()); bool isFirst = true; for (auto& val : vals) { if (isFirst) isFirst = false; else - os << UNIVERSAL_STR(", "); - - nonstd::visit(ValueRenderer(os), val.data()); + fmt::format_to(ctx->out(), UNIVERSAL_STR(", ").GetValue()); + nonstd::visit(ValueRenderer(ctx), val.data()); } - os << '}'; + fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue()); } - void operator() (const ValuesMap& vals) const + void operator()(const jinja2::ValuesMap& vals) const { - os << '{'; + fmt::format_to(ctx->out(), UNIVERSAL_STR("{{").GetValue()); bool isFirst = true; for (auto& val : vals) { if (isFirst) isFirst = false; else - os << UNIVERSAL_STR(", "); + fmt::format_to(ctx->out(), UNIVERSAL_STR(", ").GetValue()); - os << '{' << '"' << ConvertString>(val.first) << '"' << ','; - nonstd::visit(ValueRenderer(os), val.second.data()); - os << '}'; + fmt::format_to(ctx->out(), UNIVERSAL_STR("{{\"{}\",").GetValue(), jinja2::ConvertString>(val.first)); + nonstd::visit(ValueRenderer(ctx), val.second.data()); + fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue()); } - os << '}'; + fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue()); } - template - void operator()(const RecWrapper& val) const + void operator()(const jinja2::RecWrapper& val) const { - return this->operator()(const_cast(*val.get())); + this->operator()(const_cast(*val.get())); } - void operator() (const GenericMap& /*val*/) const - { - } + void operator()(const jinja2::GenericMap& /*val*/) const {} + + void operator()(const jinja2::GenericList& /*val*/) const {} + + void operator()(const jinja2::UserCallable& /*val*/) const {} - void operator() (const GenericList& /*val*/) const + template + void operator()(const T& val) const { + fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue(), val); } +}; +} - - void operator() (const UserCallable& /*val*/) const +namespace fmt +{ +template +struct formatter +{ + template + constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); } - template - void operator() (const T& val) const + template + auto format(const jinja2::Value& val, FormatContext& ctx) { - os << val; + nonstd::visit(ValueRenderer(&ctx), val.data()); + return fmt::format_to(ctx.out(), UNIVERSAL_STR("").GetValue()); } }; +} -template -std::basic_ostream& operator << (std::basic_ostream& os, Value val) +namespace jinja2 { - nonstd::visit(ValueRenderer(os), val.data()); - return os; -} -} template -void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& errInfo) +void RenderErrorInfo(std::basic_string& result, const ErrorInfoTpl& errInfo) { using string_t = std::basic_string; + fmt::basic_memory_buffer out; auto& loc = errInfo.GetErrorLocation(); - os << ConvertString(loc.fileName) << ':' << loc.line << ':' << loc.col << ':'; - os << UNIVERSAL_STR(" error: "); + + fmt::format_to(out, UNIVERSAL_STR("{}:{}:{}: error: ").GetValue(), ConvertString(loc.fileName), loc.line, loc.col); ErrorCode errCode = errInfo.GetCode(); switch (errCode) { case ErrorCode::Unspecified: - os << UNIVERSAL_STR("Parse error"); - break; + format_to(out, UNIVERSAL_STR("Parse error").GetValue()); + break; case ErrorCode::UnexpectedException: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Unexpected exception occurred during template processing. Exception: ") << extraParams[0]; + format_to(out, UNIVERSAL_STR("Unexpected exception occurred during template processing. Exception: {}").GetValue(), extraParams[0]); break; } case ErrorCode::YetUnsupported: - os << UNIVERSAL_STR("This feature has not been supported yet"); + format_to(out, UNIVERSAL_STR("This feature has not been supported yet").GetValue()); break; case ErrorCode::FileNotFound: - os << UNIVERSAL_STR("File not found"); + format_to(out, UNIVERSAL_STR("File not found").GetValue()); break; case ErrorCode::ExpectedStringLiteral: - os << UNIVERSAL_STR("String expected"); + format_to(out, UNIVERSAL_STR("String expected").GetValue()); break; case ErrorCode::ExpectedIdentifier: - os << UNIVERSAL_STR("Identifier expected"); + format_to(out, UNIVERSAL_STR("Identifier expected").GetValue()); break; case ErrorCode::ExpectedSquareBracket: - os << UNIVERSAL_STR("']' expected"); + format_to(out, UNIVERSAL_STR("']' expected").GetValue()); break; case ErrorCode::ExpectedRoundBracket: - os << UNIVERSAL_STR("')' expected"); + format_to(out, UNIVERSAL_STR("')' expected").GetValue()); break; case ErrorCode::ExpectedCurlyBracket: - os << UNIVERSAL_STR("'}' expected"); + format_to(out, UNIVERSAL_STR("'}}' expected").GetValue()); break; case ErrorCode::ExpectedToken: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Unexpected token '") << extraParams[0] << '\''; + format_to(out, UNIVERSAL_STR("Unexpected token '{}'").GetValue(), extraParams[0]); if (extraParams.size() > 1) { - os << UNIVERSAL_STR(". Expected: "); + format_to(out, UNIVERSAL_STR(". Expected: ").GetValue()); for (std::size_t i = 1; i < extraParams.size(); ++ i) { if (i != 1) - os << UNIVERSAL_STR(", "); - os << '\'' << extraParams[i] << '\''; + format_to(out, UNIVERSAL_STR(", ").GetValue()); + format_to(out, UNIVERSAL_STR("\'{}\'").GetValue(), extraParams[i]); } } break; @@ -161,75 +167,92 @@ void RenderErrorInfo(std::basic_ostream& os, const ErrorInfoTpl& e case ErrorCode::ExpectedExpression: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Expected expression, got: '") << extraParams[0] << '\''; + format_to(out, UNIVERSAL_STR("Expected expression, got: '{}'").GetValue(), extraParams[0]); break; } case ErrorCode::ExpectedEndOfStatement: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Expected end of statement, got: '") << extraParams[0] << '\''; + format_to(out, UNIVERSAL_STR("Expected end of statement, got: '{}'").GetValue(), extraParams[0]); break; } case ErrorCode::UnexpectedToken: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Unexpected token: '") << extraParams[0] << '\''; + format_to(out, UNIVERSAL_STR("Unexpected token: '{}'").GetValue(), extraParams[0]); break; } case ErrorCode::UnexpectedStatement: { auto& extraParams = errInfo.GetExtraParams(); - os << UNIVERSAL_STR("Unexpected statement: '") << extraParams[0] << '\''; + format_to(out, UNIVERSAL_STR("Unexpected statement: '{}'").GetValue(), extraParams[0]); break; } case ErrorCode::UnexpectedCommentBegin: - os << UNIVERSAL_STR("Unexpected comment begin"); + format_to(out, UNIVERSAL_STR("Unexpected comment begin").GetValue()); break; case ErrorCode::UnexpectedCommentEnd: - os << UNIVERSAL_STR("Unexpected comment end"); + format_to(out, UNIVERSAL_STR("Unexpected comment end").GetValue()); break; case ErrorCode::UnexpectedExprBegin: - os << UNIVERSAL_STR("Unexpected expression block begin"); + format_to(out, UNIVERSAL_STR("Unexpected expression block begin").GetValue()); break; case ErrorCode::UnexpectedExprEnd: - os << UNIVERSAL_STR("Unexpected expression block end"); + format_to(out, UNIVERSAL_STR("Unexpected expression block end").GetValue()); break; case ErrorCode::UnexpectedStmtBegin: - os << UNIVERSAL_STR("Unexpected statement block begin"); + format_to(out, UNIVERSAL_STR("Unexpected statement block begin").GetValue()); break; case ErrorCode::UnexpectedStmtEnd: - os << UNIVERSAL_STR("Unexpected statement block end"); + format_to(out, UNIVERSAL_STR("Unexpected statement block end").GetValue()); break; case ErrorCode::TemplateNotParsed: - os << UNIVERSAL_STR("Template not parsed"); + format_to(out, UNIVERSAL_STR("Template not parsed").GetValue()); break; case ErrorCode::TemplateNotFound: - os << UNIVERSAL_STR("Template(s) not found: ") << errInfo.GetExtraParams()[0]; + format_to(out, UNIVERSAL_STR("Template(s) not found: {}").GetValue(), errInfo.GetExtraParams()[0]); break; case ErrorCode::InvalidTemplateName: - os << UNIVERSAL_STR("Invalid template name: ") << errInfo.GetExtraParams()[0]; + format_to(out, UNIVERSAL_STR("Invalid template name: {}").GetValue(), errInfo.GetExtraParams()[0]); break; case ErrorCode::InvalidValueType: - os << UNIVERSAL_STR("Invalid value type"); + format_to(out, UNIVERSAL_STR("Invalid value type").GetValue()); break; case ErrorCode::ExtensionDisabled: - os << UNIVERSAL_STR("Extension disabled"); + format_to(out, UNIVERSAL_STR("Extension disabled").GetValue()); break; case ErrorCode::TemplateEnvAbsent: - os << UNIVERSAL_STR("Template environment doesn't set"); + format_to(out, UNIVERSAL_STR("Template environment doesn't set").GetValue()); break; } - os << std::endl << errInfo.GetLocationDescr(); + format_to(out, UNIVERSAL_STR("\n{}").GetValue(), errInfo.GetLocationDescr()); + result = to_string(out); +} + +template<> +std::string ErrorInfoTpl::ToString() const +{ + std::string result; + RenderErrorInfo(result, *this); + return result; +} + +template<> +std::wstring ErrorInfoTpl::ToString() const +{ + std::wstring result; + RenderErrorInfo(result, *this); + return result; } std::ostream& operator << (std::ostream& os, const ErrorInfo& res) { - RenderErrorInfo(os, res); + os << res.ToString(); return os; } std::wostream& operator << (std::wostream& os, const ErrorInfoW& res) { - RenderErrorInfo(os, res); + os << res.ToString(); return os; } } // jinja2 diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index af4c0497..a0121267 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -291,7 +291,7 @@ InternalValue CallExpression::CallArbitraryFn(RenderContext& values) } auto kind = callable->GetKind(); - if (kind != Callable::GlobalFunc && kind != Callable::UserCallable) + if (kind != Callable::GlobalFunc && kind != Callable::UserCallable && kind != Callable::Macro) return InternalValue(); if (callable->GetType() == Callable::Type::Expression) diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index 4f90e572..ba74148e 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -84,6 +85,10 @@ WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const return result; } +nonstd::optional MemoryFileSystem::GetLastModificationDate(const std::string&) const +{ + return nonstd::optional(); +} RealFileSystem::RealFileSystem(std::string rootFolder) : m_rootFolder(std::move(rootFolder)) @@ -91,11 +96,16 @@ RealFileSystem::RealFileSystem(std::string rootFolder) } -CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const +std::string RealFileSystem::GetFullFilePath(const std::string& name) const { boost::filesystem::path root(m_rootFolder); root /= name; - const auto& filePath = root.string(); + return root.string(); +} + +CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const +{ + auto filePath = GetFullFilePath(name); CharFileStreamPtr result(new std::ifstream(filePath), [](std::istream* s) {delete static_cast(s);}); if (result->good()) @@ -106,9 +116,7 @@ CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const { - boost::filesystem::path root(m_rootFolder); - root /= name; - const auto& filePath = root.string(); + auto filePath = GetFullFilePath(name); WCharFileStreamPtr result(new std::wifstream(filePath), [](std::wistream* s) {delete static_cast(s);}); if (result->good()) @@ -116,5 +124,24 @@ WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const return WCharFileStreamPtr(nullptr, [](std::wistream*){;}); } +nonstd::optional RealFileSystem::GetLastModificationDate(const std::string& name) const +{ + boost::filesystem::path root(m_rootFolder); + root /= name; + + auto modify_time = boost::filesystem::last_write_time(root); + + return std::chrono::system_clock::from_time_t(modify_time); +} +CharFileStreamPtr RealFileSystem::OpenByteStream(const std::string& name) const +{ + auto filePath = GetFullFilePath(name); + + CharFileStreamPtr result(new std::ifstream(filePath, std::ios_base::binary), [](std::istream* s) {delete static_cast(s);}); + if (result->good()) + return result; + + return CharFileStreamPtr(nullptr, [](std::istream*){}); +} } // jinja2 diff --git a/src/filters.cpp b/src/filters.cpp index 4bcb1eca..340d5508 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -655,7 +655,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte size_t count = 0; for (; it != end; ++ it, ++ count) { - bool doCopy = count == 0 || std::uniform_int_distribution<>(0, count)(gen) == 0; + bool doCopy = count == 0 || std::uniform_int_distribution(0, count)(gen) == 0; if (doCopy) result = *it; } diff --git a/src/generic_adapters.h b/src/generic_adapters.h index 8a4b4abf..f475c8b0 100644 --- a/src/generic_adapters.h +++ b/src/generic_adapters.h @@ -95,11 +95,6 @@ class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAc return static_cast(this)->GetItemsCountImpl(); } - size_t GetItemsCount() const override - { - return static_cast(this)->GetItemsCountImpl(); - } - const IndexBasedAccessor* GetIndexer() const override { return this; diff --git a/src/helpers.h b/src/helpers.h index 115e457f..4b699cb5 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -45,7 +45,8 @@ struct MultiStringLiteral } }; -#define UNIVERSAL_STR(Str) MultiStringLiteral{Str, L##Str} +#define UNIVERSAL_STR(Str) \ + ::jinja2::MultiStringLiteral { Str, L##Str } //! CompileEscapes replaces escape characters by their meanings. /** diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 4c184253..918968c5 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -779,6 +779,33 @@ Value IntValue2Value(const InternalValue& val) return Apply(val); } +class ContextMapper : public MapItemAccessor +{ +public: + explicit ContextMapper(RenderContext* context) + : m_context(context) + { + } + + size_t GetSize() const override { return std::numeric_limits::max(); } + bool HasValue(const std::string& name) const override + { + bool found = false; + m_context->FindValue(name, found); + return found; + } + Value GetValueByName(const std::string& name) const override + { + bool found = false; + auto p = m_context->FindValue(name, found); + return found ? IntValue2Value(p->second) : Value(); + } + std::vector GetKeys() const override { return std::vector(); } + +private: + RenderContext* m_context; +}; + UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderContext& context, const std::vector& argsInfo) { UserCallableParams result; @@ -789,7 +816,7 @@ UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderCon for (auto& argInfo : argsInfo) { - if (argInfo.name == "*args" || argInfo.name == "**kwargs") + if (argInfo.name.size() > 1 && argInfo.name[0] == '*') continue; auto p = args.args.find(argInfo.name); @@ -812,6 +839,7 @@ UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderCon for (auto p : args.extraPosArgs) extraPosArgs.push_back(IntValue2Value(p->Evaluate(context))); result.extraPosArgs = Value(std::move(extraPosArgs)); + result.context = GenericMap([accessor = ContextMapper(&context)]() -> const MapItemAccessor* { return &accessor; }); return result; } diff --git a/src/internal_value.h b/src/internal_value.h index b9a9351d..f73bd5ff 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -12,7 +12,7 @@ #if defined(_MSC_VER) && _MSC_VER <= 1900 // robin_hood hash map doesn't compatible with MSVC 14.0 #include #else -#include +#include "robin_hood.h" #endif diff --git a/src/render_context.h b/src/render_context.h index f7e5f368..bcc666d8 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -39,6 +39,16 @@ class RenderContext (*m_currentScope)["self"] = CreateMapAdapter(InternalValueMap()); } + RenderContext(const RenderContext& other) + : m_externalScope(other.m_externalScope) + , m_globalScope(other.m_globalScope) + , m_scopes(other.m_scopes) + , m_rendererCallback(other.m_rendererCallback) + , m_boundScope(other.m_boundScope) + { + m_currentScope = &m_scopes.back(); + } + InternalValueMap& EnterScope() { m_scopes.push_back(InternalValueMap()); diff --git a/src/robin_hood.h b/src/robin_hood.h new file mode 100644 index 00000000..15cdea60 --- /dev/null +++ b/src/robin_hood.h @@ -0,0 +1,2019 @@ +// ______ _____ ______ _________ +// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / +// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / +// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / +// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ +// _/_____/ +// +// robin_hood::unordered_map for C++11 +// version 3.3.2 +// https://github.com/martinus/robin-hood-hashing +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2019 Martin Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ROBIN_HOOD_H_INCLUDED +#define ROBIN_HOOD_H_INCLUDED + +// see https://semver.org/ +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 3 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 2 // for backwards-compatible bug fixes + +#include +#include +#include +#include +#include +#include +#include +#include + +// #define ROBIN_HOOD_LOG_ENABLED +#ifdef ROBIN_HOOD_LOG_ENABLED +# include +# define ROBIN_HOOD_LOG(x) std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +#else +# define ROBIN_HOOD_LOG(x) +#endif + +// #define ROBIN_HOOD_TRACE_ENABLED +#ifdef ROBIN_HOOD_TRACE_ENABLED +# include +# define ROBIN_HOOD_TRACE(x) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +#else +# define ROBIN_HOOD_TRACE(x) +#endif + +// all non-argument macros should use this facility. See +// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() + +// mark unused members with this macro +#define ROBIN_HOOD_UNUSED(identifier) + +// bitness +#if SIZE_MAX == UINT32_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 +#elif SIZE_MAX == UINT64_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 +#else +# error Unsupported bitness +#endif + +// endianess +#ifdef _WIN32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#endif + +// inline +#ifdef _WIN32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) +#endif + +// count leading/trailing bits +#ifdef _WIN32 +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept->int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + } \ + (x) +#else +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) +#endif + +// fallthrough +#ifndef __has_cpp_attribute // For backwards compatibility +# define __has_cpp_attribute(x) 0 +#endif +#if __has_cpp_attribute(clang::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() +#endif + +// likely/unlikely +#if defined(_WIN32) +# define ROBIN_HOOD_LIKELY(condition) condition +# define ROBIN_HOOD_UNLIKELY(condition) condition +#else +# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) +# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) +#else +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() +#endif + +namespace robin_hood { + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) +# define ROBIN_HOOD_STD std +#else + +// c++11 compatibility layer +namespace ROBIN_HOOD_STD { +template +struct alignment_of + : std::integral_constant::type)> {}; + +template +class integer_sequence { +public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } +}; +template +using index_sequence = integer_sequence; + +namespace detail_ { +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; +} // namespace detail_ + +template +using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = make_index_sequence; + +} // namespace ROBIN_HOOD_STD + +#endif + +namespace detail { + +// umul +#if defined(__SIZEOF_INT128__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1 +# if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +using uint128_t = unsigned __int128; +# pragma GCC diagnostic pop +# endif +inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept { + auto result = static_cast(a) * static_cast(b); + *high = static_cast(result >> 64U); + return static_cast(result); +} +#elif (defined(_WIN32) && ROBIN_HOOD(BITNESS) == 64) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1 +# include // for __umulh +# pragma intrinsic(__umulh) +# ifndef _M_ARM64 +# pragma intrinsic(_umul128) +# endif +inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept { +# ifdef _M_ARM64 + *high = __umulh(a, b); + return ((uint64_t)(a)) * (b); +# else + return _umul128(a, b, high); +# endif +} +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 0 +#endif + +// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to +// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with +// care! +template +inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { + return reinterpret_cast(ptr); +} + +template +inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { + return reinterpret_cast(ptr); +} + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +template +ROBIN_HOOD(NOINLINE) +void doThrow(Args&&... args) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + throw E(std::forward(args)...); +} + +template +T* assertNotNull(T* t, Args&&... args) { + if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { + doThrow(std::forward(args)...); + } + return t; +} + +template +inline T unaligned_load(void const* ptr) noexcept { + // using memcpy so we don't get into unaligned load problems. + // compiler should optimize this very well anyways. + T t; + std::memcpy(&t, ptr, sizeof(T)); + return t; +} + +// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, +// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a +// pointer. +template +class BulkPoolAllocator { +public: + BulkPoolAllocator() noexcept = default; + + // does not copy anything, just creates a new allocator. + BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept + : mHead(nullptr) + , mListForFree(nullptr) {} + + BulkPoolAllocator(BulkPoolAllocator&& o) noexcept + : mHead(o.mHead) + , mListForFree(o.mListForFree) { + o.mListForFree = nullptr; + o.mHead = nullptr; + } + + BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { + reset(); + mHead = o.mHead; + mListForFree = o.mListForFree; + o.mListForFree = nullptr; + o.mHead = nullptr; + return *this; + } + + BulkPoolAllocator& + operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { + // does not do anything + return *this; + } + + ~BulkPoolAllocator() noexcept { + reset(); + } + + // Deallocates all allocated memory. + void reset() noexcept { + while (mListForFree) { + T* tmp = *mListForFree; + free(mListForFree); + mListForFree = reinterpret_cast_no_cast_align_warning(tmp); + } + mHead = nullptr; + } + + // allocates, but does NOT initialize. Use in-place new constructor, e.g. + // T* obj = pool.allocate(); + // ::new (static_cast(obj)) T(); + T* allocate() { + T* tmp = mHead; + if (!tmp) { + tmp = performAllocation(); + } + + mHead = *reinterpret_cast_no_cast_align_warning(tmp); + return tmp; + } + + // does not actually deallocate but puts it in store. + // make sure you have already called the destructor! e.g. with + // obj->~T(); + // pool.deallocate(obj); + void deallocate(T* obj) noexcept { + *reinterpret_cast_no_cast_align_warning(obj) = mHead; + mHead = obj; + } + + // Adds an already allocated block of memory to the allocator. This allocator is from now on + // responsible for freeing the data (with free()). If the provided data is not large enough to + // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. + void addOrFree(void* ptr, const size_t numBytes) noexcept { + // calculate number of available elements in ptr + if (numBytes < ALIGNMENT + ALIGNED_SIZE) { + // not enough data for at least one element. Free and return. + free(ptr); + } else { + add(ptr, numBytes); + } + } + + void swap(BulkPoolAllocator& other) noexcept { + using std::swap; + swap(mHead, other.mHead); + swap(mListForFree, other.mListForFree); + } + +private: + // iterates the list of allocated memory to calculate how many to alloc next. + // Recalculating this each time saves us a size_t member. + // This ignores the fact that memory blocks might have been added manually with addOrFree. In + // practice, this should not matter much. + ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { + auto tmp = mListForFree; + size_t numAllocs = MinNumAllocs; + + while (numAllocs * 2 <= MaxNumAllocs && tmp) { + auto x = reinterpret_cast(tmp); + tmp = *x; + numAllocs *= 2; + } + + return numAllocs; + } + + // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). + void add(void* ptr, const size_t numBytes) noexcept { + const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; + + auto data = reinterpret_cast(ptr); + + // link free list + auto x = reinterpret_cast(data); + *x = mListForFree; + mListForFree = data; + + // create linked list for newly allocated data + auto const headT = + reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); + + auto const head = reinterpret_cast(headT); + + // Visual Studio compiler automatically unrolls this loop, which is pretty cool + for (size_t i = 0; i < numElements; ++i) { + *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = + head + (i + 1) * ALIGNED_SIZE; + } + + // last one points to 0 + *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = + mHead; + mHead = headT; + } + + // Called when no memory is available (mHead == 0). + // Don't inline this slow path. + ROBIN_HOOD(NOINLINE) T* performAllocation() { + size_t const numElementsToAlloc = calcNumElementsToAlloc(); + + // alloc new memory: [prev |T, T, ... T] + // std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl; + size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; + add(assertNotNull(malloc(bytes)), bytes); + return mHead; + } + + // enforce byte alignment of the T's +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) + static constexpr size_t ALIGNMENT = + (std::max)(std::alignment_of::value, std::alignment_of::value); +#else + static const size_t ALIGNMENT = + (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) + ? ROBIN_HOOD_STD::alignment_of::value + : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround +#endif + + static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; + + static_assert(MinNumAllocs >= 1, "MinNumAllocs"); + static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); + static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); + static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); + static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); + + T* mHead{nullptr}; + T** mListForFree{nullptr}; +}; + +template +struct NodeAllocator; + +// dummy allocator that does nothing +template +struct NodeAllocator { + + // we are not using the data, so just free it. + void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { + free(ptr); + } +}; + +template +struct NodeAllocator : public BulkPoolAllocator {}; + +// dummy hash, unsed as mixer when robin_hood::hash is already used +template +struct identity_hash { + constexpr size_t operator()(T const& obj) const noexcept { + return static_cast(obj); + } +}; + +// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making +// my own here. +namespace swappable { +using std::swap; +template +struct nothrow { + static const bool value = noexcept(swap(std::declval(), std::declval())); +}; + +} // namespace swappable + +} // namespace detail + +struct is_transparent_tag {}; + +// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, +// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is +// also tested. +template +struct pair { + using first_type = T1; + using second_type = T2; + + template ::value && + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(T1()) && noexcept(T2())) + : first() + , second() {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair const& o) noexcept( + noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) + : first(o.first) + , second(o.second) {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair&& o) noexcept( + noexcept(T1(std::move(std::declval()))) && + noexcept(T2(std::move(std::declval())))) + : first(std::move(o.first)) + , second(std::move(o.second)) {} + + constexpr pair(T1&& a, T2&& b) noexcept(noexcept(T1(std::move(std::declval()))) && + noexcept(T2(std::move(std::declval())))) + : first(std::move(a)) + , second(std::move(b)) {} + + template + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward(std::declval()))) && + noexcept(T2(std::forward(std::declval())))) + : first(std::forward(a)) + , second(std::forward(b)) {} + + template + constexpr pair( + std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) + : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()) {} + + // constructor called from the std::piecewise_construct_t ctor + template + pair(std::tuple& a, std::tuple& b, + ROBIN_HOOD_STD::index_sequence /*unused*/, + ROBIN_HOOD_STD::index_sequence< + I2...> /*unused*/) noexcept(noexcept(T1(std:: + forward(std::get( + std::declval< + std::tuple&>()))...)) && + noexcept(T2(std::forward( + std::get(std::declval&>()))...))) + : first(std::forward(std::get(a))...) + , second(std::forward(std::get(b))...) { + // make visual studio compiler happy about warning about unused a & b. + // Visual studio's pair implementation disables warning 4100. + (void)a; + (void)b; + } + + ROBIN_HOOD(NODISCARD) first_type& getFirst() noexcept { + return first; + } + ROBIN_HOOD(NODISCARD) first_type const& getFirst() const noexcept { + return first; + } + ROBIN_HOOD(NODISCARD) second_type& getSecond() noexcept { + return second; + } + ROBIN_HOOD(NODISCARD) second_type const& getSecond() const noexcept { + return second; + } + + void swap(pair& o) noexcept((detail::swappable::nothrow::value) && + (detail::swappable::nothrow::value)) { + using std::swap; + swap(first, o.first); + swap(second, o.second); + } + + T1 first; // NOLINT(misc-non-private-member-variables-in-classes) + T2 second; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +template +void swap(pair& a, pair& b) noexcept( + noexcept(std::declval&>().swap(std::declval&>()))) { + a.swap(b); +} + +// Hash an arbitrary amount of bytes. This is basically Murmur2 hash without caring about big +// endianness. TODO(martinus) add a fallback for very large strings? +static size_t hash_bytes(void const* ptr, size_t const len) noexcept { + static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + static constexpr uint64_t seed = UINT64_C(0xe17a1465); + static constexpr unsigned int r = 47; + + auto const data64 = static_cast(ptr); + uint64_t h = seed ^ (len * m); + + size_t const n_blocks = len / 8; + for (size_t i = 0; i < n_blocks; ++i) { + auto k = detail::unaligned_load(data64 + i); + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + auto const data8 = reinterpret_cast(data64 + n_blocks); + switch (len & 7U) { + case 7: + h ^= static_cast(data8[6]) << 48U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 6: + h ^= static_cast(data8[5]) << 40U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 5: + h ^= static_cast(data8[4]) << 32U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 4: + h ^= static_cast(data8[3]) << 24U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 3: + h ^= static_cast(data8[2]) << 16U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 2: + h ^= static_cast(data8[1]) << 8U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 1: + h ^= static_cast(data8[0]); + h *= m; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + default: + break; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + return static_cast(h); +} + +inline size_t hash_int(uint64_t obj) noexcept { +#if ROBIN_HOOD(HAS_UMUL128) + // 167079903232 masksum, 120428523 ops best: 0xde5fb9d2630458e9 + static constexpr uint64_t k = UINT64_C(0xde5fb9d2630458e9); + uint64_t h; + uint64_t l = detail::umul128(obj, k, &h); + return h + l; +#elif ROBIN_HOOD(BITNESS) == 32 + uint64_t const r = obj * UINT64_C(0xca4bcaa75ec3f625); + auto h = static_cast(r >> 32U); + auto l = static_cast(r); + return h + l; +#else + // murmurhash 3 finalizer + uint64_t h = obj; + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + h ^= h >> 33; + return static_cast(h); +#endif +} + +// A thin wrapper around std::hash, performing an additional simple mixing step of the result. +template +struct hash : public std::hash { + size_t operator()(T const& obj) const + noexcept(noexcept(std::declval>().operator()(std::declval()))) { + // call base hash + auto result = std::hash::operator()(obj); + // return mixed of that, to be save against identity has + return hash_int(static_cast(result)); + } +}; + +template <> +struct hash { + size_t operator()(std::string const& str) const noexcept { + return hash_bytes(str.data(), str.size()); + } +}; + +template +struct hash { + size_t operator()(T* ptr) const noexcept { + return hash_int(reinterpret_cast(ptr)); + } +}; + +#define ROBIN_HOOD_HASH_INT(T) \ + template <> \ + struct hash { \ + size_t operator()(T obj) const noexcept { \ + return hash_int(static_cast(obj)); \ + } \ + } + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif +// see https://en.cppreference.com/w/cpp/utility/hash +ROBIN_HOOD_HASH_INT(bool); +ROBIN_HOOD_HASH_INT(char); +ROBIN_HOOD_HASH_INT(signed char); +ROBIN_HOOD_HASH_INT(unsigned char); +ROBIN_HOOD_HASH_INT(char16_t); +ROBIN_HOOD_HASH_INT(char32_t); +ROBIN_HOOD_HASH_INT(wchar_t); +ROBIN_HOOD_HASH_INT(short); +ROBIN_HOOD_HASH_INT(unsigned short); +ROBIN_HOOD_HASH_INT(int); +ROBIN_HOOD_HASH_INT(unsigned int); +ROBIN_HOOD_HASH_INT(long); +ROBIN_HOOD_HASH_INT(long long); +ROBIN_HOOD_HASH_INT(unsigned long); +ROBIN_HOOD_HASH_INT(unsigned long long); +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif +namespace detail { + +// A highly optimized hashmap implementation, using the Robin Hood algorithm. +// +// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but be +// about 2x faster in most cases and require much less allocations. +// +// This implementation uses the following memory layout: +// +// [Node, Node, ... Node | info, info, ... infoSentinel ] +// +// * Node: either a DataNode that directly has the std::pair as member, +// or a DataNode with a pointer to std::pair. Which DataNode representation to use +// depends on how fast the swap() operation is. Heuristically, this is automatically choosen based +// on sizeof(). there are always 2^n Nodes. +// +// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. +// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the +// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it +// actually belongs to the previous position and was pushed out because that place is already +// taken. +// +// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the need +// for a idx +// variable. +// +// According to STL, order of templates has effect on throughput. That's why I've moved the boolean +// to the front. +// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ +template +class unordered_map + : public Hash, + public KeyEqual, + detail::NodeAllocator< + robin_hood::pair::type, T>, 4, 16384, + IsFlatMap> { +public: + using key_type = Key; + using mapped_type = T; + using value_type = + robin_hood::pair::type, T>; + using size_type = size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using Self = + unordered_map; + static constexpr bool is_flat_map = IsFlatMap; + +private: + static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, + "MaxLoadFactor100 needs to be >10 && < 100"); + + // configuration defaults + + // make sure we have 8 elements, needed to quickly rehash mInfo + static constexpr size_t InitialNumElements = sizeof(uint64_t); + static constexpr uint32_t InitialInfoNumBits = 5; + static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; + static constexpr uint8_t InitialInfoHashShift = sizeof(size_t) * 8 - InitialInfoNumBits; + using DataPool = detail::NodeAllocator; + + // type needs to be wider than uint8_t. + using InfoType = uint32_t; + + // DataNode //////////////////////////////////////////////////////// + + // Primary template for the data node. We have special implementations for small and big + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these on + // the heap so swap merely swaps a pointer. + template + class DataNode {}; + + // Small: just allocate on the stack. + template + class DataNode final { + public: + template + explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( + noexcept(value_type(std::forward(args)...))) + : mData(std::forward(args)...) {} + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( + std::is_nothrow_move_constructible::value) + : mData(std::move(n.mData)) {} + + // doesn't do anything + void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} + void destroyDoNotDeallocate() noexcept {} + + value_type const* operator->() const noexcept { + return &mData; + } + value_type* operator->() noexcept { + return &mData; + } + + const value_type& operator*() const noexcept { + return mData; + } + + value_type& operator*() noexcept { + return mData; + } + + ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() noexcept { + return mData.first; + } + + ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const noexcept { + return mData.first; + } + + ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() noexcept { + return mData.second; + } + + ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const noexcept { + return mData.second; + } + + void swap(DataNode& o) noexcept( + noexcept(std::declval().swap(std::declval()))) { + mData.swap(o.mData); + } + + private: + value_type mData; + }; + + // big object: allocate on heap. + template + class DataNode { + public: + template + explicit DataNode(M& map, Args&&... args) + : mData(map.allocate()) { + ::new (static_cast(mData)) value_type(std::forward(args)...); + } + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept + : mData(std::move(n.mData)) {} + + void destroy(M& map) noexcept { + // don't deallocate, just put it into list of datapool. + mData->~value_type(); + map.deallocate(mData); + } + + void destroyDoNotDeallocate() noexcept { + mData->~value_type(); + } + + value_type const* operator->() const noexcept { + return mData; + } + + value_type* operator->() noexcept { + return mData; + } + + const value_type& operator*() const { + return *mData; + } + + value_type& operator*() { + return *mData; + } + + ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() { + return mData->first; + } + + ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const { + return mData->first; + } + + ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() { + return mData->second; + } + + ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const { + return mData->second; + } + + void swap(DataNode& o) noexcept { + using std::swap; + swap(mData, o.mData); + } + + private: + value_type* mData; + }; + + using Node = DataNode; + + // Cloner ////////////////////////////////////////////////////////// + + template + struct Cloner; + + // fast path: Just copy data, without allocating anything. + template + struct Cloner { + void operator()(M const& source, M& target) const { + // std::memcpy(target.mKeyVals, source.mKeyVals, + // target.calcNumBytesTotal(target.mMask + 1)); + auto src = reinterpret_cast(source.mKeyVals); + auto tgt = reinterpret_cast(target.mKeyVals); + std::copy(src, src + target.calcNumBytesTotal(target.mMask + 1), tgt); + } + }; + + template + struct Cloner { + void operator()(M const& s, M& t) const { + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(t.mMask + 1), t.mInfo); + + for (size_t i = 0; i < t.mMask + 1; ++i) { + if (t.mInfo[i]) { + ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); + } + } + } + }; + + // Destroyer /////////////////////////////////////////////////////// + + template + struct Destroyer {}; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + } + }; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + for (size_t idx = 0; idx <= m.mMask; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroy(m); + n.~Node(); + } + } + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + for (size_t idx = 0; idx <= m.mMask; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroyDoNotDeallocate(); + n.~Node(); + } + } + } + }; + + // Iter //////////////////////////////////////////////////////////// + + struct fast_forward_tag {}; + + // generic iterator for both const_iterator and iterator. + template + // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) + class Iter { + private: + using NodePtr = typename std::conditional::type; + + public: + using difference_type = std::ptrdiff_t; + using value_type = typename Self::value_type; + using reference = typename std::conditional::type; + using pointer = typename std::conditional::type; + using iterator_category = std::forward_iterator_tag; + + // default constructed iterator can be compared to itself, but WON'T return true when + // compared to end(). + Iter() = default; + + // Rule of zero: nothing specified. The conversion constructor is only enabled for iterator + // to const_iterator, so it doesn't accidentally work as a copy ctor. + + // Conversion constructor from iterator to const_iterator. + template ::type> + // NOLINTNEXTLINE(hicpp-explicit-conversions) + Iter(Iter const& other) noexcept + : mKeyVals(other.mKeyVals) + , mInfo(other.mInfo) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr, + fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) { + fastForward(); + } + + template ::type> + Iter& operator=(Iter const& other) noexcept { + mKeyVals = other.mKeyVals; + mInfo = other.mInfo; + return *this; + } + + // prefix increment. Undefined behavior if we are at end()! + Iter& operator++() noexcept { + mInfo++; + mKeyVals++; + fastForward(); + return *this; + } + + reference operator*() const { + return **mKeyVals; + } + + pointer operator->() const { + return &**mKeyVals; + } + + template + bool operator==(Iter const& o) const noexcept { + return mKeyVals == o.mKeyVals; + } + + template + bool operator!=(Iter const& o) const noexcept { + return mKeyVals != o.mKeyVals; + } + + private: + // fast forward to the next non-free info byte + void fastForward() noexcept { + int inc; + do { + auto const n = detail::unaligned_load(mInfo); +#if ROBIN_HOOD(LITTLE_ENDIAN) + inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +#else + inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +#endif + mInfo += inc; + mKeyVals += inc; + } while (inc == static_cast(sizeof(size_t))); + } + + friend class unordered_map; + NodePtr mKeyVals{nullptr}; + uint8_t const* mInfo{nullptr}; + }; + + //////////////////////////////////////////////////////////////////// + + // highly performance relevant code. + // Lower bits are used for indexing into the array (2^n size) + // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. + template + void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { + // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as an + // additional mixing step. This serves as a bad hash prevention, if the given data is badly + // mixed. + using Mix = + typename std::conditional, hasher>::value, + ::robin_hood::detail::identity_hash, + ::robin_hood::hash>::type; + *idx = Mix{}(Hash::operator()(key)); + + *info = mInfoInc + static_cast(*idx >> mInfoHashShift); + *idx &= mMask; + } + + // forwards the index by one, wrapping around at the end + void next(InfoType* info, size_t* idx) const noexcept { + *idx = (*idx + 1) & mMask; + *info += mInfoInc; + } + + void nextWhileLess(InfoType* info, size_t* idx) const noexcept { + // unrolling this by hand did not bring any speedups. + while (*info < mInfo[*idx]) { + next(info, idx); + } + } + + // Shift everything up by one element. Tries to move stuff around. + // True if some shifting has occured (entry under idx is a constructed object) + // Fals if no shift has occured (entry under idx is unconstructed memory) + void + shiftUp(size_t idx, + size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + while (idx != insertion_idx) { + size_t prev_idx = (idx - 1) & mMask; + if (mInfo[idx]) { + mKeyVals[idx] = std::move(mKeyVals[prev_idx]); + } else { + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[prev_idx])); + } + mInfo[idx] = static_cast(mInfo[prev_idx] + mInfoInc); + if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + idx = prev_idx; + } + } + + void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { + // until we find one that is either empty or has zero offset. + // TODO(martinus) we don't need to move everything, just the last one for the same bucket. + mKeyVals[idx].destroy(*this); + + // until we find one that is either empty or has zero offset. + size_t nextIdx = (idx + 1) & mMask; + while (mInfo[nextIdx] >= 2 * mInfoInc) { + mInfo[idx] = static_cast(mInfo[nextIdx] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[nextIdx]); + idx = nextIdx; + nextIdx = (idx + 1) & mMask; + } + + mInfo[idx] = 0; + // don't destroy, we've moved it + // mKeyVals[idx].destroy(*this); + mKeyVals[idx].~Node(); + } + + // copy of find(), except that it returns iterator instead of const_iterator. + template + ROBIN_HOOD(NODISCARD) + size_t findIdx(Other const& key) const { + size_t idx; + InfoType info; + keyToIdx(key, &idx, &info); + + do { + // unrolling this twice gives a bit of a speedup. More unrolling did not help. + if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + return idx; + } + next(&info, &idx); + if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + return idx; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found! + return mMask == 0 ? 0 : mMask + 1; + } + + void cloneData(const unordered_map& o) { + Cloner()(o, *this); + } + + // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. + // @return index where the element was created + size_t insert_move(Node&& keyval) { + // we don't retry, fail if overflowing + // don't need to check max num elements + if (0 == mMaxNumElementsAllowed && !try_increase_info()) { + throwOverflowError(); + } + + size_t idx; + InfoType info; + keyToIdx(keyval.getFirst(), &idx, &info); + + // skip forward. Use <= because we are certain that the element is not there. + while (info <= mInfo[idx]) { + idx = (idx + 1) & mMask; + info += mInfoInc; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = static_cast(info); + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(std::move(keyval)); + } else { + shiftUp(idx, insertion_idx); + l = std::move(keyval); + } + + // put at empty spot + mInfo[insertion_idx] = insertion_info; + + ++mNumElements; + return insertion_idx; + } + +public: + using iterator = Iter; + using const_iterator = Iter; + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. This + // tremendously speeds up ctor & dtor of a map that never receives an element. The penalty is + // payed at the first insert, and not before. Lookup of this empty map works because everybody + // points to DummyInfoByte::b. parameter bucket_count is dictated by the standard, but we can + // ignore it. + explicit unordered_map(size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && + noexcept(KeyEqual(equal))) + : Hash(h) + , KeyEqual(equal) { + ROBIN_HOOD_TRACE(this); + } + + template + unordered_map(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : Hash(h) + , KeyEqual(equal) { + ROBIN_HOOD_TRACE(this); + insert(first, last); + } + + unordered_map(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : Hash(h) + , KeyEqual(equal) { + ROBIN_HOOD_TRACE(this); + insert(initlist.begin(), initlist.end()); + } + + unordered_map(unordered_map&& o) noexcept + : Hash(std::move(static_cast(o))) + , KeyEqual(std::move(static_cast(o))) + , DataPool(std::move(static_cast(o))) { + ROBIN_HOOD_TRACE(this); + if (o.mMask) { + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + // set other's mask to 0 so its destructor won't do anything + o.init(); + } + } + + unordered_map& operator=(unordered_map&& o) noexcept { + ROBIN_HOOD_TRACE(this); + if (&o != this) { + if (o.mMask) { + // only move stuff if the other map actually has some data + destroy(); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + Hash::operator=(std::move(static_cast(o))); + KeyEqual::operator=(std::move(static_cast(o))); + DataPool::operator=(std::move(static_cast(o))); + + o.init(); + + } else { + // nothing in the other map => just clear us. + clear(); + } + } + return *this; + } + + unordered_map(const unordered_map& o) + : Hash(static_cast(o)) + , KeyEqual(static_cast(o)) + , DataPool(static_cast(o)) { + ROBIN_HOOD_TRACE(this); + if (!o.empty()) { + // not empty: create an exact copy. it is also possible to just iterate through all + // elements and insert them, but copying is probably faster. + + mKeyVals = static_cast( + detail::assertNotNull(malloc(calcNumBytesTotal(o.mMask + 1)))); + // no need for calloc because clonData does memcpy + mInfo = reinterpret_cast(mKeyVals + o.mMask + 1); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + } + } + + // Creates a copy of the given map. Copy constructor of each entry is used. + unordered_map& operator=(unordered_map const& o) { + ROBIN_HOOD_TRACE(this); + if (&o == this) { + // prevent assigning of itself + return *this; + } + + // we keep using the old allocator and not assign the new one, because we want to keep the + // memory available. when it is the same size. + if (o.empty()) { + if (0 == mMask) { + // nothing to do, we are empty too + return *this; + } + + // not empty: destroy what we have there + // clear also resets mInfo to 0, that's sometimes not necessary. + destroy(); + init(); + Hash::operator=(static_cast(o)); + KeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + + return *this; + } + + // clean up old stuff + Destroyer::value>{}.nodes(*this); + + if (mMask != o.mMask) { + // no luck: we don't have the same array size allocated, so we need to realloc. + if (0 != mMask) { + // only deallocate if we actually have data! + free(mKeyVals); + } + + mKeyVals = static_cast( + detail::assertNotNull(malloc(calcNumBytesTotal(o.mMask + 1)))); + + // no need for calloc here because cloneData performs a memcpy. + mInfo = reinterpret_cast(mKeyVals + o.mMask + 1); + // sentinel is set in cloneData + } + Hash::operator=(static_cast(o)); + KeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + + return *this; + } + + // Swaps everything between the two maps. + void swap(unordered_map& o) { + ROBIN_HOOD_TRACE(this); + using std::swap; + swap(o, *this); + } + + // Clears all data, without resizing. + void clear() { + ROBIN_HOOD_TRACE(this); + if (empty()) { + // don't do anything! also important because we don't want to write to DummyInfoByte::b, + // even though we would just write 0 to it. + return; + } + + Destroyer::value>{}.nodes(*this); + + // clear everything except the sentinel + // std::memset(mInfo, 0, sizeof(uint8_t) * (mMask + 1)); + uint8_t const z = 0; + std::fill(mInfo, mInfo + (sizeof(uint8_t) * (mMask + 1)), z); + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // Destroys the map and all it's contents. + ~unordered_map() { + ROBIN_HOOD_TRACE(this); + destroy(); + } + + // Checks if both maps contain the same entries. Order is irrelevant. + bool operator==(const unordered_map& other) const { + ROBIN_HOOD_TRACE(this); + if (other.size() != size()) { + return false; + } + for (auto const& otherEntry : other) { + auto const myIt = find(otherEntry.first); + if (myIt == end() || !(myIt->second == otherEntry.second)) { + return false; + } + } + + return true; + } + + bool operator!=(const unordered_map& other) const { + ROBIN_HOOD_TRACE(this); + return !operator==(other); + } + + mapped_type& operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this); + return doCreateByKey(key); + } + + mapped_type& operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this); + return doCreateByKey(std::move(key)); + } + + template + void insert(Iter first, Iter last) { + for (; first != last; ++first) { + // value_type ctor needed because this might be called with std::pair's + insert(value_type(*first)); + } + } + + template + std::pair emplace(Args&&... args) { + ROBIN_HOOD_TRACE(this); + Node n{*this, std::forward(args)...}; + auto r = doInsert(std::move(n)); + if (!r.second) { + // insertion not possible: destroy node + // NOLINTNEXTLINE(bugprone-use-after-move) + n.destroy(*this); + } + return r; + } + + std::pair insert(const value_type& keyval) { + ROBIN_HOOD_TRACE(this); + return doInsert(keyval); + } + + std::pair insert(value_type&& keyval) { + return doInsert(std::move(keyval)); + } + + // Returns 1 if key is found, 0 otherwise. + size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + mapped_type& at(key_type const& key) { + ROBIN_HOOD_TRACE(this); + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + mapped_type const& at(key_type const& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { + ROBIN_HOOD_TRACE(this); + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator find(const key_type& key) { + ROBIN_HOOD_TRACE(this); + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { + ROBIN_HOOD_TRACE(this); + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator begin() { + ROBIN_HOOD_TRACE(this); + if (empty()) { + return end(); + } + return iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + const_iterator begin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return cbegin(); + } + const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + if (empty()) { + return cend(); + } + return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + + iterator end() { + ROBIN_HOOD_TRACE(this); + // no need to supply valid info pointer: end() must not be dereferenced, and only node + // pointer is compared. + return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + const_iterator end() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return cend(); + } + const_iterator cend() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + + iterator erase(const_iterator pos) { + ROBIN_HOOD_TRACE(this); + // its safe to perform const cast here + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); + } + + // Erases element at pos, returns iterator to the next element. + iterator erase(iterator pos) { + ROBIN_HOOD_TRACE(this); + // we assume that pos always points to a valid entry, and not end(). + auto const idx = static_cast(pos.mKeyVals - mKeyVals); + + shiftDown(idx); + --mNumElements; + + if (*pos.mInfo) { + // we've backward shifted, return this again + return pos; + } + + // no backward shift, return next element + return ++pos; + } + + size_t erase(const key_type& key) { + ROBIN_HOOD_TRACE(this); + size_t idx; + InfoType info; + keyToIdx(key, &idx, &info); + + // check while info matches with the source idx + do { + if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + shiftDown(idx); + --mNumElements; + return 1; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found to delete + return 0; + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // exactly the same as reserve(c). + void rehash(size_t c) { + reserve(c); + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // Exactly the same as resize(c). Use resize(0) to shrink to fit. + void reserve(size_t c) { + ROBIN_HOOD_TRACE(this); + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + rehashPowerOfTwo(newSize); + } + + size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return mNumElements; + } + + size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return static_cast(-1); + } + + ROBIN_HOOD(NODISCARD) bool empty() const noexcept { + ROBIN_HOOD_TRACE(this); + return 0 == mNumElements; + } + + float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return MaxLoadFactor100 / 100.0F; + } + + // Average number of elements per bucket. Since we allow only 1 per bucket + float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this); + return static_cast(size()) / static_cast(mMask + 1); + } + + ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { + ROBIN_HOOD_TRACE(this); + return mMask; + } + + ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { + if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { + return maxElements * MaxLoadFactor100 / 100; + } + + // we might be a bit inprecise, but since maxElements is quite large that doesn't matter + return (maxElements / 100) * MaxLoadFactor100; + } + + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const { + return numElements + sizeof(uint64_t); + } + + // calculation ony allowed for 2^n values + ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { +#if ROBIN_HOOD(BITNESS) == 64 + return numElements * sizeof(Node) + calcNumBytesInfo(numElements); +#else + // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. + auto const ne = static_cast(numElements); + auto const s = static_cast(sizeof(Node)); + auto const infos = static_cast(calcNumBytesInfo(numElements)); + + auto const total64 = ne * s + infos; + auto const total = static_cast(total64); + + if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { + throwOverflowError(); + } + return total; +#endif + } + +private: + // reserves space for at least the specified number of elements. + // only works if numBuckets if power of two + void rehashPowerOfTwo(size_t numBuckets) { + ROBIN_HOOD_TRACE(this); + + Node* const oldKeyVals = mKeyVals; + uint8_t const* const oldInfo = mInfo; + + const size_t oldMaxElements = mMask + 1; + + // resize operation: move stuff + init_data(numBuckets); + if (oldMaxElements > 1) { + for (size_t i = 0; i < oldMaxElements; ++i) { + if (oldInfo[i] != 0) { + insert_move(std::move(oldKeyVals[i])); + // destroy the node but DON'T destroy the data. + oldKeyVals[i].~Node(); + } + } + + // don't destroy old data: put it into the pool instead + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElements)); + } + } + + ROBIN_HOOD(NOINLINE) void throwOverflowError() const { + throw std::overflow_error("robin_hood::map overflow"); + } + + void init_data(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + + // calloc also zeroes everything + mKeyVals = reinterpret_cast( + detail::assertNotNull(calloc(1, calcNumBytesTotal(max_elements)))); + mInfo = reinterpret_cast(mKeyVals + max_elements); + + // set sentinel + mInfo[max_elements] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + template + mapped_type& doCreateByKey(Arg&& key) { + while (true) { + size_t idx; + InfoType info; + keyToIdx(key, &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match. Can't do a do-while here because when mInfo is 0 + // we don't want to skip forward + while (info == mInfo[idx]) { + if (KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + // key already exists, do not insert. + return mKeyVals[idx].getSecond(); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + // put at empty spot. This forwards all arguments into the node where the object is + // constructed exactly where it is needed. + ::new (static_cast(&l)) + Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } + + // mKeyVals[idx].getFirst() = std::move(key); + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return mKeyVals[insertion_idx].getSecond(); + } + } + + // This is exactly the same code as operator[], except for the return values + template + std::pair doInsert(Arg&& keyval) { + while (true) { + size_t idx; + InfoType info; + keyToIdx(keyval.getFirst(), &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match + while (info == mInfo[idx]) { + if (KeyEqual::operator()(keyval.getFirst(), mKeyVals[idx].getFirst())) { + // key already exists, do NOT insert. + // see http://en.cppreference.com/w/cpp/container/unordered_map/insert + return std::make_pair(iterator(mKeyVals + idx, mInfo + idx), + false); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(*this, std::forward(keyval)); + } else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::forward(keyval)); + } + + // put at empty spot + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true); + } + } + + bool try_increase_info() { + ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements + << ", maxNumElementsAllowed=" + << calcMaxNumElementsAllowed(mMask + 1)); + if (mInfoInc <= 2) { + // need to be > 2 so that shift works (otherwise undefined behavior!) + return false; + } + // we got space left, try to make info smaller + mInfoInc = static_cast(mInfoInc >> 1U); + + // remove one bit of the hash, leaving more space for the distance info. + // This is extremely fast because we can operate on 8 bytes at once. + ++mInfoHashShift; + auto const data = reinterpret_cast_no_cast_align_warning(mInfo); + auto const numEntries = (mMask + 1) / 8; + + for (size_t i = 0; i < numEntries; ++i) { + data[i] = (data[i] >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + } + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + return true; + } + + void increase_size() { + // nothing allocated yet? just allocate InitialNumElements + if (0 == mMask) { + init_data(InitialNumElements); + return; + } + + auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + if (mNumElements < maxNumElementsAllowed && try_increase_info()) { + return; + } + + ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" + << maxNumElementsAllowed << ", load=" + << (static_cast(mNumElements) * 100.0 / + (static_cast(mMask) + 1))); + // it seems we have a really bad hash function! don't try to resize again + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { + throwOverflowError(); + } + + rehashPowerOfTwo((mMask + 1) * 2); + } + + void destroy() { + if (0 == mMask) { + // don't deallocate! + return; + } + + Destroyer::value>{} + .nodesDoNotDeallocate(*this); + free(mKeyVals); + } + + void init() noexcept { + mKeyVals = reinterpret_cast(&mMask); + mInfo = reinterpret_cast(&mMask); + mNumElements = 0; + mMask = 0; + mMaxNumElementsAllowed = 0; + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // members are sorted so no padding occurs + Node* mKeyVals = reinterpret_cast(&mMask); // 8 byte 8 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 16 + size_t mNumElements = 0; // 8 byte 24 + size_t mMask = 0; // 8 byte 32 + size_t mMaxNumElementsAllowed = 0; // 8 byte 40 + InfoType mInfoInc = InitialInfoInc; // 4 byte 44 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48 + // 16 byte 56 if NodeAllocator +}; + +} // namespace detail + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_flat_map = detail::unordered_map; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_node_map = detail::unordered_map; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_map = + detail::unordered_map) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + +} // namespace robin_hood + +#endif diff --git a/src/statements.cpp b/src/statements.cpp index aa595cae..4df184ea 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -16,18 +16,18 @@ void ForStatement::Render(OutStream& os, RenderContext& values) { InternalValue loopVal = m_value->Evaluate(values); - RenderLoop(loopVal, os, values); + RenderLoop(loopVal, os, values, 0); } -void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, RenderContext& values) -{ +void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, + RenderContext &values, int level) { auto& context = values.EnterScope(); InternalValueMap loopVar; context["loop"s] = CreateMapAdapter(&loopVar); if (m_isRecursive) { - loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this](const CallParams& params, OutStream& stream, RenderContext& context) { + loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this, level](const CallParams& params, OutStream& stream, RenderContext& context) { bool isSucceeded = false; auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded); if (!isSucceeded) @@ -37,8 +37,10 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende if (!var) return; - RenderLoop(var->Evaluate(context), stream, context); + RenderLoop(var->Evaluate(context), stream, context, level + 1); }); + loopVar["depth"] = static_cast(level + 1); + loopVar["depth0"] = static_cast(level); } bool isConverted = false; @@ -138,7 +140,9 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende else context[m_vars[0]] = curValue; + values.EnterScope(); m_mainBody->Render(os, values); + values.ExitScope(); } if (!loopRendered && m_elseBody) @@ -437,7 +441,19 @@ class IncludedTemplateRenderer : public RendererBase void Render(OutStream& os, RenderContext& values) override { RenderContext innerContext = values.Clone(m_withContext); + if (m_withContext) + innerContext.EnterScope(); + m_template->GetRenderer()->Render(os, innerContext); + if (m_withContext) + { + auto& innerScope = innerContext.GetCurrentScope(); + auto& scope = values.GetCurrentScope(); + for (auto& v : innerScope) + { + scope[v.first] = std::move(v.second); + } + } } private: diff --git a/src/statements.h b/src/statements.h index eaeba8c9..55aa5eec 100644 --- a/src/statements.h +++ b/src/statements.h @@ -55,7 +55,8 @@ class ForStatement : public Statement void Render(OutStream& os, RenderContext& values) override; private: - void RenderLoop(const InternalValue& val, OutStream& os, RenderContext& values); + void RenderLoop(const InternalValue &loopVal, OutStream &os, + RenderContext &values, int level); ListAdapter CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const; private: diff --git a/src/template_env.cpp b/src/template_env.cpp index 4f912312..c92f54f3 100644 --- a/src/template_env.cpp +++ b/src/template_env.cpp @@ -1,7 +1,6 @@ #include #include - namespace jinja2 { template @@ -11,44 +10,47 @@ template<> struct TemplateFunctions { using ResultType = nonstd::expected; - static Template CreateTemplate(TemplateEnv* env) - { - return Template(env); - } - static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) - { - return fs->OpenStream(fileName); - } + static Template CreateTemplate(TemplateEnv* env) { return Template(env); } + static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenStream(fileName); } }; template<> struct TemplateFunctions { using ResultType = nonstd::expected; - static TemplateW CreateTemplate(TemplateEnv* env) - { - return TemplateW(env); - } - static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) - { - return fs->OpenWStream(fileName); - } + static TemplateW CreateTemplate(TemplateEnv* env) { return TemplateW(env); } + static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenWStream(fileName); } }; -template -auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers) +template +auto TemplateEnv::LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache) { using Functions = TemplateFunctions; using ResultType = typename Functions::ResultType; - using ErrorType = typename ResultType::error_type; + using ErrorType = typename ResultType::error_type; auto tpl = Functions::CreateTemplate(env); + { + std::shared_lock l(m_guard); + auto p = cache.find(fileName); + if (p != cache.end()) + { + if (m_settings.autoReload) + { + auto lastModified = p->second.handler->GetLastModificationDate(fileName); + if (!lastModified || (p->second.lastModification && lastModified.value() <= p->second.lastModification.value())) + return ResultType(p->second.tpl); + } + else + return ResultType(p->second.tpl); + } + } + for (auto& fh : filesystemHandlers) { if (!fh.prefix.empty() && fileName.find(fh.prefix) != 0) continue; - auto stream = Functions::LoadFile(fileName, fh.handler.get()); if (stream) { @@ -56,28 +58,38 @@ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesyste if (!res) return ResultType(res.get_unexpected()); - return ResultType(tpl); + if (m_settings.cacheSize != 0) + { + auto lastModified = fh.handler->GetLastModificationDate(fileName); + std::unique_lock l(m_guard); + auto& cacheEntry = cache[fileName]; + cacheEntry.tpl = tpl; + cacheEntry.handler = fh.handler; + cacheEntry.lastModification = lastModified; + } + + 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)); + 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) { - return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers); + return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers, m_templateCache); } nonstd::expected TemplateEnv::LoadTemplateW(std::string fileName) { - return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers); + return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers, m_templateWCache); } } // jinja2 diff --git a/src/template_parser.cpp b/src/template_parser.cpp index 7ad31310..5d68a637 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -447,12 +447,16 @@ StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, auto extendsStmt = std::static_pointer_cast(extendsInfo.renderer); extendsStmt->AddBlock(std::static_pointer_cast(info.renderer)); } - else + else if (info.type == StatementInfo::ParentBlockStatement) { auto blockStmt = std::static_pointer_cast(info.renderer); blockStmt->SetMainBody(info.compositions[0]); statementsInfo.back().currentComposition->AddRenderer(info.renderer); } + else + { + return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok); + } return ParseResult(); } diff --git a/src/template_parser.h b/src/template_parser.h index ae8fce27..82a3baa2 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -348,6 +348,7 @@ class TemplateParser : public LexerHelper if (!result) { foundErrors.push_back(result.error()); + return nonstd::make_unexpected(std::move(foundErrors)); } } while (matchBegin != matchEnd); FinishCurrentLine(m_template->size()); @@ -395,7 +396,10 @@ class TemplateParser : public LexerHelper break; case RM_CommentBegin: if (m_currentBlockInfo.type != TextBlockType::RawText) - return MakeParseError(ErrorCode::UnexpectedCommentBegin, MakeToken(Token::CommentBegin, {matchStart, matchStart + 2})); + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedCommentBegin, MakeToken(Token::CommentBegin, { matchStart, matchStart + 2 })); + } FinishCurrentBlock(matchStart); m_currentBlockInfo.range.startOffset = matchStart + 2; @@ -404,7 +408,10 @@ class TemplateParser : public LexerHelper case RM_CommentEnd: if (m_currentBlockInfo.type != TextBlockType::Comment) - return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, {matchStart, matchStart + 2})); + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, { matchStart, matchStart + 2 })); + } FinishCurrentBlock(matchStart); m_currentBlockInfo.range.startOffset = matchStart + 2; @@ -414,7 +421,10 @@ class TemplateParser : public LexerHelper break; case RM_ExprEnd: if (m_currentBlockInfo.type == TextBlockType::RawText) - return MakeParseError(ErrorCode::UnexpectedExprEnd, MakeToken(Token::ExprEnd, {matchStart, matchStart + 2})); + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedExprEnd, MakeToken(Token::ExprEnd, { matchStart, matchStart + 2 })); + } else if (m_currentBlockInfo.type != TextBlockType::Expression || (*m_template)[match.position() - 1] == '\'') break; @@ -425,7 +435,10 @@ class TemplateParser : public LexerHelper break; case RM_StmtEnd: if (m_currentBlockInfo.type == TextBlockType::RawText) - return MakeParseError(ErrorCode::UnexpectedStmtEnd, MakeToken(Token::StmtEnd, {matchStart, matchStart + 2})); + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedStmtEnd, MakeToken(Token::StmtEnd, { matchStart, matchStart + 2 })); + } else if (m_currentBlockInfo.type != TextBlockType::Statement || (*m_template)[match.position() - 1] == '\'') break; @@ -681,10 +694,10 @@ class TemplateParser : public LexerHelper if (p == m_lines.end()) { - if (offset != m_lines.back().range.endOffset) + if (m_lines.empty() || offset != m_lines.back().range.endOffset) { - line = 0; - col = 0; + line = 1; + col = 1; return; } p = m_lines.end() - 1; diff --git a/test/errors_test.cpp b/test/errors_test.cpp index 513832e0..0d01fb7d 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -390,6 +390,11 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_1, 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{R"({% extends "/_layouts/default.html" } + {% block content %} + {% endblock %} + )", + "noname.j2tpl:1:37: error: Expected end of statement, got: '}'\n{% extends \"/_layouts/default.html\" }\n ---^-------"}, InputOutputPair{"{% import %}", "noname.j2tpl:1:11: error: Unexpected token: '<>'\n{% import %}\n ---^-------"}, InputOutputPair{"{% import 'foo' %}", diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index f57e4d15..015f0f2e 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -234,18 +234,19 @@ INSTANTIATE_TEST_CASE_P(IndexSubscriptionTest, ExpressionSubstitutionTest, ::tes InputOutputPair{"reflectedVal['StrValue']", ""} )); -INSTANTIATE_TEST_CASE_P(DotSubscriptionTest, ExpressionSubstitutionTest, ::testing::Values( - InputOutputPair{"mapValue.intVal", "10"}, - InputOutputPair{"mapValue.dblVal", "100.5"}, - InputOutputPair{"mapValue.stringVal", "string100.5"}, - InputOutputPair{"mapValue.boolValue", "true"}, - InputOutputPair{"mapValue.intVAl", ""}, - InputOutputPair{"reflectedVal.intValue", "0"}, - InputOutputPair{"reflectedVal.dblValue", "0"}, - InputOutputPair{"reflectedVal.boolValue", "false"}, - InputOutputPair{"reflectedVal.strValue", "test string 0"}, - InputOutputPair{"reflectedVal.StrValue", ""} - )); +INSTANTIATE_TEST_CASE_P(DotSubscriptionTest, ExpressionSubstitutionTest, ::testing::Values(InputOutputPair{ "mapValue.intVal", "10" }, + InputOutputPair{ "mapValue.dblVal", "100.5" }, + InputOutputPair{ "mapValue.stringVal", "string100.5" }, + InputOutputPair{ "mapValue.boolValue", "true" }, + InputOutputPair{ "mapValue.intVAl", "" }, + InputOutputPair{ "reflectedVal.intValue", "0" }, + InputOutputPair{ "reflectedVal.dblValue", "0" }, + InputOutputPair{ "reflectedVal.boolValue", "false" }, + InputOutputPair{ "reflectedVal.strValue", "test string 0" }, + InputOutputPair{ "reflectedVal.wstrValue", "test string 0" }, + InputOutputPair{ "reflectedVal.strViewValue", "test string 0" }, + InputOutputPair{ "reflectedVal.wstrViewValue", "test string 0" }, + InputOutputPair{ "reflectedVal.StrValue", "" })); INSTANTIATE_TEST_CASE_P(ComplexSubscriptionTest, ExpressionSubstitutionTest, ::testing::Values( diff --git a/test/filesystem_handler_test.cpp b/test/filesystem_handler_test.cpp index fa485a8f..b271d2e3 100644 --- a/test/filesystem_handler_test.cpp +++ b/test/filesystem_handler_test.cpp @@ -1,6 +1,10 @@ #include #include +#include + +#include +#include class FilesystemHandlerTest : public testing::Test { @@ -125,3 +129,166 @@ LR"(Hello World! EXPECT_TRUE((bool)test1Stream); EXPECT_EQ(test1Content, ReadFile(test1Stream)); } + +TEST_F(FilesystemHandlerTest, TestDefaultCaching) +{ + const std::string test1Content = R"( +Line1 +Line2 +Line3 +)"; + const std::string test2Content = R"( +Line6 +Line7 +Line8 +)"; + jinja2::MemoryFileSystem fs; + fs.AddFile("test1.j2tpl", test1Content); + + jinja2::TemplateEnv env; + + env.AddFilesystemHandler("", fs); + auto tpl1 = env.LoadTemplate("test1.j2tpl").value(); + EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); + + fs.AddFile("test1.j2tpl", test2Content); + auto tpl2 = env.LoadTemplate("test1.j2tpl").value(); + EXPECT_EQ(test1Content, tpl2.RenderAsString({}).value()); +} + +TEST_F(FilesystemHandlerTest, TestNoCaching) +{ + const std::string test1Content = R"( +Line1 +Line2 +Line3 +)"; + const std::string test2Content = R"( +Line6 +Line7 +Line8 +)"; + jinja2::MemoryFileSystem fs; + fs.AddFile("test1.j2tpl", test1Content); + + jinja2::TemplateEnv env; + env.GetSettings().cacheSize = 0; + + env.AddFilesystemHandler("", fs); + auto tpl1 = env.LoadTemplate("test1.j2tpl").value(); + EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); + + fs.AddFile("test1.j2tpl", test2Content); + auto tpl2 = env.LoadTemplate("test1.j2tpl").value(); + EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); +} + +TEST_F(FilesystemHandlerTest, TestDefaultRFSCaching) +{ + const std::string test1Content = R"( +Line1 +Line2 +Line3 +)"; + const std::string test2Content = R"( +Line6 +Line7 +Line8 +)"; + const std::string fileName = "test_data/cached_content.j2tpl"; + + jinja2::RealFileSystem fs; + { + std::ofstream os(fileName); + os << test1Content; + } + + jinja2::TemplateEnv env; + env.GetSettings().autoReload = false; + + env.AddFilesystemHandler("", fs); + auto tpl1 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); + + { + std::ofstream os(fileName); + os << test2Content; + } + + auto tpl2 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test1Content, tpl2.RenderAsString({}).value()); +} + +TEST_F(FilesystemHandlerTest, TestRFSCachingReload) +{ + const std::string test1Content = R"( +Line1 +Line2 +Line3 +)"; + const std::string test2Content = R"( +Line6 +Line7 +Line8 +)"; + const std::string fileName = "test_data/cached_content.j2tpl"; + + jinja2::RealFileSystem fs; + { + std::ofstream os(fileName); + os << test1Content; + } + + jinja2::TemplateEnv env; + + env.AddFilesystemHandler("", fs); + auto tpl1 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); + + std::this_thread::sleep_for(std::chrono::seconds(2)); + + { + std::ofstream os(fileName); + os << test2Content; + } + + auto tpl2 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); +} + +TEST_F(FilesystemHandlerTest, TestNoRFSCaching) +{ + const std::string test1Content = R"( +Line1 +Line2 +Line3 +)"; + const std::string test2Content = R"( +Line6 +Line7 +Line8 +)"; + const std::string fileName = "test_data/cached_content.j2tpl"; + + jinja2::RealFileSystem fs; + { + std::ofstream os(fileName); + os << test1Content; + } + + jinja2::TemplateEnv env; + env.GetSettings().cacheSize = 0; + + env.AddFilesystemHandler("", fs); + auto tpl1 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); + + { + std::ofstream os(fileName); + os << test2Content; + } + + auto tpl2 = env.LoadTemplate(fileName).value(); + EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); +} + diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 4375f069..b31a146f 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -355,8 +355,15 @@ INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values( InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true) | pprint", "['Value': 'ItemValue', 'key': 'itemName']"}, InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort(case_sensitive=true, reverse=true) | pprint", "['key': 'itemName', 'Value': 'ItemValue']"}, InputOutputPair{"simpleMapValue | dictsort | pprint", "['boolValue': true, 'dblVal': 100.5, 'intVal': 10, 'stringVal': 'string100.5']"}, - InputOutputPair{"reflectedVal | dictsort | pprint", "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': {'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'wstrValue': 'test string 0']"} - )); + InputOutputPair{ + "reflectedVal | dictsort | pprint", + "['basicCallable': , 'boolValue': false, 'dblValue': 0, 'getInnerStruct': , 'getInnerStructValue': , 'innerStruct': " + "{'strValue': 'Hello World!'}, 'innerStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, 'strValue': 'test string 0', 'strViewValue': 'test " + "string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}], 'wstrValue': 'test string 0', 'wstrViewValue': 'test string 0']" })); INSTANTIATE_TEST_CASE_P(UrlEncode, FilterGenericTest, ::testing::Values( InputOutputPair{"'Hello World' | urlencode", "Hello+World"}, diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index eba02da3..e3057377 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -48,6 +48,24 @@ a[2] = image[2]; { } +MULTISTR_TEST(ForLoopTest, InnerVarsLoop, + R"( +{% set var = 0 %} +{% for i in (0, 1, 2) %} +{% set var = var + i %} +a[{{i}}] = image[{{var}}]; +{% endfor %} +)", +//--------- + R"( +a[0] = image[0]; +a[1] = image[1]; +a[2] = image[2]; +)" +) +{ +} + MULTISTR_TEST(ForLoopTest, EmptyLoop, R"( {% for i in ints %} @@ -294,11 +312,11 @@ R"( {'name'='child3_3'} ]} ] %} -{% for i in items recursive %}{{i.name}} -> {{loop(i.children)}}{% endfor %} +{% for i in items recursive %}{{i.name}}({{ loop.depth }}-{{ loop.depth0 }}) -> {{loop(i.children)}}{% endfor %} )", //--------- R"( -root1 -> child1_1 -> child1_2 -> child1_3 -> root2 -> child2_1 -> child2_2 -> child2_3 -> root3 -> child3_1 -> child3_2 -> child3_3 -> )" +root1(1-0) -> child1_1(2-1) -> child1_2(2-1) -> child1_3(2-1) -> root2(1-0) -> child2_1(2-1) -> child2_2(2-1) -> child2_3(2-1) -> root3(1-0) -> child3_1(2-1) -> child3_2(2-1) -> child3_3(2-1) -> )" ) { } diff --git a/test/includes_test.cpp b/test/includes_test.cpp index 3f30eb7d..9048a4ae 100644 --- a/test/includes_test.cpp +++ b/test/includes_test.cpp @@ -14,7 +14,9 @@ class IncludeTest : public TemplateEnvFixture TemplateEnvFixture::SetUp(); AddFile("header", "[{{ foo }}|{{ bar }}]"); + AddFile("header1", "{% set inner_foo = 10 %}[{{ foo }}|{{ bar }}]{{inner_foo}}"); AddFile("o_printer", "({{ o }})"); + AddFile("missing_inner_header", "{% include 'missing' %}"); m_env.AddGlobal("bar", 23); } @@ -24,7 +26,9 @@ TEST_F(IncludeTest, TestContextInclude) { jinja2::ValuesMap params{{"foo", 42}}; - auto result = Render(R"({% include "header" %})", params); + auto result = Render(R"({% include "header1" with context %})", params); + EXPECT_EQ("[42|23]10", result); + result = Render(R"({% include "header" %})", params); EXPECT_EQ("[42|23]", result); result = Render(R"({% include "header" with context %})", params); EXPECT_EQ("[42|23]", result); @@ -80,6 +84,26 @@ TEST_F(IncludeTest, TestMissingIncludesError1) EXPECT_EQ("missing", (*filesList->begin()).asString()); } +TEST_F(IncludeTest, TestMissingInnerIncludesError) +{ + jinja2::ValuesMap params{}; + + jinja2::Template tpl(&m_env); + auto loadResult = tpl.Load(R"({% include "missing_inner_header" %})"); + 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().value()); + EXPECT_EQ("missing", (*filesList->begin()).asString()); +} + TEST_F(IncludeTest, TestMissingIncludesError2) { jinja2::ValuesMap params{}; diff --git a/test/macro_test.cpp b/test/macro_test.cpp index a2a6c5fc..47ea14cf 100644 --- a/test/macro_test.cpp +++ b/test/macro_test.cpp @@ -46,6 +46,20 @@ R"( params = PrepareTestData(); } +MULTISTR_TEST(MacroTest, + OneParamRecursiveMacro, + R"( +{% macro fib(param) %}{{ 1 if param == 1 else (fib(param - 1) | int + param) }}{% endmacro %} +{{ fib(10) }} +)", + //----------- + R"( +55 +)") +{ + params = PrepareTestData(); +} + MULTISTR_TEST(MacroTest, OneDefaultParamMacro, R"( {% macro test(param='Hello') %} diff --git a/test/test_tools.h b/test/test_tools.h index 5db11922..7fad740f 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -107,16 +107,12 @@ inline jinja2::ValuesMap PrepareTestData() inline std::string ErrorToString(const jinja2::ErrorInfo& error) { - std::ostringstream errorDescr; - errorDescr << error; - return errorDescr.str(); + return error.ToString(); } inline std::wstring ErrorToString(const jinja2::ErrorInfoW& error) { - std::wostringstream errorDescr; - errorDescr << error; - return errorDescr.str(); + return error.ToString(); } inline void StringToConsole(const std::string& str) @@ -290,7 +286,11 @@ struct TypeReflection : TypeReflected static auto& GetAccessors() { static std::unordered_map accessors = { - {"intValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.intValue;}}, + { "intValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.intValue); + } }, {"intEvenValue", [](const TestStruct& obj) -> Value { assert(obj.isAlive); @@ -298,10 +298,36 @@ struct TypeReflection : TypeReflected return {}; return {obj.intValue}; }}, - {"dblValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.dblValue;}}, - {"boolValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.boolValue;}}, - {"strValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.strValue;}}, - {"wstrValue", [](const TestStruct& obj) {assert(obj.isAlive); return obj.wstrValue;}}, + { "dblValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.dblValue); + } }, + { "boolValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.boolValue); + } }, + { "strValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.strValue); + } }, + { "wstrValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.wstrValue); + } }, + { "strViewValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(nonstd::string_view(obj.strValue)); + } }, + { "wstrViewValue", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(nonstd::wstring_view(obj.wstrValue)); + } }, {"innerStruct", [](const TestStruct& obj) { assert(obj.isAlive); diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index 179f68ac..030d1403 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -187,6 +187,9 @@ TEST_P(UserCallableParamConvertTest, Test) params["VarKwArgsFn"] = MakeCallable([](const ValuesMap& val) { return val; }, ArgInfo{"**kwargs"}); + params["ContextArgFn"] = + MakeCallable([](const GenericMap& val, const std::string& varName) { return val.GetValueByName(varName); }, ArgInfo{ "*context" }, ArgInfo{ "name" }); + params["test_value"] = 100500; PerformBothTests(source, testParam.result, params); } @@ -265,6 +268,8 @@ INSTANTIATE_TEST_CASE_P(VarKwArgsParamsConvert, UserCallableParamConvertTest, :: "['arg1': 10.123, 'arg2': [1, 2, 3]]"} )); +INSTANTIATE_TEST_CASE_P(GlobalContextAccess, UserCallableParamConvertTest, ::testing::Values(InputOutputPair{ "ContextArgFn(name='test_value')", "100500" })); + INSTANTIATE_TEST_CASE_P(StringParamConvert, UserCallableParamConvertTest, ::testing::Values( InputOutputPair{"StringFn()", "''"}, InputOutputPair{"StringFn('Hello World')", "'Hello World'"}, @@ -323,12 +328,12 @@ INSTANTIATE_TEST_CASE_P(MapParamConvert, UserCallableParamConvertTest, ::testing "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], 'intEvenValue': 0, 'intValue': 0, " - "'strValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, " - "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " + "'strValue': 'test string 0', 'strViewValue': 'test string 0', 'tmpStructList': [{'strValue': 'Hello World!'}, " + "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, " "{'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}, {'strValue': 'Hello World!'}], " - "'wstrValue': 'test string 0']"}, - InputOutputPair{"GMapFn(reflectedVal.innerStruct) | dictsort", "['strValue': 'Hello World!']"} + "'wstrValue': 'test string 0', 'wstrViewValue': 'test string 0']" }, + InputOutputPair{"GMapFn(reflectedVal.innerStruct) | dictsort", "['strValue': 'Hello World!']"} )); INSTANTIATE_TEST_CASE_P(UserDefinedFilter, UserCallableFilterTest, ::testing::Values( diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 5fbadd22..a802aa2a 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,5 +1,5 @@ if(MSVC) - set (THIRDPARTY_RUNTIME_TYPE ${MSVC_RUNTIME_TYPE}) + set (THIRDPARTY_RUNTIME_TYPE ${JINJA2CPP_MSVC_RUNTIME_TYPE}) if ("${THIRDPARTY_RUNTIME_TYPE}" STREQUAL "") string (FIND "${CURRENT_CXX_FLAGS}" "MT" THIRDPARTY_MT_POS REVERSE) string (FIND "${CURRENT_CXX_FLAGS}" "MD" THIRDPARTY_MD_POS REVERSE) @@ -8,7 +8,7 @@ if(MSVC) elseif (NOT THIRDPARTY_MD_POS EQUAL -1) set (THIRDPARTY_RUNTIME_TYPE "/MD") else () - message (STATUS "Dynamic C runtime assumed. Use 'MSVC_RUNTIME_TYPE' variable for override") + message (STATUS "Dynamic C runtime assumed. Use 'JINJA2CPP_MSVC_RUNTIME_TYPE' variable for override") set (THIRDPARTY_RUNTIME_TYPE "/MD") endif() endif () @@ -64,7 +64,7 @@ if(JINJA2CPP_BUILD_TESTS) endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) - set (JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} boost_variant boost_filesystem boost_algorithm fmt rh_lib) + set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} boost_variant boost_filesystem boost_algorithm fmt) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index 5dd4ceb0..3ec01cd4 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -35,30 +35,9 @@ if (JINJA2CPP_BUILD_TESTS) find_package(RapidJSON) endif() -update_submodule(robin-hood-hashing) -add_library(rh_lib INTERFACE) -target_include_directories(rh_lib - INTERFACE - $ - $) -# set_target_properties(rh_lib PROPERTIES -# INTERFACE_INCLUDE_DIRECTORIES -# BLA-BLA-BLA -# "$" -# "$" -# ) - install (FILES thirdparty/nonstd/expected-lite/include/nonstd/expected.hpp thirdparty/nonstd/variant-lite/include/nonstd/variant.hpp thirdparty/nonstd/optional-lite/include/nonstd/optional.hpp thirdparty/nonstd/string-view-lite/include/nonstd/string_view.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd) - -install(TARGETS rh_lib - EXPORT InstallTargets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - ) - diff --git a/thirdparty/robin-hood-hashing b/thirdparty/robin-hood-hashing deleted file mode 160000 index d6079c33..00000000 --- a/thirdparty/robin-hood-hashing +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6079c3306a472e5b876a13a0663abf440716f70 diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index e27b7ba0..5da3eea5 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -5,7 +5,7 @@ find_package(variant-lite) find_package(optional-lite) find_package(string-view-lite) find_package(boost) -find_package(fmtlib) +find_package(fmt) -set (JINJA2_PRIVATE_LIBS_INT boost::boost fmt::fmt) +set(JINJA2_PRIVATE_LIBS_INT boost::boost fmt::fmt) set (JINJA2_PUBLIC_LIBS_INT expected-lite::expected-lite variant-lite::variant-lite optional-lite::optional-lite string-view-lite::string-view-lite) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake index b40b0c5f..256974d3 100644 --- a/thirdparty/thirdparty-external.cmake +++ b/thirdparty/thirdparty-external.cmake @@ -39,7 +39,6 @@ find_hdr_package(variant-lite nonstd/variant.hpp) find_hdr_package(optional-lite nonstd/optional.hpp) find_hdr_package(string-view-lite nonstd/string_view.hpp) find_hdr_package(fmt-header-only fmt/format.h) -find_hdr_package(rh_lib robin_hood.h) if (TARGET fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) @@ -54,7 +53,7 @@ install(TARGETS expected-lite variant-lite optional-lite string-view-lite PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd ) -install(TARGETS fmt-header-only rh_lib +install(TARGETS fmt-header-only EXPORT InstallTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} From fa3dffa57b2fb36a8e44f7c884358b9a257a091b Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 1 Oct 2019 23:53:14 +0300 Subject: [PATCH 103/206] [skip ci] Update to the release 1.0.0 --- README.md | 69 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 082908ee..0c9240fa 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![conan.io](https://api.bintray.com/packages/flexferrum/conan-packages/jinja2cpp:flexferrum/images/download.svg?version=1.0.0:testing) ](https://bintray.com/flexferrum/conan-packages/jinja2cpp:flexferrum/1.0.0:testing/link) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) -C++ implementation of Jinja2 Python template engine. This library was originally inspired by [Jinja2CppLight](https://github.com/hughperkins/Jinja2CppLight) project and brings support of mostly all Jinja2 templates features into C++ world. +C++ implementation of Jinja2 Python template engine. This library brings support of powerful Jinja2 templates features into the C++ world. Reports and dynamic html pages, source code generation and ## Introduction @@ -28,6 +28,7 @@ Main features of Jinja2C++: - Templates extention, including and importing - Macros - Rich error reporting. +- Shared template enironment with templates cache support For instance, this simple code: @@ -60,7 +61,7 @@ hello; world!!! In order to use Jinja2C++ in your project you have to: * Clone the Jinja2C++ repository -* Build it according with the instructions +* Build it according with the [instructions](https://jinja2cpp.dev/docs/build_and_install.html) * Link to your project. Usage of Jinja2C++ in the code is pretty simple: @@ -73,7 +74,7 @@ jinja2::Template tpl; 2. Populate it with template: ```c++ -tpl.Load("{{'Hello World' }}!!!"); +tpl.Load("{{ 'Hello World' }}!!!"); ``` 3. Render the template: @@ -95,7 +96,7 @@ More detailed examples and features describtion can be found in the documentatio ## Current Jinja2 support Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Jinja2Cpp is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: - expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. -- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize**) +- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape**) - big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) - limited number of functions (**range**, **loop.cycle**) - 'if' statement (with 'elif' and 'else' branches) @@ -115,19 +116,12 @@ Full information about Jinja2 specification support and compatibility table can ## Supported compilers Compilation of Jinja2Cpp tested on the following compilers (with C++14 and C++17 enabled features): -- Linux gcc 5.0 -- Linux gcc 6.0 -- Linux gcc 7.0 -- Linux clang 5.0 -- Linux clang 6.0 -- Linux clang 7 -- Linux clang 8 +- Linux gcc 5.5 - 9.0 +- Linux clang 5.0 - 9 - MacOS X-Code 9 - MacOS X-Code 10 - MacOS X-Code 11 (C++14 in default build, C++17 with externally-provided boost) -- Microsoft Visual Studio 2015 x86, x64 -- Microsoft Visual Studio 2017 x86, x64 -- Microsoft Visual Studio 2019 x86, x64 +- Microsoft Visual Studio 2015 - 2019 x86, x64 - MinGW gcc compiler 7.3 - MinGW gcc compiler 8.1 @@ -135,7 +129,7 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 and C++17 ## Build and install Jinja2Cpp has several external dependencies: -- `boost` library (at least version 1.55) +- `boost` library (at least version 1.65) - `nonstd::expected-lite` [https://github.com/martinmoene/expected-lite](https://github.com/martinmoene/expected-lite) - `nonstd::variant-lite` [https://github.com/martinmoene/variant-lite](https://github.com/martinmoene/variant-lite) - `nonstd::value-ptr-lite` [https://github.com/martinmoene/value-ptr-lite](https://github.com/martinmoene/value-ptr-lite) @@ -176,12 +170,6 @@ In simpliest case to compile Jinja2Cpp you need: > cmake --build . --target install ``` -6. Also you can run the tests: - -``` -> ctest -C Release -``` - In this case Jinja2Cpp will be built with internally-shipped dependencies and install them respectively. But Jinja2Cpp supports build with externally-provided deps. Different Jinja2Cpp usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build ### Usage with conan.io dependency manager @@ -191,11 +179,11 @@ Jinja2Cpp can be used as conan.io package. In this case you should do the follow 2. Register the following remote conan.io repositories: * https://api.bintray.com/conan/martinmoene/nonstd-lite * https://api.bintray.com/conan/bincrafters/public-conan - * https://api.bintray.com/conan/manu343726/conan-packages + * https://api.bintray.com/conan/flexferrum/conan-packages The sample command is: `conan remote add martin https://api.bintray.com/conan/martinmoene/nonstd-lite` -3. Add reference to Jinja2Cpp package (`jinja2cpp/0.9.1@Manu343726/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: +3. Add reference to Jinja2Cpp package (`jinja2cpp/1.0.0@flexferrum/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: ```cmake include (../../cmake/conan.cmake) @@ -204,7 +192,7 @@ if (NOT MSVC) endif () conan_cmake_run(REQUIRES - jinja2cpp/0.9.1@Manu343726/testing + jinja2cpp/1.0.0@flexferrum/testing gtest/1.7.0@bincrafters/stable BASIC_SETUP ${CONAN_SETTINGS} @@ -231,7 +219,7 @@ You can define (via -D command line CMake option) the following build flags: - **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2Cpp tests. - **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). - **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. -- **MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). +- **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). - **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: - `internal` In this mode Jinja2Cpp build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. - `external-boost` In this mode Jinja2Cpp build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. @@ -240,7 +228,7 @@ You can define (via -D command line CMake option) the following build flags: ### Build with C++17 standard enabled -In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD` macro in the build settings. +In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_NONSTD optional_CONFIG_SELECT_OPTIONAL=optional_OPTIONAL_NONSTD` macros in the build settings. ## Acknowledgments Thanks to **@manu343726** for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. @@ -258,6 +246,35 @@ Thanks to **@rmorozov** for stanitized builds setup ## Changelog +### Version 1.0.0 +#### Changes and improvements +- `default` attribute added to the `map` filter (#48) +- escape sequences support added to the string literals (#49) +- arbitrary ranges, generated sequences, input iterators etc. now can be used with `GenericList` type (#66) +- nonstd::string_view is now one of the possible types for the `Value` +- `filter` tag support added to the template parser (#44) +- `escape` filter support added to the template parser (#140) +- `capitalize` filter support added to the template parser (#137) +- multiline version of `set` tag added to the parser (#45) +- added built-in reflection for nlohmann json and rapid json libraries (#78) +- `loop.depth` and `loop.depth0` variables support added +- {fmt} is now used as a formatting library instead of iostreams +- robin hood hash maps is now used for internal value storage +- rendering performance improvements +- template cache implemented in `TemplateEnv` +- user-defined callables now can accept global context via `*context` special param +- MinGW, clang >= 7.0, XCode >= 9, gcc >= 7.0 are now officially supported as a target compilers (#79) + +#### Fixed bugs +- Fixed pipe (`|`) operator precedence (#47) +- Fixed bug in internal char <-> wchar_t converter on Windows +- Fixed crash in parsing `endblock` tag +- Fixed scope control for `include` and `for` tags +- Fixed bug with macros call within expression context + +#### Breaking changes +- MSVC runtime type is now defines by `JINJA2CPP_MSVC_RUNTIME_TYPE` CMake variable + ### Version 0.9.2 #### Major changes - User-defined callables implemented. Now you can define your own callable objects, pass them as input parameters and use them inside templates as regular (global) functions, filters or testers. See details here: https://jinja2cpp.dev/docs/usage/ud_callables.html From 1939d4095fd0c85f0f94cbcf39d882c17a0580eb Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 1 Oct 2019 23:55:16 +0300 Subject: [PATCH 104/206] [skip ci] Jinja2Cpp -> Jinja2C++ --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0c9240fa..90ba3c44 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ That's all! More detailed examples and features describtion can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) ## Current Jinja2 support -Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Jinja2Cpp is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: +Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: - expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. - big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape**) - big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) @@ -115,7 +115,7 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way, Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.dev/docs/j2_compatibility.html). ## Supported compilers -Compilation of Jinja2Cpp tested on the following compilers (with C++14 and C++17 enabled features): +Compilation of Jinja2C++ tested on the following compilers (with C++14 and C++17 enabled features): - Linux gcc 5.5 - 9.0 - Linux clang 5.0 - 9 - MacOS X-Code 9 @@ -128,7 +128,7 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 and C++17 **Note:** Support of gcc version >= 9.x or clang version >= 8.0 depends on version of Boost library provided. ## Build and install -Jinja2Cpp has several external dependencies: +Jinja2C++ has several external dependencies: - `boost` library (at least version 1.65) - `nonstd::expected-lite` [https://github.com/martinmoene/expected-lite](https://github.com/martinmoene/expected-lite) - `nonstd::variant-lite` [https://github.com/martinmoene/variant-lite](https://github.com/martinmoene/variant-lite) @@ -138,7 +138,7 @@ Jinja2Cpp has several external dependencies: - `fmtlib::fmt` [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt) - `robin-hood-hashing` [https://github.com/martinus/robin-hood-hashing](https://github.com/martinus/robin-hood-hashing) -In simpliest case to compile Jinja2Cpp you need: +In simpliest case to compile Jinja2C++ you need: 1. Install CMake build system (at least version 3.0) 2. Clone jinja2cpp repository and update submodules: @@ -162,7 +162,7 @@ In simpliest case to compile Jinja2Cpp you need: > cmake .. -DCMAKE_INSTALL_PREFIX= > cmake --build . --target all ``` -"Path to install folder" here is a path to the folder where you want to install Jinja2Cpp lib. +"Path to install folder" here is a path to the folder where you want to install Jinja2C++ lib. 5. Install library: @@ -170,10 +170,10 @@ In simpliest case to compile Jinja2Cpp you need: > cmake --build . --target install ``` -In this case Jinja2Cpp will be built with internally-shipped dependencies and install them respectively. But Jinja2Cpp supports build with externally-provided deps. Different Jinja2Cpp usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build +In this case Jinja2C++ will be built with internally-shipped dependencies and install them respectively. But Jinja2C++ supports build with externally-provided deps. Different Jinja2C++ usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build ### Usage with conan.io dependency manager -Jinja2Cpp can be used as conan.io package. In this case you should do the following steps: +Jinja2C++ can be used as conan.io package. In this case you should do the following steps: 1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html ) 2. Register the following remote conan.io repositories: @@ -183,7 +183,7 @@ Jinja2Cpp can be used as conan.io package. In this case you should do the follow The sample command is: `conan remote add martin https://api.bintray.com/conan/martinmoene/nonstd-lite` -3. Add reference to Jinja2Cpp package (`jinja2cpp/1.0.0@flexferrum/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: +3. Add reference to Jinja2C++ package (`jinja2cpp/1.0.0@flexferrum/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: ```cmake include (../../cmake/conan.cmake) @@ -216,15 +216,15 @@ set_target_properties (${TARGET_NAME} PROPERTIES ### Additional CMake build flags You can define (via -D command line CMake option) the following build flags: -- **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2Cpp tests. +- **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2C++ tests. - **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). -- **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2Cpp library library link type. +- **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2C++ library library link type. - **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). - **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: - - `internal` In this mode Jinja2Cpp build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. - - `external-boost` In this mode Jinja2Cpp build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. + - `internal` In this mode Jinja2C++ build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. + - `external-boost` In this mode Jinja2C++ build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) - - `conan-build` Special mode for building Jinja2Cpp via conan recipe. + - `conan-build` Special mode for building Jinja2C++ via conan recipe. ### Build with C++17 standard enabled From bc8c2c270eabd69e8f93c5a43df29b77c03586df Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Wed, 2 Oct 2019 01:29:52 +0300 Subject: [PATCH 105/206] [skip ci] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 90ba3c44..8cbe9a8e 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,6 @@ You can define (via -D command line CMake option) the following build flags: - **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2C++ tests. - **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). -- **JINJA2CPP_BUILD_SHARED** (default OFF) - Specify Jinja2C++ library library link type. - **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). - **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: - `internal` In this mode Jinja2C++ build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. From bcda1003e7bc6202a7eb36aaed14f6077ba57650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kerem=20Halla=C3=A7?= Date: Fri, 4 Oct 2019 20:18:41 +0300 Subject: [PATCH 106/206] [skip ci] Added point marks to Aknowledgments (#151) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8cbe9a8e..33aac0b3 100644 --- a/README.md +++ b/README.md @@ -234,13 +234,13 @@ Thanks to **@manu343726** for CMake scripts improvement, bugs hunting and fixing Thanks to **@martinmoene** for the perfectly implemented xxx-lite libraries. -Thanks to **@vitaut** for the amazing text formatting library +Thanks to **@vitaut** for the amazing text formatting library. -Thanks to **@martinus** for the fast hash maps implementation +Thanks to **@martinus** for the fast hash maps implementation. -Thanks to **@palchukovsky** for the great contribution into codebase +Thanks to **@palchukovsky** for the great contribution into codebase. -Thanks to **@rmorozov** for stanitized builds setup +Thanks to **@rmorozov** for stanitized builds setup. ## Changelog From a6d544c524f8cf5ea3c4b6b7fb4cf4c659d3d523 Mon Sep 17 00:00:00 2001 From: morenol Date: Sat, 5 Oct 2019 03:55:55 -0400 Subject: [PATCH 107/206] Implement raw/endraw (#148) * Strip Right function * Support for raw/endraw --- include/jinja2cpp/error_info.h | 3 + src/error_info.cpp | 9 ++ src/lexer.h | 2 + src/template_parser.h | 126 +++++++++++++++++++++----- test/errors_test.cpp | 8 +- test/statements_tets.cpp | 161 ++++++++++++++++++++++++++++++++- 6 files changed, 282 insertions(+), 27 deletions(-) diff --git a/include/jinja2cpp/error_info.h b/include/jinja2cpp/error_info.h index 222302bf..a1edf143 100644 --- a/include/jinja2cpp/error_info.h +++ b/include/jinja2cpp/error_info.h @@ -32,6 +32,7 @@ enum class ErrorCode ExpectedToken, //!< Specific token(s) expected. ExtraParams[0] contains the actual token, rest of ExtraParams contain set of expected tokens ExpectedExpression, //!< Expression expected ExpectedEndOfStatement, //!< End of statement expected. ExtraParams[0] contains the expected end of statement tag + ExpectedRawEnd, //!< {% endraw %} expected UnexpectedToken, //!< Unexpected token. ExtraParams[0] contains the invalid token UnexpectedStatement, //!< Unexpected statement. ExtraParams[0] contains the invalid statement tag UnexpectedCommentBegin, //!< Unexpected comment block begin (`{#`) @@ -40,6 +41,8 @@ enum class ErrorCode UnexpectedExprEnd, //!< Unexpected expression block end (`}}`) UnexpectedStmtBegin, //!< Unexpected statement block begin (`{%`) UnexpectedStmtEnd, //!< Unexpected statment block end (`%}`) + UnexpectedRawBegin, //!< Unexpected raw block begin {% raw %} + UnexpectedRawEnd, //!< Unexpected raw block end {% endraw %} }; /*! diff --git a/src/error_info.cpp b/src/error_info.cpp index ede3367f..51822e44 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -176,6 +176,9 @@ void RenderErrorInfo(std::basic_string& result, const ErrorInfoTpl format_to(out, UNIVERSAL_STR("Expected end of statement, got: '{}'").GetValue(), extraParams[0]); break; } + case ErrorCode::ExpectedRawEnd: + format_to(out, UNIVERSAL_STR("Expected end of raw block").GetValue()); + break; case ErrorCode::UnexpectedToken: { auto& extraParams = errInfo.GetExtraParams(); @@ -194,6 +197,12 @@ void RenderErrorInfo(std::basic_string& result, const ErrorInfoTpl case ErrorCode::UnexpectedCommentEnd: format_to(out, UNIVERSAL_STR("Unexpected comment end").GetValue()); break; + case ErrorCode::UnexpectedRawBegin: + format_to(out, UNIVERSAL_STR("Unexpected raw block begin").GetValue()); + break; + case ErrorCode::UnexpectedRawEnd: + format_to(out, UNIVERSAL_STR("Unexpected raw block end").GetValue()); + break; case ErrorCode::UnexpectedExprBegin: format_to(out, UNIVERSAL_STR("Unexpected expression block begin").GetValue()); break; diff --git a/src/lexer.h b/src/lexer.h index 78ea48b4..82edd9fc 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -98,6 +98,8 @@ struct Token // Template control CommentBegin, CommentEnd, + RawBegin, + RawEnd, StmtBegin, StmtEnd, ExprBegin, diff --git a/src/template_parser.h b/src/template_parser.h index 82a3baa2..54bcc0ec 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -56,7 +56,7 @@ struct ParserTraits : public ParserTraitsBase<> { static std::regex GetRoughTokenizer() { - return std::regex(R"((\{\{)|(\}\})|(\{%)|(%\})|(\{#)|(#\})|(\n))"); + return std::regex(R"((\{\{)|(\}\})|(\{%[\+\-]?\s+raw\s+[\+\-]?%\})|(\{%[\+\-]?\s+endraw\s+[\+\-]?%\})|(\{%)|(%\})|(\{#)|(#\})|(\n))"); } static std::regex GetKeywords() { @@ -112,7 +112,7 @@ struct ParserTraits : public ParserTraitsBase<> { static std::wregex GetRoughTokenizer() { - return std::wregex(LR"((\{\{)|(\}\})|(\{%)|(%\})|(\{#)|(#\})|(\n))"); + return std::wregex(LR"((\{\{)|(\}\})|(\{%[\+\-]?\s+raw\s+[\+\-]?%\})|(\{%[\+\-]?\s+endraw\s+[\+\-]?%\})|(\{%)|(%\})|(\{#)|(#\})|(\n))"); } static std::wregex GetKeywords() { @@ -289,6 +289,8 @@ class TemplateParser : public LexerHelper RM_Unknown = 0, RM_ExprBegin = 1, RM_ExprEnd, + RM_RawBegin, + RM_RawEnd, RM_StmtBegin, RM_StmtEnd, RM_CommentBegin, @@ -308,7 +310,8 @@ class TemplateParser : public LexerHelper Expression, Statement, Comment, - LineStatement + LineStatement, + RawBlock }; struct TextBlockInfo @@ -352,6 +355,14 @@ class TemplateParser : public LexerHelper } } while (matchBegin != matchEnd); FinishCurrentLine(m_template->size()); + + if ( m_currentBlockInfo.type == TextBlockType::RawBlock) + { + nonstd::expected result = MakeParseError(ErrorCode::ExpectedRawEnd, MakeToken(Token::RawEnd, {m_template->size(), m_template->size() })); + foundErrors.push_back(result.error()); + return nonstd::make_unexpected(std::move(foundErrors)); + } + FinishCurrentBlock(m_template->size()); if (!foundErrors.empty()) @@ -395,6 +406,8 @@ class TemplateParser : public LexerHelper } break; case RM_CommentBegin: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; if (m_currentBlockInfo.type != TextBlockType::RawText) { FinishCurrentLine(match.position() + 2); @@ -407,6 +420,8 @@ class TemplateParser : public LexerHelper break; case RM_CommentEnd: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; if (m_currentBlockInfo.type != TextBlockType::Comment) { FinishCurrentLine(match.position() + 2); @@ -444,6 +459,26 @@ class TemplateParser : public LexerHelper m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); break; + case RM_RawBegin: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; + else if (m_currentBlockInfo.type != TextBlockType::RawText && m_currentBlockInfo.type != TextBlockType::Comment) + { + FinishCurrentLine(match.position() + match.length()); + return MakeParseError(ErrorCode::UnexpectedRawBegin, MakeToken(Token::RawBegin, {matchStart, matchStart + match.length()})); + } + StartControlBlock(TextBlockType::RawBlock, matchStart); + break; + case RM_RawEnd: + if (m_currentBlockInfo.type == TextBlockType::Comment) + break; + else if (m_currentBlockInfo.type != TextBlockType::RawBlock) + { + FinishCurrentLine(match.position() + match.length()); + return MakeParseError(ErrorCode::UnexpectedRawEnd, MakeToken(Token::RawEnd, {matchStart, matchStart + match.length()})); + } + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); + break; } return nonstd::expected(); @@ -453,7 +488,7 @@ class TemplateParser : public LexerHelper { size_t startOffset = matchStart + 2; size_t endOffset = matchStart; - if (m_currentBlockInfo.type != TextBlockType::RawText) + if (m_currentBlockInfo.type != TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::RawBlock ) return; else endOffset = StripBlockLeft(m_currentBlockInfo, startOffset, endOffset); @@ -465,8 +500,53 @@ class TemplateParser : public LexerHelper (*m_template)[startOffset] == '-') ++ startOffset; } - m_currentBlockInfo.range.startOffset = startOffset; + m_currentBlockInfo.type = blockType; + + if (blockType==TextBlockType::RawBlock) + startOffset = StripBlockRight(m_currentBlockInfo, matchStart); + + m_currentBlockInfo.range.startOffset = startOffset; + } + + size_t StripBlockRight(TextBlockInfo& currentBlockInfo, size_t position) + { + bool doTrim = m_settings.trimBlocks && (m_currentBlockInfo.type == TextBlockType::Statement || m_currentBlockInfo.type == TextBlockType::RawBlock); + + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + { + position+=2; + for(; position < m_template->size(); ++ position) + { + if ('%' == (*m_template)[position]) + break; + } + } + + 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 (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; + } + } + return newPos; } size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset) @@ -483,7 +563,7 @@ class TemplateParser : public LexerHelper doStrip |= doTotalStrip; } - if (!doStrip || currentBlockInfo.type != TextBlockType::RawText) + if (!doStrip || (currentBlockInfo.type != TextBlockType::RawText && currentBlockInfo.type != TextBlockType::RawBlock)) return endOffset; auto locale = std::locale(); @@ -512,6 +592,7 @@ class TemplateParser : public LexerHelper switch (block.type) { + case TextBlockType::RawBlock: case TextBlockType::RawText: { if (block.range.size() == 0) @@ -648,32 +729,25 @@ class TemplateParser : public LexerHelper size_t FinishCurrentBlock(size_t position) { - bool doTrim = m_settings.trimBlocks && m_currentBlockInfo.type == TextBlockType::Statement; - size_t newPos = position + 2; + size_t newPos; - if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0) + if (m_currentBlockInfo.type == TextBlockType::RawBlock) { - auto ctrlChar = (*m_template)[position - 1]; - doTrim = ctrlChar == '-' ? true : (ctrlChar == '+' ? false : doTrim); - if (ctrlChar == '+' || ctrlChar == '-') - -- position; + size_t currentPosition = position; + position = StripBlockLeft(m_currentBlockInfo, currentPosition+2, currentPosition); + newPos = StripBlockRight(m_currentBlockInfo, currentPosition); } - - if (doTrim) + else { - auto locale = std::locale(); - for (;newPos < m_template->size(); ++ newPos) + newPos = StripBlockRight(m_currentBlockInfo, position); + if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0) { - auto ch = (*m_template)[newPos]; - if (ch == '\n') - { - ++ newPos; - break; - } - if (!std::isspace(ch, locale)) - break; + auto ctrlChar = (*m_template)[position - 1]; + if (ctrlChar == '+' || ctrlChar == '-') + -- position; } } + m_currentBlockInfo.range.endOffset = position; m_textBlocks.push_back(m_currentBlockInfo); m_currentBlockInfo.type = TextBlockType::RawText; @@ -928,6 +1002,8 @@ std::unordered_map ParserTraitsBase::s_tokens = { {Token::From, UNIVERSAL_STR("form")}, {Token::As, UNIVERSAL_STR("as")}, {Token::Do, UNIVERSAL_STR("do")}, + {Token::RawBegin, UNIVERSAL_STR("{% raw %}")}, + {Token::RawEnd, UNIVERSAL_STR("{% endraw %}")}, {Token::CommentBegin, UNIVERSAL_STR("{#")}, {Token::CommentEnd, UNIVERSAL_STR("#}")}, {Token::StmtBegin, UNIVERSAL_STR("{%")}, diff --git a/test/errors_test.cpp b/test/errors_test.cpp index 0d01fb7d..ab592748 100644 --- a/test/errors_test.cpp +++ b/test/errors_test.cpp @@ -485,7 +485,13 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values( InputOutputPair{"{% if a == 42 %}{% endwith %}", "noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% if a == 42 %}{% endwith %}\n ---^-------"}, InputOutputPair{"{{}}", - "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"} + "noname.j2tpl:1:3: error: Unexpected token: '<>'\n{{}}\n--^-------"}, + InputOutputPair{"{% raw %}{% raw %}{{ x }{% endraw %}{% endraw %}", + "noname.j2tpl:1:37: error: Unexpected raw block end\n{% raw %}{% raw %}{{ x }{% endraw %}{% endraw %}\n ---^-------"}, + InputOutputPair{"{% raw %}", + "noname.j2tpl:1:10: error: Expected end of raw block\n{% raw %}\n ---^-------"}, + InputOutputPair{"{{ 2 + 3 + {% raw %} }}", + "noname.j2tpl:1:12: error: Unexpected raw block begin\n{{ 2 + 3 + {% raw %}\n ---^-------"} )); INSTANTIATE_TEST_CASE_P(ExtensionStatementsTest, ErrorsGenericExtensionsTest, ::testing::Values( diff --git a/test/statements_tets.cpp b/test/statements_tets.cpp index f5e14ce6..85a5f72c 100644 --- a/test/statements_tets.cpp +++ b/test/statements_tets.cpp @@ -287,4 +287,163 @@ TEST(SetBlockStatement, MoreVarsFiltered) const auto result = tpl.RenderAsString({}).value(); EXPECT_STREQ("\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); -} \ No newline at end of file +} + +using RawTest = BasicTemplateRenderer; + +TEST(RawTest, General) +{ + const std::string source = R"( +{% raw %} + This is a raw text {{ 2 + 2 }} +{% endraw %} +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("\n\n This is a raw text {{ 2 + 2 }}\n", result.c_str()); +} + +TEST(RawTest, KeywordsInside) +{ + const std::string source = R"( +{% raw %} +
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
{% endraw %} +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("\n\n
    \n {% for item in seq %}\n
  • {{ item }}
  • \n {% endfor %}\n
", result.c_str()); +} + +TEST(RawTest, BrokenExpression) +{ + const std::string source = R"({% raw %}{{ x }{% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("{{ x }", result.c_str()); +} + +TEST(RawTest, BrokenTag) +{ + const std::string source = R"({% raw %}{% if im_broken }still work{% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("{% if im_broken }still work", result.c_str()); +} + +TEST(RawTest, ExtraSpaces) +{ + const std::string source = R"({% raw %}abc{% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("abc", result.c_str()); +} + +TEST(RawTest, ExtraSpaces2) +{ + const std::string source = R"({% raw %}abc{% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("abc", result.c_str()); +} + +TEST(RawTest, TrimPostRaw) +{ + const std::string source = R"({% raw -%} abc{% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("abc", result.c_str()); +} + +TEST(RawTest, TrimRawEndRaw) +{ + const std::string source = R"({% raw -%} abc {%- endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("abc", result.c_str()); +} + +TEST(RawTest, TrimPostEndRaw) +{ + const std::string source = R"({% raw %}abc{% endraw -%} defg)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("abcdefg", result.c_str()); +} + +TEST(RawTest, TrimBeforeEndRaw) +{ + const std::string source = R"({% raw %} abc {%- endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ(" abc", result.c_str()); +} + +TEST(RawTest, TrimBeforeRaw) +{ + const std::string source = R"( {%- raw %} abc {% endraw %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ(" abc ", result.c_str()); +} + +TEST(RawTest, ForRaw) +{ + const std::string source = R"({% for i in (0, 1, 2) -%} + {%- raw %}{{ x }} {% endraw %} + {%- endfor %})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("{{ x }} {{ x }} {{ x }} ", result.c_str()); +} + +TEST(RawTest, CommentRaw) +{ + const std::string source = R"({# {% raw %} {% endraw %} #})"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString({}).value(); + std::cout << result << std::endl; + EXPECT_STREQ("", result.c_str()); +} From b320a27a94e3022acb38d5583f3209c50a1e0d5b Mon Sep 17 00:00:00 2001 From: Mateusz Date: Sat, 5 Oct 2019 18:13:00 +0200 Subject: [PATCH 108/206] Batch filter (#150) --- src/filters.cpp | 51 ++++++++++++++++++++++++++++++++++++++++--- src/filters.h | 4 ++++ test/filters_test.cpp | 19 ++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index 340d5508..a23a1d8a 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -779,16 +779,61 @@ InternalValue Serialize::Filter(const InternalValue&, RenderContext&) return InternalValue(); } -Slice::Slice(FilterParams, Slice::Mode) +Slice::Slice(FilterParams params, Slice::Mode mode) + : m_mode{mode} { - + if(m_mode == BatchMode) + { + ParseParams({{"linecount"s, true}, {"fill_with"s, false}}, params); + } } -InternalValue Slice::Filter(const InternalValue&, RenderContext&) +InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context) { + if(m_mode == BatchMode) + return Batch(baseVal, context); + return InternalValue(); } +InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context) +{ + auto linecount_value = ConvertToInt(GetArgumentValue("linecount", context)); + InternalValue fillWith = GetArgumentValue("fill_with", context); + + if(linecount_value <= 0) + return InternalValue(); + auto linecount = static_cast(linecount_value); + + bool isConverted = false; + auto list = ConvertToList(baseVal, isConverted); + if (!isConverted) + return InternalValue(); + + auto elementsCount = list.GetSize().value_or(0); + if(!elementsCount) + return InternalValue(); + + InternalValueList resultList; + resultList.reserve(linecount); + + const auto remainder = elementsCount % linecount; + const auto columns = elementsCount / linecount + (remainder > 0 ? 1 : 0); + for(std::size_t line = 0, idx = 0; line < linecount; ++line) + { + const auto elems = columns - (remainder && line >= remainder ? 1 : 0); + InternalValueList row; + row.reserve(columns); + std::fill_n(std::back_inserter(row), columns, fillWith); + + for(std::size_t column = 0; column < elems; ++column) + row[column] = list.GetValueByIndex(idx++); + + resultList.push_back(ListAdapter::CreateAdapter(std::move(row))); + } + return ListAdapter::CreateAdapter(std::move(resultList)); +} + StringFormat::StringFormat(FilterParams, StringFormat::Mode) { diff --git a/src/filters.h b/src/filters.h index f7f683a0..2c1c0b43 100644 --- a/src/filters.h +++ b/src/filters.h @@ -154,6 +154,10 @@ class Slice : public FilterBase Slice(FilterParams params, Mode mode); InternalValue Filter(const InternalValue& baseVal, RenderContext& context); +private: + InternalValue Batch(const InternalValue& baseVal, RenderContext& context); + + Mode m_mode; }; class Sort : public FilterBase diff --git a/test/filters_test.cpp b/test/filters_test.cpp index b31a146f..54365380 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -489,3 +489,22 @@ INSTANTIATE_TEST_CASE_P(Escape, FilterGenericTest, ::testing::Values( InputOutputPair{"'abcd&> Date: Mon, 7 Oct 2019 03:50:27 -0400 Subject: [PATCH 109/206] [skip ci] Spelling and grammar check (#155) --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 33aac0b3..aed0e39f 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ Main features of Jinja2C++: - Built-in reflection for the common C++ types, nlohmann and rapid JSON libraries. - Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions. - Control statements (`set`, `for`, `if`, `filter`, `do`, `with`). -- Templates extention, including and importing +- Templates extension, including and importing - Macros - Rich error reporting. -- Shared template enironment with templates cache support +- Shared template environment with templates cache support For instance, this simple code: @@ -91,7 +91,7 @@ Hello World!!! That's all! -More detailed examples and features describtion can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) +More detailed examples and features description can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) ## Current Jinja2 support Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: @@ -138,7 +138,7 @@ Jinja2C++ has several external dependencies: - `fmtlib::fmt` [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt) - `robin-hood-hashing` [https://github.com/martinus/robin-hood-hashing](https://github.com/martinus/robin-hood-hashing) -In simpliest case to compile Jinja2C++ you need: +In simplest case to compile Jinja2C++ you need: 1. Install CMake build system (at least version 3.0) 2. Clone jinja2cpp repository and update submodules: @@ -219,7 +219,7 @@ You can define (via -D command line CMake option) the following build flags: - **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2C++ tests. - **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). - **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). -- **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependencies handling. Following values possible: +- **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependency handling. Following values possible: - `internal` In this mode Jinja2C++ build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. - `external-boost` In this mode Jinja2C++ build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) @@ -230,7 +230,7 @@ You can define (via -D command line CMake option) the following build flags: In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_NONSTD optional_CONFIG_SELECT_OPTIONAL=optional_OPTIONAL_NONSTD` macros in the build settings. ## Acknowledgments -Thanks to **@manu343726** for CMake scripts improvement, bugs hunting and fixing and conan.io packaging. +Thanks to **@manu343726** for CMake scripts improvement, bug hunting and fixing and conan.io packaging. Thanks to **@martinmoene** for the perfectly implemented xxx-lite libraries. @@ -272,7 +272,7 @@ Thanks to **@rmorozov** for stanitized builds setup. - Fixed bug with macros call within expression context #### Breaking changes -- MSVC runtime type is now defines by `JINJA2CPP_MSVC_RUNTIME_TYPE` CMake variable +- MSVC runtime type is now defined by `JINJA2CPP_MSVC_RUNTIME_TYPE` CMake variable ### Version 0.9.2 #### Major changes @@ -314,6 +314,6 @@ Thanks to **@rmorozov** for stanitized builds setup. ### Version 0.6 - A lot of filters has been implemented. Full set of supported filters listed here: [https://github.com/flexferrum/Jinja2Cpp/issues/7](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](https://github.com/flexferrum/Jinja2Cpp/issues/8) -- 'Contatenate as string' operator ('~') has been implemented +- 'Concatenate as string' operator ('~') has been implemented - For-loop with 'if' condition has been implemented - Fixed some bugs in parser From a400faae0ca37c97ab62da0c95651fa5debf0863 Mon Sep 17 00:00:00 2001 From: Rafael Date: Mon, 7 Oct 2019 00:51:06 -0700 Subject: [PATCH 110/206] [skip ci] Fix typos/spelling in README.md (#153) * Fix typos/spelling in README.md * fixed grammar/spelling simpliest From dcd84162aedfb86bec09c13bc19461d050394519 Mon Sep 17 00:00:00 2001 From: debarghya472 <54908977+debarghya472@users.noreply.github.com> Date: Mon, 7 Oct 2019 13:21:45 +0530 Subject: [PATCH 111/206] [skip ci] corrected grammer (#152) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aed0e39f..6d869bab 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![conan.io](https://api.bintray.com/packages/flexferrum/conan-packages/jinja2cpp:flexferrum/images/download.svg?version=1.0.0:testing) ](https://bintray.com/flexferrum/conan-packages/jinja2cpp:flexferrum/1.0.0:testing/link) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) -C++ implementation of Jinja2 Python template engine. This library brings support of powerful Jinja2 templates features into the C++ world. Reports and dynamic html pages, source code generation and +C++ implementation of Jinja2 Python template engine. This library brings support of powerful Jinja2 template features into the C++ world, reports dynamic html pages and source code generation. ## Introduction From c85814ec618f9a6d96b0541c1c5b32a8b2ba610e Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Mon, 7 Oct 2019 16:31:18 +0300 Subject: [PATCH 112/206] GitHub actions as an additional CI (#156) * Add github actions --- .github/workflows/linux-build.yml | 129 ++++++++++++++++++ .github/workflows/windows-build.yml | 79 +++++++++++ .travis.yml | 203 ++-------------------------- appveyor.yml | 12 +- 4 files changed, 225 insertions(+), 198 deletions(-) create mode 100644 .github/workflows/linux-build.yml create mode 100644 .github/workflows/windows-build.yml diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml new file mode 100644 index 00000000..22c9967a --- /dev/null +++ b/.github/workflows/linux-build.yml @@ -0,0 +1,129 @@ +name: CI-linux-build + + +on: + push: + paths-igone: + - 'docs/*' + - '*.md' + pull_request: + paths-igone: + - 'docs/*' + - '*.md' + +jobs: + linux-gcc-build: + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + max-parallel: 8 + matrix: + compiler: [gcc-8, gcc-6, gcc-7, gcc-5, gcc-9] + base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] + build-config: [Release, Debug] + + include: + - compiler: gcc-8 + extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF + - compiler: gcc-9 + extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF + + exclude: + - compiler: gcc-5 + base-flags: -DJINJA2CPP_CXX_STANDARD=17 + - compiler: gcc-6 + base-flags: -DJINJA2CPP_CXX_STANDARD=17 + + steps: + - uses: actions/checkout@v1 + - name: Setup environment + env: + INPUT_COMPILER: ${{ matrix.compiler }} + INPUT_BASE_FLAGS: ${{ matrix.base-flags }} + INPUT_BASE_CONFIG: ${{ matrix.build-config }} + INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} + run: | + sudo apt-get install -y cmake build-essential ${INPUT_COMPILER} + - name: Prepare build + env: + INPUT_COMPILER: ${{ matrix.compiler }} + INPUT_BASE_FLAGS: ${{ matrix.base-flags }} + INPUT_BASE_CONFIG: ${{ matrix.build-config }} + INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} + run: | + export BUILD_TARGET=all + export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=OFF + if [[ "${INPUT_COMPILER}" != "" ]]; then export CXX=${INPUT_COMPILER}; fi + export BUILD_CONFIG=${INPUT_BUILD_CONFIG} + $CXX --version + export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" + + - name: Build + run: | + mkdir -p .build && cd .build + cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 + + - name: Test + run: | + cd .build && ctest -C $BUILD_CONFIG -V + + linux-clang-build: + + runs-on: ubuntu-18.04 + container: + image: ${{matrix.docker-image}} + env: + BUILD_DIRECTORY: /home/conan/.build + HOME: /home/conan + + strategy: + fail-fast: false + max-parallel: 8 + matrix: + compiler: [5.0, 6.0, 7, 8] + base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] + build-config: [Release, Debug] + + include: + - compiler: 5.0 + docker-image: conanio/clang50 + - compiler: 6.0 + docker-image: conanio/clang60 + - compiler: 7 + docker-image: conanio/clang7 + - compiler: 8 + docker-image: conanio/clang8 + - compiler: 9 + docker-image: conanio/clang9 + + exclude: + - compiler: 5.0 + base-flags: -DJINJA2CPP_CXX_STANDARD=17 + steps: + - uses: actions/checkout@v1 + + - name: Build + env: + INPUT_COMPILER: clang-${{ matrix.compiler }} + INPUT_BASE_FLAGS: ${{ matrix.base-flags }} + INPUT_BASE_CONFIG: ${{ matrix.build-config }} + INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} + HOME: /home/conan + run: | + export BUILD_TARGET=all + export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=OFF + export BUILD_CONFIG=${INPUT_BUILD_CONFIG} + export WORKSPACE=$GITHUB_WORKSPACE + $CXX --version + export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" + mkdir $BUILD_DIRECTORY && cd $BUILD_DIRECTORY + sudo chmod gou+rw -R $WORKSPACE + cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS $WORKSPACE && cmake --build . --config $BUILD_CONFIG --target all -- -j4 + + - name: Test + run: | + cd $BUILD_DIRECTORY + ctest -C $BUILD_CONFIG -V + diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml new file mode 100644 index 00000000..57eaf75b --- /dev/null +++ b/.github/workflows/windows-build.yml @@ -0,0 +1,79 @@ +name: CI-windows-build + +on: + push: + paths-igone: + - 'docs/*' + - '*.md' + pull_request: + paths-igone: + - 'docs/*' + - '*.md' + +jobs: + windows-msvc-build: + + runs-on: ${{matrix.run-machine}} + + strategy: + fail-fast: false + max-parallel: 20 + matrix: + compiler: [msvc-2019, msvc-2019] + base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] + build-config: [Release, Debug] + build-platform: [x64, x64] + build-runtime: ["", /MT, /MD] + + include: + - compiler: msvc-2017 + build-platform: x86 + run-machine: windows-2016 + generator: Visual Studio 15 2017 + vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars32.bat + - compiler: msvc-2017 + build-platform: x64 + run-machine: windows-2016 + generator: Visual Studio 15 2017 Win64 + vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars64.bat + - compiler: msvc-2019 + build-platform: x86 + run-machine: windows-2019 + generator: Visual Studio 16 2019 + vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat + - compiler: msvc-2019 + build-platform: x64 + run-machine: windows-2019 + generator: Visual Studio 16 2019 + vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat + extra_flags: -A Win64 + + + steps: + - uses: actions/checkout@v1 + + - name: Build + env: + INPUT_COMPILER: ${{ matrix.compiler }} + INPUT_BASE_FLAGS: ${{ matrix.base-flags }} + INPUT_BUILD_CONFIG: ${{ matrix.build-config }} + INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} + INPUT_BUILD_PLATFORM: ${{ matrix.build-platform }} + INPUT_BUILD_RUNTIME: ${{ matrix.build-runtime }} + INPUT_GENERATOR: ${{ matrix.generator }} + VC_VARS: "${{ matrix.vc_vars }}" + run: | + call "%VC_VARS%" + mkdir -p .build && cd .build + cmake .. -G "%INPUT_GENERATOR%" -DCMAKE_BUILD_TYPE=%INPUT_BUILD_CONFIG% -DJINJA2CPP_MSVC_RUNTIME_TYPE="%INPUT_BUILD_RUNTIME%" -DJINJA2CPP_DEPS_MODE=external-boost %INPUT_BASE_FLAGS% %INPUT_EXTRA_FLAGS% + cmake --build . --config %INPUT_BUILD_CONFIG% + + - name: Test + env: + INPUT_BUILD_CONFIG: ${{ matrix.build-config }} + VC_VARS: "${{ matrix.vc_vars }}" + run: | + cd .build + call "%VC_VARS%" + set path=%BOOST_ROOT%\lib;%PATH% + ctest -C %INPUT_BUILD_CONFIG% -V diff --git a/.travis.yml b/.travis.yml index c6690502..36bd8f12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,199 +1,10 @@ --- -after_success: - - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then echo \"Uploading code coverate report\" ; fi" - - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --directory . --capture --output-file coverage.info ; fi" - - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --remove coverage.info '/usr/*' --output-file coverage.info ; fi" - - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --list coverage.info ; fi" - - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then bash <(curl -s https://codecov.io/bash) -t \"225d6d7a-2b71-4dbe-bf87-fbf75eb7c119\" || echo \"Codecov did not collect coverage reports\"; fi" -before_install: - - "date -u" - - "uname -a" - - "if [[ \"${SYSTEM_BOOST_PACKAGE}\" != \"\" ]]; then sudo add-apt-repository -y ppa:samuel-bachmann/boost && sudo apt-get update -qq; fi" dist: xenial -install: - - "if [[ \"${SYSTEM_BOOST_PACKAGE}\" != \"\" ]]; then sudo apt-get install libboost1.60-all-dev; fi" language: cpp +sudo: required + matrix: include: - - - addons: - apt: - packages: - - cmake - - g++-5 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: COMPILER=g++-5 - os: linux - - - addons: - apt: - packages: - - cmake - - g++-6 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: COMPILER=g++-6 - os: linux - - - addons: - apt: - packages: - - cmake - - g++-7 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: COMPILER=g++-7 - os: linux - - - addons: - apt: - packages: - - cmake - - g++-7 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: "COMPILER=g++-7 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" - os: linux - - - addons: - apt: - packages: - - cmake - - g++-8 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: "COMPILER=g++-8 EXTRA_FLAGS=-DJINJA2CPP_STRICT_WARNINGS=OFF" - os: linux - - - addons: - apt: - packages: - - cmake - - g++-8 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: "COMPILER=g++-8 EXTRA_FLAGS='-DJINJA2CPP_CXX_STANDARD=17 -DJINJA2CPP_STRICT_WARNINGS=OFF'" - os: linux - - - addons: - apt: - packages: - - cmake - - g++-9 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: "COMPILER=g++-9 EXTRA_FLAGS=-DJINJA2CPP_STRICT_WARNINGS=OFF" - os: linux - - - addons: - apt: - packages: - - cmake - - g++-9 - sources: - - ubuntu-toolchain-r-test - compiler: gcc - env: "COMPILER=g++-9 EXTRA_FLAGS='-DJINJA2CPP_CXX_STANDARD=17 -DJINJA2CPP_STRICT_WARNINGS=OFF'" - os: linux - - - addons: - apt: - packages: - - cmake - - clang-5.0 - - g++-7 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-5.0 - compiler: clang - env: COMPILER=clang++-5.0 - os: linux - - - addons: - apt: - packages: - - cmake - - clang-6.0 - - g++-7 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-6.0 - compiler: clang - env: COMPILER=clang++-6.0 - os: linux - - - addons: - apt: - packages: - - cmake - - clang-6.0 - - g++-7 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-6.0 - compiler: clang - env: "COMPILER=clang++-6.0 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" - os: linux - - - addons: - apt: - packages: - - cmake - - clang-7 - - g++-7 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-7 - compiler: clang - env: COMPILER=clang++-7 - os: linux - - - addons: - apt: - packages: - - cmake - - clang-7 - - g++-7 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-7 - compiler: clang - env: "COMPILER=clang++-7 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" - os: linux - - - addons: - apt: - packages: - - cmake - - clang-8 - - g++-8 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-8 - compiler: clang - env: COMPILER=clang++-8 - os: linux - - - addons: - apt: - packages: - - cmake - - clang-8 - - g++-8 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-8 - compiler: clang - env: "COMPILER=clang++-8 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17" - os: linux - compiler: clang env: COMPILER='clang++' @@ -234,6 +45,9 @@ matrix: compiler: clang env: "COMPILER=clang++-8 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 SANITIZE_BUILD=address+undefined" os: linux +before_install: + - "date -u" + - "uname -a" script: - "export BUILD_TARGET=\"all\"" - "export CMAKE_OPTS=\"-DCMAKE_VERBOSE_MAKEFILE=OFF\"" @@ -245,4 +59,9 @@ script: - "mkdir -p build && cd build" - "cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4" - "ctest -C $BUILD_CONFIG -V" -sudo: required +after_success: + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then echo \"Uploading code coverate report\" ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --directory . --capture --output-file coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --remove coverage.info '/usr/*' --output-file coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --list coverage.info ; fi" + - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then bash <(curl -s https://codecov.io/bash) -t \"225d6d7a-2b71-4dbe-bf87-fbf75eb7c119\" || echo \"Codecov did not collect coverage reports\"; fi" diff --git a/appveyor.yml b/appveyor.yml index b91a00f7..c17caebb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,6 +55,10 @@ matrix: BUILD_PLATFORM: MinGW8 - os: Visual Studio 2015 BUILD_PLATFORM: clang + - os: Visual Studio 2017 + BUILD_PLATFORM: x86 + - os: Visual Studio 2017 + BUILD_PLATFORM: x64 - platform: Win32 BUILD_PLATFORM: x64 - platform: Win32 @@ -68,10 +72,6 @@ matrix: init: - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.1;%PATH% - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="x64" set PATH=%BOOST_ROOT%\lib64-msvc-14.1;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="MinGW7" set PATH=C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="MinGW8" set PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" if "%BUILD_PLATFORM%"=="clang" set PATH=%BOOST_ROOT%\lib64-msvc-14.1;C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64\bin;C:\Libraries\llvm-5.0.0\bin;%PATH% && call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" @@ -83,7 +83,7 @@ init: build_script: - mkdir -p build && cd build - cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%configuration% -DJINJA2CPP_MSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=%DEPS_MODE% %EXTRA_CMAKE_ARGS% - - cmake --build . --target all --config Release + - cmake --build . --target all --config %configuration% test_script: - - ctest -C Release -V + - ctest -C %configuration% -V From fff06d6f53af44699058c541c4a1b80c898d5bea Mon Sep 17 00:00:00 2001 From: morenol Date: Wed, 9 Oct 2019 01:55:16 -0400 Subject: [PATCH 113/206] Repeat string operator (#162) --- src/value_visitors.h | 28 ++++++++++++++++++++++++++++ test/expressions_test.cpp | 10 ++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/value_visitors.h b/src/value_visitors.h index 1d33542d..e60f73f6 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -688,6 +688,34 @@ struct BinaryMathOperation : BaseVisitor<> return ProcessStrings(left, nonstd::basic_string_view(rightStr)); } + template + InternalValue operator() (const std::basic_string &left, int64_t right) const + { + return RepeatString(nonstd::basic_string_view(left), right); + } + + template + InternalValue operator() (const nonstd::basic_string_view &left, int64_t right) const + { + return RepeatString(left, right); + } + + template + InternalValue RepeatString(const nonstd::basic_string_view& left, const int64_t right) const + { + using string = std::basic_string; + InternalValue result; + + if(m_oper == jinja2::BinaryExpression::Mul) + { + string str; + for (int i = 0; i < right; ++i) + str.append(left.begin(), left.end()); + result = std::move(str); + } + return result; + } + template InternalValue ProcessStrings(const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const { diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 015f0f2e..23b92d82 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -30,6 +30,11 @@ R"( {{ wstringValue + ' ' + wstringValue }} {{ stringValue + ' ' + stringValue }} {{ 'Hello' ~ " " ~ 123 ~ ' ' ~ 1.234 ~ " " ~ true ~ " " ~ intValue ~ " " ~ false ~ ' ' ~ 'World ' ~ stringValue ~ ' ' ~ wstringValue}} +{{ 'abc' * 0 }} +{{ 'abc' * 1 }} +{{ '123' * intValue }} +{{ stringValue * intValue }} +{{ wstringValue * intValue }} )", //----------- R"( @@ -51,6 +56,11 @@ rain rain rain rain rain rain Hello 123 1.234 true 3 false World rain rain + +abc +123123123 +rainrainrain +rainrainrain )") { params = { From 69476050b7d71aa68216cc039e73e7b5159aab45 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Sun, 13 Oct 2019 16:51:34 +0300 Subject: [PATCH 114/206] Fix for issues #159, #161 and #163 (#165) * Resolves #159 Fix lstripblocks/trimblocks behavior * Update filters_test.cpp * Fix build * Resolves #161 . Fix parent `block` statement behavior according to the docs * Resolves #163 Alternate form of `MakeCallable` * Add `MakeCallable` overload for class member functions * Remove obsolete code --- include/jinja2cpp/user_callable.h | 172 +++++++- include/jinja2cpp/value.h | 16 + src/statements.cpp | 136 +++---- src/template_parser.h | 652 +++++++++++++++--------------- test/basic_tests.cpp | 247 ++++++++++- test/expressions_test.cpp | 3 +- test/extends_test.cpp | 35 +- test/filters_test.cpp | 7 +- test/forloop_test.cpp | 78 +++- test/if_test.cpp | 6 + test/includes_test.cpp | 2 +- test/macro_test.cpp | 42 +- test/statements_tets.cpp | 33 +- test/user_callable_test.cpp | 57 ++- 14 files changed, 1030 insertions(+), 456 deletions(-) diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h index 2f9bb37e..4055e6b7 100644 --- a/include/jinja2cpp/user_callable.h +++ b/include/jinja2cpp/user_callable.h @@ -1,10 +1,12 @@ #ifndef USER_CALLABLE_H #define USER_CALLABLE_H -#include "value.h" #include "string_helpers.h" +#include "value.h" + #include +#include #include namespace jinja2 @@ -185,6 +187,131 @@ Value InvokeUserCallable(Fn&& fn, const UserCallableParams& params, ArgDescr&& . auto invoker = UCInvoker(fn, params); return nonstd::visit(ParamUnwrapper>(&invoker), GetParamValue(params, ad).data()...); } + +template +struct TypedParam +{ + using decayed_t = std::decay_t; + nonstd::variant data; + + bool HasValue() const { return data.index() != 0; } + T GetValue() const + { + if (data.index() == 1) + return nonstd::get(data); + else + return *nonstd::get(data); + } + + void SetPointer(const decayed_t* ptr) { data = ptr; } + + void SetValue(decayed_t&& val) { data = std::move(val); } +}; + +template +struct TypedParamUnwrapper +{ + TypedParam* param; + using ValueType = typename TypedParam::decayed_t; + TypedParamUnwrapper(TypedParam& p) + : param(&p) + { + } + + template + struct PromoteTester + { + using decayed_u = std::decay_t; + struct InvalidType + { + }; + + static auto TestFn(T) -> int; + static auto TestFn(...) -> char; + + template + static auto PromotedType(U1 u) -> decltype(TestFn(Promote(u))); + static auto PromotedType(...) -> char; + + enum { value = std::is_same::value ? false : sizeof(PromotedType(std::declval())) == sizeof(int) }; + }; + + void operator()(const ValueType& val) const { param->SetPointer(&val); } + + void operator()(const EmptyValue&) const { param->SetValue(ValueType()); } + + template + auto operator()(const U& v) -> std::enable_if_t::value && !std::is_same, ValueType>::value> + { + param->SetValue(Promote(v)); + } + + template + auto operator()(const U& v) -> std::enable_if_t::value && !std::is_same, ValueType>::value> + { + } +}; + +template +auto TypedUnwrapParam(const V& value) +{ + TypedParam param; + TypedParamUnwrapper visitor(param); + nonstd::visit(ParamUnwrapper>(&visitor), value); + return param; +} + +#if !optional_CPP17_OR_GREATER +inline bool TypedParamHasValue() +{ + return true; +} + +template +bool TypedParamHasValue(const TypedParam& param, Params&&... params) +{ + return param.HasValue() && TypedParamHasValue(params...); +} + +template +Value InvokeTypedUserCallableImpl(Fn&& fn, Tuple&& tuple, std::index_sequence&&) +{ + bool has_value = TypedParamHasValue(std::get(tuple)...); + if (!has_value) + return Value(); + + return fn(std::get(tuple).GetValue()...); +} +#endif + +template +Value InvokeTypedUserCallable(Fn&& fn, const UserCallableParams& params, ArgDescr&&... ad) +{ + auto typed_params = std::make_tuple(TypedUnwrapParam::type>(GetParamValue(params, ad).data())...); +#if !optional_CPP17_OR_GREATER + return InvokeTypedUserCallableImpl(fn, typed_params, std::index_sequence_for()); +#else + return std::apply( + [&fn](auto&... args) { + bool has_value = (true && ... && args.HasValue()); + if (!has_value) + return Value(); + + return Value(fn(args.GetValue()...)); + }, + typed_params); +#endif +} + +template +struct ArgDescrHasType : std::false_type +{ +}; + +template +struct ArgDescrHasType...> : std::true_type +{ +}; } // detail #endif // JINJA2CPP_NO_DOXYGEN @@ -220,7 +347,7 @@ Value InvokeUserCallable(Fn&& fn, const UserCallableParams& params, ArgDescr&& . * \returns Instance of the properly initialized \ref UserCallable structure */ template -auto MakeCallable(Fn&& f, ArgDescr&& ... ad) +auto MakeCallable(Fn&& f, ArgDescr&&... ad) -> typename std::enable_if::value, UserCallable>::type { return UserCallable { [=, fn = std::forward(f)](const UserCallableParams& params) { @@ -230,6 +357,40 @@ auto MakeCallable(Fn&& f, ArgDescr&& ... ad) }; } +template +auto MakeCallable(Fn&& f, ArgDescr&&... ad) -> typename std::enable_if::value, UserCallable>::type +{ + return UserCallable{ [=, fn = std::forward(f)](const UserCallableParams& params) { return detail::InvokeTypedUserCallable(fn, params, ad...); }, + { ArgInfo(std::forward(ad))... } }; +} + +template +auto MakeCallable(R (*f)(Args...), ArgDescr&&... ad) -> UserCallable +{ + return UserCallable{ [=, fn = f](const UserCallableParams& params) { return detail::InvokeTypedUserCallable(fn, params, ArgInfoT(ad)...); }, + { ArgInfoT(std::forward(ad))... } }; +} + +template +auto MakeCallable(R (T::*f)(Args...), T* obj, ArgDescr&&... ad) -> UserCallable +{ + return UserCallable{ [=, fn = f](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable( + [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); + }, + { ArgInfoT(std::forward(ad))... } }; +} + +template +auto MakeCallable(R (T::*f)(Args...) const, const T* obj, ArgDescr&&... ad) -> UserCallable +{ + return UserCallable{ [=, fn = f](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable( + [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); + }, + { ArgInfoT(std::forward(ad))... } }; +} + /*! * \brief Create user-callable from the function with no params. * @@ -240,12 +401,7 @@ auto MakeCallable(Fn&& f, ArgDescr&& ... ad) template auto MakeCallable(Fn&& f) { - return UserCallable{ - [=, fn = std::forward(f)](const UserCallableParams& params) { - return fn(); - }, - {} - }; + return UserCallable{ [=, fn = std::forward(f)](const UserCallableParams& params) { return fn(); }, {} }; } } // jinja2 diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 86626c08..ff251b62 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -518,6 +518,22 @@ struct ArgInfo , defValue(std::move(defVal)) {} }; +template +struct ArgInfoT : public ArgInfo +{ + using type = T; + + using ArgInfo::ArgInfo; + ArgInfoT(const ArgInfo& info) + : ArgInfo(info) + { + } + ArgInfoT(ArgInfo&& info) noexcept + : ArgInfo(std::move(info)) + { + } +}; + /*! * \brief User-callable descriptor * diff --git a/src/statements.cpp b/src/statements.cpp index 4df184ea..548adfc3 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -1,5 +1,6 @@ -#include "expression_evaluator.h" #include "statements.h" + +#include "expression_evaluator.h" #include "template_impl.h" #include "value_visitors.h" @@ -19,8 +20,8 @@ void ForStatement::Render(OutStream& os, RenderContext& values) RenderLoop(loopVal, os, values, 0); } -void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, - RenderContext &values, int level) { +void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, RenderContext& values, int level) +{ auto& context = values.EnterScope(); InternalValueMap loopVar; @@ -28,17 +29,17 @@ void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, if (m_isRecursive) { loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this, level](const CallParams& params, OutStream& stream, RenderContext& context) { - bool isSucceeded = false; - auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded); - if (!isSucceeded) - return; + bool isSucceeded = false; + auto parsedParams = helpers::ParseCallParams({ { "var", true } }, params, isSucceeded); + if (!isSucceeded) + return; - auto var = parsedParams["var"]; - if (!var) - return; + auto var = parsedParams["var"]; + if (!var) + return; - RenderLoop(var->Evaluate(context), stream, context, level + 1); - }); + RenderLoop(var->Evaluate(context), stream, context, level + 1); + }); loopVar["depth"] = static_cast(level + 1); loopVar["depth0"] = static_cast(level); } @@ -70,8 +71,7 @@ void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, } bool isLast = false; - auto makeIndexedList = [&enumerator, &listSize, &indexedList, &itemIdx, &isLast] - { + auto makeIndexedList = [&enumerator, &listSize, &indexedList, &itemIdx, &isLast] { if (isLast) listSize = itemIdx; @@ -94,11 +94,11 @@ void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, } else { - loopVar["length"s] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& /*params*/, RenderContext& /*context*/) -> InternalValue { - if (!listSize) - makeIndexedList(); - return static_cast(listSize.value()); - }); + loopVar["length"s] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& /*params*/, RenderContext & /*context*/) -> InternalValue { + if (!listSize) + makeIndexedList(); + return static_cast(listSize.value()); + }); } bool loopRendered = false; isLast = !enumerator->MoveNext(); @@ -106,7 +106,7 @@ void ForStatement::RenderLoop(const InternalValue &loopVal, OutStream &os, InternalValue curValue; InternalValue nextValue; loopVar["cycle"s] = static_cast(LoopCycleFn); - for (;!isLast; ++ itemIdx) + for (; !isLast; ++itemIdx) { prevValue = std::move(curValue); if (itemIdx != 0) @@ -164,7 +164,8 @@ ListAdapter ForStatement::CreateFilteredAdapter(const ListAdapter& loopItems, Re { for (auto& varName : m_vars) tempContext[varName] = Subscript(curValue, varName, &values); - } else + } + else { tempContext[m_vars[0]] = curValue; } @@ -217,7 +218,7 @@ void ElseBranchStatement::Render(OutStream& os, RenderContext& values) void SetStatement::AssignBody(InternalValue body, RenderContext& values) { - auto &scope = values.GetCurrentScope(); + auto& scope = values.GetCurrentScope(); if (m_fields.size() == 1) scope[m_fields.front()] = std::move(body); else @@ -268,7 +269,10 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) bool found = false; auto parentTplVal = values.FindValue("$$__parent_template", found); if (!found) + { + m_mainBody->Render(os, values); return; + } bool isConverted = false; auto parentTplsList = ConvertToList(parentTplVal->second, isConverted); @@ -292,14 +296,15 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) } if (!blockRenderer) + { + m_mainBody->Render(os, values); return; - + } auto& scope = innerContext.EnterScope(); 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); - }); + scope["super"] = + Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { m_mainBody->Render(stream, context); }); if (!m_isScoped) scope["$$__parent_template"] = parentTplsList; @@ -310,8 +315,8 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) auto selfMap = GetIf(&globalScope[std::string("self")]); if (!selfMap->HasValue(m_name)) selfMap->SetValue(m_name, MakeWrapped(Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { - Render(stream, context); - }))); + Render(stream, context); + }))); } void BlockStatement::Render(OutStream& os, RenderContext& values) @@ -359,10 +364,7 @@ class ParentTemplateRenderer : public BlocksRenderer p->second->Render(os, values); } - bool HasBlock(const std::string &blockName) override - { - return m_blocks->count(blockName) != 0; - } + bool HasBlock(const std::string& blockName) override { return m_blocks->count(blockName) != 0; } private: std::shared_ptr> m_template; @@ -379,7 +381,8 @@ struct TemplateImplVisitor explicit TemplateImplVisitor(const Fn& fn, bool throwError) : m_fn(fn) , m_throwError(throwError) - {} + { + } template Result operator()(nonstd::expected>, ErrorInfoTpl> tpl) const @@ -388,17 +391,14 @@ struct TemplateImplVisitor { return Result{}; } - else if (!tpl) - { - throw tpl.error(); - } + else if (!tpl) + { + throw tpl.error(); + } return m_fn(tpl.value()); } - Result operator()(EmptyValue) const - { - return Result(); - } + Result operator()(EmptyValue) const { return Result(); } }; template @@ -407,8 +407,8 @@ Result VisitTemplateImpl(Arg&& tpl, bool throwError, Fn&& fn) return visit(TemplateImplVisitor(fn, throwError), tpl); } -template class RendererTpl, typename CharT, typename ... Args> -auto CreateTemplateRenderer(std::shared_ptr> tpl, Args&& ... args) +template class RendererTpl, typename CharT, typename... Args> +auto CreateTemplateRenderer(std::shared_ptr> tpl, Args&&... args) { return std::make_shared>(tpl, std::forward(args)...); } @@ -421,9 +421,8 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) return; } auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName); - auto renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { - return CreateTemplateRenderer(tplPtr, &m_blocks); - }); + auto renderer = + VisitTemplateImpl(tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer(tplPtr, &m_blocks); }); if (renderer) renderer->Render(os, values); } @@ -467,15 +466,13 @@ void IncludeStatement::Render(OutStream& os, RenderContext& values) bool isConverted = false; ListAdapter list = ConvertToList(templateNames, isConverted); - auto doRender = [this, &values, &os](auto&& name) -> bool - { + auto doRender = [this, &values, &os](auto&& name) -> bool { auto tpl = values.GetRendererCallback()->LoadTemplate(name); try { - auto renderer = VisitTemplateImpl(tpl, true, [this](auto tplPtr) { - return CreateTemplateRenderer(tplPtr, m_withContext); - }); + auto renderer = VisitTemplateImpl( + tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer(tplPtr, m_withContext); }); if (renderer) { @@ -536,12 +533,11 @@ class ImportedMacroRenderer : public RendererBase explicit ImportedMacroRenderer(InternalValueMap&& map, bool withContext) : m_importedContext(std::move(map)) , m_withContext(withContext) - {} - - void Render(OutStream& /*os*/, RenderContext& /*values*/) override { } + 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); @@ -576,9 +572,7 @@ void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) if (!m_renderer) { auto tpl = values.GetRendererCallback()->LoadTemplate(name); - m_renderer = VisitTemplateImpl(tpl, true, [](auto tplPtr) { - return CreateTemplateRenderer(tplPtr, true); - }); + m_renderer = VisitTemplateImpl(tpl, true, [](auto tplPtr) { return CreateTemplateRenderer(tplPtr, true); }); } if (!m_renderer) @@ -602,11 +596,11 @@ void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) } ImportNames(values, importedScope, scopeName); - values.GetCurrentScope()[scopeName] = std::static_pointer_cast(std::make_shared(std::move(importedScope), m_withContext)); + 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 +void ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const { InternalValueMap importedNs; @@ -664,9 +658,8 @@ void MacroStatement::Render(OutStream&, RenderContext& values) { PrepareMacroParams(values); - values.GetCurrentScope()[m_name] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { - InvokeMacroRenderer(callParams, stream, context); - }); + values.GetCurrentScope()[m_name] = Callable( + Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(callParams, stream, context); }); } void MacroStatement::InvokeMacroRenderer(const CallParams& callParams, OutStream& stream, RenderContext& context) @@ -700,7 +693,12 @@ void MacroStatement::InvokeMacroRenderer(const CallParams& callParams, OutStream context.ExitScope(); } -void MacroStatement::SetupCallArgs(const std::vector& argsInfo, const CallParams& callParams, RenderContext& context, InternalValueMap& callArgs, InternalValueMap& kwArgs, InternalValueList& varArgs) +void MacroStatement::SetupCallArgs(const std::vector& argsInfo, + const CallParams& callParams, + RenderContext& context, + InternalValueMap& callArgs, + InternalValueMap& kwArgs, + InternalValueList& varArgs) { bool isSucceeded = true; ParsedArguments args = helpers::ParseCallParams(argsInfo, callParams, isSucceeded); @@ -740,9 +738,8 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values) if (hasCallerVal) prevCaller = callerP->second; - curScope["caller"] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { - InvokeMacroRenderer(callParams, stream, context); - }); + curScope["caller"] = Callable( + Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(callParams, stream, context); }); callable->GetStatementCallable()(m_callParams, os, values); @@ -752,10 +749,7 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values) values.GetCurrentScope().erase("caller"); } -void MacroCallStatement::SetupMacroScope(InternalValueMap&) -{ - -} +void MacroCallStatement::SetupMacroScope(InternalValueMap&) {} void DoStatement::Render(OutStream& /*os*/, RenderContext& values) { diff --git a/src/template_parser.h b/src/template_parser.h index 54bcc0ec..1fff8042 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -1,26 +1,25 @@ #ifndef TEMPLATE_PARSER_H #define TEMPLATE_PARSER_H -#include "renderer.h" -#include "template_parser.h" -#include "lexer.h" -#include "lexertk.h" #include "error_handling.h" #include "expression_parser.h" -#include "statements.h" #include "helpers.h" +#include "lexer.h" +#include "lexertk.h" +#include "renderer.h" +#include "statements.h" +#include "template_parser.h" #include "value_visitors.h" #include #include - #include -#include -#include -#include #include +#include #include +#include +#include namespace jinja2 { @@ -40,7 +39,6 @@ struct TokenStrInfo : MultiStringLiteral { return MultiStringLiteral::template GetValue(); } - }; template @@ -76,10 +74,7 @@ struct ParserTraits : public ParserTraitsBase<> } return std::regex(pattern); } - static std::string GetAsString(const std::string& str, CharRange range) - { - return str.substr(range.startOffset, range.size()); - } + static std::string GetAsString(const std::string& str, CharRange range) { return str.substr(range.startOffset, range.size()); } static InternalValue RangeToNum(const std::string& str, CharRange range, Token::Type hint) { char buff[std::max(std::numeric_limits::max_digits10, std::numeric_limits::max_digits10) * 2 + 1]; @@ -166,8 +161,7 @@ struct ParserTraits : public ParserTraitsBase<> struct StatementInfo { - enum Type - { + enum Type { TemplateRoot, IfStatement, ElseIfStatement, @@ -210,14 +204,15 @@ class StatementsParser StatementsParser(const Settings& settings, TemplateEnv* env) : m_settings(settings) , m_env(env) - {} + { + } ParseResult Parse(LexScanner& lexer, StatementInfoList& statementsInfo); private: - ParseResult ParseFor(LexScanner &lexer, StatementInfoList &statementsInfo, const Token& stmtTok); + ParseResult ParseFor(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseEndFor(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); - ParseResult ParseIf(LexScanner &lexer, StatementInfoList &statementsInfo, const Token& stmtTok); + ParseResult ParseIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseElse(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseElIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok); ParseResult ParseEndIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& pos); @@ -284,19 +279,7 @@ class TemplateParser : public LexerHelper } private: - enum - { - RM_Unknown = 0, - RM_ExprBegin = 1, - RM_ExprEnd, - RM_RawBegin, - RM_RawEnd, - RM_StmtBegin, - RM_StmtEnd, - RM_CommentBegin, - RM_CommentEnd, - RM_NewLine - }; + enum { RM_Unknown = 0, RM_ExprBegin = 1, RM_ExprEnd, RM_RawBegin, RM_RawEnd, RM_StmtBegin, RM_StmtEnd, RM_CommentBegin, RM_CommentEnd, RM_NewLine }; struct LineInfo { @@ -304,15 +287,7 @@ class TemplateParser : public LexerHelper unsigned lineNumber; }; - enum class TextBlockType - { - RawText, - Expression, - Statement, - Comment, - LineStatement, - RawBlock - }; + enum class TextBlockType { RawText, Expression, Statement, Comment, LineStatement, RawBlock }; struct TextBlockInfo { @@ -331,9 +306,10 @@ class TemplateParser : public LexerHelper // One line, no customization if (matches == 0) { - CharRange range{0ULL, m_template->size()}; - m_lines.push_back(LineInfo{range, 0}); - m_textBlocks.push_back(TextBlockInfo{range, (!m_template->empty() && m_template->front() == '#') ? TextBlockType::LineStatement : TextBlockType::RawText}); + CharRange range{ 0ULL, m_template->size() }; + m_lines.push_back(LineInfo{ range, 0 }); + m_textBlocks.push_back( + TextBlockInfo{ range, (!m_template->empty() && m_template->front() == '#') ? TextBlockType::LineStatement : TextBlockType::RawText }); return nonstd::expected>(); } @@ -356,14 +332,15 @@ class TemplateParser : public LexerHelper } while (matchBegin != matchEnd); FinishCurrentLine(m_template->size()); - if ( m_currentBlockInfo.type == TextBlockType::RawBlock) + if (m_currentBlockInfo.type == TextBlockType::RawBlock) { - nonstd::expected result = MakeParseError(ErrorCode::ExpectedRawEnd, MakeToken(Token::RawEnd, {m_template->size(), m_template->size() })); + nonstd::expected result = + MakeParseError(ErrorCode::ExpectedRawEnd, MakeToken(Token::RawEnd, { m_template->size(), m_template->size() })); foundErrors.push_back(result.error()); return nonstd::make_unexpected(std::move(foundErrors)); } - FinishCurrentBlock(m_template->size()); + FinishCurrentBlock(m_template->size(), TextBlockType::RawText); if (!foundErrors.empty()) return nonstd::make_unexpected(std::move(foundErrors)); @@ -372,9 +349,9 @@ class TemplateParser : public LexerHelper nonstd::expected ParseRoughMatch(sregex_iterator& curMatch, const sregex_iterator& /*endMatch*/) { auto match = *curMatch; - ++ curMatch; + ++curMatch; unsigned matchType = RM_Unknown; - for (unsigned idx = 1; idx != match.size(); ++ idx) + for (unsigned idx = 1; idx != match.size(); ++idx) { if (match.length(idx) != 0) { @@ -387,98 +364,98 @@ class TemplateParser : public LexerHelper switch (matchType) { - case RM_NewLine: - FinishCurrentLine(match.position()); - m_currentLineInfo.range.startOffset = m_currentLineInfo.range.endOffset + 1; - if (m_currentLineInfo.range.startOffset < m_template->size() && + case RM_NewLine: + FinishCurrentLine(match.position()); + m_currentLineInfo.range.startOffset = m_currentLineInfo.range.endOffset + 1; + if (m_currentLineInfo.range.startOffset < m_template->size() && (m_currentBlockInfo.type == TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::LineStatement)) - { - if (m_currentBlockInfo.type == TextBlockType::LineStatement) { - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = m_currentLineInfo.range.startOffset; + if (m_currentBlockInfo.type == TextBlockType::LineStatement) + { + FinishCurrentBlock(matchStart, TextBlockType::RawText); + m_currentBlockInfo.range.startOffset = m_currentLineInfo.range.startOffset; + } + + if (m_settings.useLineStatements) + m_currentBlockInfo.type = + (*m_template)[m_currentLineInfo.range.startOffset] == '#' ? TextBlockType::LineStatement : TextBlockType::RawText; + else + m_currentBlockInfo.type = 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: - if (m_currentBlockInfo.type == TextBlockType::RawBlock) break; - if (m_currentBlockInfo.type != TextBlockType::RawText) - { - FinishCurrentLine(match.position() + 2); - return MakeParseError(ErrorCode::UnexpectedCommentBegin, MakeToken(Token::CommentBegin, { matchStart, matchStart + 2 })); - } - - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; - m_currentBlockInfo.type = TextBlockType::Comment; - break; + case RM_CommentBegin: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; + if (m_currentBlockInfo.type != TextBlockType::RawText) + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedCommentBegin, MakeToken(Token::CommentBegin, { matchStart, matchStart + 2 })); + } - case RM_CommentEnd: - if (m_currentBlockInfo.type == TextBlockType::RawBlock) + FinishCurrentBlock(matchStart, TextBlockType::Comment); + m_currentBlockInfo.range.startOffset = matchStart + 2; + m_currentBlockInfo.type = TextBlockType::Comment; break; - if (m_currentBlockInfo.type != TextBlockType::Comment) - { - FinishCurrentLine(match.position() + 2); - return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, { matchStart, matchStart + 2 })); - } - FinishCurrentBlock(matchStart); - m_currentBlockInfo.range.startOffset = matchStart + 2; - break; - case RM_ExprBegin: - StartControlBlock(TextBlockType::Expression, matchStart); - break; - case RM_ExprEnd: - if (m_currentBlockInfo.type == TextBlockType::RawText) - { - FinishCurrentLine(match.position() + 2); - return MakeParseError(ErrorCode::UnexpectedExprEnd, MakeToken(Token::ExprEnd, { matchStart, matchStart + 2 })); - } - else if (m_currentBlockInfo.type != TextBlockType::Expression || (*m_template)[match.position() - 1] == '\'') + case RM_CommentEnd: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; + if (m_currentBlockInfo.type != TextBlockType::Comment) + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, { matchStart, matchStart + 2 })); + } + + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText); break; + case RM_ExprBegin: + StartControlBlock(TextBlockType::Expression, matchStart); + break; + case RM_ExprEnd: + if (m_currentBlockInfo.type == TextBlockType::RawText) + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedExprEnd, MakeToken(Token::ExprEnd, { matchStart, matchStart + 2 })); + } + else if (m_currentBlockInfo.type != TextBlockType::Expression || (*m_template)[match.position() - 1] == '\'') + break; - m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); - break; - case RM_StmtBegin: - StartControlBlock(TextBlockType::Statement, matchStart); - break; - case RM_StmtEnd: - if (m_currentBlockInfo.type == TextBlockType::RawText) - { - FinishCurrentLine(match.position() + 2); - return MakeParseError(ErrorCode::UnexpectedStmtEnd, MakeToken(Token::StmtEnd, { matchStart, matchStart + 2 })); - } - else if (m_currentBlockInfo.type != TextBlockType::Statement || (*m_template)[match.position() - 1] == '\'') + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText); + break; + case RM_StmtBegin: + StartControlBlock(TextBlockType::Statement, matchStart); break; + case RM_StmtEnd: + if (m_currentBlockInfo.type == TextBlockType::RawText) + { + FinishCurrentLine(match.position() + 2); + return MakeParseError(ErrorCode::UnexpectedStmtEnd, MakeToken(Token::StmtEnd, { matchStart, matchStart + 2 })); + } + else if (m_currentBlockInfo.type != TextBlockType::Statement || (*m_template)[match.position() - 1] == '\'') + break; - m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); - break; - case RM_RawBegin: - if (m_currentBlockInfo.type == TextBlockType::RawBlock) + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText); break; - else if (m_currentBlockInfo.type != TextBlockType::RawText && m_currentBlockInfo.type != TextBlockType::Comment) - { - FinishCurrentLine(match.position() + match.length()); - return MakeParseError(ErrorCode::UnexpectedRawBegin, MakeToken(Token::RawBegin, {matchStart, matchStart + match.length()})); - } - StartControlBlock(TextBlockType::RawBlock, matchStart); - break; - case RM_RawEnd: - if (m_currentBlockInfo.type == TextBlockType::Comment) + case RM_RawBegin: + if (m_currentBlockInfo.type == TextBlockType::RawBlock) + break; + else if (m_currentBlockInfo.type != TextBlockType::RawText && m_currentBlockInfo.type != TextBlockType::Comment) + { + FinishCurrentLine(match.position() + match.length()); + return MakeParseError(ErrorCode::UnexpectedRawBegin, MakeToken(Token::RawBegin, { matchStart, matchStart + match.length() })); + } + StartControlBlock(TextBlockType::RawBlock, matchStart); + break; + case RM_RawEnd: + if (m_currentBlockInfo.type == TextBlockType::Comment) + break; + else if (m_currentBlockInfo.type != TextBlockType::RawBlock) + { + FinishCurrentLine(match.position() + match.length()); + return MakeParseError(ErrorCode::UnexpectedRawEnd, MakeToken(Token::RawEnd, { matchStart, matchStart + match.length() })); + } + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText); break; - else if (m_currentBlockInfo.type != TextBlockType::RawBlock) - { - FinishCurrentLine(match.position() + match.length()); - return MakeParseError(ErrorCode::UnexpectedRawEnd, MakeToken(Token::RawEnd, {matchStart, matchStart + match.length()})); - } - m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart); - break; } return nonstd::expected(); @@ -488,36 +465,37 @@ class TemplateParser : public LexerHelper { size_t startOffset = matchStart + 2; size_t endOffset = matchStart; - if (m_currentBlockInfo.type != TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::RawBlock ) + if (m_currentBlockInfo.type != TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::RawBlock) return; else - endOffset = StripBlockLeft(m_currentBlockInfo, startOffset, endOffset); + endOffset = StripBlockLeft(m_currentBlockInfo, startOffset, endOffset, blockType == TextBlockType::Expression ? false : m_settings.lstripBlocks); - FinishCurrentBlock(endOffset); + FinishCurrentBlock(endOffset, blockType); if (startOffset < m_template->size()) { - if ((*m_template)[startOffset] == '+' || - (*m_template)[startOffset] == '-') - ++ startOffset; + if ((*m_template)[startOffset] == '+' || (*m_template)[startOffset] == '-') + ++startOffset; } m_currentBlockInfo.type = blockType; - if (blockType==TextBlockType::RawBlock) - startOffset = StripBlockRight(m_currentBlockInfo, matchStart); + if (blockType == TextBlockType::RawBlock) + startOffset = StripBlockRight(m_currentBlockInfo, matchStart, m_settings.trimBlocks); m_currentBlockInfo.range.startOffset = startOffset; } - size_t StripBlockRight(TextBlockInfo& currentBlockInfo, size_t position) + size_t StripBlockRight(TextBlockInfo& currentBlockInfo, size_t position, bool trimBlocks) { - bool doTrim = m_settings.trimBlocks && (m_currentBlockInfo.type == TextBlockType::Statement || m_currentBlockInfo.type == TextBlockType::RawBlock); + bool doTrim = trimBlocks; + // &&(m_currentBlockInfo.type == TextBlockType::Statement || m_currentBlockInfo.type == TextBlockType::Comment || + // m_currentBlockInfo.type == TextBlockType::RawBlock); if (m_currentBlockInfo.type == TextBlockType::RawBlock) { - position+=2; - for(; position < m_template->size(); ++ position) - { + position += 2; + for (; position < m_template->size(); ++position) + { if ('%' == (*m_template)[position]) break; } @@ -534,12 +512,12 @@ class TemplateParser : public LexerHelper if (doTrim) { auto locale = std::locale(); - for (;newPos < m_template->size(); ++ newPos) + for (; newPos < m_template->size(); ++newPos) { auto ch = (*m_template)[newPos]; if (ch == '\n') { - ++ newPos; + ++newPos; break; } if (!std::isspace(ch, locale)) @@ -549,9 +527,8 @@ class TemplateParser : public LexerHelper return newPos; } - size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset) + size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset, bool doStrip) { - bool doStrip = m_settings.lstripBlocks; bool doTotalStrip = false; if (ctrlCharPos < m_template->size()) { @@ -568,11 +545,24 @@ class TemplateParser : public LexerHelper auto locale = std::locale(); auto& tpl = *m_template; - for (; endOffset != currentBlockInfo.range.startOffset && endOffset > 0; -- endOffset) + auto originalOffset = endOffset; + bool sameLine = true; + for (; endOffset != currentBlockInfo.range.startOffset && endOffset > 0; --endOffset) { auto ch = tpl[endOffset - 1]; - if (!std::isspace(ch, locale) || (!doTotalStrip && ch == '\n')) - break; + if (!std::isspace(ch, locale)) + { + if (!sameLine) + break; + + return doTotalStrip ? endOffset : originalOffset; + } + if (ch == '\n') + { + if (!doTotalStrip) + break; + sameLine = false; + } } return endOffset; } @@ -580,7 +570,6 @@ class TemplateParser : public LexerHelper nonstd::expected> DoFineParsing(std::shared_ptr renderers) { std::vector errors; - TextBlockInfo* prevBlock = nullptr; StatementInfoList statementsStack; StatementInfo root = StatementInfo::Create(StatementInfo::TemplateRoot, Token(), renderers); statementsStack.push_back(root); @@ -588,46 +577,42 @@ class TemplateParser : public LexerHelper { auto block = origBlock; if (block.type == TextBlockType::LineStatement) - ++ block.range.startOffset; + ++block.range.startOffset; switch (block.type) { - case TextBlockType::RawBlock: - case TextBlockType::RawText: - { - if (block.range.size() == 0) + case TextBlockType::RawBlock: + case TextBlockType::RawText: + { + if (block.range.size() == 0) + break; + auto range = block.range; + if (range.size() == 0) + break; + auto renderer = std::make_shared(m_template->data() + range.startOffset, range.size()); + statementsStack.back().currentComposition->AddRenderer(renderer); break; - auto range = block.range; - if ((*m_template)[range.startOffset] == '\n' && prevBlock != nullptr && - prevBlock->type != TextBlockType::RawText && prevBlock->type != TextBlockType::Expression) - range.startOffset ++; - if (range.size() == 0) + } + case TextBlockType::Expression: + { + auto parseResult = InvokeParser(block); + if (parseResult) + statementsStack.back().currentComposition->AddRenderer(*parseResult); + else + errors.push_back(parseResult.error()); + break; + } + case TextBlockType::Statement: + case TextBlockType::LineStatement: + { + auto parseResult = InvokeParser(block, statementsStack); + if (!parseResult) + errors.push_back(parseResult.error()); + break; + } + default: break; - auto renderer = std::make_shared(m_template->data() + range.startOffset, range.size()); - statementsStack.back().currentComposition->AddRenderer(renderer); - break; - } - case TextBlockType::Expression: - { - auto parseResult = InvokeParser(block); - if (parseResult) - statementsStack.back().currentComposition->AddRenderer(*parseResult); - else - errors.push_back(parseResult.error()); - break; - } - case TextBlockType::Statement: - case TextBlockType::LineStatement: - { - auto parseResult = InvokeParser(block, statementsStack); - if (!parseResult) - errors.push_back(parseResult.error()); - break; - } - default: - break; } - prevBlock = &origBlock; } if (!errors.empty()) @@ -635,24 +620,26 @@ class TemplateParser : public LexerHelper return nonstd::expected>(); } - template - nonstd::expected InvokeParser(const TextBlockInfo& block, Args&& ... args) + template + nonstd::expected InvokeParser(const TextBlockInfo& block, Args&&... args) { lexertk::generator tokenizer; auto range = block.range; auto start = m_template->data(); if (!tokenizer.process(start + range.startOffset, start + range.endOffset)) - return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, {range.startOffset, range.startOffset + 1})); + return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, { range.startOffset, range.startOffset + 1 })); tokenizer.begin(); - Lexer lexer([&tokenizer, adjust = range.startOffset]() mutable { - lexertk::token tok = tokenizer.next_token(); - tok.position += adjust; - return tok; - }, this); + Lexer lexer( + [&tokenizer, adjust = range.startOffset]() mutable { + lexertk::token tok = tokenizer.next_token(); + tok.position += adjust; + return tok; + }, + this); if (!lexer.Preprocess()) - return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, {range.startOffset, range.startOffset + 1})); + return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, { range.startOffset, range.startOffset + 1 })); P praser(m_settings, m_env); LexScanner scanner(lexer); @@ -727,24 +714,29 @@ class TemplateParser : public LexerHelper return string_t(); } - size_t FinishCurrentBlock(size_t position) + size_t FinishCurrentBlock(size_t position, TextBlockType nextBlockType) { - size_t newPos; + size_t newPos = position; if (m_currentBlockInfo.type == TextBlockType::RawBlock) { size_t currentPosition = position; - position = StripBlockLeft(m_currentBlockInfo, currentPosition+2, currentPosition); - newPos = StripBlockRight(m_currentBlockInfo, currentPosition); + position = StripBlockLeft(m_currentBlockInfo, currentPosition + 2, currentPosition, m_settings.lstripBlocks); + newPos = StripBlockRight(m_currentBlockInfo, currentPosition, m_settings.trimBlocks); } else { - newPos = StripBlockRight(m_currentBlockInfo, position); - if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0) + if (m_currentBlockInfo.type == TextBlockType::RawText) + position = + StripBlockLeft(m_currentBlockInfo, position + 2, position, nextBlockType == TextBlockType::Expression ? false : m_settings.lstripBlocks); + else if (nextBlockType == TextBlockType::RawText) + newPos = StripBlockRight(m_currentBlockInfo, position, m_currentBlockInfo.type == TextBlockType::Expression ? false : m_settings.trimBlocks); + + if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0) { auto ctrlChar = (*m_template)[position - 1]; if (ctrlChar == '+' || ctrlChar == '-') - -- position; + --position; } } @@ -758,13 +750,13 @@ class TemplateParser : public LexerHelper { m_currentLineInfo.range.endOffset = static_cast(position); m_lines.push_back(m_currentLineInfo); - m_currentLineInfo.lineNumber ++; + m_currentLineInfo.lineNumber++; } void OffsetToLinePos(size_t offset, unsigned& line, unsigned& col) { - auto p = std::find_if(m_lines.begin(), m_lines.end(), [offset](const LineInfo& info) { - return offset >= info.range.startOffset && offset < info.range.endOffset;}); + auto p = std::find_if( + m_lines.begin(), m_lines.end(), [offset](const LineInfo& info) { return offset >= info.range.startOffset && offset < info.range.endOffset; }); if (p == m_lines.end()) { @@ -786,10 +778,10 @@ class TemplateParser : public LexerHelper if (line == 0 && col == 0) return string_t(); - -- line; - -- col; + --line; + --col; - auto toCharT = [](char ch) {return static_cast(ch);}; + auto toCharT = [](char ch) { return static_cast(ch); }; auto& lineInfo = m_lines[line]; std::basic_ostringstream os; @@ -811,11 +803,11 @@ class TemplateParser : public LexerHelper if (col < spacePrefixLen) { - for (unsigned i = 0; i < col; ++ i) + for (unsigned i = 0; i < col; ++i) os << toCharT(' '); os << toCharT('^'); - for (int i = 0; i < tailLen; ++ i) + for (int i = 0; i < tailLen; ++i) os << toCharT('-'); return os.str(); } @@ -825,29 +817,25 @@ class TemplateParser : public LexerHelper if (actualHeadLen == headLen) { - for (std::size_t i = 0; i < col - actualHeadLen - spacePrefixLen; ++ i) + for (std::size_t i = 0; i < col - actualHeadLen - spacePrefixLen; ++i) os << toCharT(' '); } - for (int i = 0; i < actualHeadLen; ++ i) + for (int i = 0; i < actualHeadLen; ++i) os << toCharT('-'); os << toCharT('^'); - for (int i = 0; i < tailLen; ++ i) + for (int i = 0; i < tailLen; ++i) os << toCharT('-'); return os.str(); } // LexerHelper interface - std::string GetAsString(const CharRange& range) override - { - return traits_t::GetAsString(*m_template, range); - } + std::string GetAsString(const CharRange& range) override { return traits_t::GetAsString(*m_template, range); } InternalValue GetAsValue(const CharRange& range, Token::Type type) override { if (type == Token::String) { - auto rawValue = CompileEscapes( - m_template->substr(range.startOffset, range.size())); + auto rawValue = CompileEscapes(m_template->substr(range.startOffset, range.size())); return InternalValue(std::move(rawValue)); } if (type == Token::IntegerNum || type == Token::FloatNum) @@ -865,7 +853,7 @@ class TemplateParser : public LexerHelper return Keyword::Unknown; auto& match = *matchBegin; - for (size_t idx = 1; idx != match.size(); ++ idx) + for (size_t idx = 1; idx != match.size(); ++idx) { if (match.length(idx) != 0) { @@ -875,10 +863,8 @@ class TemplateParser : public LexerHelper return Keyword::Unknown; } - char GetCharAt(size_t /*pos*/) override - { - return '\0'; - } + char GetCharAt(size_t /*pos*/) override { return '\0'; } + private: const string_t* m_template; std::string m_templateName; @@ -894,122 +880,122 @@ class TemplateParser : public LexerHelper template KeywordsInfo ParserTraitsBase::s_keywordsInfo[41] = { - {UNIVERSAL_STR("for"), Keyword::For}, - {UNIVERSAL_STR("endfor"), Keyword::Endfor}, - {UNIVERSAL_STR("in"), Keyword::In}, - {UNIVERSAL_STR("if"), Keyword::If}, - {UNIVERSAL_STR("else"), Keyword::Else}, - {UNIVERSAL_STR("elif"), Keyword::ElIf}, - {UNIVERSAL_STR("endif"), Keyword::EndIf}, - {UNIVERSAL_STR("or"), Keyword::LogicalOr}, - {UNIVERSAL_STR("and"), Keyword::LogicalAnd}, - {UNIVERSAL_STR("not"), Keyword::LogicalNot}, - {UNIVERSAL_STR("is"), Keyword::Is}, - {UNIVERSAL_STR("block"), Keyword::Block}, - {UNIVERSAL_STR("endblock"), Keyword::EndBlock}, - {UNIVERSAL_STR("extends"), Keyword::Extends}, - {UNIVERSAL_STR("macro"), Keyword::Macro}, - {UNIVERSAL_STR("endmacro"), Keyword::EndMacro}, - {UNIVERSAL_STR("call"), Keyword::Call}, - {UNIVERSAL_STR("endcall"), Keyword::EndCall}, - {UNIVERSAL_STR("filter"), Keyword::Filter}, - {UNIVERSAL_STR("endfilter"), Keyword::EndFilter}, - {UNIVERSAL_STR("set"), Keyword::Set}, - {UNIVERSAL_STR("endset"), Keyword::EndSet}, - {UNIVERSAL_STR("include"), Keyword::Include}, - {UNIVERSAL_STR("import"), Keyword::Import}, - {UNIVERSAL_STR("true"), Keyword::True}, - {UNIVERSAL_STR("false"), Keyword::False}, - {UNIVERSAL_STR("True"), Keyword::True}, - {UNIVERSAL_STR("False"), Keyword::False}, - {UNIVERSAL_STR("none"), Keyword::None}, - {UNIVERSAL_STR("None"), Keyword::None}, - {UNIVERSAL_STR("recursive"), Keyword::Recursive}, - {UNIVERSAL_STR("scoped"), Keyword::Scoped}, - {UNIVERSAL_STR("with"), Keyword::With}, - {UNIVERSAL_STR("endwith"), Keyword::EndWith}, - {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}, - {UNIVERSAL_STR("do"), Keyword::Do}, + { UNIVERSAL_STR("for"), Keyword::For }, + { UNIVERSAL_STR("endfor"), Keyword::Endfor }, + { UNIVERSAL_STR("in"), Keyword::In }, + { UNIVERSAL_STR("if"), Keyword::If }, + { UNIVERSAL_STR("else"), Keyword::Else }, + { UNIVERSAL_STR("elif"), Keyword::ElIf }, + { UNIVERSAL_STR("endif"), Keyword::EndIf }, + { UNIVERSAL_STR("or"), Keyword::LogicalOr }, + { UNIVERSAL_STR("and"), Keyword::LogicalAnd }, + { UNIVERSAL_STR("not"), Keyword::LogicalNot }, + { UNIVERSAL_STR("is"), Keyword::Is }, + { UNIVERSAL_STR("block"), Keyword::Block }, + { UNIVERSAL_STR("endblock"), Keyword::EndBlock }, + { UNIVERSAL_STR("extends"), Keyword::Extends }, + { UNIVERSAL_STR("macro"), Keyword::Macro }, + { UNIVERSAL_STR("endmacro"), Keyword::EndMacro }, + { UNIVERSAL_STR("call"), Keyword::Call }, + { UNIVERSAL_STR("endcall"), Keyword::EndCall }, + { UNIVERSAL_STR("filter"), Keyword::Filter }, + { UNIVERSAL_STR("endfilter"), Keyword::EndFilter }, + { UNIVERSAL_STR("set"), Keyword::Set }, + { UNIVERSAL_STR("endset"), Keyword::EndSet }, + { UNIVERSAL_STR("include"), Keyword::Include }, + { UNIVERSAL_STR("import"), Keyword::Import }, + { UNIVERSAL_STR("true"), Keyword::True }, + { UNIVERSAL_STR("false"), Keyword::False }, + { UNIVERSAL_STR("True"), Keyword::True }, + { UNIVERSAL_STR("False"), Keyword::False }, + { UNIVERSAL_STR("none"), Keyword::None }, + { UNIVERSAL_STR("None"), Keyword::None }, + { UNIVERSAL_STR("recursive"), Keyword::Recursive }, + { UNIVERSAL_STR("scoped"), Keyword::Scoped }, + { UNIVERSAL_STR("with"), Keyword::With }, + { UNIVERSAL_STR("endwith"), Keyword::EndWith }, + { 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 }, + { UNIVERSAL_STR("do"), Keyword::Do }, }; template std::unordered_map ParserTraitsBase::s_tokens = { - {Token::Unknown, UNIVERSAL_STR("<>")}, - {Token::Lt, UNIVERSAL_STR("<")}, - {Token::Gt, UNIVERSAL_STR(">")}, - {Token::Plus, UNIVERSAL_STR("+")}, - {Token::Minus, UNIVERSAL_STR("-")}, - {Token::Percent, UNIVERSAL_STR("%")}, - {Token::Mul, UNIVERSAL_STR("*")}, - {Token::Div, UNIVERSAL_STR("/")}, - {Token::LBracket, UNIVERSAL_STR("(")}, - {Token::RBracket, UNIVERSAL_STR(")")}, - {Token::LSqBracket, UNIVERSAL_STR("[")}, - {Token::RSqBracket, UNIVERSAL_STR("]")}, - {Token::LCrlBracket, UNIVERSAL_STR("{")}, - {Token::RCrlBracket, UNIVERSAL_STR("}")}, - {Token::Assign, UNIVERSAL_STR("=")}, - {Token::Comma, UNIVERSAL_STR(",")}, - {Token::Eof, UNIVERSAL_STR("<>")}, - {Token::Equal, UNIVERSAL_STR("==")}, - {Token::NotEqual, UNIVERSAL_STR("!=")}, - {Token::LessEqual, UNIVERSAL_STR("<=")}, - {Token::GreaterEqual, UNIVERSAL_STR(">=")}, - {Token::StarStar, UNIVERSAL_STR("**")}, - {Token::DashDash, UNIVERSAL_STR("//")}, - {Token::LogicalOr, UNIVERSAL_STR("or")}, - {Token::LogicalAnd, UNIVERSAL_STR("and")}, - {Token::LogicalNot, UNIVERSAL_STR("not")}, - {Token::MulMul, UNIVERSAL_STR("**")}, - {Token::DivDiv, UNIVERSAL_STR("//")}, - {Token::True, UNIVERSAL_STR("true")}, - {Token::False, UNIVERSAL_STR("false")}, - {Token::None, UNIVERSAL_STR("none")}, - {Token::In, UNIVERSAL_STR("in")}, - {Token::Is, UNIVERSAL_STR("is")}, - {Token::For, UNIVERSAL_STR("for")}, - {Token::Endfor, UNIVERSAL_STR("endfor")}, - {Token::If, UNIVERSAL_STR("if")}, - {Token::Else, UNIVERSAL_STR("else")}, - {Token::ElIf, UNIVERSAL_STR("elif")}, - {Token::EndIf, UNIVERSAL_STR("endif")}, - {Token::Block, UNIVERSAL_STR("block")}, - {Token::EndBlock, UNIVERSAL_STR("endblock")}, - {Token::Extends, UNIVERSAL_STR("extends")}, - {Token::Macro, UNIVERSAL_STR("macro")}, - {Token::EndMacro, UNIVERSAL_STR("endmacro")}, - {Token::Call, UNIVERSAL_STR("call")}, - {Token::EndCall, UNIVERSAL_STR("endcall")}, - {Token::Filter, UNIVERSAL_STR("filter")}, - {Token::EndFilter, UNIVERSAL_STR("endfilter")}, - {Token::Set, UNIVERSAL_STR("set")}, - {Token::EndSet, UNIVERSAL_STR("endset")}, - {Token::Include, UNIVERSAL_STR("include")}, - {Token::Import, UNIVERSAL_STR("import")}, - {Token::Recursive, UNIVERSAL_STR("recursive")}, - {Token::Scoped, UNIVERSAL_STR("scoped")}, - {Token::With, UNIVERSAL_STR("with")}, - {Token::EndWith, UNIVERSAL_STR("endwith")}, - {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::Do, UNIVERSAL_STR("do")}, - {Token::RawBegin, UNIVERSAL_STR("{% raw %}")}, - {Token::RawEnd, UNIVERSAL_STR("{% endraw %}")}, - {Token::CommentBegin, UNIVERSAL_STR("{#")}, - {Token::CommentEnd, UNIVERSAL_STR("#}")}, - {Token::StmtBegin, UNIVERSAL_STR("{%")}, - {Token::StmtEnd, UNIVERSAL_STR("%}")}, - {Token::ExprBegin, UNIVERSAL_STR("{{")}, - {Token::ExprEnd, UNIVERSAL_STR("}}")}, + { Token::Unknown, UNIVERSAL_STR("<>") }, + { Token::Lt, UNIVERSAL_STR("<") }, + { Token::Gt, UNIVERSAL_STR(">") }, + { Token::Plus, UNIVERSAL_STR("+") }, + { Token::Minus, UNIVERSAL_STR("-") }, + { Token::Percent, UNIVERSAL_STR("%") }, + { Token::Mul, UNIVERSAL_STR("*") }, + { Token::Div, UNIVERSAL_STR("/") }, + { Token::LBracket, UNIVERSAL_STR("(") }, + { Token::RBracket, UNIVERSAL_STR(")") }, + { Token::LSqBracket, UNIVERSAL_STR("[") }, + { Token::RSqBracket, UNIVERSAL_STR("]") }, + { Token::LCrlBracket, UNIVERSAL_STR("{") }, + { Token::RCrlBracket, UNIVERSAL_STR("}") }, + { Token::Assign, UNIVERSAL_STR("=") }, + { Token::Comma, UNIVERSAL_STR(",") }, + { Token::Eof, UNIVERSAL_STR("<>") }, + { Token::Equal, UNIVERSAL_STR("==") }, + { Token::NotEqual, UNIVERSAL_STR("!=") }, + { Token::LessEqual, UNIVERSAL_STR("<=") }, + { Token::GreaterEqual, UNIVERSAL_STR(">=") }, + { Token::StarStar, UNIVERSAL_STR("**") }, + { Token::DashDash, UNIVERSAL_STR("//") }, + { Token::LogicalOr, UNIVERSAL_STR("or") }, + { Token::LogicalAnd, UNIVERSAL_STR("and") }, + { Token::LogicalNot, UNIVERSAL_STR("not") }, + { Token::MulMul, UNIVERSAL_STR("**") }, + { Token::DivDiv, UNIVERSAL_STR("//") }, + { Token::True, UNIVERSAL_STR("true") }, + { Token::False, UNIVERSAL_STR("false") }, + { Token::None, UNIVERSAL_STR("none") }, + { Token::In, UNIVERSAL_STR("in") }, + { Token::Is, UNIVERSAL_STR("is") }, + { Token::For, UNIVERSAL_STR("for") }, + { Token::Endfor, UNIVERSAL_STR("endfor") }, + { Token::If, UNIVERSAL_STR("if") }, + { Token::Else, UNIVERSAL_STR("else") }, + { Token::ElIf, UNIVERSAL_STR("elif") }, + { Token::EndIf, UNIVERSAL_STR("endif") }, + { Token::Block, UNIVERSAL_STR("block") }, + { Token::EndBlock, UNIVERSAL_STR("endblock") }, + { Token::Extends, UNIVERSAL_STR("extends") }, + { Token::Macro, UNIVERSAL_STR("macro") }, + { Token::EndMacro, UNIVERSAL_STR("endmacro") }, + { Token::Call, UNIVERSAL_STR("call") }, + { Token::EndCall, UNIVERSAL_STR("endcall") }, + { Token::Filter, UNIVERSAL_STR("filter") }, + { Token::EndFilter, UNIVERSAL_STR("endfilter") }, + { Token::Set, UNIVERSAL_STR("set") }, + { Token::EndSet, UNIVERSAL_STR("endset") }, + { Token::Include, UNIVERSAL_STR("include") }, + { Token::Import, UNIVERSAL_STR("import") }, + { Token::Recursive, UNIVERSAL_STR("recursive") }, + { Token::Scoped, UNIVERSAL_STR("scoped") }, + { Token::With, UNIVERSAL_STR("with") }, + { Token::EndWith, UNIVERSAL_STR("endwith") }, + { 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::Do, UNIVERSAL_STR("do") }, + { Token::RawBegin, UNIVERSAL_STR("{% raw %}") }, + { Token::RawEnd, UNIVERSAL_STR("{% endraw %}") }, + { Token::CommentBegin, UNIVERSAL_STR("{#") }, + { Token::CommentEnd, UNIVERSAL_STR("#}") }, + { Token::StmtBegin, UNIVERSAL_STR("{%") }, + { Token::StmtEnd, UNIVERSAL_STR("%}") }, + { Token::ExprBegin, UNIVERSAL_STR("{{") }, + { Token::ExprEnd, UNIVERSAL_STR("}}") }, }; } // jinga2 diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index f7d28f4c..05823ed8 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -30,6 +30,7 @@ R"(Hello World {#Comment to skip #} from Parser!)", R"(Hello World + from Parser!)") { } @@ -40,6 +41,7 @@ R"(Hello World skip #} from Parser!)", R"(Hello World + from Parser!)") { } @@ -55,10 +57,252 @@ skip #} skip #} from Parser!)", R"(Hello World + + from Parser!)") { } +TEST(BasicTests, StripSpaces_None) +{ + std::string source = R"(Hello World +# + {{ ' -expr- ' }} +# + {% if true %}{{ ' -stmt- ' }}{% endif %} +# + {# comment 1 #}{{ ' -comm- ' }}{# comment 2 #} +# + i = {{ '-expr- ' }} +# + i = {% if true %}{{ '-stmt- ' }}{% endif %} +# + i = {# comment 1 #}{{ '-comm- ' }}{# comment 2 #} +# +{% for i in range(3) %} + #### + i = {{i}} + #### +{% endfor %} + #### +from Parser!)"; + + TemplateEnv env; + env.GetSettings().lstripBlocks = false; + env.GetSettings().trimBlocks = false; + Template tpl(&env); + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}).value(); + std::cout << result << std::endl; + std::string expectedResult = "Hello World\n" + "#\n" + " -expr- \n" + "#\n" + " -stmt- \n" + "#\n" + " -comm- \n" + "#\n" + " i = -expr- \n" + "#\n" + " i = -stmt- \n" + "#\n" + " i = -comm- \n" + "#\n" + "\n" + " ####\n" + " i = 0\n" + " ####\n" + "\n" + " ####\n" + " i = 1\n" + " ####\n" + "\n" + " ####\n" + " i = 2\n" + " ####\n" + "\n" + " ####\n" + "from Parser!"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripSpaces_LStripBlocks) +{ + std::string source = R"(Hello World +# + {{ ' -expr- ' }} +# + {% if true %}{{ ' -stmt- ' }}{% endif %} +# + {# comment 1 #}{{ ' -comm- ' }}{# comment 2 #} +# + i = {{ '-expr- ' }} +# + i = {% if true %}{{ '-stmt- ' }}{% endif %} +# + i = {# comment 1 #}{{ '-comm- ' }}{# comment 2 #} +# +{% for i in range(3) %} + #### + i = {{i}} + #### +{% endfor %} + #### +from Parser!)"; + + TemplateEnv env; + env.GetSettings().lstripBlocks = true; + env.GetSettings().trimBlocks = false; + Template tpl(&env); + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}).value(); + std::cout << result << std::endl; + std::string expectedResult = "Hello World\n" + "#\n" + " -expr- \n" + "#\n" + " -stmt- \n" + "#\n" + " -comm- \n" + "#\n" + " i = -expr- \n" + "#\n" + " i = -stmt- \n" + "#\n" + " i = -comm- \n" + "#\n" + "\n" + " ####\n" + " i = 0\n" + " ####\n" + "\n" + " ####\n" + " i = 1\n" + " ####\n" + "\n" + " ####\n" + " i = 2\n" + " ####\n" + "\n" + " ####\n" + "from Parser!"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripSpaces_TrimBlocks) +{ + std::string source = R"(Hello World +# + {{ ' -expr- ' }} +# + {% if true %}{{ ' -stmt- ' }}{% endif %} +# + {# comment 1 #}{{ ' -comm- ' }}{# comment 2 #} +# + i = {{ '-expr- ' }} +# + i = {% if true %}{{ '-stmt- ' }}{% endif %} +# + i = {# comment 1 #}{{ '-comm- ' }}{# comment 2 #} +# +{% for i in range(3) %} + #### + i = {{i}} + #### +{% endfor %} + #### +from Parser!)"; + + TemplateEnv env; + env.GetSettings().lstripBlocks = false; + env.GetSettings().trimBlocks = true; + Template tpl(&env); + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}).value(); + std::cout << result << std::endl; + std::string expectedResult = "Hello World\n" + "#\n" + " -expr- \n" + "#\n" + " -stmt- #\n" + " -comm- #\n" + " i = -expr- \n" + "#\n" + " i = -stmt- #\n" + " i = -comm- #\n" + " ####\n" + " i = 0\n" + " ####\n" + " ####\n" + " i = 1\n" + " ####\n" + " ####\n" + " i = 2\n" + " ####\n" + " ####\n" + "from Parser!"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + +TEST(BasicTests, StripSpaces_LStripBlocks_TrimBlocks) +{ + std::string source = R"(Hello World +# + {{ ' -expr- ' }} +# + {% if true %}{{ ' -stmt- ' }}{% endif %} +# + {# comment 1 #}{{ ' -comm- ' }}{# comment 2 #} +# + i = {{ '-expr- ' }} +# + i = {% if true %}{{ '-stmt- ' }}{% endif %} +# + i = {# comment 1 #}{{ '-comm- ' }}{# comment 2 #} +# +{% for i in range(3) %} + #### + i = {{i}} + #### +{% endfor %} + #### +from Parser!)"; + + TemplateEnv env; + env.GetSettings().lstripBlocks = true; + env.GetSettings().trimBlocks = true; + Template tpl(&env); + ASSERT_TRUE(tpl.Load(source)); + + std::string result = tpl.RenderAsString(ValuesMap{}).value(); + std::cout << result << std::endl; + std::string expectedResult = "Hello World\n" + "#\n" + " -expr- \n" + "#\n" + " -stmt- #\n" + " -comm- #\n" + " i = -expr- \n" + "#\n" + " i = -stmt- #\n" + " i = -comm- #\n" + " ####\n" + " i = 0\n" + " ####\n" + " ####\n" + " i = 1\n" + " ####\n" + " ####\n" + " i = 2\n" + " ####\n" + " ####\n" + "from Parser!"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} + TEST(BasicTests, StripLSpaces_1) { std::string source = R"(Hello World @@ -350,7 +594,8 @@ TEST(BasicTests, TrimSpaces_8) std::string result = tpl.RenderAsString(ValuesMap{}).value(); std::cout << result << std::endl; - std::string expectedResult = R"(Hello World> -- < + std::string expectedResult = R"(Hello World +> -- < from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index 23b92d82..efd1b1da 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -126,7 +126,8 @@ TEST(ExpressionTest, DoStatement) std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( -Outer ValueInner Value +Outer Value +Inner Value )"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); diff --git a/test/extends_test.cpp b/test/extends_test.cpp index 1ec421c9..92eed789 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -59,7 +59,7 @@ TEST_F(ExtendsTest, TwoLevelBlockExtends) EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; - expectedResult = "Hello World! ->Extended block!<-"; + expectedResult = "Hello World! ->Extended block!=>innerB1 content<=<-"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); std::string result2 = tpl2.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result2 << std::endl; @@ -95,7 +95,7 @@ TEST_F(ExtendsTest, SuperBlocksExtends) std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; - std::string expectedResult = "Hello World! -><- -><-"; + std::string expectedResult = "Hello World! ->=>block b1<=<- -><-"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; @@ -118,15 +118,19 @@ TEST_F(ExtendsTest, SuperAndSelfBlocksExtends) std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; - std::string expectedResult = R"(Hello World!-><- ---><----><---><- + std::string expectedResult = R"(Hello World! +->=>block b1 - first entry<=<- +--><----><-- +-><- --><----><-- )"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); 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!<- + expectedResult = R"(Hello World! +->Extended block b1!=>block b1 - first entry<=<- +-->Extended block b1!=>block b1 - first entry<=<----><-- +->Extended block b2!<- -->Extended block b1!=>block b1 - second entry<=<---->Extended block b2!<-- )"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -151,15 +155,19 @@ TEST_F(ExtendsTest, InnerBlocksExtends) std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; - std::string expectedResult = R"(Hello World!-><- ---><----><---><- + std::string expectedResult = R"(Hello World! +->=>block b1 - first entry<=<- +--><----><-- +-><- --><----><-- )"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); 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!<- + 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!<- -->Extended block b1!=>block b1 - second entry<=###Extended innerB1 block second entry!###<---->Extended block b2!<-- )"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); @@ -206,12 +214,15 @@ Some Stuff std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << baseResult << std::endl; - std::string expectedResult = "Hello World!\n"; + std::string expectedResult = "Hello World!\n\n\n\n"; EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); std::cout << result << std::endl; expectedResult = R"(Hello World! --><-->SCOPEDMACROTEXT<-)"; + +-><- +->SCOPEDMACROTEXT<- +)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 54365380..874914a2 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -50,9 +50,9 @@ TEST_P(FilterGroupByTest, Test) std::string source = R"( {% for grouper, list in )" + testParam.tpl + R"( %}grouper: {{grouper | pprint }} -{% for i in list %} +{%- for i in list %} {'intValue': {{i.intValue}}, 'dblValue': {{i.dblValue}}, 'boolValue': {{i.boolValue}}, 'strValue': '{{i.strValue}}', 'wstrValue': ''} -{% endfor %} +{%- endfor %} {% endfor %})"; PerformBothTests(source, testParam.result, params); @@ -68,6 +68,7 @@ R"( )", //------------- R"( + HELLO WORLD! STR1, STR2, STR3 )" @@ -83,7 +84,9 @@ MULTISTR_TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest, )", //-------- R"( + STR1->STR2->STR3 + )" ) { diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index e3057377..7b4f032a 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -21,9 +21,13 @@ a[{{i}}] = image[{{i}}]; )", //-------- R"( + a[0] = image[0]; + a[1] = image[1]; + a[2] = image[2]; + )" ) { @@ -40,9 +44,13 @@ a[{{i}}] = image[{{i}}]; )", //--------- R"( + a[0] = image[0]; + a[1] = image[1]; + a[2] = image[2]; + )" ) { @@ -58,9 +66,17 @@ a[{{i}}] = image[{{var}}]; )", //--------- R"( + + + a[0] = image[0]; + + a[1] = image[1]; + + a[2] = image[2]; + )" ) { @@ -74,6 +90,7 @@ a[{{i}}] = image[{{i}}]; )", //--------- R"( + )" ) { @@ -90,9 +107,13 @@ a[{{i}}] = image[{{i}}]; )", //---------- R"( + a[0] = image[0]; + a[1] = image[1]; + a[2] = image[2]; + )" ) { @@ -138,11 +159,17 @@ a[{{i}}] = image[{{loop.cycle(2, 4, 6)}}]; )", //----------- R"( + a[0] = image[2]; + a[1] = image[4]; + a[2] = image[6]; + a[3] = image[2]; + a[4] = image[4]; + )" ) { @@ -156,11 +183,17 @@ a[{{i}}] = image[{{loop.cycle("a", "b", "c")}}]; )", //-------- R"( + a[0] = image[a]; + a[1] = image[b]; + a[2] = image[c]; + a[3] = image[a]; + a[4] = image[b]; + )" ) { @@ -174,11 +207,17 @@ a[{{i}}] = image[{{i}}]; )", //--------- R"( + a[0] = image[0]; + a[2] = image[2]; + a[4] = image[4]; + a[6] = image[6]; + a[8] = image[8]; + )" ) { @@ -199,8 +238,12 @@ No indexes given )", //------- R"( + No indexes given + + No indexes given + )" ) { @@ -214,9 +257,13 @@ R"( )", //----------- R"( + 0 length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=2; + 2 length=3, index=2, index0=1, first=false, last=false, previtem=0, nextitem=4; + 4 length=3, index=3, index0=2, first=false, last=true, previtem=2, nextitem=; + )" ) { @@ -233,9 +280,13 @@ length={{loop.length}}, index={{loop.index}}, index0={{loop.index0}}, first={{lo )", //-------------- R"( + length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=1; + length=3, index=2, index0=1, first=false, last=false, previtem=0, nextitem=2; + length=3, index=3, index0=2, first=false, last=true, previtem=1, nextitem=; + )" ) { @@ -252,9 +303,13 @@ a[{{i}}] = "{{name}}_{{loop.index0}}"; )", //--------- R"( + a[1] = "image1_0"; + a[2] = "image2_1"; + a[3] = "image3_2"; + )") { params = { @@ -279,12 +334,16 @@ R"( a[0] = image[0]; b[0] = image[0]; b[1] = image[1]; + a[1] = image[1]; b[0] = image[0]; b[1] = image[1]; + a[2] = image[2]; b[0] = image[0]; b[1] = image[1]; + + )") { params = { @@ -315,8 +374,7 @@ R"( {% for i in items recursive %}{{i.name}}({{ loop.depth }}-{{ loop.depth0 }}) -> {{loop(i.children)}}{% endfor %} )", //--------- -R"( -root1(1-0) -> child1_1(2-1) -> child1_2(2-1) -> child1_3(2-1) -> root2(1-0) -> child2_1(2-1) -> child2_2(2-1) -> child2_3(2-1) -> root3(1-0) -> child3_1(2-1) -> child3_2(2-1) -> child3_3(2-1) -> )" +"\n\nroot1(1-0) -> child1_1(2-1) -> child1_2(2-1) -> child1_3(2-1) -> root2(1-0) -> child2_1(2-1) -> child2_2(2-1) -> child2_3(2-1) -> root3(1-0) -> child3_1(2-1) -> child3_2(2-1) -> child3_3(2-1) -> \n" ) { } @@ -330,7 +388,9 @@ R"( //---------- R"( none ->10<>20<>30<>40<>50<>60<>70<>80<>90<)" +>10<>20<>30<>40<>50<>60<>70<>80<>90< + +)" ) { params = { @@ -363,7 +423,9 @@ TEST_F(ForLoopTestSingle, GenericListTest_InputIterator) std::string expectedResult = R"( none ->10<>20<>30<>40<>50<>60<>70<>80<>90<)"; +>10<>20<>30<>40<>50<>60<>70<>80<>90< + +)"; BasicTemplateRenderer::ExecuteTest(source, expectedResult, params, "Narrow version"); } @@ -383,7 +445,9 @@ TEST_F(ForLoopTestSingle, GenericListTest_ForwardIterator) std::string expectedResult = R"( none ->10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)"; +>10<>20<>30<>40<>50<>60<>70<>80<>90< +>10<>20<>30<>40<>50<>60<>70<>80<>90< +)"; PerformBothTests(source, expectedResult, params); } @@ -403,7 +467,9 @@ TEST_F(ForLoopTestSingle, GenericListTest_RandomIterator) std::string expectedResult = R"( 10 ->10<>20<>30<>40<>50<>60<>70<>80<>90<>10<>20<>30<>40<>50<>60<>70<>80<>90<)"; +>10<>20<>30<>40<>50<>60<>70<>80<>90< +>10<>20<>30<>40<>50<>60<>70<>80<>90< +)"; PerformBothTests(source, expectedResult, params); } diff --git a/test/if_test.cpp b/test/if_test.cpp index 26e7be89..3d48b348 100644 --- a/test/if_test.cpp +++ b/test/if_test.cpp @@ -26,7 +26,9 @@ Hello from Jinja template! std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( + Hello from Jinja template! + )"; EXPECT_EQ(expectedResult, result); } @@ -52,7 +54,9 @@ Else branch triggered! std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( + Else branch triggered! + )"; EXPECT_EQ(expectedResult, result); } @@ -82,7 +86,9 @@ ElseIf branch triggered! std::string result = tpl.RenderAsString(params).value(); std::cout << result << std::endl; std::string expectedResult = R"( + ElseIf 2 branch triggered! + )"; EXPECT_EQ(expectedResult, result); } diff --git a/test/includes_test.cpp b/test/includes_test.cpp index 9048a4ae..57942641 100644 --- a/test/includes_test.cpp +++ b/test/includes_test.cpp @@ -143,5 +143,5 @@ R"({% macro outer(o) %} {%- endmacro %} {{ outer("FOO") }})"); - EXPECT_EQ("(FOO)", result); + EXPECT_EQ("\n\n\n\n(FOO)\n", result); } diff --git a/test/macro_test.cpp b/test/macro_test.cpp index 47ea14cf..3ec2f176 100644 --- a/test/macro_test.cpp +++ b/test/macro_test.cpp @@ -19,7 +19,10 @@ Hello World! )", //------------- R"( + + Hello World! + Hello World! )" @@ -37,7 +40,10 @@ R"( )", //----------- R"( + + -->Hello<-- + -->World!<-- )" @@ -47,13 +53,14 @@ R"( } MULTISTR_TEST(MacroTest, - OneParamRecursiveMacro, - R"( +OneParamRecursiveMacro, +R"( {% macro fib(param) %}{{ 1 if param == 1 else (fib(param - 1) | int + param) }}{% endmacro %} {{ fib(10) }} )", - //----------- - R"( +//----------- +R"( + 55 )") { @@ -69,7 +76,10 @@ R"( )", //-------------- R"( + + -->Hello<-- + -->World!<-- )" @@ -92,8 +102,18 @@ R"( )", //----------- R"( + + + + + + -->-->Some Value -> Hello World<--<-- -->-->HELLO WORLD<--<-- + + + + -->-->Some ValueWorld! -> Hello World<--<-- -->-->HELLO WORLD<--<-- @@ -116,6 +136,8 @@ kwargs: {{ kwargs | pprint }} )", //----------- R"( + + name: test arguments: ['param1', 'param2', 'param3'] defaults: ['Hello', none, 'World'] @@ -137,7 +159,10 @@ Hello World! -> {{ caller() }} <- )", //----------------- R"( + + Hello World! -> Message from caller <- + )" ) { @@ -153,7 +178,10 @@ R"( )", //------------ R"( + + -> HELLO WORLD <- + )" ) { @@ -169,7 +197,10 @@ R"( )", //------------- R"( + + Hello World >>> -> hello world <--> HELLO WORLD <- + )" ) { @@ -189,11 +220,14 @@ kwargs: {{ kwargs | pprint }} )", //-------------- R"( + + name: $call$ arguments: ['param1', 'param2', 'param3'] defaults: ['Hello', none, 'World'] varargs: [4, 6] kwargs: {'extraValue': 5} + )" ) { diff --git a/test/statements_tets.cpp b/test/statements_tets.cpp index 85a5f72c..d3dac52b 100644 --- a/test/statements_tets.cpp +++ b/test/statements_tets.cpp @@ -19,6 +19,7 @@ paramsVal: {{intValue}} )", //------------ R"( + localVal: 3 paramsVal: 3 )" @@ -41,6 +42,7 @@ lastName: {{lastName}} )", //-------------- R"( + firtsName: John lastName: Dow )") @@ -61,6 +63,7 @@ world: {{tuple[1]}} )", //------------ R"( + hello: Hello world: World )") @@ -74,6 +77,7 @@ hello: {{tuple[0]}} world: {{tuple[1]}} )", R"( + hello: Hello world: World )" @@ -90,6 +94,7 @@ world: {{dict.world}} )", //-------- R"( + hello: Hello world: World )" @@ -106,7 +111,7 @@ R"( {%- endwith %} )", //---------- -"\n42" +"\n\n42\n" ) { } @@ -119,7 +124,7 @@ R"( {%- endwith %} )", //---------- -"\n42\nHello World" +"\n\n42\nHello World\n" ) { } @@ -134,7 +139,7 @@ R"( {{ outer }} )", //--------------- -"\nWorld Hello\n42\nHello WorldWorld Hello\n" +"\nWorld Hello\n\n42\nHello World\nWorld Hello\n" ) { params = {{"outer", "World Hello"}}; @@ -150,7 +155,7 @@ R"( {{ outer }} )", //-------------- -"\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n" +"\nWorld Hello\n\nWorld Hello\nHello World\nWorld Hello\n" ) { params = {{"outer", "World Hello"}}; @@ -167,7 +172,7 @@ R"( {{ outer }} )", //-------------- -"\nWorld Hello\nHello World\nHello WorldWorld Hello\n" +"\nWorld Hello\n\n\nHello World\nHello World\nWorld Hello\n" ) { params = {{"outer", "World Hello"}}; @@ -184,7 +189,7 @@ R"( >> {{ inner2 }} << )", //--------------- -"\n42\nWorld Hello>> <<\n>> <<\n" +"\n\n\n42\nWorld Hello\n>> <<\n>> <<\n" ) { params = {{"outer", "World Hello"}}; @@ -202,7 +207,7 @@ TEST(FilterStatement, General) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n THIS TEXT BECOMES UPPERCASE\n", result.c_str()); + EXPECT_STREQ("\n\n THIS TEXT BECOMES UPPERCASE\n\n", result.c_str()); } TEST(FilterStatement, ChainAndParams) @@ -217,7 +222,7 @@ TEST(FilterStatement, ChainAndParams) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n9+8+7+6+5+4+3+2+1+0", result.c_str()); + EXPECT_STREQ("\n9+8+7+6+5+4+3+2+1+0\n", result.c_str()); } TEST(SetBlockStatement, OneVar) @@ -233,7 +238,7 @@ TEST(SetBlockStatement, OneVar) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n|11222333445556677890\n|\n", result.c_str()); + EXPECT_STREQ("\n\n|\n11222333445556677890\n|\n", result.c_str()); } TEST(SetBlockStatement, MoreVars) @@ -251,7 +256,7 @@ TEST(SetBlockStatement, MoreVars) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n|11222333445556677890\n|\n|11222333445556677890\n|\n|11222333445556677890\n|\n", result.c_str()); + EXPECT_STREQ("\n\n|\n11222333445556677890\n|\n|\n11222333445556677890\n|\n|\n11222333445556677890\n|\n", result.c_str()); } TEST(SetBlockStatement, OneVarFiltered) @@ -268,7 +273,7 @@ TEST(SetBlockStatement, OneVarFiltered) ASSERT_TRUE(load) << load.error(); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); + EXPECT_STREQ("\n\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); } TEST(SetBlockStatement, MoreVarsFiltered) @@ -286,7 +291,7 @@ TEST(SetBlockStatement, MoreVarsFiltered) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); - EXPECT_STREQ("\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); + EXPECT_STREQ("\n\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n|9+8+7+6+5+4+3+2+1+0|\n", result.c_str()); } using RawTest = BasicTemplateRenderer; @@ -304,7 +309,7 @@ TEST(RawTest, General) const auto result = tpl.RenderAsString({}).value(); std::cout << result << std::endl; - EXPECT_STREQ("\n\n This is a raw text {{ 2 + 2 }}\n", result.c_str()); + EXPECT_STREQ("\n\n This is a raw text {{ 2 + 2 }}\n\n", result.c_str()); } TEST(RawTest, KeywordsInside) @@ -322,7 +327,7 @@ TEST(RawTest, KeywordsInside) ASSERT_TRUE(tpl.Load(source)); const auto result = tpl.RenderAsString({}).value(); std::cout << result << std::endl; - EXPECT_STREQ("\n\n
    \n {% for item in seq %}\n
  • {{ item }}
  • \n {% endfor %}\n
", result.c_str()); + EXPECT_STREQ("\n\n
    \n {% for item in seq %}\n
  • {{ item }}
  • \n {% endfor %}\n
\n", result.c_str()); } TEST(RawTest, BrokenExpression) diff --git a/test/user_callable_test.cpp b/test/user_callable_test.cpp index 030d1403..7bb2e303 100644 --- a/test/user_callable_test.cpp +++ b/test/user_callable_test.cpp @@ -10,7 +10,27 @@ using namespace jinja2; -using UserCallableTest = BasicTemplateRenderer; +std::string UrlProcessorGlobal(const std::string& urlLink, const std::string& labelName, int limitWord, const std::string& targetStr) +{ + std::string result = urlLink + "?"; + for (int n = 0; n < limitWord; ++n) + result += labelName; + result += "#" + targetStr; + return result; +} + +class UserCallableTest : public BasicTemplateRenderer +{ +public: + std::string UrlProcessor(const std::string& urlLink, const std::string& labelName, int limitWord, const std::string& targetStr) + { + return UrlProcessorGlobal(urlLink, labelName, limitWord, targetStr); + } + std::string UrlProcessorConst(const std::string& urlLink, const std::string& labelName, int limitWord, const std::string& targetStr) const + { + return UrlProcessorGlobal(urlLink, labelName, limitWord, targetStr); + } +}; MULTISTR_TEST(UserCallableTest, SimpleUserCallable, R"( @@ -63,7 +83,7 @@ Hello World! } MULTISTR_TEST(UserCallableTest, SimpleUserCallableWithParams2, -R"( + R"( {{ test('Hello', 'World!') }} {{ test(str2='World!', str1='Hello') }} {{ test(str2='World!') }} @@ -73,9 +93,13 @@ R"( {{ test_w(str2='World!') }} {{ test_w('Hello') }} {{ test2(['H', 'e', 'l', 'l', 'o']) }} +{{ test3("https://google.com", "label1", 3, "someTarget") }} +{{ test4("https://google.com", "label1", 3, "someTarget") }} +{{ test5("https://google.com", "label1", 3, "someTarget") }} +{{ test6("https://google.com", "label1", 3, "someTarget") }} )", //------------- -R"( + R"( Hello World! Hello World! World! @@ -85,6 +109,10 @@ Hello World! World! Hello default Hello +https://google.com?label1label1label1#someTarget +https://google.com?label1label1label1#someTarget +https://google.com?label1label1label1#someTarget +https://google.com?label1label1label1#someTarget )" ) { @@ -111,6 +139,29 @@ Hello }, ArgInfo{"list"} ); + params["test3"] = + MakeCallable([](auto& urlLink, auto& labelName, auto limitWord, auto& targetStr) { return UrlProcessorGlobal(urlLink, labelName, limitWord, targetStr); }, + jinja2::ArgInfoT{ "urlLink" }, + jinja2::ArgInfoT{ "labelName" }, + jinja2::ArgInfoT{ "limitWord", false, 0 }, + jinja2::ArgInfoT{ "targetStr", false, "" }); + params["test4"] = MakeCallable(UrlProcessorGlobal, + jinja2::ArgInfo{ "urlLink" }, + jinja2::ArgInfo{ "labelName" }, + jinja2::ArgInfo{ "limitWord", false, 0 }, + jinja2::ArgInfo{ "targetStr", false, "" }); + params["test5"] = MakeCallable(&UserCallableTest::UrlProcessor, + const_cast(&test), + jinja2::ArgInfo{ "urlLink" }, + jinja2::ArgInfo{ "labelName" }, + jinja2::ArgInfo{ "limitWord", false, 0 }, + jinja2::ArgInfo{ "targetStr", false, "" }); + params["test6"] = MakeCallable(&UserCallableTest::UrlProcessorConst, + &test, + jinja2::ArgInfo{ "urlLink" }, + jinja2::ArgInfo{ "labelName" }, + jinja2::ArgInfo{ "limitWord", false, 0 }, + jinja2::ArgInfo{ "targetStr", false, "" }); } TEST(UserCallableTestSingle, ReflectedCallable) From 2f962ef097f2206ccca453fc688ef81cbcf3f337 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Mon, 14 Oct 2019 21:11:54 +0300 Subject: [PATCH 115/206] Resolves #166 Add global intrinsic functions into the proper map --- src/template_impl.h | 2 +- test/extends_test.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/template_impl.h b/src/template_impl.h index 5ffe65f2..c225d05c 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -187,7 +187,7 @@ class TemplateImpl : public ITemplateImpl } convertFn(params); - SetupGlobals(intParams); + SetupGlobals(extParams); RendererCallback callback(this); RenderContext context(intParams, extParams, &callback); diff --git a/test/extends_test.cpp b/test/extends_test.cpp index 92eed789..5214d1f0 100644 --- a/test/extends_test.cpp +++ b/test/extends_test.cpp @@ -195,6 +195,29 @@ TEST_F(ExtendsTest, ScopedBlocksExtends) EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } +TEST_F(ExtendsTest, NoScopedGlobalVarsAccess) +{ + m_templateFs->AddFile("base.j2tpl", + "Hello World! ->{% block b1 %}=>block b1<={% endblock %}<- ->{% block b2 %}{% endblock b2%}{% block b3 %}Parent block loop {% for i " + "in range(num) %}Foo{{i+1}},{% endfor %}{% endblock b3%}<-"); + m_templateFs->AddFile( + "derived.j2tpl", + R"({% extends "base.j2tpl" %}{%block b1%}Extended block b1!{{super()}}{%endblock%}Some Stuff{%block b2%}Extended block b2!{%endblock%}{% block b3 %}This is overriden block "A". {% for i in range(num) %}Foo{{i+1}},{% endfor %}{% endblock b3 %})"); + + auto baseTpl = m_env.LoadTemplate("base.j2tpl").value(); + auto tpl = m_env.LoadTemplate("derived.j2tpl").value(); + + m_env.AddGlobal("num", 3); + + std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{}).value(); + std::cout << baseResult << std::endl; + std::string expectedResult = "Hello World! ->=>block b1<=<- ->Parent block loop Foo1,Foo2,Foo3,<-"; + EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str()); + std::string result = tpl.RenderAsString(jinja2::ValuesMap{}).value(); + std::cout << result << std::endl; + expectedResult = "Hello World! ->Extended block b1!=>block b1<=<- ->Extended block b2!This is overriden block \"A\". Foo1,Foo2,Foo3,<-"; + EXPECT_STREQ(expectedResult.c_str(), result.c_str()); +} TEST_F(ExtendsTest, MacroUsage) { From d8954a47fd0c6cf315d07d0e43cb7fc8f74e65da Mon Sep 17 00:00:00 2001 From: Manuel Herrmann Date: Tue, 15 Oct 2019 11:22:27 +0200 Subject: [PATCH 116/206] filter slice: implement non-batch version with tests (#141) (#167) * filter slice: implement non-batch version with tests (#141) * filter slice: review: implement non-batch version with tests (#141) --- src/filters.cpp | 44 ++++++++++++++++++++++++++++++++++++++----- test/filters_test.cpp | 24 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index a23a1d8a..bfb53c5d 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -782,18 +782,52 @@ InternalValue Serialize::Filter(const InternalValue&, RenderContext&) Slice::Slice(FilterParams params, Slice::Mode mode) : m_mode{mode} { - if(m_mode == BatchMode) - { + if (m_mode == BatchMode) ParseParams({{"linecount"s, true}, {"fill_with"s, false}}, params); - } + else + ParseParams({{"slices"s, true}, {"fill_with"s, false}}, params); } InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context) { - if(m_mode == BatchMode) + if (m_mode == BatchMode) return Batch(baseVal, context); - return InternalValue(); + InternalValue result; + + bool isConverted = false; + ListAdapter list = ConvertToList(baseVal, isConverted); + + if (!isConverted) + return result; + + InternalValue sliceLengthValue = GetArgumentValue("slices", context); + int64_t sliceLength = ConvertToInt(sliceLengthValue); + InternalValue fillWith = GetArgumentValue("fill_with", context); + + InternalValueList resultList; + InternalValueList sublist; + int sublistItemIndex = 0; + for (auto& item : list) + { + if (sublistItemIndex == 0) sublist.clear(); + if (sublistItemIndex == sliceLength) + { + resultList.push_back(ListAdapter::CreateAdapter(std::move(sublist))); + sublist.clear(); + sublistItemIndex %= sliceLength; + } + sublist.push_back(item); + ++sublistItemIndex; + } + if (!IsEmpty(fillWith)) + { + while (sublistItemIndex++ < sliceLength) + sublist.push_back(fillWith); + } + if (sublistItemIndex > 0) resultList.push_back(ListAdapter::CreateAdapter(std::move(sublist))); + + return InternalValue(ListAdapter::CreateAdapter(std::move(resultList))); } InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context) diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 874914a2..9d7ba7af 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -14,6 +14,9 @@ using ListIteratorTest = InputOutputPairTest; struct GroupByTestTag; using FilterGroupByTest = InputOutputPairTest; +struct ListSliceTestTag; +using ListSliceTest = InputOutputPairTest; + TEST_P(ListIteratorTest, Test) { auto& testParam = GetParam(); @@ -92,6 +95,14 @@ STR1->STR2->STR3 { } +TEST_P(ListSliceTest, Test) +{ + auto& testParam = GetParam(); + std::string source = "{{ " + testParam.tpl + " }}"; + + PerformBothTests(source, testParam.result); +} + INSTANTIATE_TEST_CASE_P(StringJoin, FilterGenericTest, ::testing::Values( InputOutputPair{"['str1', 'str2', 'str3'] | join", "str1str2str3"}, InputOutputPair{"['str1', 'str2', 'str3'] | join(' ')", "str1 str2 str3"}, @@ -511,3 +522,16 @@ INSTANTIATE_TEST_CASE_P(Batch, FilterGenericTest, ::testing::Values( InputOutputPair{"'some string' | batch(0) | pprint", "none"}, InputOutputPair{"[] | batch(0) | pprint", "none"} )); + +INSTANTIATE_TEST_CASE_P(ListSlice, ListSliceTest, ::testing::Values( + InputOutputPair{"1 | slice(3) | pprint", "none"}, + InputOutputPair{"[] | slice(3) | pprint", "[]"}, + InputOutputPair{"[1, 2, 3] | slice(3) | pprint", "[[1, 2, 3]]"}, + InputOutputPair{"[1, 2, 3] | slice(3, 0) | pprint", "[[1, 2, 3]]"}, + InputOutputPair{"[1, 2] | slice(3) | pprint", "[[1, 2]]"}, + InputOutputPair{"[1, 2] | slice(3, 0) | pprint", "[[1, 2, 0]]"}, + InputOutputPair{"[1, 2, 3, 4, 5, 6, 7, 8, 9] | slice(3) | pprint", "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]"}, + InputOutputPair{"[1, 2, 3, 4, 5, 6, 7, 8, 9] | slice(3, 0) | pprint", "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]"}, + InputOutputPair{"[1, 2, 3, 4, 5, 6, 7] | slice(3) | pprint", "[[1, 2, 3], [4, 5, 6], [7]]"}, + InputOutputPair{"[1, 2, 3, 4, 5, 6, 7] | slice(3, 0) | pprint", "[[1, 2, 3], [4, 5, 6], [7, 0, 0]]"} + )); From 68817854028068bdc7d9bac353c5a5b70e42eb56 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Tue, 15 Oct 2019 22:00:01 +0300 Subject: [PATCH 117/206] Resolves #168. Fix temporary string copying --- src/internal_value.cpp | 4 +- test/expressions_test.cpp | 4 +- test/test_tools.h | 226 ++++++++++++++++++++------------------ 3 files changed, 126 insertions(+), 108 deletions(-) diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 918968c5..a100dbd4 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -293,8 +293,8 @@ class GenericListAdapter : public IListAccessor if (!indexer) return nonstd::optional(); - const auto& val = indexer->GetItemByIndex(idx); - return visit(visitors::InputValueConvertor(true, true), val.data()).get(); + auto val = indexer->GetItemByIndex(idx); + return visit(visitors::InputValueConvertor(true, false), std::move(val.data())).get(); } bool ShouldExtendLifetime() const override {return m_values.ShouldExtendLifetime();} ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index efd1b1da..db983cea 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -238,7 +238,9 @@ INSTANTIATE_TEST_CASE_P(IndexSubscriptionTest, ExpressionSubstitutionTest, ::tes InputOutputPair{"mapValue[0]", ""}, InputOutputPair{"(mapValue | dictsort | first)['key']", "boolValue"}, InputOutputPair{"(mapValue | dictsort | first)['value']", "true"}, - InputOutputPair{"reflectedVal['intValue']", "0"}, + InputOutputPair{ "reflectedStringVector[0]", "9" }, + InputOutputPair{ "reflectedStringViewVector[0]", "9" }, + InputOutputPair{"reflectedVal['intValue']", "0"}, InputOutputPair{"reflectedVal['dblValue']", "0"}, InputOutputPair{"reflectedVal['boolValue']", "false"}, InputOutputPair{"reflectedVal['strValue']", "test string 0"}, diff --git a/test/test_tools.h b/test/test_tools.h index 7fad740f..34e645be 100644 --- a/test/test_tools.h +++ b/test/test_tools.h @@ -1,20 +1,21 @@ #ifndef TEST_TOOLS_H #define TEST_TOOLS_H +#include "../src/helpers.h" + #include +#include #include #include -#include -#include #include -#include "../src/helpers.h" +#include struct InputOutputPair { std::string tpl; std::string result; - friend std::ostream& operator << (std::ostream& os, const InputOutputPair& rp) + friend std::ostream& operator<<(std::ostream& os, const InputOutputPair& rp) { os << rp.tpl << " -> " << rp.result; return os; @@ -23,7 +24,7 @@ struct InputOutputPair struct TestInnerStruct { - ~TestInnerStruct() {isAlive = false;} + ~TestInnerStruct() { isAlive = false; } bool isAlive = true; std::string strValue = "Hello World!"; @@ -31,7 +32,7 @@ struct TestInnerStruct struct TestStruct { - ~TestStruct() {isAlive = false;} + ~TestStruct() { isAlive = false; } bool isAlive = true; int64_t intValue; @@ -47,7 +48,7 @@ inline jinja2::ValuesMap PrepareTestData() { jinja2::ValuesList testData; TestStruct sampleStruct; - for (int n = 0; n < 10; ++ n) + for (int n = 0; n < 10; ++n) { TestStruct s; std::ostringstream str; @@ -70,38 +71,30 @@ inline jinja2::ValuesMap PrepareTestData() std::shared_ptr emptyTestStruct; std::shared_ptr filledTestStruct = std::make_shared(sampleStruct); sampleStruct.innerStruct = std::make_shared(); - for (int n = 0; n < 10; ++ n) + for (int n = 0; n < 10; ++n) sampleStruct.innerStructList.push_back(std::make_shared()); - return jinja2::ValuesMap { - {"intValue", 3}, - {"intList", jinja2::ValuesList{9, 0, 8, 1, 7, 2, 6, 3, 5, 4}}, - {"doubleValue", 12.123f}, - {"doubleList", jinja2::ValuesList{9.5, 0.5, 8.5, 1.5, 7.5, 2.5, 6.4, 3.8, 5.2, -4.7}}, - {"intAsDoubleList", jinja2::ValuesList{9.0, 0.0, 8.0, 1.0, 7.0, 2.0, 6.0, 3.0, 5.0, 4.0}}, - {"stringValue", "rain"}, - {"wstringValue", std::wstring(L" hello world ")}, - {"stringList", jinja2::ValuesList{"string9", "string0", "string8", "string1", "string7", "string2", "string6", "string3", "string5", "string4"}}, - {"boolFalseValue", false}, - {"boolTrueValue", true}, - {"mapValue", jinja2::ValuesMap{ - {"intVal", 10}, - {"dblVal", 100.5}, - {"stringVal", "string100.5"}, - {"boolValue", true}, - {"reflectedList", testData} - }}, - {"simpleMapValue", jinja2::ValuesMap{ - {"intVal", 10}, - {"dblVal", 100.5}, - {"stringVal", "string100.5"}, - {"boolValue", true} - }}, - {"reflectedVal", jinja2::Reflect(sampleStruct)}, - {"emptyReflectedPtrVal", jinja2::Reflect(emptyTestStruct)}, - {"filledReflectedPtrVal", jinja2::Reflect(filledTestStruct)}, - {"reflectedIntVector", jinja2::Reflect(std::vector{9, 0, 8, 1, 7, 2, 6, 3, 5, 4})}, - {"reflectedList", std::move(testData)} + return jinja2::ValuesMap{ + { "intValue", 3 }, + { "intList", jinja2::ValuesList{ 9, 0, 8, 1, 7, 2, 6, 3, 5, 4 } }, + { "doubleValue", 12.123f }, + { "doubleList", jinja2::ValuesList{ 9.5, 0.5, 8.5, 1.5, 7.5, 2.5, 6.4, 3.8, 5.2, -4.7 } }, + { "intAsDoubleList", jinja2::ValuesList{ 9.0, 0.0, 8.0, 1.0, 7.0, 2.0, 6.0, 3.0, 5.0, 4.0 } }, + { "stringValue", "rain" }, + { "wstringValue", std::wstring(L" hello world ") }, + { "stringList", jinja2::ValuesList{ "string9", "string0", "string8", "string1", "string7", "string2", "string6", "string3", "string5", "string4" } }, + { "boolFalseValue", false }, + { "boolTrueValue", true }, + { "mapValue", + jinja2::ValuesMap{ { "intVal", 10 }, { "dblVal", 100.5 }, { "stringVal", "string100.5" }, { "boolValue", true }, { "reflectedList", testData } } }, + { "simpleMapValue", jinja2::ValuesMap{ { "intVal", 10 }, { "dblVal", 100.5 }, { "stringVal", "string100.5" }, { "boolValue", true } } }, + { "reflectedVal", jinja2::Reflect(sampleStruct) }, + { "emptyReflectedPtrVal", jinja2::Reflect(emptyTestStruct) }, + { "filledReflectedPtrVal", jinja2::Reflect(filledTestStruct) }, + { "reflectedIntVector", jinja2::Reflect(std::vector{ 9, 0, 8, 1, 7, 2, 6, 3, 5, 4 }) }, + { "reflectedStringVector", jinja2::Reflect(std::vector{ "9", "0", "8", "1", "7", "2", "6", "3", "5", "4" }) }, + { "reflectedStringViewVector", jinja2::Reflect(std::vector{ "9", "0", "8", "1", "7", "2", "6", "3", "5", "4" }) }, + { "reflectedList", std::move(testData) } }; } @@ -129,7 +122,10 @@ class BasicTemplateRenderer : public ::testing::Test { public: template - static void ExecuteTest(const std::basic_string& source, const std::basic_string& expectedResult, const jinja2::ValuesMap& params, const char* version = "") + static void ExecuteTest(const std::basic_string& source, + const std::basic_string& expectedResult, + const jinja2::ValuesMap& params, + const char* version = "") { TemplateT tpl; auto parseRes = tpl.Load(source); @@ -169,13 +165,17 @@ class SubstitutionTestBase : public ::testing::TestWithParam void PerformWideTest(const InputOutputPair& testParam) { - BasicTemplateRenderer::ExecuteTest(L"{{ " + jinja2::ConvertString(testParam.tpl) + L" }}", jinja2::ConvertString(testParam.result), PrepareTestData(), "Wide version"); + BasicTemplateRenderer::ExecuteTest(L"{{ " + jinja2::ConvertString(testParam.tpl) + L" }}", + jinja2::ConvertString(testParam.result), + PrepareTestData(), + "Wide version"); } void PerformBothTests(const std::string& tpl, const std::string result, const jinja2::ValuesMap& params = PrepareTestData()) { BasicTemplateRenderer::ExecuteTest(tpl, result, params, "Narrow version"); - BasicTemplateRenderer::ExecuteTest(jinja2::ConvertString(tpl), jinja2::ConvertString(result), params, "Wide version"); + BasicTemplateRenderer::ExecuteTest( + jinja2::ConvertString(tpl), jinja2::ConvertString(result), params, "Wide version"); } }; @@ -193,10 +193,7 @@ class TemplateEnvFixture : public ::testing::Test m_env.AddFilesystemHandler(std::string(), m_templateFs); } - void AddFile(std::string fileName, std::string content) - { - m_templateFs->AddFile(std::move(fileName), std::move(content)); - } + void AddFile(std::string fileName, std::string content) { m_templateFs->AddFile(std::move(fileName), std::move(content)); } jinja2::Template Load(std::string tplBody) { @@ -223,7 +220,7 @@ class TemplateEnvFixture : public ::testing::Test std::cout << "Template rendering error: " << renderResult.error() << std::endl; return ""; } - + return renderResult.value(); } @@ -232,38 +229,37 @@ class TemplateEnvFixture : public ::testing::Test jinja2::TemplateEnv m_env; }; +#define MULTISTR_TEST_IMPL(Fixture, TestName, StringT, TemplateT, Tpl, Result, ParamsGetter) \ + TEST_F(Fixture, TestName) \ + { \ + jinja2::ValuesMap params; \ + ParamsGetter(params, *this); \ + \ + PerformTest(StringT(Tpl), StringT(Result), params); \ + } -#define MULTISTR_TEST_IMPL(Fixture, TestName, StringT, TemplateT, Tpl, Result, ParamsGetter) \ -TEST_F(Fixture, TestName) \ -{ \ - jinja2::ValuesMap params; \ - ParamsGetter(params, *this); \ - \ - PerformTest(StringT(Tpl), StringT(Result), params); \ -} - -#define MULTISTR_TEST(Fixture, TestName, Tpl, Result) \ -void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test);\ -MULTISTR_TEST_IMPL(Fixture, TestName##_Narrow, std::string, jinja2::Template, Tpl, Result, Fixture##_##TestName##_Params_Getter) \ -MULTISTR_TEST_IMPL(Fixture, TestName##_Wide, std::wstring, jinja2::TemplateW, L##Tpl, L##Result, Fixture##_##TestName##_Params_Getter) \ -void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test) +#define MULTISTR_TEST(Fixture, TestName, Tpl, Result) \ + void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test); \ + MULTISTR_TEST_IMPL(Fixture, TestName##_Narrow, std::string, jinja2::Template, Tpl, Result, Fixture##_##TestName##_Params_Getter) \ + MULTISTR_TEST_IMPL(Fixture, TestName##_Wide, std::wstring, jinja2::TemplateW, L##Tpl, L##Result, Fixture##_##TestName##_Params_Getter) \ + void Fixture##_##TestName##_Params_Getter(jinja2::ValuesMap& params, const Fixture& test) struct SubstitutionGenericTestTag; using SubstitutionGenericTest = InputOutputPairTest; -#define SUBSTITUION_TEST_P(TestName) \ -struct TestName##Tag; \ -using TestName = InputOutputPairTest;\ -TEST_P(TestName, Test##_Narrow) \ -{ \ - auto& testParam = GetParam(); \ - PerformNarrowTest(testParam); \ -} \ -TEST_P(TestName, Test##_Wide) \ -{ \ - auto& testParam = GetParam(); \ - PerformWideTest(testParam); \ -} +#define SUBSTITUION_TEST_P(TestName) \ + struct TestName##Tag; \ + using TestName = InputOutputPairTest; \ + TEST_P(TestName, Test##_Narrow) \ + { \ + auto& testParam = GetParam(); \ + PerformNarrowTest(testParam); \ + } \ + TEST_P(TestName, Test##_Wide) \ + { \ + auto& testParam = GetParam(); \ + PerformWideTest(testParam); \ + } namespace jinja2 { @@ -273,7 +269,11 @@ struct TypeReflection : TypeReflected static auto& GetAccessors() { static std::unordered_map accessors = { - {"strValue", [](const TestInnerStruct& obj) {assert(obj.isAlive); return obj.strValue;}}, + { "strValue", + [](const TestInnerStruct& obj) { + assert(obj.isAlive); + return obj.strValue; + } }, }; return accessors; @@ -291,13 +291,13 @@ struct TypeReflection : TypeReflected assert(obj.isAlive); return jinja2::Reflect(obj.intValue); } }, - {"intEvenValue", [](const TestStruct& obj) -> Value - { - assert(obj.isAlive); - if (obj.intValue % 2) - return {}; - return {obj.intValue}; - }}, + { "intEvenValue", + [](const TestStruct& obj) -> Value { + assert(obj.isAlive); + if (obj.intValue % 2) + return {}; + return { obj.intValue }; + } }, { "dblValue", [](const TestStruct& obj) { assert(obj.isAlive); @@ -328,31 +328,47 @@ struct TypeReflection : TypeReflected assert(obj.isAlive); return jinja2::Reflect(nonstd::wstring_view(obj.wstrValue)); } }, - {"innerStruct", [](const TestStruct& obj) - { - assert(obj.isAlive); - return obj.innerStruct ? jinja2::Reflect(obj.innerStruct) : Value(); - }}, - {"innerStructList", [](const TestStruct& obj) {assert(obj.isAlive); return jinja2::Reflect(obj.innerStructList);}}, - {"tmpStructList", [](const TestStruct& obj) - { - assert(obj.isAlive); - using list_t = std::vector>; - list_t vals; - for (int n = 0; n < 10; ++ n) - vals.push_back(std::make_shared()); - return jinja2::Reflect(list_t(vals.begin(), vals.end())); - }}, - {"basicCallable",[](const TestStruct& obj) { - return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return obj.intValue; }); - }}, - {"getInnerStruct",[](const TestStruct& obj) { - return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(obj.innerStruct); }); - }}, - {"getInnerStructValue",[](const TestStruct& obj) { - return jinja2::MakeCallable([&obj]() { assert(obj.isAlive); return jinja2::Reflect(*obj.innerStruct); }); - }}, - }; + { "innerStruct", + [](const TestStruct& obj) { + assert(obj.isAlive); + return obj.innerStruct ? jinja2::Reflect(obj.innerStruct) : Value(); + } }, + { "innerStructList", + [](const TestStruct& obj) { + assert(obj.isAlive); + return jinja2::Reflect(obj.innerStructList); + } }, + { "tmpStructList", + [](const TestStruct& obj) { + assert(obj.isAlive); + using list_t = std::vector>; + list_t vals; + for (int n = 0; n < 10; ++n) + vals.push_back(std::make_shared()); + return jinja2::Reflect(list_t(vals.begin(), vals.end())); + } }, + { "basicCallable", + [](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { + assert(obj.isAlive); + return obj.intValue; + }); + } }, + { "getInnerStruct", + [](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { + assert(obj.isAlive); + return jinja2::Reflect(obj.innerStruct); + }); + } }, + { "getInnerStructValue", + [](const TestStruct& obj) { + return jinja2::MakeCallable([&obj]() { + assert(obj.isAlive); + return jinja2::Reflect(*obj.innerStruct); + }); + } }, + }; return accessors; } From 49559d2bc0387751288e753f75acffc2e392ae42 Mon Sep 17 00:00:00 2001 From: Mateusz Date: Wed, 16 Oct 2019 15:31:07 +0200 Subject: [PATCH 118/206] Filter 'format' #145 (#164) * Filter 'format' * Fixing tests * Add null arg to ensure, that fmt won't generate segfault * Fix Codacy error * Removed unnecessary if and added more tests --- src/filters.cpp | 173 +++++++++++++++++++++++++++++++++++++++++- src/filters.h | 9 +-- test/filters_test.cpp | 20 +++++ 3 files changed, 192 insertions(+), 10 deletions(-) diff --git a/src/filters.cpp b/src/filters.cpp index bfb53c5d..92b83df0 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -45,7 +45,7 @@ std::unordered_map s_filters = { {"escapecpp", FilterFactory::MakeCreator(filters::StringConverter::EscapeCppMode)}, {"first", FilterFactory::MakeCreator(filters::SequenceAccessor::FirstItemMode)}, {"float", FilterFactory::MakeCreator(filters::ValueConverter::ToFloatMode)}, - {"format", FilterFactory::MakeCreator(filters::StringFormat::PythonMode)}, + {"format", FilterFactory::Create}, {"groupby", &FilterFactory::Create}, {"int", FilterFactory::MakeCreator(filters::ValueConverter::ToIntMode)}, {"join", &FilterFactory::Create}, @@ -868,14 +868,179 @@ InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context) return ListAdapter::CreateAdapter(std::move(resultList)); } -StringFormat::StringFormat(FilterParams, StringFormat::Mode) +StringFormat::StringFormat(FilterParams params) { + ParseParams({}, params); + m_params.kwParams = std::move(m_args.extraKwArgs); + m_params.posParams = std::move(m_args.extraPosArgs); +} + + +namespace { + +using FormatContext = fmt::format_context; +using FormatArgument = fmt::basic_format_arg; + +template +struct FormatArgumentConverter : visitors::BaseVisitor +{ + using result_t = FormatArgument; + + using BaseVisitor::operator(); + + FormatArgumentConverter(const RenderContext* context, const ResultDecorator& decorator) + : m_context(context), m_decorator(decorator) + {} + + result_t operator()(const ListAdapter& list) const + { + return make_result(Apply(list, m_context)); + } + + result_t operator()(const MapAdapter& map) const + { + return make_result(Apply(map, m_context)); + } + + result_t operator()(const std::string& str) const + { + return make_result(str); + } + + result_t operator()(const nonstd::string_view& str) const + { + return make_result(std::string(str.data(), str.size())); + } + + result_t operator()(const std::wstring& str) const + { + return make_result(ConvertString(str)); + } + + result_t operator()(const nonstd::wstring_view& str) const + { + return make_result(ConvertString(str)); + } + + result_t operator()(double val) const + { + return make_result(val); + } + + result_t operator()(int64_t val) const + { + return make_result(val); + } + + result_t operator()(bool val) const + { + return make_result(val ? "true"s : "false"s); + } + + result_t operator()(EmptyValue) const + { + return make_result("none"s); + } + + result_t operator()(const Callable&) const + { + return make_result(""s); + } + + template + result_t make_result(const T& t) const + { + return fmt::internal::make_arg(m_decorator(t)); + } + + + const RenderContext* m_context; + const ResultDecorator& m_decorator; +}; + +template +using NamedArgument = fmt::internal::named_arg; + +using ValueHandle = nonstd::variant< + bool, + std::string, + int64_t, + double, + NamedArgument, + NamedArgument, + NamedArgument, + NamedArgument +>; +using ValuesBuffer = std::vector; + +struct CachingIdentity +{ +public: + explicit CachingIdentity(ValuesBuffer& values) : m_values(values) + {} + + template + const auto& operator()(const T& t) const + { + m_values.push_back(t); + return m_values.back().get(); + } +private: + ValuesBuffer& m_values; +}; + +class NamedArgumentCreator +{ +public: + NamedArgumentCreator(const std::string& name, ValuesBuffer& valuesBuffer) + : m_name(name), m_valuesBuffer(valuesBuffer) + {} + + template + const auto& operator()(const T& t) const + { + m_valuesBuffer.push_back(m_name); + const auto& name = m_valuesBuffer.back().get(); + m_valuesBuffer.push_back(t); + const auto& value = m_valuesBuffer.back().get(); + m_valuesBuffer.emplace_back(fmt::arg(name, value)); + return m_valuesBuffer.back().get>(); + } +private: + const std::string m_name; + ValuesBuffer& m_valuesBuffer; +}; } -InternalValue StringFormat::Filter(const InternalValue&, RenderContext&) +InternalValue StringFormat::Filter(const InternalValue& baseVal, RenderContext& context) { - return InternalValue(); + // Format library internally likes using non-owning views to complex arguments. + // In order to ensure proper lifetime of values and named args, + // helper buffer is created and passed to visitors. + ValuesBuffer valuesBuffer; + valuesBuffer.reserve(m_params.posParams.size() + 3 * m_params.kwParams.size()); + + std::vector args; + for(auto& arg : m_params.posParams) { + args.push_back(Apply>( + arg->Evaluate(context), &context, CachingIdentity{valuesBuffer} + )); + } + + for(auto& arg : m_params.kwParams) { + args.push_back(Apply>( + arg.second->Evaluate(context), &context, + NamedArgumentCreator{arg.first, valuesBuffer} + )); + } + // fmt process arguments until reaching empty argument + args.push_back(FormatArgument{}); + + return InternalValue(fmt::vformat( + AsString(baseVal), + fmt::format_args(args.data(), static_cast(args.size() - 1)) + )); } Tester::Tester(FilterParams params, Tester::Mode mode) diff --git a/src/filters.h b/src/filters.h index 2c1c0b43..ce60fca1 100644 --- a/src/filters.h +++ b/src/filters.h @@ -200,14 +200,11 @@ class StringConverter : public FilterBase class StringFormat : public FilterBase { public: - enum Mode - { - PythonMode, - }; - - StringFormat(FilterParams params, Mode mode); + StringFormat(FilterParams params); InternalValue Filter(const InternalValue& baseVal, RenderContext& context); +private: + CallParams m_params; }; class Tester : public FilterBase diff --git a/test/filters_test.cpp b/test/filters_test.cpp index 9d7ba7af..52ae8115 100644 --- a/test/filters_test.cpp +++ b/test/filters_test.cpp @@ -523,6 +523,26 @@ INSTANTIATE_TEST_CASE_P(Batch, FilterGenericTest, ::testing::Values( InputOutputPair{"[] | batch(0) | pprint", "none"} )); +INSTANTIATE_TEST_CASE_P(Format, FilterGenericTest, ::testing::Values( + InputOutputPair{"'Hello {}!' | format('World') ", "Hello World!"}, + InputOutputPair{"'{1} {0}!' | format('World', 'Hello') | pprint", "'Hello World!'"}, + InputOutputPair{"'{}' | format(1024) | pprint", "'1024'"}, + InputOutputPair{"'{}' | format([1, 'a'])", "[1, 'a']"}, + InputOutputPair{"'{}' | format({'a'=1})", "{'a': 1}"}, + InputOutputPair{"'{}' | format(stringValue)","rain"}, + InputOutputPair{"'{}' | format(wstringValue)", " hello world "}, + InputOutputPair{"'{:07d}' | format(1024) | pprint", "'0001024'"}, + InputOutputPair{"'{0:.2f}' | format(13.949999988079071) | pprint", "'13.95'"}, + InputOutputPair{"'{0:.15f}' | format(13.949999988079071) | pprint", "'13.949999988079071'"}, + InputOutputPair{"'{0:.2f} != {1:02d}' | format(13.949999988079071, 7) | pprint", "'13.95 != 07'"}, + InputOutputPair{"'PI = {pi:.2f}' | format(pi=3.1415) | pprint", "'PI = 3.14'"}, + InputOutputPair{"'ONE = {one:02d}, PI = {pi:.2f}' | format(pi=3.1415, one=1) | pprint", "'ONE = 01, PI = 3.14'"}, + InputOutputPair{"'Hello {name}!' | format(name='World')", "Hello World!"}, + InputOutputPair{"'Hello {array}!' | format(array=[1, 2, 3])", "Hello [1, 2, 3]!"}, + InputOutputPair{"'Hello {boolean}!' | format(boolean=True)", "Hello true!"}, + InputOutputPair{"'Hello {empty}!' | format(empty=nonexistent)", "Hello none!"} + )); + INSTANTIATE_TEST_CASE_P(ListSlice, ListSliceTest, ::testing::Values( InputOutputPair{"1 | slice(3) | pprint", "none"}, InputOutputPair{"[] | slice(3) | pprint", "[]"}, From 328623d7351f10935644cbb8a3a4e32ec9bf25c0 Mon Sep 17 00:00:00 2001 From: Ilyas Hamadouche Date: Sat, 19 Oct 2019 14:53:24 +0300 Subject: [PATCH 119/206] [skip ci] Fix typos, grammar and spelling in README.md (#170) --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6d869bab..2ad8c821 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![conan.io](https://api.bintray.com/packages/flexferrum/conan-packages/jinja2cpp:flexferrum/images/download.svg?version=1.0.0:testing) ](https://bintray.com/flexferrum/conan-packages/jinja2cpp:flexferrum/1.0.0:testing/link) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) -C++ implementation of Jinja2 Python template engine. This library brings support of powerful Jinja2 template features into the C++ world, reports dynamic html pages and source code generation. +C++ implementation of the Jinja2 Python template engine. This library brings support of powerful Jinja2 template features into the C++ world, reports dynamic HTML pages and source code generation. ## Introduction @@ -59,9 +59,9 @@ hello; world!!! ## Getting started -In order to use Jinja2C++ in your project you have to: +To use Jinja2C++ in your project you have to: * Clone the Jinja2C++ repository -* Build it according with the [instructions](https://jinja2cpp.dev/docs/build_and_install.html) +* Build it according to the [instructions](https://jinja2cpp.dev/docs/build_and_install.html) * Link to your project. Usage of Jinja2C++ in the code is pretty simple: @@ -94,11 +94,11 @@ That's all! More detailed examples and features description can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) ## Current Jinja2 support -Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: -- expressions. You can use almost every style of expressions: simple, filtered, conditional, and so on. -- big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape**) -- big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) -- limited number of functions (**range**, **loop.cycle**) +Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be a full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: +- expressions. You can use almost every expression style: simple, filtered, conditional, and so on. +- the big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape**) +- the big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) +- the number of functions (**range**, **loop.cycle**) - 'if' statement (with 'elif' and 'else' branches) - 'for' statement (with 'else' branch and 'if' part support) - 'include' statement @@ -125,7 +125,7 @@ Compilation of Jinja2C++ tested on the following compilers (with C++14 and C++17 - MinGW gcc compiler 7.3 - MinGW gcc compiler 8.1 -**Note:** Support of gcc version >= 9.x or clang version >= 8.0 depends on version of Boost library provided. +**Note:** Support of gcc version >= 9.x or clang version >= 8.0 depends on the version of the Boost library provided. ## Build and install Jinja2C++ has several external dependencies: @@ -170,10 +170,10 @@ In simplest case to compile Jinja2C++ you need: > cmake --build . --target install ``` -In this case Jinja2C++ will be built with internally-shipped dependencies and install them respectively. But Jinja2C++ supports build with externally-provided deps. Different Jinja2C++ usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build +In this case, Jinja2C++ will be built with internally-shipped dependencies and install them respectively. But Jinja2C++ supports build with externally-provided deps. Different Jinja2C++ usage scenarios can be found in this repository: https://github.com/jinja2cpp/examples-build ### Usage with conan.io dependency manager -Jinja2C++ can be used as conan.io package. In this case you should do the following steps: +Jinja2C++ can be used as conan.io package. In this case, you should do the following steps: 1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html ) 2. Register the following remote conan.io repositories: @@ -183,7 +183,7 @@ Jinja2C++ can be used as conan.io package. In this case you should do the follow The sample command is: `conan remote add martin https://api.bintray.com/conan/martinmoene/nonstd-lite` -3. Add reference to Jinja2C++ package (`jinja2cpp/1.0.0@flexferrum/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with usage of `conan-cmake` integration it could be written this way: +3. Add a reference to Jinja2C++ package (`jinja2cpp/1.0.0@flexferrum/testing`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with the usage of `conan-cmake` integration it could be written this way: ```cmake include (../../cmake/conan.cmake) @@ -214,15 +214,15 @@ set_target_properties (${TARGET_NAME} PROPERTIES ### Additional CMake build flags -You can define (via -D command line CMake option) the following build flags: +You can define (via -D command-line CMake option) the following build flags: - **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2C++ tests. -- **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror and etc). +- **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror, etc). - **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler). - **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependency handling. Following values possible: - `internal` In this mode Jinja2C++ build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally. - - `external-boost` In this mode Jinja2C++ build script uses only `boost` as externally-provided dependency. All other dependencies taken from subprojects. - - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) + - `external-boost` In this mode Jinja2C++ build script uses only `boost` as an externally-provided dependency. All other dependencies are taken from subprojects. + - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs, etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR) - `conan-build` Special mode for building Jinja2C++ via conan recipe. @@ -230,7 +230,7 @@ You can define (via -D command line CMake option) the following build flags: In case of C++17 standard enabled for your project you should define `variant_CONFIG_SELECT_VARIANT=variant_VARIANT_NONSTD nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_NONSTD optional_CONFIG_SELECT_OPTIONAL=optional_OPTIONAL_NONSTD` macros in the build settings. ## Acknowledgments -Thanks to **@manu343726** for CMake scripts improvement, bug hunting and fixing and conan.io packaging. +Thanks to **@manu343726** for CMake scripts improvement, bug hunting, and fixing and conan.io packaging. Thanks to **@martinmoene** for the perfectly implemented xxx-lite libraries. @@ -238,9 +238,9 @@ Thanks to **@vitaut** for the amazing text formatting library. Thanks to **@martinus** for the fast hash maps implementation. -Thanks to **@palchukovsky** for the great contribution into codebase. +Thanks to **@palchukovsky** for the great contribution into the codebase. -Thanks to **@rmorozov** for stanitized builds setup. +Thanks to **@rmorozov** for sanitized builds setup. ## Changelog @@ -249,16 +249,16 @@ Thanks to **@rmorozov** for stanitized builds setup. #### Changes and improvements - `default` attribute added to the `map` filter (#48) - escape sequences support added to the string literals (#49) -- arbitrary ranges, generated sequences, input iterators etc. now can be used with `GenericList` type (#66) +- arbitrary ranges, generated sequences, input iterators, etc. now can be used with `GenericList` type (#66) - nonstd::string_view is now one of the possible types for the `Value` - `filter` tag support added to the template parser (#44) - `escape` filter support added to the template parser (#140) - `capitalize` filter support added to the template parser (#137) -- multiline version of `set` tag added to the parser (#45) -- added built-in reflection for nlohmann json and rapid json libraries (#78) +- the multiline version of `set` tag added to the parser (#45) +- added built-in reflection for nlohmann JSON and RapidJSON libraries (#78) - `loop.depth` and `loop.depth0` variables support added - {fmt} is now used as a formatting library instead of iostreams -- robin hood hash maps is now used for internal value storage +- robin hood hash map is now used for internal value storage - rendering performance improvements - template cache implemented in `TemplateEnv` - user-defined callables now can accept global context via `*context` special param @@ -277,7 +277,7 @@ Thanks to **@rmorozov** for stanitized builds setup. ### Version 0.9.2 #### Major changes - User-defined callables implemented. Now you can define your own callable objects, pass them as input parameters and use them inside templates as regular (global) functions, filters or testers. See details here: https://jinja2cpp.dev/docs/usage/ud_callables.html -- Now you can define global (template environment-wide) parameters which are accessible for all templates bound to this environment. +- Now you can define global (template environment-wide) parameters that are accessible for all templates bound to this environment. - `include`, `import` and `from` statements implemented. Now it's possible to include other templates and use macros from other templates. - `with` statement implemented - `do` statement implemented @@ -296,7 +296,7 @@ Thanks to **@rmorozov** for stanitized builds setup. - Release bundles (archives) are configured with `external` dependency management mode by default ### Version 0.9.1 -- `applymacro` filter added which allows to apply arbitrary macro as a filter +- `applymacro` filter added which allows applying arbitrary macro as a filter - dependencies to boost removed from the public interface - CMake scripts improved - Various bugs fixed @@ -312,8 +312,8 @@ Thanks to **@rmorozov** for stanitized builds setup. - 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](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](https://github.com/flexferrum/Jinja2Cpp/issues/8) +- A lot of filters have been implemented. Full set of supported filters listed here: [https://github.com/flexferrum/Jinja2Cpp/issues/7](https://github.com/flexferrum/Jinja2Cpp/issues/7) +- A lot of testers have been implemented. Full set of supported testers listed here: [https://github.com/flexferrum/Jinja2Cpp/issues/8](https://github.com/flexferrum/Jinja2Cpp/issues/8) - 'Concatenate as string' operator ('~') has been implemented - For-loop with 'if' condition has been implemented - Fixed some bugs in parser From 2e7453b7a9a7b78a67736d1240ea1d3827ffb9e5 Mon Sep 17 00:00:00 2001 From: vvish Date: Tue, 22 Oct 2019 16:44:21 +0200 Subject: [PATCH 120/206] Added initial implementation for tojson filter (#142) (#169) * Added initial implementation for tojson filter (#142) * Implemented serialization to json based or rapidjson framework. * Added initial filter implementation + tests. * Entry added to the list of supported filters in README. * Add rapidjson as a thirdparty library * Encoding for Json serialization changed to UTF8 (#142) * Unit-tests for 'tojson' filter fixed to be implementation/platform independant (#142) * Fix integration with RapidJSON library * Tests for rapidjson based serializer fixed to be platform independent (#142) * Minor style corrections in the 'tojson' filter tests (#142) --- CMakeLists.txt | 6 +- README.md | 2 +- src/filters.cpp | 31 +++++- src/filters.h | 3 + src/rapid_json_serializer.cpp | 124 ++++++++++++++++++++++++ src/rapid_json_serializer.h | 50 ++++++++++ test/rapid_json_serializer_test.cpp | 66 +++++++++++++ test/tojson_filter_test.cpp | 112 +++++++++++++++++++++ thirdparty/CMakeLists.txt | 2 +- thirdparty/internal_deps.cmake | 31 ++++-- thirdparty/thirdparty-conan-build.cmake | 3 +- thirdparty/thirdparty-external.cmake | 17 +++- 12 files changed, 428 insertions(+), 19 deletions(-) create mode 100644 src/rapid_json_serializer.cpp create mode 100644 src/rapid_json_serializer.h create mode 100644 test/rapid_json_serializer_test.cpp create mode 100644 test/tojson_filter_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ba03ad4e..1db7c50b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,8 @@ target_link_libraries( target_include_directories(${LIB_TARGET_NAME} PUBLIC $ - $) + $ + ) if(JINJA2CPP_STRICT_WARNINGS) if(NOT MSVC) @@ -193,8 +194,7 @@ if (JINJA2CPP_BUILD_TESTS) CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test) add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders}) - target_link_libraries(jinja2cpp_tests gtest gtest_main nlohmann_json ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS} ) - target_include_directories(jinja2cpp_tests PRIVATE ${RapidJSON_INCLUDE_DIR}) + target_link_libraries(jinja2cpp_tests gtest gtest_main nlohmann_json ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS} ${JINJA2CPP_PRIVATE_LIBS}) set_target_properties(jinja2cpp_tests PROPERTIES CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} diff --git a/README.md b/README.md index 2ad8c821..7acd2efb 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ More detailed examples and features description can be found in the documentatio ## Current Jinja2 support Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be a full [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: - expressions. You can use almost every expression style: simple, filtered, conditional, and so on. -- the big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape**) +- the big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape, tojson**) - the big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**) - the number of functions (**range**, **loop.cycle**) - 'if' statement (with 'elif' and 'else' branches) diff --git a/src/filters.cpp b/src/filters.cpp index 92b83df0..22d784dc 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -4,6 +4,9 @@ #include "value_visitors.h" #include "value_helpers.h" #include "generic_adapters.h" +#include "rapid_json_serializer.h" + +#include #include #include @@ -769,13 +772,35 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte return result; } -Serialize::Serialize(FilterParams, Serialize::Mode) +Serialize::Serialize(const FilterParams params, const Serialize::Mode mode) + : m_mode(mode) { - + switch (mode) + { + case JsonMode: + ParseParams({ { "indent", false, static_cast(0) } }, params); + break; + default: + break; + } } -InternalValue Serialize::Filter(const InternalValue&, RenderContext&) +InternalValue Serialize::Filter(const InternalValue& value, RenderContext& context) { + if (m_mode == JsonMode) + { + const auto indent = ConvertToInt(this->GetArgumentValue("indent", context)); + jinja2::rapidjson_serializer::DocumentWrapper jsonDoc; + const auto jsonValue = jsonDoc.CreateValue(value); + auto jsonString = jsonValue.AsString(indent); + boost::algorithm::replace_all(jsonString, "'", "\\u0027"); + boost::algorithm::replace_all(jsonString, "<", "\\u003c"); + boost::algorithm::replace_all(jsonString, ">", "\\u003e"); + boost::algorithm::replace_all(jsonString, "&", "\\u0026"); + + return jsonString; + } + return InternalValue(); } diff --git a/src/filters.h b/src/filters.h index ce60fca1..75dfa661 100644 --- a/src/filters.h +++ b/src/filters.h @@ -140,6 +140,9 @@ class Serialize : public FilterBase Serialize(FilterParams params, Mode mode); InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + +private: + Mode m_mode; }; class Slice : public FilterBase diff --git a/src/rapid_json_serializer.cpp b/src/rapid_json_serializer.cpp new file mode 100644 index 00000000..6addd799 --- /dev/null +++ b/src/rapid_json_serializer.cpp @@ -0,0 +1,124 @@ +#include "rapid_json_serializer.h" + +#include "value_visitors.h" + +#include + +namespace jinja2 +{ +namespace rapidjson_serializer +{ +namespace +{ +struct JsonInserter : visitors::BaseVisitor +{ + using BaseVisitor::operator(); + + explicit JsonInserter(rapidjson::Document::AllocatorType& allocator) + : m_allocator(allocator) + { + } + + rapidjson::Value operator()(const ListAdapter& list) const + { + rapidjson::Value listValue(rapidjson::kArrayType); + + for (auto& v : list) + { + listValue.PushBack(Apply(v, m_allocator), m_allocator); + } + return listValue; + } + + rapidjson::Value operator()(const MapAdapter& map) const + { + rapidjson::Value mapNode(rapidjson::kObjectType); + + const auto& keys = map.GetKeys(); + for (auto& k : keys) + { + mapNode.AddMember(rapidjson::Value(k.c_str(), m_allocator), Apply(map.GetValueByName(k), m_allocator), m_allocator); + } + + return mapNode; + } + + rapidjson::Value operator()(const KeyValuePair& kwPair) const + { + rapidjson::Value pairNode(rapidjson::kObjectType); + pairNode.AddMember(rapidjson::Value(kwPair.key.c_str(), m_allocator), Apply(kwPair.value, m_allocator), m_allocator); + + return pairNode; + } + + rapidjson::Value operator()(const std::string& str) const { return rapidjson::Value(str.c_str(), m_allocator); } + + rapidjson::Value operator()(const nonstd::string_view& str) const { return rapidjson::Value(str.data(), str.size(), m_allocator); } + + rapidjson::Value operator()(const std::wstring& str) const + { + auto s = ConvertString(str); + return rapidjson::Value(s.c_str(), m_allocator); + } + + rapidjson::Value operator()(const nonstd::wstring_view& str) const + { + auto s = ConvertString(str); + return rapidjson::Value(s.c_str(), m_allocator); + } + + rapidjson::Value operator()(bool val) const { return rapidjson::Value(val); } + + rapidjson::Value operator()(EmptyValue) const { return rapidjson::Value(); } + + rapidjson::Value operator()(const Callable&) const { return rapidjson::Value(""); } + + rapidjson::Value operator()(double val) const { return rapidjson::Value(val); } + + rapidjson::Value operator()(int64_t val) const { return rapidjson::Value(val); } + + rapidjson::Document::AllocatorType& m_allocator; +}; +} // namespace + +DocumentWrapper::DocumentWrapper() + : m_document(std::make_shared()) +{ +} + +ValueWrapper DocumentWrapper::CreateValue(const InternalValue& value) const +{ + auto v = Apply(value, m_document->GetAllocator()); + return ValueWrapper(std::move(v), m_document); +} + +ValueWrapper::ValueWrapper(rapidjson::Value&& value, std::shared_ptr document) + : m_value(std::move(value)) + , m_document(document) +{ +} + +std::string ValueWrapper::AsString(const uint8_t indent) const +{ + using Writer = rapidjson::Writer>; + using PrettyWriter = rapidjson::PrettyWriter>; + + rapidjson::StringBuffer buffer; + if (indent == 0) + { + Writer writer(buffer); + m_value.Accept(writer); + } + else + { + PrettyWriter writer(buffer); + writer.SetIndent(' ', indent); + writer.SetFormatOptions(rapidjson::kFormatSingleLineArray); + m_value.Accept(writer); + } + + return buffer.GetString(); +} + +} // namespace rapidjson_serializer +} // namespace jinja2 diff --git a/src/rapid_json_serializer.h b/src/rapid_json_serializer.h new file mode 100644 index 00000000..59a6667f --- /dev/null +++ b/src/rapid_json_serializer.h @@ -0,0 +1,50 @@ +#ifndef JINJA2CPP_RAPID_JSON_SERIALIZER_H +#define JINJA2CPP_RAPID_JSON_SERIALIZER_H + +#include "internal_value.h" + +#include +#include + +#include + +namespace jinja2 +{ +namespace rapidjson_serializer +{ + +class ValueWrapper +{ + friend class DocumentWrapper; + +public: + ValueWrapper(ValueWrapper&&) = default; + ValueWrapper& operator=(ValueWrapper&&) = default; + + std::string AsString(uint8_t indent = 0) const; + +private: + ValueWrapper(rapidjson::Value&& value, std::shared_ptr document); + + rapidjson::Value m_value; + std::shared_ptr m_document; +}; + +class DocumentWrapper +{ +public: + DocumentWrapper(); + + DocumentWrapper(DocumentWrapper&&) = default; + DocumentWrapper& operator=(DocumentWrapper&&) = default; + + ValueWrapper CreateValue(const InternalValue& value) const; + +private: + std::shared_ptr m_document; +}; + +} // namespace rapidjson_serializer +} // namespace jinja2 + +#endif // JINJA2CPP_RAPID_JSON_SERIALIZER_H diff --git a/test/rapid_json_serializer_test.cpp b/test/rapid_json_serializer_test.cpp new file mode 100644 index 00000000..33e80071 --- /dev/null +++ b/test/rapid_json_serializer_test.cpp @@ -0,0 +1,66 @@ +#include "../src/rapid_json_serializer.h" +#include "gtest/gtest.h" + +namespace +{ +template +jinja2::InternalValue MakeInternalValue(T&& v) +{ + return jinja2::InternalValue(std::forward(v)); +} +} +TEST(RapidJsonSerializerTest, SerializeTrivialTypes) +{ + const jinja2::rapidjson_serializer::DocumentWrapper document; + + const auto stringValue = document.CreateValue(MakeInternalValue("string")); + const auto intValue = document.CreateValue(MakeInternalValue(123)); + const auto doubleValue = document.CreateValue(MakeInternalValue(12.34)); + + EXPECT_EQ("\"string\"", stringValue.AsString()); + EXPECT_EQ("123", intValue.AsString()); + EXPECT_EQ("12.34", doubleValue.AsString()); +} + +TEST(RapidJsonSerializerTest, SerializeComplexTypes) +{ + jinja2::rapidjson_serializer::DocumentWrapper document; + { + jinja2::InternalValueMap params = { { "string", MakeInternalValue("hello") } }; + const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); + EXPECT_EQ("{\"string\":\"hello\"}", jsonValue.AsString()); + } + + { + jinja2::InternalValueMap params = { { "int", MakeInternalValue(123) } }; + const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); + EXPECT_EQ("{\"int\":123}", jsonValue.AsString()); + } + + { + jinja2::InternalValueList array{ MakeInternalValue(1), MakeInternalValue(2), MakeInternalValue(3) }; + jinja2::InternalValueMap map{ { "array", jinja2::ListAdapter::CreateAdapter(std::move(array)) } }; + jinja2::InternalValueMap params = { { "map", CreateMapAdapter(std::move(map)) } }; + const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); + EXPECT_EQ("{\"map\":{\"array\":[1,2,3]}}", jsonValue.AsString()); + } +} + +TEST(RapidJsonSerializerTest, SerializeComplexTypesWithIndention) +{ + const jinja2::rapidjson_serializer::DocumentWrapper document; + + jinja2::InternalValueList array{ MakeInternalValue(1), MakeInternalValue(2), MakeInternalValue(3) }; + jinja2::InternalValueMap map{ { "array", jinja2::ListAdapter::CreateAdapter(std::move(array)) } }; + jinja2::InternalValueMap params = { { "map", CreateMapAdapter(std::move(map)) } }; + const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); + + auto indentedDocument = +R"({ + "map": { + "array": [1, 2, 3] + } +})"; + + EXPECT_EQ(indentedDocument, jsonValue.AsString(4)); +} diff --git a/test/tojson_filter_test.cpp b/test/tojson_filter_test.cpp new file mode 100644 index 00000000..6f9cddfc --- /dev/null +++ b/test/tojson_filter_test.cpp @@ -0,0 +1,112 @@ +#include "jinja2cpp/template.h" +#include "test_tools.h" + +#include + +using namespace jinja2; + +SUBSTITUION_TEST_P(JsonFilterSubstitutionTest) + +INSTANTIATE_TEST_CASE_P(ToJson, + JsonFilterSubstitutionTest, + ::testing::Values(InputOutputPair{ "(1, 2, 3) | tojson", "[1,2,3]" }, + InputOutputPair{ "(1, 2, 3) | tojson(indent = 1)", "[1, 2, 3]" }, + InputOutputPair{ "'\"ba&r\\'' | tojson", "\"\\\"ba\\u0026r\\u0027\"" }, + InputOutputPair{ "'' | tojson", "\"\\u003cbar\\u003e\"" })); + +struct ToJson : ::testing::Test +{ + ValuesMap GetObjectParam() const + { + const ValuesMap object{ { "intValue", 3 }, + { "doubleValue", 12.123f }, + { "stringValue", "rain" }, + { "wstringValue", std::wstring(L"rain") }, + { "boolFalseValue", false }, + { "boolTrueValue", true }, + { "listValue", ValuesList{ 1, 2, 3 } }, + { "map", ValuesMap{ { "str1", 1 } } } }; + + return ValuesMap{ { "obj", object } }; + } + + ValuesMap GetKeyValuePairParam() const + { + const ValuesMap pair{ { "foo", "bar" } }; + return ValuesMap{ { "obj", pair } }; + } + + template + void PerformJsonTest(const StringType& source, const StringType& expectedResult, const jinja2::ValuesMap& params) + { + TemplateType tpl; + ASSERT_TRUE(tpl.Load(source)); + const auto result = tpl.RenderAsString(params).value(); + + const auto expectedJson = nlohmann::json::parse(expectedResult); + const auto resultJson = nlohmann::json::parse(result); + + EXPECT_EQ(expectedJson, resultJson); + } + + void PerformBothJsonTests(const std::string& source, const std::string& expectedResult, const jinja2::ValuesMap& params) + { + PerformJsonTest