diff --git a/CMakeLists.txt b/CMakeLists.txt index 175650c..4aabcff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.23.0) -project(Jinja2Cpp VERSION 1.3.0) + +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + +project(Jinja2Cpp VERSION 1.3.2) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") set(JINJA2CPP_IS_MAIN_PROJECT TRUE) @@ -23,6 +28,7 @@ option(JINJA2CPP_STRICT_WARNINGS "Enable additional warnings and treat them as e option(JINJA2CPP_BUILD_SHARED "Build shared linkage version of Jinja2Cpp" OFF) option(JINJA2CPP_PIC "Control -fPIC option for library build" OFF) option(JINJA2CPP_VERBOSE "Add extra debug output to the build scripts" OFF) +option(JINJA2CPP_INSTALL "Add installation rules for JinjaCpp targets" ${JINJA2CPP_IS_MAIN_PROJECT}) if (DEFINED BUILD_SHARED_LIBS) set(JINJA2CPP_BUILD_SHARED ${BUILD_SHARED_LIBS}) @@ -298,51 +304,53 @@ Jinja2CppGetTargetIncludeDir(STRING-VIEW-LITE nonstd::string-view-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 - FILE_SET HEADERS -) +if(JINJA2CPP_INSTALL) + install( + TARGETS + ${LIB_TARGET_NAME} + EXPORT + InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + FILE_SET HEADERS + ) -install( - FILES - ${CMAKE_BINARY_DIR}/jinja2cpp.pc - DESTINATION - ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig -) + 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( + 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.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 -) + 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} -) + 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} + ) +endif() \ No newline at end of file diff --git a/README.md b/README.md index 61b0f32..3014731 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Hello World!!! That's all! -More detailed examples and features description can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.github.io/docs/usage) +More detailed examples and features description can be found in the documentation: [https://jinja2cpp.github.io/docs/usage](https://jinja2cpp.github.io/docs/usage) ## Current Jinja2 support Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be a fully [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: @@ -111,7 +111,7 @@ Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, - recursive loops - space control and 'raw'/'endraw' blocks -Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.github.io/docs/j2_compatibility.html). +Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.github.io/docs/j2_compatibility.html](https://jinja2cpp.github.io/docs/j2_compatibility.html). ## Supported compilers Compilation of Jinja2C++ tested on the following compilers (with C++14 and C++17 enabled features): @@ -134,10 +134,10 @@ Compilation of Jinja2C++ tested on the following compilers (with C++14 and C++17 | **X-Code** 9, 10, 11 | [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) | | **MSVC** 2017 (x86, x64), **MSVC** 2019 (x86, x64), C++14/C++17 | [![](https://github.com/jinja2cpp/Jinja2Cpp/workflows/CI-windows-build/badge.svg)](https://github.com/jinja2cpp/Jinja2Cpp/actions?query=workflow%3ACI-windows-build) | | **g++** 5, 6, 7, 8, 9, 10, 11 **clang** 5, 6, 7, 8, 9, 10, 11, 12 C++14/C++17/C++20 | [![](https://github.com/jinja2cpp/Jinja2Cpp/workflows/CI-linux-build/badge.svg)](https://github.com/jinja2cpp/Jinja2Cpp/actions?query=workflow%3ACI-linux-build) | - + ## Build and install Jinja2C++ has several external dependencies: -- `boost` library (at least version 1.65) +- `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::optional-lite` [https://github.com/martinmoene/optional-lite](https://github.com/martinmoene/optional-lite) @@ -182,24 +182,37 @@ In this case, Jinja2C++ will be built with internally-shipped dependencies and i 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. Add a reference to Jinja2C++ package (`jinja2cpp/1.1.0`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with the usage of `conan-cmake` integration it could be written this way: +2. Add a reference to Jinja2C++ package (`jinja2cpp/1.2.1`) 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) -if (NOT MSVC) - set (CONAN_SETTINGS SETTINGS compiler.libcxx=libstdc++11) -endif () -conan_cmake_run(REQUIRES +cmake_minimum_required(VERSION 3.24) +project(Jinja2CppSampleConan CXX) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) +list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) + +add_definitions("-std=c++14") + +if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") + message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") + file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/0.18.1/conan.cmake" + "${CMAKE_BINARY_DIR}/conan.cmake" + TLS_VERIFY ON) +endif() +include(${CMAKE_BINARY_DIR}/conan.cmake) + +conan_cmake_autodetect(settings) +conan_cmake_run(REQUIRES jinja2cpp/1.1.0 - gtest/1.7.0@bincrafters/stable + gtest/1.14.0 BASIC_SETUP ${CONAN_SETTINGS} - OPTIONS - jinja2cpp:shared=False - gtest:shared=False + OPTIONS + jinja2cpp/*:shared=False + gtest/*:shared=False BUILD missing) - + set (TARGET_NAME jinja2cpp_build_test) add_executable (${TARGET_NAME} main.cpp) @@ -229,6 +242,8 @@ You can define (via -D command-line CMake option) the following build flags: Jinja2C++ tries to use standard versions of `std::variant`, `std::string_view` and `std::optional` if possible. ## Acknowledgments +Thanks to **@flexferrum** for creating this library, for being one of the brightest minds in software engineering community. Rest in peace, friend. + 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. @@ -240,6 +255,48 @@ Thanks to **@martinus** for the fast hash maps implementation. ## Changelog +### Version 1.3.2 + +#### What's Changed +- Fix empty dict literal parsing in https://github.com/jinja2cpp/Jinja2Cpp/pull/243 +- Fixing missing life rage protection for the reverse filter (and others). by @jferreyra-sc in https://github.com/jinja2cpp/Jinja2Cpp/pull/246 +- Ability to disable JinjaCpp installation rules by @ilya-lavrenov in https://github.com/jinja2cpp/Jinja2Cpp/pull/250 +- Cmake finds only downloaded rapidjson by @ilya-lavrenov in https://github.com/jinja2cpp/Jinja2Cpp/pull/254 +- Update boost dependencies by @Cheaterdev in https://github.com/jinja2cpp/Jinja2Cpp/pull/253 + +#### New Contributors +- @jferreyra-sc made their first contribution in https://github.com/jinja2cpp/Jinja2Cpp/pull/246 +- @ilya-lavrenov made their first contribution in https://github.com/jinja2cpp/Jinja2Cpp/pull/250 +- @Cheaterdev made their first contribution in https://github.com/jinja2cpp/Jinja2Cpp/pull/253 + +### Version 1.3.1 + +#### Changes and improvements +- bump deps versions +- add new json binding - boost::json +- speedup regex parsing by switching to boost::regex(std::regex extremely slow) + - templates are now loading faster + +#### Fixed bugs +- small fixes across code base + +#### Breaking changes +- internal deps now used through cmake fetch_content +- default json serializer/deserializer is switched to boost::json + +### Version 1.2.1 + +#### Changes and improvements +- bump deps versions +- support modern compilers(up to Clang 12) and standards(C++20) +- tiny code style cleanup + +#### Fixed bugs +- small fixes across code base + +#### Breaking changes +- internal deps point to make based boost build + ### Version 1.1.0 #### Changes and improvements - `batch` filter added @@ -297,13 +354,13 @@ Thanks to **@martinus** for the fast hash maps implementation. ### 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 +- 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.github.io/docs/usage/ud_callables.html - 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 - Sample build projects for various Jinja2C++ usage variants created: https://github.com/jinja2cpp/examples-build](https://github.com/jinja2cpp/examples-build) -- Documentation site created for Jinja2C++: https://jinja2cpp.dev/ +- Documentation site created for Jinja2C++: https://jinja2cpp.github.io #### Minor changes - Render-time error handling added diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake index 0aabecb..3e8720f 100644 --- a/cmake/coverage.cmake +++ b/cmake/coverage.cmake @@ -15,10 +15,12 @@ function(add_coverage_target _TARGET) ) target_link_libraries(${_TARGET} INTERFACE gcov) - install( - TARGETS - ${_TARGET} - EXPORT - InstallTargets - ) + if(JINJA2CPP_INSTALL) + install( + TARGETS + ${_TARGET} + EXPORT + InstallTargets + ) + endif() endfunction() diff --git a/cmake/patches/0001-fix-skip-install-rules.patch b/cmake/patches/0001-fix-skip-install-rules.patch deleted file mode 100644 index d20445a..0000000 --- a/cmake/patches/0001-fix-skip-install-rules.patch +++ /dev/null @@ -1,25 +0,0 @@ -From d924c3bf4d83e9ef3ce66a6ac1e80ef1cb7cc4ca Mon Sep 17 00:00:00 2001 -From: Ruslan Morozov -Date: Sat, 3 Jun 2023 12:21:15 +0300 -Subject: [PATCH] [PATCH] fix skip install rules - ---- - include/BoostRoot.cmake | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tools/cmake/include/BoostRoot.cmake b/tools/cmake/include/BoostRoot.cmake -index e93f907..f0380b3 100644 ---- a/tools/cmake/include/BoostRoot.cmake -+++ b/tools/cmake/include/BoostRoot.cmake -@@ -108,7 +108,7 @@ else() - endif() - - set(BUILD_TESTING OFF) -- set(BOOST_SKIP_INSTALL_RULES ON) -+ set(BOOST_SKIP_INSTALL_RULES OFF) - - endif() - --- -2.39.2 (Apple Git-143) - diff --git a/cmake/public/jinja2cpp-config.cmake.in b/cmake/public/jinja2cpp-config.cmake.in index a437f2a..37e6652 100644 --- a/cmake/public/jinja2cpp-config.cmake.in +++ b/cmake/public/jinja2cpp-config.cmake.in @@ -62,7 +62,7 @@ if (JINJA2CPP_BUILD_SHARED) endif() -# INTERFACE_LINK_LIBRARIES "nonstd::expected-lite;nonstd::variant-lite;nonstd::value_ptr-lite;nonstd::optional-lite;\$;\$;\$" +# 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.") diff --git a/cmake/sanitizer.cmake b/cmake/sanitizer.cmake index 36e4bc3..7b63759 100644 --- a/cmake/sanitizer.cmake +++ b/cmake/sanitizer.cmake @@ -30,12 +30,14 @@ function(add_sanitizer_target _TARGET) ${_BASE_ENABLE_SANITIZER_FLAGS} ) - install( - TARGETS - ${_TARGET} - EXPORT - InstallTargets - ) + if(JINJA2CPP_INSTALL) + install( + TARGETS + ${_TARGET} + EXPORT + InstallTargets + ) + endif() endfunction() diff --git a/conanfile.txt b/conanfile.txt index 1c661f7..13be302 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,5 +1,5 @@ [requires] -boost/1.83.0 +boost/1.85.0 expected-lite/0.6.3 fmt/10.1.1 nlohmann_json/3.11.2 diff --git a/include/jinja2cpp/polymorphic_value.h b/include/jinja2cpp/polymorphic_value.h index 69663d8..c5cb86a 100644 --- a/include/jinja2cpp/polymorphic_value.h +++ b/include/jinja2cpp/polymorphic_value.h @@ -31,6 +31,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +// required variant_CPP17_OR_GREATER definition +#include // // in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index 136e33d..019f149 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -127,7 +127,7 @@ InternalValue BinaryExpression::Evaluate(RenderContext& context) case jinja2::BinaryExpression::Minus: case jinja2::BinaryExpression::Mul: case jinja2::BinaryExpression::Div: - case jinja2::BinaryExpression::DivReminder: + case jinja2::BinaryExpression::DivRemainder: case jinja2::BinaryExpression::DivInteger: case jinja2::BinaryExpression::Pow: result = Apply2(leftVal, rightVal, m_oper); diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index 06b1c40..5bd7031 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -452,7 +452,7 @@ class BinaryExpression : public Expression Minus, Mul, Div, - DivReminder, + DivRemainder, DivInteger, Pow, StringConcat diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index edc599c..3567d96 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -242,7 +242,7 @@ ExpressionParser::ParseResult> ExpressionPars operation = BinaryExpression::DivInteger; break; case '%': - operation = BinaryExpression::DivReminder; + operation = BinaryExpression::DivRemainder; break; default: lexer.ReturnToken(); @@ -370,7 +370,7 @@ ExpressionParser::ParseResult> ExpressionPars ExpressionEvaluatorPtr result; std::unordered_map> items; - if (lexer.EatIfEqual(']')) + if (lexer.EatIfEqual('}')) return std::make_shared(std::move(items)); do diff --git a/src/filters.cpp b/src/filters.cpp index fca0bd6..3ab9f33 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -458,6 +458,12 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte if (!isConverted) return result; + + auto ProtectedValue = [&baseVal](InternalValue value) { + if (baseVal.ShouldExtendLifetime()) + value.SetParentData(baseVal); + return value; + }; InternalValue attrName = GetArgumentValue("attribute", context); InternalValue isCsVal = GetArgumentValue("case_sensitive", context, InternalValue(false)); @@ -482,23 +488,23 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte { case FirstItemMode: if (listSize) - result = list.GetValueByIndex(0); + result = ProtectedValue( list.GetValueByIndex(0) ); else { auto it = list.begin(); if (it != list.end()) - result = *it; + result = ProtectedValue(*it); } break; case LastItemMode: if (listSize) - result = list.GetValueByIndex(listSize.value() - 1); + result = ProtectedValue(list.GetValueByIndex(listSize.value() - 1)); else { auto it = list.begin(); auto end = list.end(); for (; it != end; ++it) - result = *it; + result = ProtectedValue(*it); } break; case LengthMode: @@ -514,7 +520,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte if (listSize) { std::uniform_int_distribution<> dis(0, static_cast(listSize.value()) - 1); - result = list.GetValueByIndex(dis(gen)); + result = ProtectedValue(list.GetValueByIndex(dis(gen))); } else { @@ -525,7 +531,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte { bool doCopy = count == 0 || std::uniform_int_distribution(0, count)(gen) == 0; if (doCopy) - result = *it; + result = ProtectedValue(*it); } } break; @@ -535,7 +541,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte auto b = list.begin(); auto e = list.end(); auto p = std::max_element(list.begin(), list.end(), lessComparator); - result = p != e ? *p : InternalValue(); + result = p != e ? ProtectedValue(*p) : InternalValue(); break; } case MinItemMode: @@ -543,7 +549,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte auto b = list.begin(); auto e = list.end(); auto p = std::min_element(b, e, lessComparator); - result = p != e ? *p : InternalValue(); + result = p != e ? ProtectedValue(*p) : InternalValue(); break; } case ReverseMode: @@ -553,7 +559,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte auto size = listSize.value(); InternalValueList resultList(size); for (std::size_t n = 0; n < size; ++n) - resultList[size - n - 1] = list.GetValueByIndex(n); + resultList[size - n - 1] = ProtectedValue( list.GetValueByIndex(n) ); result = ListAdapter::CreateAdapter(std::move(resultList)); } else @@ -562,7 +568,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte auto it = list.begin(); auto end = list.end(); for (; it != end; ++it) - resultList.push_back(*it); + resultList.push_back( ProtectedValue(*it) ); std::reverse(resultList.begin(), resultList.end()); result = ListAdapter::CreateAdapter(std::move(resultList)); @@ -625,7 +631,7 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte std::stable_sort(items.begin(), items.end(), [](auto& i1, auto& i2) { return i1.idx < i2.idx; }); for (auto& i : items) - resultList.push_back(list.GetValueByIndex(i.idx)); + resultList.push_back( ProtectedValue( list.GetValueByIndex(i.idx) )); result = ListAdapter::CreateAdapter(std::move(resultList)); break; @@ -656,6 +662,12 @@ InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context if (!isConverted) return result; + auto ProtectedValue = [&baseVal](InternalValue value) { + if (baseVal.ShouldExtendLifetime()) + value.SetParentData(baseVal); + return value; + }; + InternalValue sliceLengthValue = GetArgumentValue("slices", context); int64_t sliceLength = ConvertToInt(sliceLengthValue); InternalValue fillWith = GetArgumentValue("fill_with", context); @@ -673,7 +685,7 @@ InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context sublist.clear(); sublistItemIndex %= sliceLength; } - sublist.push_back(item); + sublist.push_back( ProtectedValue(item) ); ++sublistItemIndex; } if (!IsEmpty(fillWith)) @@ -707,7 +719,13 @@ InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context) InternalValueList resultList; resultList.reserve(linecount); - + + auto ProtectedValue = [&baseVal](InternalValue value) { + if (baseVal.ShouldExtendLifetime()) + value.SetParentData(baseVal); + return value; + }; + 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) @@ -718,7 +736,7 @@ InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context) std::fill_n(std::back_inserter(row), columns, fillWith); for (std::size_t column = 0; column < elems; ++column) - row[column] = list.GetValueByIndex(idx++); + row[column] = ProtectedValue( list.GetValueByIndex(idx++) ); resultList.push_back(ListAdapter::CreateAdapter(std::move(row))); } diff --git a/src/serialize_filters.cpp b/src/serialize_filters.cpp index e467fd8..a8e4363 100644 --- a/src/serialize_filters.cpp +++ b/src/serialize_filters.cpp @@ -152,28 +152,21 @@ InternalValue Serialize::Filter(const InternalValue& value, RenderContext& conte DocumentWrapper jsonDoc; const auto jsonValue = jsonDoc.CreateValue(value); const auto jsonString = jsonValue.AsString(static_cast(indent)); - const auto result = std::accumulate(jsonString.begin(), jsonString.end(), ""s, [](const auto &str, const auto &c) - { - switch (c) - { - case '<': - return str + "\\u003c"; - break; - case '>': - return str +"\\u003e"; - break; - case '&': - return str +"\\u0026"; - break; - case '\'': - return str +"\\u0027"; - break; - default: - return str + c; - break; + std::string result = ""s; + result.reserve(jsonString.size()); + for (char c : jsonString) { + if (c == '<') { + result.append("\\u003c"); + } else if (c == '>') { + result.append("\\u003e"); + } else if (c == '&') { + result.append("\\u0026"); + } else if (c == '\'') { + result.append("\\u0027"); + } else { + result.push_back(c); } - }); - + } return result; } diff --git a/src/value_visitors.h b/src/value_visitors.h index 4ac874f..193680c 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -552,8 +552,8 @@ struct BinaryMathOperation : BaseVisitor<> case jinja2::BinaryExpression::Div: result = left / right; break; - case jinja2::BinaryExpression::DivReminder: - result = std::remainder(left, right); + case jinja2::BinaryExpression::DivRemainder: + result = std::fmod(left, right); break; case jinja2::BinaryExpression::DivInteger: { @@ -607,7 +607,7 @@ struct BinaryMathOperation : BaseVisitor<> result = left / right; break; case jinja2::BinaryExpression::Div: - case jinja2::BinaryExpression::DivReminder: + case jinja2::BinaryExpression::DivRemainder: case jinja2::BinaryExpression::Pow: result = this->operator ()(static_cast(left), static_cast(right)); break; diff --git a/test/expressions_test.cpp b/test/expressions_test.cpp index b528102..462f50b 100644 --- a/test/expressions_test.cpp +++ b/test/expressions_test.cpp @@ -20,6 +20,7 @@ R"( {{ 7 / 3}} {{ 7 // 3 }} {{ 7 % intValue }} +{{ 11 % 7 }} {{ 3 ** 4 }} {{ 10 ** -2 }} {{ 10/10 + 2*5 }} @@ -48,6 +49,7 @@ R"( 2.3333333 2 1 +4 81 0.01 11 @@ -100,6 +102,19 @@ rain }; } +MULTISTR_TEST(ExpressionsMultiStrTest, EmptyDict, +R"( +{% set d = {} %} +{{ d.asdf|default(42) }} +)", +//----------- +R"( + +42 +)") +{ +} + TEST(ExpressionTest, DoStatement) { std::string source = R"( diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fc8f51d..57c8f8e 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -71,7 +71,7 @@ endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} Boost::variant Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json - Boost::regex fmt RapidJson) + Boost::regex Boost::numeric_conversion fmt RapidJson) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () diff --git a/thirdparty/external_boost_deps.cmake b/thirdparty/external_boost_deps.cmake index a14484f..e6305d2 100644 --- a/thirdparty/external_boost_deps.cmake +++ b/thirdparty/external_boost_deps.cmake @@ -19,45 +19,56 @@ if (MSVC) endif () endif () -find_package(boost_algorithm ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_json ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_optional ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_regex ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_algorithm ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_numeric_conversion ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_json ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_optional ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_regex ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_lexical_cast ${FIND_BOOST_PACKAGE_QUIET}) if (boost_algorithm_FOUND AND boost_filesystem_FOUND AND + boost_numeric_conversion_FOUND AND boost_json_FOUND AND boost_optional_FOUND AND boost_variant_FOUND AND boost_regex_FOUND) - imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) - imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) - imported_target_alias(boost_json ALIAS boost_json::boost_json) - imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) - imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) - imported_target_alias(boost_regex ALIAS boost_regex::boost_regex) + imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) + imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) + imported_target_alias(boost_numeric_conversion ALIAS numeric_conversion::numeric_conversion) + imported_target_alias(boost_json ALIAS boost_json::boost_json) + imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) + imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) + imported_target_alias(boost_regex ALIAS boost_regex::boost_regex) + imported_target_alias(boost_lexical_cast ALIAS boost_regex::lexical_cast) + else () - find_package(Boost COMPONENTS system filesystem json regex ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) + find_package(Boost COMPONENTS system filesystem numeric_conversion json regex optional variant algorithm lexical_cast ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) if (Boost_FOUND) - imported_target_alias(boost_algorithm ALIAS Boost::boost) - imported_target_alias(boost_filesystem ALIAS Boost::filesystem) - imported_target_alias(boost_json ALIAS Boost::json) - imported_target_alias(boost_optional ALIAS Boost::boost) - imported_target_alias(boost_variant ALIAS Boost::boost) - imported_target_alias(boost_regex ALIAS Boost::regex) + imported_target_alias(boost_algorithm ALIAS Boost::boost) + imported_target_alias(boost_filesystem ALIAS Boost::filesystem) + imported_target_alias(boost_numeric_conversion ALIAS Boost::numeric_conversion) + imported_target_alias(boost_json ALIAS Boost::json) + imported_target_alias(boost_optional ALIAS Boost::boost) + imported_target_alias(boost_variant ALIAS Boost::boost) + imported_target_alias(boost_regex ALIAS Boost::regex) + imported_target_alias(boost_lexical_cast ALIAS Boost::lexical_cast) endif () endif () set(_additional_boost_install_targets) if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost") -set(_additional_boost_install_targets "boost_regex") + set(_additional_boost_install_targets "boost_regex") +endif() + +if(JINJA2CPP_INSTALL) + install(TARGETS boost_algorithm boost_filesystem boost_numeric_conversion boost_json boost_optional boost_variant ${_additional_boost_install_targets} + 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 + ) endif() -install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant ${_additional_boost_install_targets} - 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 - ) diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index 03311db..e09cee0 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -2,37 +2,37 @@ include(FetchContent) FetchContent_Declare( expected-lite - GIT_REPOSITORY https://github.com/martinmoene/expected-lite.git - GIT_TAG master + URL https://github.com/martinmoene/expected-lite/archive/refs/tags/v0.8.0.tar.gz + URL_HASH SHA256=27649f30bd9d4fe7b193ab3eb6f78c64d0f585c24c085f340b4722b3d0b5e701 ) FetchContent_MakeAvailable(expected-lite) FetchContent_Declare( variant-lite - GIT_REPOSITORY https://github.com/martinmoene/variant-lite.git - GIT_TAG master + URL https://github.com/martinmoene/variant-lite/archive/5015e841cf143487f2d7e2f619b618d455658fab.tar.gz + URL_HASH SHA256=d343cfa347517a2ee318864f3e2a08af8e17e699de701c69c1cdbdab79d9331f ) FetchContent_MakeAvailable(variant-lite) FetchContent_Declare( optional-lite - GIT_REPOSITORY https://github.com/martinmoene/optional-lite.git - GIT_TAG master + URL https://github.com/martinmoene/optional-lite/archive/refs/tags/v3.6.0.tar.gz + URL_HASH SHA256=2be17fcfc764809612282c3e728cabc42afe703b9dc333cc87c48d882fcfc2c2 ) FetchContent_MakeAvailable(optional-lite) FetchContent_Declare( string-view-lite - GIT_REPOSITORY https://github.com/martinmoene/string-view-lite.git - GIT_TAG master + URL https://github.com/martinmoene/string-view-lite/archive/refs/tags/v1.8.0.tar.gz + URL_HASH SHA256=9b38c32621eb1a81a7fa59427144309225c414a7bae522ab3a2d9ae239dd35be ) FetchContent_MakeAvailable(string-view-lite) set (FMT_INSTALL ON CACHE BOOL "" FORCE) FetchContent_Declare( fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 10.1.1 + URL https://github.com/fmtlib/fmt/archive/refs/tags/10.2.1.tar.gz + URL_HASH SHA256=1250e4cc58bf06ee631567523f48848dc4596133e163f02615c97f78bab6c811 ) FetchContent_MakeAvailable(fmt) @@ -44,18 +44,24 @@ set (RAPIDJSON_ENABLE_INSTRUMENTATION_OPT OFF CACHE BOOL "" FORCE) FetchContent_Declare( rapidjson - GIT_REPOSITORY https://github.com/Tencent/rapidjson.git - GIT_TAG 973dc9c06dcd3d035ebd039cfb9ea457721ec213 + # GIT_TAG f9d53419e912910fd8fa57d5705fa41425428c35 - latest but broken revision + URL https://github.com/Tencent/rapidjson/archive/973dc9c06dcd3d035ebd039cfb9ea457721ec213.tar.gz + URL_HASH SHA256=d0c9e52823d493206eb721d38cb3a669ca0212360862bd15a3c2f7d35ea7c6f7 ) -# GIT_TAG f9d53419e912910fd8fa57d5705fa41425428c35 - latest but broken revision FetchContent_MakeAvailable(rapidjson) -find_package(RapidJSON REQUIRED) + +find_package(RapidJSON REQUIRED + PATHS "${rapidjson_BINARY_DIR}" + NO_CMAKE_FIND_ROOT_PATH + NO_DEFAULT_PATH) + add_library(RapidJson INTERFACE) target_include_directories(RapidJson INTERFACE $ $ ) + if (JINJA2CPP_BUILD_TESTS) set (JSON_BuildTests OFF CACHE BOOL "" FORCE) set (JSON_Install OFF CACHE BOOL "" FORCE) @@ -63,8 +69,8 @@ if (JINJA2CPP_BUILD_TESTS) FetchContent_Declare( nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG develop + URL https://github.com/nlohmann/json/archive/8c391e04fe4195d8be862c97f38cfe10e2a3472e.tar.gz + URL_HASH SHA256=8ca375182e9557612f043eaa62dfc4224b41ddf07af704577666aadb7dd99a79 ) FetchContent_MakeAvailable(nlohmann_json) endif() diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index 773cb87..e1bc3a4 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -6,10 +6,10 @@ find_package(optional-lite REQUIRED) find_package(string-view-lite REQUIRED) find_package(nlohmann_json REQUIRED) -find_package(Boost COMPONENTS algorithm filesystem json optional variant regex REQUIRED) +find_package(Boost COMPONENTS algorithm filesystem numeric_conversion json optional variant regex REQUIRED) find_package(fmt REQUIRED) find_package(RapidJSON REQUIRED) -set(JINJA2_PRIVATE_LIBS_INT Boost::headers Boost::filesystem) +set(JINJA2_PRIVATE_LIBS_INT Boost::headers Boost::filesystem Boost::numeric_conversion) set(JINJA2_PUBLIC_LIBS_INT Boost::json fmt::fmt rapidjson Boost::regex nlohmann_json::nlohmann_json nonstd::expected-lite nonstd::variant-lite nonstd::optional-lite nonstd::string-view-lite) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake index 524dabe..a0de94a 100644 --- a/thirdparty/thirdparty-external.cmake +++ b/thirdparty/thirdparty-external.cmake @@ -53,26 +53,28 @@ if (TARGET fmt-header-only) 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} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nonstd - ) +if(JINJA2CPP_INSTALL) + install(TARGETS expected-lite variant-lite optional-lite string-view-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 + ) -install(TARGETS fmt-header-only - EXPORT InstallTargets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - ) + install(TARGETS fmt-header-only + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + ) -install(TARGETS RapidJson - EXPORT InstallTargets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - ) + install(TARGETS RapidJson + EXPORT InstallTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static + ) +endif() include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake index eafd93b..a47b272 100644 --- a/thirdparty/thirdparty-internal.cmake +++ b/thirdparty/thirdparty-internal.cmake @@ -8,6 +8,7 @@ list(APPEND BOOST_INCLUDE_LIBRARIES assert atomic filesystem + numeric/conversion lexical_cast optional variant @@ -18,10 +19,11 @@ list(APPEND BOOST_INCLUDE_LIBRARIES include(FetchContent) FetchContent_Declare( Boost - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-1.83.0 - PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true + URL https://github.com/boostorg/boost/releases/download/boost-1.85.0/boost-1.85.0-cmake.tar.gz + URL_HASH SHA256=ab9c9c4797384b0949dd676cf86b4f99553f8c148d767485aaac412af25183e6 ) + +set(BOOST_SKIP_INSTALL_RULES OFF) FetchContent_MakeAvailable(Boost) if(NOT MSVC) @@ -41,7 +43,6 @@ if(NOT MSVC) 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