├── .clang-format ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── linux-build.yml │ └── windows-build.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── cmake ├── build_thirdparty.cmake ├── code_coverage.cmake ├── collect_sources.cmake ├── coverage.cmake ├── public │ ├── FindJinja2Cpp.cmake │ ├── jinja2cpp-config-deps-conan-build.cmake.in │ ├── jinja2cpp-config-deps-conan.cmake.in │ ├── jinja2cpp-config-deps-external-boost.cmake.in │ ├── jinja2cpp-config-deps-external.cmake.in │ ├── jinja2cpp-config-deps-internal.cmake.in │ └── jinja2cpp-config.cmake.in ├── sanitizer.address+undefined.cmake ├── sanitizer.cmake └── sanitizer.memory.cmake ├── conanfile.txt ├── include └── jinja2cpp │ ├── binding │ ├── boost_json.h │ ├── nlohmann_json.h │ └── rapid_json.h │ ├── config.h │ ├── error_handler.h │ ├── error_info.h │ ├── filesystem_handler.h │ ├── generic_list.h │ ├── generic_list_impl.h │ ├── generic_list_iterator.h │ ├── polymorphic_value.h │ ├── reflected_value.h │ ├── string_helpers.h │ ├── template.h │ ├── template_env.h │ ├── user_callable.h │ ├── utils │ └── i_comparable.h │ ├── value.h │ └── value_ptr.h ├── jinja2cpp.pc.in ├── scripts └── build.sh ├── src ├── ast_visitor.h ├── binding │ ├── boost_json_serializer.cpp │ ├── boost_json_serializer.h │ ├── rapid_json_serializer.cpp │ └── rapid_json_serializer.h ├── error_handling.h ├── error_info.cpp ├── expression_evaluator.cpp ├── expression_evaluator.h ├── expression_parser.cpp ├── expression_parser.h ├── filesystem_handler.cpp ├── filters.cpp ├── filters.h ├── function_base.h ├── generic_adapters.h ├── generic_list.cpp ├── helpers.h ├── internal_value.cpp ├── internal_value.h ├── lexer.cpp ├── lexer.h ├── lexertk.h ├── out_stream.h ├── render_context.h ├── renderer.h ├── robin_hood.h ├── serialize_filters.cpp ├── statements.cpp ├── statements.h ├── string_converter_filter.cpp ├── template.cpp ├── template_env.cpp ├── template_impl.h ├── template_parser.cpp ├── template_parser.h ├── testers.cpp ├── testers.h ├── value.cpp ├── value_helpers.h └── value_visitors.h ├── test ├── basic_tests.cpp ├── binding │ ├── boost_json_binding_test.cpp │ ├── nlohmann_json_binding_test.cpp │ ├── rapid_json_binding_test.cpp │ └── rapid_json_serializer_test.cpp ├── errors_test.cpp ├── expressions_test.cpp ├── extends_test.cpp ├── filesystem_handler_test.cpp ├── filters_test.cpp ├── forloop_test.cpp ├── helpers_tests.cpp ├── if_test.cpp ├── import_test.cpp ├── includes_test.cpp ├── macro_test.cpp ├── metadata_test.cpp ├── perf_test.cpp ├── statements_tets.cpp ├── test_data │ └── simple_template1.j2tpl ├── test_tools.h ├── testers_test.cpp ├── tojson_filter_test.cpp └── user_callable_test.cpp └── thirdparty ├── CMakeLists.txt ├── external_boost_deps.cmake ├── internal_deps.cmake ├── thirdparty-conan-build.cmake ├── thirdparty-external-boost.cmake ├── thirdparty-external.cmake └── thirdparty-internal.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | IndentWidth: 4 3 | TabWidth: 8 4 | ColumnLimit: 160 5 | BreakBeforeBraces: Custom 6 | BraceWrapping: 7 | AfterControlStatement: true 8 | AfterClass: true 9 | AfterNamespace: true 10 | AfterFunction: true 11 | BeforeCatch: true 12 | BeforeElse: true 13 | AfterStruct: true 14 | AccessModifierOffset: -4 15 | BinPackParameters: false 16 | AlignAfterOpenBracket: AlwaysBreak 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakAfterDefinitionReturnType: None 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | ConstructorInitializerIndentWidth: 4 21 | NamespaceIndentation: None 22 | PointerAlignment: Left 23 | Standard: Cpp11 24 | UseTab: Never 25 | AlignAfterOpenBracket: Align 26 | PenaltyReturnTypeOnItsOwnLine: 0 27 | IncludeBlocks: Regroup 28 | IncludeCategories: 29 | - Regex: '^((<|")(shared)\/)' 30 | Priority: 4 31 | - Regex: '^((<|")(components)\/)' 32 | Priority: 3 33 | - Regex: '^(<.*\.(h|hpp|hxx)>)' 34 | Priority: 2 35 | - Regex: '^".*' 36 | Priority: 1 37 | - Regex: '^(<[\w]*>)' 38 | Priority: 5 39 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '23 15 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | # - name: Autobuild 56 | # uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Build 70 | run: | 71 | #!/bin/bash 72 | set -ex 73 | export BUILD_TARGET=all 74 | export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=ON 75 | export WORKSPACE=$GITHUB_WORKSPACE 76 | if [ "${INPUT_COMPILER}" == "clang-12" ] ; then 77 | export INPUT_BASE_FLAGS="-DJINJA2CPP_CXX_STANDARD=20" ; 78 | fi 79 | export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" 80 | mkdir $BUILD_DIRECTORY && cd $BUILD_DIRECTORY 81 | sudo chmod gou+rw -R $WORKSPACE 82 | cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=ON $EXTRA_FLAGS $WORKSPACE && cmake --build . --config Release --target all -- -j4 83 | 84 | 85 | - name: Perform CodeQL Analysis 86 | uses: github/codeql-action/analyze@v2 87 | -------------------------------------------------------------------------------- /.github/workflows/linux-build.yml: -------------------------------------------------------------------------------- 1 | name: CI-linux-build 2 | 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - main 9 | paths-ignore: 10 | - 'docs/**' 11 | - '**.md' 12 | pull_request: 13 | branches: 14 | - master 15 | - main 16 | paths-ignore: 17 | - 'docs/**' 18 | - '**.md' 19 | 20 | jobs: 21 | linux-build: 22 | 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | fail-fast: false 27 | max-parallel: 8 28 | matrix: 29 | compiler: [g++-9, g++-10, g++-11, clang++-12, clang++-13, clang++-14] 30 | base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] 31 | build-config: [Release, Debug] 32 | build-shared: [TRUE, FALSE] 33 | 34 | include: 35 | - compiler: g++-9 36 | extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF 37 | - compiler: g++-10 38 | extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF 39 | - compiler: g++-11 40 | extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF 41 | 42 | steps: 43 | - uses: actions/checkout@v3 44 | - name: Setup environment 45 | env: 46 | INPUT_COMPILER: ${{ matrix.compiler }} 47 | INPUT_BASE_FLAGS: ${{ matrix.base-flags }} 48 | INPUT_BASE_CONFIG: ${{ matrix.build-config }} 49 | INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} 50 | run: | 51 | sudo apt-get update 52 | sudo apt-get install -y cmake build-essential ${INPUT_COMPILER} 53 | - name: Prepare build 54 | env: 55 | INPUT_COMPILER: ${{ matrix.compiler }} 56 | INPUT_BASE_FLAGS: ${{ matrix.base-flags }} 57 | INPUT_BASE_CONFIG: ${{ matrix.build-config }} 58 | INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} 59 | run: | 60 | set -ex 61 | export BUILD_TARGET=all 62 | export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=OFF 63 | if [[ "${INPUT_COMPILER}" != "" ]]; then export CXX=${INPUT_COMPILER}; fi 64 | export BUILD_CONFIG=${INPUT_BASE_CONFIG} 65 | $CXX --version 66 | export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" 67 | 68 | - name: Build 69 | env: 70 | INPUT_BASE_CONFIG: ${{ matrix.build-config }} 71 | INPUT_BASE_FLAGS: ${{ matrix.base-flags }} 72 | INPUT_BUILD_SHARED: ${{ matrix.build-shared }} 73 | INPUT_COMPILER: ${{ matrix.compiler }} 74 | INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} 75 | run: | 76 | set -ex 77 | export BUILD_TARGET=all 78 | export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=ON 79 | if [[ "${INPUT_COMPILER}" != "" ]]; then export CXX=${INPUT_COMPILER}; fi 80 | export BUILD_CONFIG=${INPUT_BASE_CONFIG} 81 | $CXX --version 82 | cmake --version 83 | export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" 84 | mkdir -p .build && cd .build 85 | cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_STRICT_WARNINGS=OFF -DJINJA2CPP_BUILD_SHARED=$INPUT_BUILD_SHARED $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 86 | 87 | - name: Test 88 | env: 89 | BUILD_CONFIG: ${{ matrix.build-config }} 90 | run: | 91 | cd .build && ctest -C $BUILD_CONFIG -V 92 | 93 | -------------------------------------------------------------------------------- /.github/workflows/windows-build.yml: -------------------------------------------------------------------------------- 1 | name: CI-windows-build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths-ignore: 9 | - 'docs/**' 10 | - '**.md' 11 | pull_request: 12 | branches: 13 | - master 14 | - main 15 | paths-ignore: 16 | - 'docs/**' 17 | - '**.md' 18 | 19 | jobs: 20 | windows-msvc-build: 21 | 22 | runs-on: ${{matrix.run-machine}} 23 | 24 | strategy: 25 | fail-fast: false 26 | max-parallel: 20 27 | matrix: 28 | compiler: [msvc-2019] 29 | base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] 30 | build-config: [Release, Debug] 31 | build-platform: [x86, x64] 32 | build-runtime: ["", /MT, /MD] 33 | build-shared: [FALSE, TRUE] 34 | 35 | include: 36 | - compiler: msvc-2019 37 | build-platform: x86 38 | run-machine: windows-2019 39 | generator: Visual Studio 16 2019 40 | vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat 41 | - compiler: msvc-2019 42 | build-platform: x64 43 | run-machine: windows-2019 44 | generator: Visual Studio 16 2019 45 | vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat 46 | 47 | 48 | steps: 49 | - uses: actions/checkout@v3 50 | 51 | - name: Build 52 | shell: cmd 53 | env: 54 | INPUT_COMPILER: ${{ matrix.compiler }} 55 | INPUT_BASE_FLAGS: ${{ matrix.base-flags }} 56 | INPUT_BUILD_CONFIG: ${{ matrix.build-config }} 57 | INPUT_BUILD_SHARED: ${{ matrix.build-shared }} 58 | INPUT_EXTRA_FLAGS: ${{ matrix.extra_flags }} 59 | INPUT_BUILD_PLATFORM: ${{ matrix.build-platform }} 60 | INPUT_BUILD_RUNTIME: ${{ matrix.build-runtime }} 61 | INPUT_GENERATOR: ${{ matrix.generator }} 62 | VC_VARS: "${{ matrix.vc_vars }}" 63 | run: | 64 | call "%VC_VARS%" 65 | mkdir -p .build 66 | cd .build 67 | cmake --version 68 | cmake .. -G "%INPUT_GENERATOR%" -DCMAKE_BUILD_TYPE=%INPUT_BUILD_CONFIG% -DJINJA2CPP_MSVC_RUNTIME_TYPE="%INPUT_BUILD_RUNTIME%" -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=%INPUT_BUILD_SHARED% %INPUT_BASE_FLAGS% %INPUT_EXTRA_FLAGS% 69 | cmake --build . --config %INPUT_BUILD_CONFIG% --verbose 70 | 71 | - name: Test 72 | shell: cmd 73 | env: 74 | INPUT_BUILD_CONFIG: ${{ matrix.build-config }} 75 | VC_VARS: "${{ matrix.vc_vars }}" 76 | run: | 77 | cd .build 78 | call "%VC_VARS%" 79 | set path=%BOOST_ROOT%\lib;%PATH% 80 | ctest -C %INPUT_BUILD_CONFIG% -V 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | *.pyc 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Compiled Dynamic libraries 13 | *.so 14 | *.dylib 15 | *.dll 16 | 17 | # Fortran module files 18 | *.mod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | .* 32 | 33 | build/ 34 | dist/ 35 | 36 | compile_commands.json 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dist: xenial 3 | language: cpp 4 | sudo: required 5 | 6 | matrix: 7 | include: 8 | - 9 | compiler: clang 10 | env: COMPILER='clang++' 11 | os: osx 12 | osx_image: xcode9 13 | - 14 | compiler: clang 15 | env: COMPILER='clang++' 16 | os: osx 17 | osx_image: xcode10 18 | - 19 | compiler: clang 20 | env: COMPILER='clang++' 21 | os: osx 22 | osx_image: xcode11 23 | - 24 | addons: 25 | apt: 26 | packages: 27 | - cmake 28 | - g++-6 29 | - lcov 30 | sources: 31 | - ubuntu-toolchain-r-test 32 | compiler: gcc 33 | env: "COMPILER=g++-6 COLLECT_COVERAGE=1" 34 | os: linux 35 | - 36 | addons: 37 | apt: 38 | packages: 39 | - cmake 40 | - clang-8 41 | - g++-8 42 | sources: 43 | - ubuntu-toolchain-r-test 44 | - llvm-toolchain-xenial-8 45 | compiler: clang 46 | env: "COMPILER=clang++-8 EXTRA_FLAGS=-DJINJA2CPP_CXX_STANDARD=17 SANITIZE_BUILD=address+undefined" 47 | os: linux 48 | before_install: 49 | - "date -u" 50 | - "uname -a" 51 | script: ./scripts/build.sh 52 | after_success: 53 | - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then echo \"Uploading code coverate report\" ; fi" 54 | - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --directory . --capture --output-file coverage.info ; fi" 55 | - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --remove coverage.info '/usr/*' --output-file coverage.info ; fi" 56 | - "if [[ \"${COLLECT_COVERAGE}\" != \"\" ]]; then lcov --list coverage.info ; fi" 57 | - "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" 58 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.0.{build} 2 | 3 | skip_commits: 4 | message: /.*/ 5 | skip_branch_with_pr: true 6 | skip_tags: true 7 | skip_non_tags: true 8 | 9 | os: 10 | - Visual Studio 2015 11 | - Visual Studio 2017 12 | 13 | platform: 14 | - Win32 15 | - x64 16 | 17 | configuration: 18 | - Debug 19 | - Release 20 | 21 | environment: 22 | BOOST_ROOT: C:\Libraries\boost_1_65_1 23 | GENERATOR: "\"NMake Makefiles\"" 24 | DEPS_MODE: internal 25 | 26 | matrix: 27 | # - BUILD_PLATFORM: clang 28 | # MSVC_RUNTIME_TYPE: 29 | # GENERATOR: "\"Unix Makefiles\"" 30 | # DEPS_MODE: internal 31 | # 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 32 | - BUILD_PLATFORM: MinGW7 33 | MSVC_RUNTIME_TYPE: 34 | GENERATOR: "\"Unix Makefiles\"" 35 | DEPS_MODE: internal 36 | 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 37 | - BUILD_PLATFORM: MinGW8 38 | MSVC_RUNTIME_TYPE: 39 | GENERATOR: "\"Unix Makefiles\"" 40 | DEPS_MODE: internal 41 | 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 42 | - BUILD_PLATFORM: x64 43 | MSVC_RUNTIME_TYPE: /MD 44 | - BUILD_PLATFORM: x64 45 | MSVC_RUNTIME_TYPE: /MT 46 | - BUILD_PLATFORM: x64 47 | MSVC_RUNTIME_TYPE: 48 | - BUILD_PLATFORM: x86 49 | MSVC_RUNTIME_TYPE: /MD 50 | - BUILD_PLATFORM: x86 51 | MSVC_RUNTIME_TYPE: /MT 52 | - BUILD_PLATFORM: x86 53 | MSVC_RUNTIME_TYPE: 54 | 55 | matrix: 56 | fast_finish: false 57 | exclude: 58 | - os: Visual Studio 2015 59 | BUILD_PLATFORM: MinGW7 60 | - os: Visual Studio 2015 61 | BUILD_PLATFORM: MinGW8 62 | - os: Visual Studio 2015 63 | BUILD_PLATFORM: clang 64 | - os: Visual Studio 2017 65 | BUILD_PLATFORM: x86 66 | - os: Visual Studio 2017 67 | BUILD_PLATFORM: x64 68 | - platform: Win32 69 | BUILD_PLATFORM: x64 70 | - platform: Win32 71 | BUILD_PLATFORM: MinGW7 72 | - platform: Win32 73 | BUILD_PLATFORM: MinGW8 74 | - platform: Win32 75 | BUILD_PLATFORM: clang 76 | - platform: x64 77 | BUILD_PLATFORM: x86 78 | 79 | 80 | init: 81 | - 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% 82 | - 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% 83 | - 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" 84 | - 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 85 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x86" set PATH=%BOOST_ROOT%\lib32-msvc-14.0;%PATH% 86 | - 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 87 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" if "%BUILD_PLATFORM%"=="x64" set PATH=%BOOST_ROOT%\lib64-msvc-14.0;%PATH% 88 | 89 | build_script: 90 | - mkdir -p build && cd build 91 | - cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%configuration% -DJINJA2CPP_MSVC_RUNTIME_TYPE=%MSVC_RUNTIME_TYPE% -DJINJA2CPP_DEPS_MODE=%DEPS_MODE% %EXTRA_CMAKE_ARGS% 92 | - cmake --build . --target all --config %configuration% 93 | 94 | test_script: 95 | - ctest -C %configuration% -V 96 | -------------------------------------------------------------------------------- /cmake/build_thirdparty.cmake: -------------------------------------------------------------------------------- 1 | macro (BuildThirdparty TargetName ThirdpartySrcPath ThirdpartyOutFile ExtraBuildOptions) 2 | 3 | set (BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TargetName}/build) 4 | set (INST_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TargetName}/install) 5 | 6 | make_directory (${BUILD_DIR}) 7 | 8 | add_custom_command ( 9 | OUTPUT ${BUILD_DIR}/CMakeCache.txt 10 | COMMAND ${CMAKE_COMMAND} ARGS -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX="${INST_DIR}" -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} "${ThirdpartySrcPath}" ${ExtraBuildOptions} 11 | WORKING_DIRECTORY ${BUILD_DIR} 12 | COMMENT "Prebuild ${TargetName} library" 13 | ) 14 | 15 | add_custom_command ( 16 | OUTPUT ${INST_DIR}/${ThirdpartyOutFile} 17 | COMMAND ${CMAKE_COMMAND} ARGS --build . --target install 18 | WORKING_DIRECTORY ${BUILD_DIR} 19 | COMMENT "Build ${TargetName} library" 20 | DEPENDS ${BUILD_DIR}/CMakeCache.txt 21 | ) 22 | 23 | add_custom_command ( 24 | OUTPUT ${INST_DIR}/.build-${TargetName} 25 | COMMAND ${CMAKE_COMMAND} ARGS -E touch ./.build-${TargetName} 26 | WORKING_DIRECTORY ${INST_DIR} 27 | COMMENT "Finishing ${TargetName} library building" 28 | DEPENDS ${INST_DIR}/${ThirdpartyOutFile} 29 | ) 30 | 31 | add_custom_target ( 32 | ${TargetName} ALL 33 | COMMENT "Build ${TargetName} libraries" 34 | DEPENDS ${INST_DIR}/.build-${TargetName} 35 | ) 36 | endmacro () 37 | -------------------------------------------------------------------------------- /cmake/collect_sources.cmake: -------------------------------------------------------------------------------- 1 | function (CollectSources SourcesVar HeadersVar RelativePath FromPath) 2 | if (NOT FromPath OR ${FromPath} STREQUAL "") 3 | set (FromPath ${CMAKE_CURRENT_SOURCE_DIR}) 4 | endif () 5 | 6 | file (GLOB_RECURSE Sources RELATIVE ${RelativePath} ${FromPath}/*.c ${FromPath}/*.cpp ${FromPath}/*.cxx) 7 | file (GLOB_RECURSE Headers RELATIVE ${RelativePath} ${FromPath}/*.h ${FromPath}/*.hpp ${FromPath}/*.hxx ${FromPath}/*.inc ${FromPath}/*.inl) 8 | 9 | set (${SourcesVar} ${Sources} PARENT_SCOPE) 10 | set (${HeadersVar} ${Headers} PARENT_SCOPE) 11 | endfunction () 12 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | if ((NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") AND (NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")) 2 | message(WARNING "coverage build is not supported on such compiler ${CMAKE_CXX_COMPILER_ID}") 3 | set(JINJA2CPP_WITH_COVERAGE OFF) 4 | return() 5 | endif() 6 | 7 | function(add_coverage_target _TARGET) 8 | if (NOT TARGET ${_TARGET}) 9 | add_library(${_TARGET} INTERFACE) 10 | endif() 11 | target_compile_options( 12 | ${_TARGET} 13 | INTERFACE 14 | -fprofile-arcs -ftest-coverage 15 | ) 16 | target_link_libraries(${_TARGET} INTERFACE gcov) 17 | 18 | if(JINJA2CPP_INSTALL) 19 | install( 20 | TARGETS 21 | ${_TARGET} 22 | EXPORT 23 | InstallTargets 24 | ) 25 | endif() 26 | endfunction() 27 | -------------------------------------------------------------------------------- /cmake/public/FindJinja2Cpp.cmake: -------------------------------------------------------------------------------- 1 | function (_Jinja2Cpp_Find_Library varName) 2 | find_library(${varName} 3 | NAMES ${ARGN} 4 | HINTS 5 | ENV JINJA2CPP_ROOT 6 | ${JINJA2CPP_INSTALL_DIR} 7 | PATH_SUFFIXES lib/static lib64/static 8 | ) 9 | mark_as_advanced(${varName}) 10 | endfunction () 11 | 12 | find_path(JINJA2CPP_INCLUDE_DIR jinja2cpp/template.h 13 | HINTS 14 | $ENV{JINJA2CPP_ROOT}/include 15 | ${JINJA2CPP_INSTALL_DIR}/include 16 | ) 17 | mark_as_advanced(${JINJA2CPP_INCLUDE_DIR}) 18 | 19 | _Jinja2Cpp_Find_Library(JINJA2CPP_LIBRARY jinja2cpp) 20 | 21 | include(FindPackageHandleStandardArgs) 22 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(JINJA2CPP DEFAULT_MSG JINJA2CPP_LIBRARY JINJA2CPP_INCLUDE_DIR) 23 | 24 | if (JINJA2CPP_FOUND) 25 | if (NOT TARGET Jinja2Cpp) 26 | add_library(Jinja2Cpp UNKNOWN IMPORTED) 27 | set_target_properties(Jinja2Cpp PROPERTIES 28 | INTERFACE_INCLUDE_DIRECTORIES "${JINJA2CPP_INCLUDE_DIR}") 29 | set_target_properties(Jinja2Cpp PROPERTIES 30 | IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" 31 | IMPORTED_LOCATION "${JINJA2CPP_LIBRARY}") 32 | endif () 33 | endif () 34 | -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config-deps-conan-build.cmake.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/2053cfabfafaeab65aff0bc083a83b105a939202/cmake/public/jinja2cpp-config-deps-conan-build.cmake.in -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config-deps-conan.cmake.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/2053cfabfafaeab65aff0bc083a83b105a939202/cmake/public/jinja2cpp-config-deps-conan.cmake.in -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config-deps-external-boost.cmake.in: -------------------------------------------------------------------------------- 1 | macro (Jinja2CppAddBoostDep name) 2 | if (TARGET Boost::${name}) 3 | list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) 4 | elseif (TARGET boost_${name}) 5 | list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) 6 | endif () 7 | endmacro () 8 | 9 | Jinja2CppAddBoostDep(variant) 10 | Jinja2CppAddBoostDep(filesystem) 11 | Jinja2CppAddBoostDep(algorithm) 12 | 13 | set_property(TARGET jinja2cpp PROPERTY 14 | INTERFACE_LINK_LIBRARIES ${JINJA2CPP_INTERFACE_LINK_LIBRARIES} 15 | ) 16 | -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config-deps-external.cmake.in: -------------------------------------------------------------------------------- 1 | # Create imported target expected-lite 2 | add_library(expected-lite INTERFACE IMPORTED) 3 | 4 | set_target_properties(expected-lite PROPERTIES 5 | INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_EXPECTED-LITE_INCLUDE_DIRECTORIES@" 6 | ) 7 | 8 | # Create imported target variant-lite 9 | add_library(variant-lite INTERFACE IMPORTED) 10 | 11 | set_target_properties(variant-lite PROPERTIES 12 | INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_VARIANT-LITE_INCLUDE_DIRECTORIES@" 13 | ) 14 | 15 | # Create imported target optional-lite 16 | add_library(optional-lite INTERFACE IMPORTED) 17 | 18 | set_target_properties(optional-lite PROPERTIES 19 | INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_OPTIONAL-LITE_INCLUDE_DIRECTORIES@" 20 | ) 21 | 22 | # Create imported target string-view-lite 23 | add_library(string-view-lite INTERFACE IMPORTED) 24 | 25 | set_target_properties(string-view-lite PROPERTIES 26 | INTERFACE_INCLUDE_DIRECTORIES "@JINJA2CPP_STRING-VIEW-LITE_INCLUDE_DIRECTORIES@" 27 | ) 28 | 29 | set (JINJA2CPP_INTERFACE_LINK_LIBRARIES expected-lite variant-lite optional-lite string-view-lite) 30 | 31 | macro (Jinja2CppAddBoostDep name) 32 | if (TARGET Boost::${name}) 33 | list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) 34 | elseif (TARGET boost_${name}) 35 | list (APPEND JINJA2CPP_INTERFACE_LINK_LIBRARIES $) 36 | endif () 37 | endmacro () 38 | 39 | Jinja2CppAddBoostDep(variant) 40 | Jinja2CppAddBoostDep(filesystem) 41 | Jinja2CppAddBoostDep(algorithm) 42 | 43 | set_property(TARGET jinja2cpp PROPERTY 44 | INTERFACE_LINK_LIBRARIES ${JINJA2CPP_INTERFACE_LINK_LIBRARIES} 45 | ) 46 | -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config-deps-internal.cmake.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/2053cfabfafaeab65aff0bc083a83b105a939202/cmake/public/jinja2cpp-config-deps-internal.cmake.in -------------------------------------------------------------------------------- /cmake/public/jinja2cpp-config.cmake.in: -------------------------------------------------------------------------------- 1 | # Based on generated file by CMake 2 | 3 | if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5) 4 | message(FATAL_ERROR "CMake >= 2.6.0 required") 5 | endif() 6 | cmake_policy(PUSH) 7 | cmake_policy(VERSION 2.6) 8 | 9 | #---------------------------------------------------------------- 10 | # Generated CMake target import file. 11 | #---------------------------------------------------------------- 12 | 13 | # Commands may need to know the format version. 14 | set(CMAKE_IMPORT_FILE_VERSION 1) 15 | 16 | # Protect against multiple inclusion, which would fail when already imported targets are added once more. 17 | set(_targetsDefined) 18 | set(_targetsNotDefined) 19 | set(_expectedTargets) 20 | foreach(_expectedTarget jinja2cpp) 21 | list(APPEND _expectedTargets ${_expectedTarget}) 22 | if(NOT TARGET ${_expectedTarget}) 23 | list(APPEND _targetsNotDefined ${_expectedTarget}) 24 | endif() 25 | if(TARGET ${_expectedTarget}) 26 | list(APPEND _targetsDefined ${_expectedTarget}) 27 | endif() 28 | endforeach() 29 | if("${_targetsDefined}" STREQUAL "${_expectedTargets}") 30 | unset(_targetsDefined) 31 | unset(_targetsNotDefined) 32 | unset(_expectedTargets) 33 | set(CMAKE_IMPORT_FILE_VERSION) 34 | cmake_policy(POP) 35 | return() 36 | endif() 37 | if(NOT "${_targetsDefined}" STREQUAL "") 38 | message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") 39 | endif() 40 | unset(_targetsDefined) 41 | unset(_targetsNotDefined) 42 | unset(_expectedTargets) 43 | 44 | 45 | # Compute the installation prefix relative to this file. 46 | get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) 47 | get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) 48 | get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) 49 | if(_IMPORT_PREFIX STREQUAL "/") 50 | set(_IMPORT_PREFIX "") 51 | endif() 52 | 53 | # Create imported target jinja2cpp 54 | add_library(jinja2cpp STATIC IMPORTED) 55 | 56 | set_target_properties(jinja2cpp PROPERTIES 57 | INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" 58 | ) 59 | 60 | if (JINJA2CPP_BUILD_SHARED) 61 | target_compile_definitions(jinja2cpp PUBLIC -DJINJA2CPP_LINK_AS_SHARED) 62 | endif() 63 | 64 | 65 | # INTERFACE_LINK_LIBRARIES "nonstd::expected-lite;nonstd::variant-lite;nonstd::value_ptr-lite;nonstd::optional-lite;\$;\$;\$;\$" 66 | 67 | if(CMAKE_VERSION VERSION_LESS 2.8.12) 68 | message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") 69 | endif() 70 | 71 | # Load information for each installed configuration. 72 | get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 73 | file(GLOB CONFIG_FILES "${_DIR}/jinja2cpp-cfg-*.cmake") 74 | foreach(f ${CONFIG_FILES}) 75 | include(${f}) 76 | endforeach() 77 | 78 | include(${_DIR}/jinja2cpp-config-deps.cmake) 79 | 80 | # Cleanup temporary variables. 81 | set(_IMPORT_PREFIX) 82 | 83 | # Commands beyond this point should not need to know the version. 84 | set(CMAKE_IMPORT_FILE_VERSION) 85 | cmake_policy(POP) 86 | -------------------------------------------------------------------------------- /cmake/sanitizer.address+undefined.cmake: -------------------------------------------------------------------------------- 1 | include(sanitizer) 2 | -------------------------------------------------------------------------------- /cmake/sanitizer.cmake: -------------------------------------------------------------------------------- 1 | #if ((NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") AND (NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")) 2 | # message(WARNING "sanitized build is not supported using this compiler ${CMAKE_CXX_COMPILER_ID}") 3 | # set(JINJA2CPP_WITH_SANITIZERS OFF) 4 | # return() 5 | #endif() 6 | 7 | set(_BASE_SANITIZER_FLAGS "-fno-omit-frame-pointer -fno-optimize-sibling-calls") 8 | separate_arguments(_BASE_SANITIZER_FLAGS) 9 | set(_BASE_ENABLE_SANITIZER_FLAGS) 10 | if(JINJA2CPP_WITH_SANITIZERS STREQUAL address+undefined) 11 | set(_BASE_ENABLE_SANITIZER_FLAGS "-fsanitize=address,undefined") 12 | endif() 13 | 14 | if(JINJA2CPP_WITH_SANITIZERS STREQUAL memory) 15 | set(_BASE_ENABLE_SANITIZER_FLAGS "-fsanitize=memory") 16 | endif() 17 | 18 | function(add_sanitizer_target _TARGET) 19 | if (NOT TARGET ${_TARGET}) 20 | add_library(${_TARGET} INTERFACE) 21 | endif() 22 | target_compile_options( 23 | ${_TARGET} 24 | INTERFACE 25 | ${_BASE_SANITIZER_FLAGS} ${_BASE_ENABLE_SANITIZER_FLAGS} 26 | ) 27 | target_link_libraries( 28 | ${_TARGET} 29 | INTERFACE 30 | ${_BASE_ENABLE_SANITIZER_FLAGS} 31 | ) 32 | 33 | if(JINJA2CPP_INSTALL) 34 | install( 35 | TARGETS 36 | ${_TARGET} 37 | EXPORT 38 | InstallTargets 39 | ) 40 | endif() 41 | endfunction() 42 | 43 | 44 | -------------------------------------------------------------------------------- /cmake/sanitizer.memory.cmake: -------------------------------------------------------------------------------- 1 | include(sanitizer) 2 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | boost/1.85.0 3 | expected-lite/0.6.3 4 | fmt/10.1.1 5 | nlohmann_json/3.11.2 6 | optional-lite/3.5.0 7 | rapidjson/cci.20220822 8 | string-view-lite/1.7.0 9 | variant-lite/2.0.0 10 | 11 | [generators] 12 | CMakeDeps 13 | CMakeToolchain 14 | -------------------------------------------------------------------------------- /include/jinja2cpp/binding/boost_json.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_BINDING_BOOST_JSON_H 2 | #define JINJA2CPP_BINDING_BOOST_JSON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace jinja2 9 | { 10 | namespace detail 11 | { 12 | 13 | class BoostJsonObjectAccessor 14 | : public IMapItemAccessor 15 | , public ReflectedDataHolder 16 | { 17 | struct SizeVisitor 18 | { 19 | size_t operator()(std::nullptr_t) { return {}; } 20 | size_t operator()(bool) { return 1; } 21 | size_t operator()(std::int64_t) { return 1; } 22 | size_t operator()(std::uint64_t) { return 1; } 23 | size_t operator()(double) { return 1; } 24 | size_t operator()(const boost::json::string&) { return 1; } 25 | size_t operator()(const boost::json::array& val) { return val.size(); } 26 | size_t operator()(const boost::json::object& val) { return val.size(); } 27 | size_t operator()(...) { return 0; } 28 | }; 29 | 30 | public: 31 | using ReflectedDataHolder::ReflectedDataHolder; 32 | ~BoostJsonObjectAccessor() override = default; 33 | 34 | size_t GetSize() const override 35 | { 36 | auto j = this->GetValue(); 37 | if (!j) 38 | return {}; 39 | // simulate nlohmann semantics 40 | SizeVisitor sv; 41 | return boost::json::visit(sv, *j); 42 | } 43 | 44 | bool HasValue(const std::string& name) const override 45 | { 46 | auto j = this->GetValue(); 47 | if (!j) 48 | return false; 49 | auto obj = j->if_object(); 50 | return obj ? obj->contains(name) : false; 51 | } 52 | 53 | Value GetValueByName(const std::string& name) const override 54 | { 55 | auto j = this->GetValue(); 56 | if (!j) 57 | return Value(); 58 | auto obj = j->if_object(); 59 | if (!obj) 60 | return Value(); 61 | auto val = obj->if_contains(name); 62 | if (!val) 63 | return Value(); 64 | return Reflect(*val); 65 | } 66 | 67 | std::vector GetKeys() const override 68 | { 69 | auto j = this->GetValue(); 70 | if (!j) 71 | return {}; 72 | auto obj = j->if_object(); 73 | if (!obj) 74 | return {}; 75 | std::vector result; 76 | result.reserve(obj->size()); 77 | for (auto& item : *obj) 78 | { 79 | result.emplace_back(item.key()); 80 | } 81 | return result; 82 | } 83 | bool IsEqual(const IComparable& other) const override 84 | { 85 | auto* val = dynamic_cast(&other); 86 | if (!val) 87 | return false; 88 | return this->GetValue() == val->GetValue(); 89 | } 90 | }; 91 | 92 | struct BoostJsonArrayAccessor 93 | : IListItemAccessor 94 | , IIndexBasedAccessor 95 | , ReflectedDataHolder 96 | { 97 | using ReflectedDataHolder::ReflectedDataHolder; 98 | 99 | nonstd::optional GetSize() const override 100 | { 101 | auto j = this->GetValue(); 102 | return j ? j->size() : nonstd::optional(); 103 | } 104 | 105 | const IIndexBasedAccessor* GetIndexer() const override { return this; } 106 | 107 | ListEnumeratorPtr CreateEnumerator() const override 108 | { 109 | using Enum = Enumerator; 110 | auto j = this->GetValue(); 111 | if (!j) 112 | return jinja2::ListEnumeratorPtr(); 113 | 114 | return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end())); 115 | } 116 | 117 | Value GetItemByIndex(int64_t idx) const override 118 | { 119 | auto j = this->GetValue(); 120 | if (!j) 121 | return Value(); 122 | 123 | return Reflect((*j)[idx]); 124 | } 125 | 126 | bool IsEqual(const IComparable& other) const override 127 | { 128 | auto* val = dynamic_cast(&other); 129 | if (!val) 130 | return false; 131 | return GetValue() == val->GetValue(); 132 | } 133 | }; 134 | 135 | template<> 136 | struct Reflector 137 | { 138 | static Value Create(boost::json::value val) 139 | { 140 | Value result; 141 | switch (val.kind()) 142 | { 143 | default: // unreachable()? 144 | case boost::json::kind::null: 145 | break; 146 | case boost::json::kind::bool_: 147 | result = val.get_bool(); 148 | break; 149 | case boost::json::kind::int64: 150 | result = val.get_int64(); 151 | break; 152 | case boost::json::kind::uint64: 153 | result = static_cast(val.get_uint64()); 154 | break; 155 | case boost::json::kind::double_: 156 | result = val.get_double(); 157 | break; 158 | case boost::json::kind::string: 159 | result = std::string(val.get_string().c_str()); 160 | break; 161 | case boost::json::kind::array: { 162 | auto array = val.get_array(); 163 | result = GenericList([accessor = BoostJsonArrayAccessor(std::move(array))]() { return &accessor; }); 164 | break; 165 | } 166 | case boost::json::kind::object: { 167 | auto obj = val.get_object(); 168 | result = GenericMap([accessor = BoostJsonObjectAccessor(std::move(val))]() { return &accessor; }); 169 | break; 170 | } 171 | } 172 | return result; 173 | } 174 | 175 | static Value CreateFromPtr(const boost::json::value* val) 176 | { 177 | Value result; 178 | switch (val->kind()) 179 | { 180 | default: // unreachable()? 181 | case boost::json::kind::null: 182 | break; 183 | case boost::json::kind::bool_: 184 | result = val->get_bool(); 185 | break; 186 | case boost::json::kind::int64: 187 | result = val->get_int64(); 188 | break; 189 | case boost::json::kind::uint64: 190 | result = static_cast(val->get_uint64()); 191 | break; 192 | case boost::json::kind::double_: 193 | result = val->get_double(); 194 | break; 195 | case boost::json::kind::string: 196 | result = std::string(val->get_string().c_str()); 197 | break; 198 | case boost::json::kind::array: 199 | { 200 | auto array = val->get_array(); 201 | result = GenericList([accessor = BoostJsonArrayAccessor(std::move(array))]() { return &accessor; }); 202 | break; 203 | } 204 | case boost::json::kind::object: 205 | { 206 | auto obj = val->get_object(); 207 | result = GenericMap([accessor = BoostJsonObjectAccessor(std::move(val))]() { return &accessor; }); 208 | break; 209 | } 210 | } 211 | return result; 212 | } 213 | }; 214 | 215 | } // namespace detail 216 | } // namespace jinja2 217 | 218 | #endif // JINJA2CPP_BINDING_BOOST_JSON_H 219 | -------------------------------------------------------------------------------- /include/jinja2cpp/binding/nlohmann_json.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_BINDING_NLOHMANN_JSON_H 2 | #define JINJA2CPP_BINDING_NLOHMANN_JSON_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace jinja2 9 | { 10 | namespace detail 11 | { 12 | 13 | class NLohmannJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHolder 14 | { 15 | public: 16 | using ReflectedDataHolder::ReflectedDataHolder; 17 | ~NLohmannJsonObjectAccessor() override = default; 18 | 19 | size_t GetSize() const override 20 | { 21 | auto j = this->GetValue(); 22 | return j ? j->size() : 0ULL; 23 | } 24 | 25 | bool HasValue(const std::string& name) const override 26 | { 27 | auto j = this->GetValue(); 28 | return j ? j->contains(name) : false; 29 | } 30 | 31 | Value GetValueByName(const std::string& name) const override 32 | { 33 | auto j = this->GetValue(); 34 | if (!j || !j->contains(name)) 35 | return Value(); 36 | 37 | return Reflect(&(*j)[name]); 38 | } 39 | 40 | std::vector GetKeys() const override 41 | { 42 | auto j = this->GetValue(); 43 | if (!j) 44 | return {}; 45 | 46 | std::vector result; 47 | result.reserve(j->size()); 48 | for (auto& item : j->items()) 49 | { 50 | result.emplace_back(item.key()); 51 | } 52 | return result; 53 | } 54 | 55 | bool IsEqual(const IComparable& other) const override 56 | { 57 | auto* val = dynamic_cast(&other); 58 | if (!val) 59 | return false; 60 | return GetValue() == val->GetValue(); 61 | } 62 | }; 63 | 64 | 65 | struct NLohmannJsonArrayAccessor : IListItemAccessor, IIndexBasedAccessor, ReflectedDataHolder 66 | { 67 | using ReflectedDataHolder::ReflectedDataHolder; 68 | 69 | nonstd::optional GetSize() const override 70 | { 71 | auto j = this->GetValue(); 72 | return j ? j->size() : nonstd::optional(); 73 | } 74 | 75 | const IIndexBasedAccessor* GetIndexer() const override 76 | { 77 | return this; 78 | } 79 | 80 | ListEnumeratorPtr CreateEnumerator() const override 81 | { 82 | using Enum = Enumerator; 83 | auto j = this->GetValue(); 84 | if (!j) 85 | return jinja2::ListEnumeratorPtr(); 86 | 87 | return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end())); 88 | } 89 | 90 | Value GetItemByIndex(int64_t idx) const override 91 | { 92 | auto j = this->GetValue(); 93 | if (!j) 94 | return Value(); 95 | 96 | return Reflect((*j)[idx]); 97 | } 98 | 99 | bool IsEqual(const IComparable& other) const override 100 | { 101 | auto* val = dynamic_cast(&other); 102 | if (!val) 103 | return false; 104 | return GetValue() == val->GetValue(); 105 | } 106 | }; 107 | 108 | template<> 109 | struct Reflector 110 | { 111 | static Value Create(nlohmann::json val) 112 | { 113 | Value result; 114 | switch (val.type()) 115 | { 116 | case nlohmann::detail::value_t::binary: 117 | break; 118 | case nlohmann::detail::value_t::null: 119 | break; 120 | case nlohmann::detail::value_t::object: 121 | result = GenericMap([accessor = NLohmannJsonObjectAccessor(std::move(val))]() { return &accessor; }); 122 | break; 123 | case nlohmann::detail::value_t::array: 124 | result = GenericList([accessor = NLohmannJsonArrayAccessor(std::move(val))]() { return &accessor; }); 125 | break; 126 | case nlohmann::detail::value_t::string: 127 | result = val.get(); 128 | break; 129 | case nlohmann::detail::value_t::boolean: 130 | result = val.get(); 131 | break; 132 | case nlohmann::detail::value_t::number_integer: 133 | case nlohmann::detail::value_t::number_unsigned: 134 | result = val.get(); 135 | break; 136 | case nlohmann::detail::value_t::number_float: 137 | result = val.get(); 138 | break; 139 | case nlohmann::detail::value_t::discarded: 140 | break; 141 | } 142 | return result; 143 | } 144 | 145 | static Value CreateFromPtr(const nlohmann::json *val) 146 | { 147 | Value result; 148 | switch (val->type()) 149 | { 150 | case nlohmann::detail::value_t::binary: 151 | break; 152 | case nlohmann::detail::value_t::null: 153 | break; 154 | case nlohmann::detail::value_t::object: 155 | result = GenericMap([accessor = NLohmannJsonObjectAccessor(val)]() { return &accessor; }); 156 | break; 157 | case nlohmann::detail::value_t::array: 158 | result = GenericList([accessor = NLohmannJsonArrayAccessor(val)]() {return &accessor;}); 159 | break; 160 | case nlohmann::detail::value_t::string: 161 | result = val->get(); 162 | break; 163 | case nlohmann::detail::value_t::boolean: 164 | result = val->get(); 165 | break; 166 | case nlohmann::detail::value_t::number_integer: 167 | case nlohmann::detail::value_t::number_unsigned: 168 | result = val->get(); 169 | break; 170 | case nlohmann::detail::value_t::number_float: 171 | result = val->get(); 172 | break; 173 | case nlohmann::detail::value_t::discarded: 174 | break; 175 | } 176 | return result; 177 | } 178 | 179 | }; 180 | 181 | } // namespace detail 182 | } // namespace jinja2 183 | 184 | #endif // JINJA2CPP_BINDING_NLOHMANN_JSON_H 185 | -------------------------------------------------------------------------------- /include/jinja2cpp/binding/rapid_json.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_BINDING_RAPID_JSON_H 2 | #define JINJA2CPP_BINDING_RAPID_JSON_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace jinja2 11 | { 12 | namespace detail 13 | { 14 | 15 | template 16 | struct RapidJsonNameConverter; 17 | 18 | template<> 19 | struct RapidJsonNameConverter 20 | { 21 | static const std::string& GetName(const std::string& str) { return str; } 22 | }; 23 | 24 | template<> 25 | struct RapidJsonNameConverter 26 | { 27 | static std::wstring GetName(const std::string& str) { return ConvertString(str); } 28 | }; 29 | 30 | template 31 | class RapidJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHolder 32 | { 33 | public: 34 | using ReflectedDataHolder::ReflectedDataHolder; 35 | using NameCvt = RapidJsonNameConverter; 36 | using ThisType = RapidJsonObjectAccessor; 37 | ~RapidJsonObjectAccessor() override = default; 38 | 39 | size_t GetSize() const override 40 | { 41 | auto j = this->GetValue(); 42 | return j ? j->MemberCount() : 0ULL; 43 | } 44 | 45 | bool HasValue(const std::string& name) const override 46 | { 47 | auto j = this->GetValue(); 48 | return j ? j->HasMember(NameCvt::GetName(name).c_str()) : false; 49 | } 50 | 51 | Value GetValueByName(const std::string& nameOrig) const override 52 | { 53 | auto j = this->GetValue(); 54 | const auto& name = NameCvt::GetName(nameOrig); 55 | if (!j || !j->HasMember(name.c_str())) 56 | return Value(); 57 | 58 | return Reflect(&(*j)[name.c_str()]); 59 | } 60 | 61 | std::vector GetKeys() const override 62 | { 63 | auto j = this->GetValue(); 64 | if (!j) 65 | return {}; 66 | 67 | std::vector result; 68 | result.reserve(j->MemberCount()); 69 | for (auto it = j->MemberBegin(); it != j->MemberEnd(); ++ it) 70 | { 71 | result.emplace_back(ConvertString(nonstd::basic_string_view(it->name.GetString()))); 72 | } 73 | return result; 74 | } 75 | 76 | bool IsEqual(const IComparable& other) const override 77 | { 78 | auto* val = dynamic_cast(&other); 79 | if (!val) 80 | return false; 81 | auto enumerator = this->GetValue(); 82 | auto otherEnum = val->GetValue(); 83 | if (enumerator && otherEnum && enumerator != otherEnum) 84 | return false; 85 | if ((enumerator && !otherEnum) || (!enumerator && otherEnum)) 86 | return false; 87 | return true; 88 | } 89 | }; 90 | 91 | template 92 | struct RapidJsonArrayAccessor 93 | : IListItemAccessor 94 | , IIndexBasedAccessor 95 | , ReflectedDataHolder, false> 96 | { 97 | using ReflectedDataHolder, false>::ReflectedDataHolder; 98 | using ThisType = RapidJsonArrayAccessor; 99 | 100 | nonstd::optional GetSize() const override 101 | { 102 | auto j = this->GetValue(); 103 | return j ? j->Size() : nonstd::optional(); 104 | } 105 | 106 | const IIndexBasedAccessor* GetIndexer() const override 107 | { 108 | return this; 109 | } 110 | 111 | ListEnumeratorPtr CreateEnumerator() const override 112 | { 113 | using Enum = Enumerator::ConstValueIterator>; 114 | auto j = this->GetValue(); 115 | if (!j) 116 | return jinja2::ListEnumeratorPtr(); 117 | 118 | return jinja2::ListEnumeratorPtr(new Enum(j->Begin(), j->End())); 119 | } 120 | 121 | Value GetItemByIndex(int64_t idx) const override 122 | { 123 | auto j = this->GetValue(); 124 | if (!j) 125 | return Value(); 126 | 127 | return Reflect((*j)[static_cast(idx)]); 128 | } 129 | 130 | bool IsEqual(const IComparable& other) const override 131 | { 132 | auto* val = dynamic_cast(&other); 133 | if (!val) 134 | return false; 135 | auto enumerator = this->GetValue(); 136 | auto otherEnum = val->GetValue(); 137 | if (enumerator && otherEnum && enumerator != otherEnum) 138 | return false; 139 | if ((enumerator && !otherEnum) || (!enumerator && otherEnum)) 140 | return false; 141 | return true; 142 | } 143 | }; 144 | 145 | template 146 | struct Reflector> 147 | { 148 | static Value CreateFromPtr(const rapidjson::GenericValue* val) 149 | { 150 | Value result; 151 | switch (val->GetType()) 152 | { 153 | case rapidjson::kNullType: 154 | break; 155 | case rapidjson::kFalseType: 156 | result = Value(false); 157 | break; 158 | case rapidjson::kTrueType: 159 | result = Value(true); 160 | break; 161 | case rapidjson::kObjectType: 162 | result = GenericMap([accessor = RapidJsonObjectAccessor>(val)]() { return &accessor; }); 163 | break; 164 | case rapidjson::kArrayType: 165 | result = GenericList([accessor = RapidJsonArrayAccessor(val)]() { return &accessor; }); 166 | break; 167 | case rapidjson::kStringType: 168 | result = std::basic_string(val->GetString(), val->GetStringLength()); 169 | break; 170 | case rapidjson::kNumberType: 171 | if (val->IsInt64() || val->IsUint64()) 172 | result = val->GetInt64(); 173 | else if (val->IsInt() || val->IsUint()) 174 | result = val->GetInt(); 175 | else 176 | result = val->GetDouble(); 177 | break; 178 | } 179 | return result; 180 | } 181 | 182 | }; 183 | 184 | template 185 | struct Reflector> 186 | { 187 | static Value Create(const rapidjson::GenericDocument& val) 188 | { 189 | return GenericMap([accessor = RapidJsonObjectAccessor>(&val)]() { return &accessor; }); 190 | } 191 | 192 | static Value CreateFromPtr(const rapidjson::GenericDocument* val) 193 | { 194 | return GenericMap([accessor = RapidJsonObjectAccessor>(val)]() { return &accessor; }); 195 | } 196 | 197 | }; 198 | } // namespace detail 199 | } // namespace jinja2 200 | 201 | #endif // JINJA2CPP_BINDING_RAPID_JSON_H 202 | -------------------------------------------------------------------------------- /include/jinja2cpp/config.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_CONFIG_H 2 | #define JINJA2CPP_CONFIG_H 3 | 4 | // The Jinja2C++ library version in the form major * 10000 + minor * 100 + patch. 5 | #define JINJA2CPP_VERSION 10100 6 | 7 | #ifdef _WIN32 8 | #define JINJA2_DECLSPEC(S) __declspec(S) 9 | #if _MSC_VER 10 | #pragma warning(disable : 4251) 11 | #endif 12 | #else 13 | #define JINJA2_DECLSPEC(S) 14 | #endif 15 | 16 | #ifdef JINJA2CPP_BUILD_AS_SHARED 17 | #define JINJA2CPP_EXPORT JINJA2_DECLSPEC(dllexport) 18 | #define JINJA2CPP_SHARED_LIB 19 | #elif JINJA2CPP_LINK_AS_SHARED 20 | #define JINJA2CPP_EXPORT JINJA2_DECLSPEC(dllimport) 21 | #define JINJA2CPP_SHARED_LIB 22 | #else 23 | #define JINJA2CPP_EXPORT 24 | #define JINJA2CPP_STATIC_LIB 25 | #endif 26 | 27 | #endif // JINJA2CPP_CONFIG_H 28 | -------------------------------------------------------------------------------- /include/jinja2cpp/error_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_ERROR_HANDLER_H 2 | #define JINJA2CPP_ERROR_HANDLER_H 3 | 4 | #include "template.h" 5 | 6 | namespace jinja2 7 | { 8 | 9 | class IErrorHandler 10 | { 11 | public: 12 | }; 13 | 14 | } // jinja2 15 | 16 | #endif // JINJA2CPP_DIAGNOSTIC_CONSUMER_H 17 | -------------------------------------------------------------------------------- /include/jinja2cpp/error_info.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_ERROR_INFO_H 2 | #define JINJA2CPP_ERROR_INFO_H 3 | 4 | #include "config.h" 5 | #include "value.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace jinja2 12 | { 13 | /*! 14 | * \brief Type of the error 15 | */ 16 | enum class ErrorCode 17 | { 18 | Unspecified = 0, //!< Error is unspecified 19 | UnexpectedException = 1, //!< Generic exception occurred during template parsing or execution. ExtraParams[0] contains `what()` string of the exception 20 | YetUnsupported, //!< Feature of the jinja2 specification which yet not supported 21 | FileNotFound, //!< Requested file was not found. ExtraParams[0] contains name of the file 22 | ExtensionDisabled, //!< Particular jinja2 extension disabled in the settings 23 | TemplateEnvAbsent, //!< Template uses `extend`, `import`, `from` or `include` features but it's loaded without the template environment set 24 | TemplateNotFound, //!< Template with the specified name was not found. ExtraParams[0] contains name of the file 25 | TemplateNotParsed, //!< Template was not parsed 26 | InvalidValueType, //!< Invalid type of the value in the particular context 27 | InvalidTemplateName, //!< Invalid name of the template. ExtraParams[0] contains the name 28 | MetadataParseError, //!< Invalid name of the template. ExtraParams[0] contains the name 29 | ExpectedStringLiteral = 1001, //!< String literal expected 30 | ExpectedIdentifier, //!< Identifier expected 31 | ExpectedSquareBracket, //!< ']' expected 32 | ExpectedRoundBracket, //!< ')' expected 33 | ExpectedCurlyBracket, //!< '}' expected 34 | ExpectedToken, //!< Specific token(s) expected. ExtraParams[0] contains the actual token, rest of ExtraParams contain set of expected tokens 35 | ExpectedExpression, //!< Expression expected 36 | ExpectedEndOfStatement, //!< End of statement expected. ExtraParams[0] contains the expected end of statement tag 37 | ExpectedRawEnd, //!< {% endraw %} expected 38 | ExpectedMetaEnd, //!< {% endmeta %} expected 39 | UnexpectedToken, //!< Unexpected token. ExtraParams[0] contains the invalid token 40 | UnexpectedStatement, //!< Unexpected statement. ExtraParams[0] contains the invalid statement tag 41 | UnexpectedCommentBegin, //!< Unexpected comment block begin (`{#`) 42 | UnexpectedCommentEnd, //!< Unexpected comment block end (`#}`) 43 | UnexpectedExprBegin, //!< Unexpected expression block begin (`{{`) 44 | UnexpectedExprEnd, //!< Unexpected expression block end (`}}`) 45 | UnexpectedStmtBegin, //!< Unexpected statement block begin (`{%`) 46 | UnexpectedStmtEnd, //!< Unexpected statement block end (`%}`) 47 | UnexpectedRawBegin, //!< Unexpected raw block begin {% raw %} 48 | UnexpectedRawEnd, //!< Unexpected raw block end {% endraw %} 49 | UnexpectedMetaBegin, //!< Unexpected meta block begin {% meta %} 50 | UnexpectedMetaEnd, //!< Unexpected meta block end {% endmeta %} 51 | }; 52 | 53 | /*! 54 | * \brief Information about the source location of the error 55 | */ 56 | struct SourceLocation 57 | { 58 | //! Name of the file 59 | std::string fileName; 60 | //! Line number (1-based) 61 | unsigned line = 0; 62 | //! Column number (1-based) 63 | unsigned col = 0; 64 | }; 65 | 66 | template 67 | /*! 68 | * \brief Detailed information about the parse-time or render-time error 69 | * 70 | * If template parsing or rendering fails the detailed error information is provided. Exact specialization of ErrorInfoTpl is an object which contains 71 | * this information. Type of specialization depends on type of the template object: \ref ErrorInfo for \ref Template and \ref ErrorInfoW for \ref TemplateW. 72 | * 73 | * Detailed information about an error contains: 74 | * - Error code 75 | * - Error location 76 | * - Other locations related to the error 77 | * - Description of the location 78 | * - Extra parameters of the error 79 | * 80 | * @tparam CharT Character type which was used in template parser 81 | */ 82 | class ErrorInfoTpl 83 | { 84 | public: 85 | struct Data 86 | { 87 | ErrorCode code = ErrorCode::Unspecified; 88 | SourceLocation srcLoc; 89 | std::vector relatedLocs; 90 | std::vector extraParams; 91 | std::basic_string locationDescr; 92 | }; 93 | 94 | //! Default constructor 95 | ErrorInfoTpl() = default; 96 | //! Initializing constructor from error description 97 | explicit ErrorInfoTpl(Data data) 98 | : m_errorData(std::move(data)) 99 | {} 100 | 101 | //! Copy constructor 102 | ErrorInfoTpl(const ErrorInfoTpl&) = default; 103 | //! Move constructor 104 | ErrorInfoTpl(ErrorInfoTpl&& val) noexcept 105 | : m_errorData(std::move(val.m_errorData)) 106 | { } 107 | 108 | //! Destructor 109 | ~ErrorInfoTpl() noexcept = default; 110 | 111 | //! Copy-assignment operator 112 | ErrorInfoTpl& operator =(const ErrorInfoTpl&) = default; 113 | //! Move-assignment operator 114 | ErrorInfoTpl& operator =(ErrorInfoTpl&& val) noexcept 115 | { 116 | if (this == &val) 117 | return *this; 118 | 119 | std::swap(m_errorData.code, val.m_errorData.code); 120 | std::swap(m_errorData.srcLoc, val.m_errorData.srcLoc); 121 | std::swap(m_errorData.relatedLocs, val.m_errorData.relatedLocs); 122 | std::swap(m_errorData.extraParams, val.m_errorData.extraParams); 123 | std::swap(m_errorData.locationDescr, val.m_errorData.locationDescr); 124 | 125 | return *this; 126 | } 127 | 128 | //! Return code of the error 129 | ErrorCode GetCode() const 130 | { 131 | return m_errorData.code; 132 | } 133 | 134 | //! Return error location in the template file 135 | auto& GetErrorLocation() const 136 | { 137 | return m_errorData.srcLoc; 138 | } 139 | 140 | //! Return locations, related to the main error location 141 | auto& GetRelatedLocations() const 142 | { 143 | return m_errorData.relatedLocs; 144 | } 145 | 146 | /*! 147 | * \brief Return location description 148 | * 149 | * Return string of two lines. First line is contents of the line with error. Second highlight the exact position within line. For instance: 150 | * ``` 151 | * {% for i in range(10) endfor%} 152 | * ---^------- 153 | * ``` 154 | * 155 | * @return Location description 156 | */ 157 | const std::basic_string& GetLocationDescr() const 158 | { 159 | return m_errorData.locationDescr; 160 | } 161 | 162 | /*! 163 | * \brief Return extra params of the error 164 | * 165 | * Extra params is a additional details assiciated with the error. For instance, name of the file which wasn't opened 166 | * 167 | * @return Vector with extra error params 168 | */ 169 | auto& GetExtraParams() const { return m_errorData.extraParams; } 170 | 171 | //! Convert error to the detailed string representation 172 | JINJA2CPP_EXPORT std::basic_string ToString() const; 173 | 174 | private: 175 | Data m_errorData; 176 | }; 177 | 178 | using ErrorInfo = ErrorInfoTpl; 179 | using ErrorInfoW = ErrorInfoTpl; 180 | 181 | JINJA2CPP_EXPORT std::ostream& operator<<(std::ostream& os, const ErrorInfo& res); 182 | JINJA2CPP_EXPORT std::wostream& operator<<(std::wostream& os, const ErrorInfoW& res); 183 | } // namespace jinja2 184 | 185 | #endif // JINJA2CPP_ERROR_INFO_H 186 | -------------------------------------------------------------------------------- /include/jinja2cpp/filesystem_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_FILESYSTEM_HANDLER_H 2 | #define JINJA2CPP_FILESYSTEM_HANDLER_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace jinja2 18 | { 19 | 20 | template 21 | using FileStreamPtr = std::unique_ptr, void (*)(std::basic_istream*)>; 22 | using CharFileStreamPtr = FileStreamPtr; 23 | using WCharFileStreamPtr = FileStreamPtr; 24 | 25 | /*! 26 | * \brief Generic interface to filesystem handlers (loaders) 27 | * 28 | * This interface should be implemented in order to provide custom file system handler. Interface provides most-common methods which are called by 29 | * the template environment to load the particular template. `OpenStream` methods return the unique pointer to the generic `istream` object implementation. 30 | * So, the exact type (ex. `ifstream`, `istringstream` etc.) of input stream is unspecified. In order to delete stream object correctly returned pointer 31 | * provide the custom deleter which should properly delete the stream object. 32 | */ 33 | class JINJA2CPP_EXPORT IFilesystemHandler : public IComparable 34 | { 35 | public: 36 | //! Destructor 37 | virtual ~IFilesystemHandler() = default; 38 | 39 | /*! 40 | * \brief Method is called to open the file with the specified name in 'narrow-char' mode. 41 | * 42 | * Method should return unique pointer to the std::istream object with custom deleter (\ref CharFileStreamPtr) . Deleter should properly delete pointee 43 | * stream object. 44 | * 45 | * @param name Name of the file to open 46 | * @return Opened stream object or empty pointer in case of any error 47 | */ 48 | virtual CharFileStreamPtr OpenStream(const std::string& name) const = 0; 49 | /*! 50 | * \brief Method is called to open the file with the specified name in 'wide-char' mode. 51 | * 52 | * Method should return unique pointer to the std::wistream object with custom deleter (\ref WCharFileStreamPtr) . Deleter should properly delete pointee 53 | * stream object. 54 | * 55 | * @param name Name of the file to open 56 | * @return Opened stream object or empty pointer in case of any error 57 | */ 58 | virtual WCharFileStreamPtr OpenWStream(const std::string& name) const = 0; 59 | /*! 60 | * \brief Method is called to obtain the modification date of the specified file (if applicable) 61 | * 62 | * If the underlaying filesystem supports retrival of the last modification date of the file this method should return this date when called. In other 63 | * 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 64 | * reload 65 | * 66 | * @param name Name of the file to get the last modification date 67 | * @return Last modification date (if applicable) or empty optional object otherwise 68 | */ 69 | virtual nonstd::optional GetLastModificationDate(const std::string& name) const = 0; 70 | }; 71 | 72 | using FilesystemHandlerPtr = std::shared_ptr; 73 | 74 | /*! 75 | * \brief Filesystem handler for files stored in memory 76 | * 77 | * 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 78 | * methods. Content of the files automatically converted to narrow/wide strings representation if necessary. 79 | */ 80 | class JINJA2CPP_EXPORT MemoryFileSystem : public IFilesystemHandler 81 | { 82 | public: 83 | /*! 84 | * \brief Add new narrow-char "file" to the filesystem handler 85 | * 86 | * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file 87 | * 88 | * @param fileName Name of the file to add 89 | * @param fileContent Content of the file to add 90 | */ 91 | void AddFile(std::string fileName, std::string fileContent); 92 | /*! 93 | * \brief Add new wide-char "file" to the filesystem handler 94 | * 95 | * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file 96 | * 97 | * @param fileName Name of the file to add 98 | * @param fileContent Content of the file to add 99 | */ 100 | void AddFile(std::string fileName, std::wstring fileContent); 101 | 102 | CharFileStreamPtr OpenStream(const std::string& name) const override; 103 | WCharFileStreamPtr OpenWStream(const std::string& name) const override; 104 | nonstd::optional GetLastModificationDate(const std::string& name) const override; 105 | 106 | /*! 107 | * \brief Compares to an object of the same type 108 | * 109 | * return true if equal 110 | */ 111 | bool IsEqual(const IComparable& other) const override; 112 | private: 113 | struct FileContent 114 | { 115 | nonstd::optional narrowContent; 116 | nonstd::optional wideContent; 117 | bool operator==(const FileContent& other) const 118 | { 119 | if (narrowContent != other.narrowContent) 120 | return false; 121 | return wideContent == other.wideContent; 122 | } 123 | bool operator!=(const FileContent& other) const 124 | { 125 | return !(*this == other); 126 | } 127 | }; 128 | mutable std::unordered_map m_filesMap; 129 | }; 130 | 131 | /*! 132 | * \brief Filesystem handler for files stored on the filesystem 133 | * 134 | * 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 135 | * the file to open is appended to the root directory path and then passed to the stream open methods. 136 | */ 137 | class JINJA2CPP_EXPORT RealFileSystem : public IFilesystemHandler 138 | { 139 | public: 140 | /*! 141 | * \brief Initializing/default constructor 142 | * 143 | * @param rootFolder Path to the root folder. This path is used as a root for every opened file 144 | */ 145 | explicit RealFileSystem(std::string rootFolder = "."); 146 | 147 | /*! 148 | * \brief Reset path to the root folder to the new value 149 | * 150 | * @param newRoot New path to the root folder 151 | */ 152 | void SetRootFolder(std::string newRoot) 153 | { 154 | m_rootFolder = std::move(newRoot); 155 | } 156 | 157 | /*! 158 | * \brief Get path to the current root folder 159 | * 160 | * @return 161 | */ 162 | const std::string& GetRootFolder() const 163 | { 164 | return m_rootFolder; 165 | } 166 | /*! 167 | * \brief Get full path to the specified file. 168 | * 169 | * Appends specified name of the file to the current root folder and returns it 170 | * 171 | * @param name Name of the file to get path to 172 | * @return Full path to the file 173 | */ 174 | std::string GetFullFilePath(const std::string& name) const; 175 | 176 | CharFileStreamPtr OpenStream(const std::string& name) const override; 177 | WCharFileStreamPtr OpenWStream(const std::string& name) const override; 178 | /*! 179 | * \brief Open the specified file as a binary stream 180 | * 181 | * Opens the specified file in the binary mode (instead of text). 182 | * 183 | * @param name Name of the file to get the last modification date 184 | * @return Last modification date (if applicable) or empty optional object otherwise 185 | */ 186 | CharFileStreamPtr OpenByteStream(const std::string& name) const; 187 | nonstd::optional GetLastModificationDate(const std::string& name) const override; 188 | 189 | /*! 190 | * \brief Compares to an object of the same type 191 | * 192 | * return true if equal 193 | */ 194 | bool IsEqual(const IComparable& other) const override; 195 | 196 | private: 197 | std::string m_rootFolder; 198 | }; 199 | } // namespace jinja2 200 | 201 | #endif // JINJA2CPP_FILESYSTEM_HANDLER_H 202 | -------------------------------------------------------------------------------- /include/jinja2cpp/generic_list_iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_GENERIC_LIST_ITERATOR_H 2 | #define JINJA2CPP_GENERIC_LIST_ITERATOR_H 3 | 4 | #include "generic_list.h" 5 | #include "value.h" 6 | #include "value_ptr.h" 7 | 8 | namespace jinja2 9 | { 10 | namespace detail 11 | { 12 | class JINJA2CPP_EXPORT GenericListIterator 13 | { 14 | public: 15 | using iterator_category = std::input_iterator_tag; 16 | using value_type = const Value; 17 | using difference_type = std::ptrdiff_t; 18 | using reference = const Value&; 19 | using pointer = const Value*; 20 | using EnumeratorPtr = types::ValuePtr; 21 | 22 | GenericListIterator() = default; 23 | 24 | GenericListIterator(ListEnumeratorPtr enumerator) 25 | : m_enumerator(types::ValuePtr(enumerator)) 26 | { 27 | if (m_enumerator) 28 | m_hasValue = m_enumerator->MoveNext(); 29 | 30 | if (m_hasValue) 31 | m_current = m_enumerator->GetCurrent(); 32 | } 33 | 34 | bool operator == (const GenericListIterator& other) const 35 | { 36 | if (m_hasValue != other.m_hasValue) 37 | return false; 38 | if (!m_enumerator && !other.m_enumerator) 39 | return true; 40 | if (this->m_enumerator && other.m_enumerator && !m_enumerator->IsEqual(*other.m_enumerator)) 41 | return false; 42 | if ((m_enumerator && !other.m_enumerator) || (!m_enumerator && other.m_enumerator)) 43 | return false; 44 | if (m_current != other.m_current) 45 | return false; 46 | return true; 47 | } 48 | 49 | bool operator != (const GenericListIterator& other) const 50 | { 51 | return !(*this == other); 52 | } 53 | 54 | reference operator *() const 55 | { 56 | return m_current; 57 | } 58 | 59 | GenericListIterator& operator ++() 60 | { 61 | if (!m_enumerator) 62 | return *this; 63 | m_hasValue = m_enumerator->MoveNext(); 64 | if (m_hasValue) 65 | { 66 | m_current = m_enumerator->GetCurrent(); 67 | } 68 | else 69 | { 70 | EnumeratorPtr temp; 71 | Value tempVal; 72 | using std::swap; 73 | swap(m_enumerator, temp); 74 | swap(m_current, tempVal); 75 | } 76 | 77 | return *this; 78 | } 79 | 80 | GenericListIterator operator++(int) 81 | { 82 | GenericListIterator result(std::move(m_current)); 83 | 84 | this->operator++(); 85 | return result; 86 | } 87 | private: 88 | explicit GenericListIterator(Value&& val) 89 | : m_hasValue(true) 90 | , m_current(std::move(val)) 91 | { 92 | 93 | } 94 | 95 | private: 96 | EnumeratorPtr m_enumerator; 97 | bool m_hasValue = false; 98 | Value m_current; 99 | }; 100 | 101 | } // namespace detail 102 | } // namespace jinja2 103 | 104 | #endif // JINJA2CPP_GENERIC_LIST_ITERATOR_H 105 | -------------------------------------------------------------------------------- /include/jinja2cpp/utils/i_comparable.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_ICOMPARABLE_H 2 | #define JINJA2CPP_ICOMPARABLE_H 3 | 4 | #include 5 | 6 | namespace jinja2 { 7 | 8 | struct JINJA2CPP_EXPORT IComparable 9 | { 10 | virtual ~IComparable() {} 11 | virtual bool IsEqual(const IComparable& other) const = 0; 12 | }; 13 | 14 | } // namespace jinja2 15 | 16 | #endif // JINJA2CPP_ICOMPARABLE_H 17 | -------------------------------------------------------------------------------- /include/jinja2cpp/value_ptr.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_VALUE_PTR_H 2 | #define JINJA2CPP_VALUE_PTR_H 3 | 4 | #include "polymorphic_value.h" 5 | 6 | namespace jinja2 { 7 | namespace types { 8 | 9 | using namespace isocpp_p0201; 10 | 11 | template 12 | using ValuePtr = polymorphic_value; 13 | 14 | template 15 | ValuePtr MakeValuePtr(Ts&&... ts) 16 | { 17 | return make_polymorphic_value(std::forward(ts)...); 18 | } 19 | 20 | template 21 | ValuePtr MakeValuePtr(Ts&&... ts) 22 | { 23 | return make_polymorphic_value(std::forward(ts)...); 24 | } 25 | 26 | } // namespace types 27 | } // namespace jinja2 28 | 29 | #endif // JINJA2CPP_VALUE_PTR_H 30 | -------------------------------------------------------------------------------- /jinja2cpp.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: @PROJECT_DESCRIPTION@ 8 | Version: @PROJECT_VERSION@ 9 | 10 | Requires: 11 | Libs: -L${libdir} -ljinja2cpp 12 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | export BUILD_TARGET="all" 6 | export CMAKE_OPTS="-DCMAKE_VERBOSE_MAKEFILE=OFF" 7 | if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi 8 | if [[ "${BUILD_CONFIG}" == "" ]]; then export BUILD_CONFIG="Release"; fi 9 | if [[ "${COLLECT_COVERAGE}" != "" ]]; then export BUILD_CONFIG="Debug" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_COVERAGE=ON"; fi 10 | if [[ "${SANITIZE_BUILD}" != "" ]]; then export BUILD_CONFIG="RelWithDebInfo" && export CMAKE_OPTS="${CMAKE_OPTS} -DJINJA2CPP_WITH_SANITIZERS=${SANITIZE_BUILD}"; fi 11 | $CXX --version 12 | mkdir -p build && cd build 13 | 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 14 | ctest -C $BUILD_CONFIG -V 15 | -------------------------------------------------------------------------------- /src/ast_visitor.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_AST_VISITOR_H 2 | #define JINJA2CPP_SRC_AST_VISITOR_H 3 | 4 | namespace jinja2 5 | { 6 | class IRendererBase; 7 | class ExpressionEvaluatorBase; 8 | class Statement; 9 | class ForStatement; 10 | class IfStatement; 11 | class ElseBranchStatement; 12 | class SetStatement; 13 | class ParentBlockStatement; 14 | class BlockStatement; 15 | class ExtendsStatement; 16 | class IncludeStatement; 17 | class ImportStatement; 18 | class MacroStatement; 19 | class MacroCallStatement; 20 | class ComposedRenderer; 21 | class RawTextRenderer; 22 | class ExpressionRenderer; 23 | 24 | class StatementVisitor; 25 | 26 | class VisitableStatement 27 | { 28 | public: 29 | virtual ~VisitableStatement() = default; 30 | 31 | virtual void ApplyVisitor(StatementVisitor* visitor) = 0; 32 | virtual void ApplyVisitor(StatementVisitor* visitor) const = 0; 33 | }; 34 | 35 | #define VISITABLE_STATEMENT() \ 36 | void ApplyVisitor(StatementVisitor* visitor) override {visitor->DoVisit(this);} \ 37 | void ApplyVisitor(StatementVisitor* visitor) const override {visitor->DoVisit(this);} \ 38 | 39 | namespace detail 40 | { 41 | template 42 | class VisitorIfaceImpl : public Base 43 | { 44 | public: 45 | using Base::DoVisit; 46 | 47 | virtual void DoVisit(Type*) {} 48 | virtual void DoVisit(const Type*) {} 49 | }; 50 | 51 | template 52 | class VisitorIfaceImpl 53 | { 54 | public: 55 | virtual void DoVisit(Type*) {} 56 | virtual void DoVisit(const Type*) {} 57 | }; 58 | 59 | template 60 | struct VisitorBaseImpl; 61 | 62 | template 63 | struct VisitorBaseImpl 64 | { 65 | using current_base = VisitorIfaceImpl; 66 | using base_type = typename VisitorBaseImpl::base_type; 67 | }; 68 | 69 | template 70 | struct VisitorBaseImpl 71 | { 72 | using base_type = VisitorIfaceImpl; 73 | }; 74 | 75 | 76 | template 77 | struct VisitorBase 78 | { 79 | using type = typename VisitorBaseImpl::base_type; 80 | }; 81 | } // namespace detail 82 | 83 | template 84 | using VisitorBase = typename detail::VisitorBase::type; 85 | 86 | class StatementVisitor : public VisitorBase< 87 | IRendererBase, 88 | Statement, 89 | ForStatement, 90 | IfStatement, 91 | ElseBranchStatement, 92 | SetStatement, 93 | ParentBlockStatement, 94 | BlockStatement, 95 | ExtendsStatement, 96 | IncludeStatement, 97 | ImportStatement, 98 | MacroStatement, 99 | MacroCallStatement, 100 | ComposedRenderer, 101 | RawTextRenderer, 102 | ExpressionRenderer> 103 | { 104 | public: 105 | void Visit(VisitableStatement* stmt) 106 | { 107 | stmt->ApplyVisitor(this); 108 | } 109 | void Visit(const VisitableStatement* stmt) 110 | { 111 | stmt->ApplyVisitor(this); 112 | } 113 | }; 114 | } // namespace jinja2 115 | 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /src/binding/boost_json_serializer.cpp: -------------------------------------------------------------------------------- 1 | #include "boost_json_serializer.h" 2 | 3 | #include "../value_visitors.h" 4 | #include 5 | #include 6 | 7 | template <> struct fmt::formatter : ostream_formatter {}; 8 | 9 | namespace jinja2 10 | { 11 | namespace boost_json_serializer 12 | { 13 | namespace 14 | { 15 | struct JsonInserter : visitors::BaseVisitor 16 | { 17 | using BaseVisitor::operator(); 18 | 19 | explicit JsonInserter() {} 20 | 21 | boost::json::value operator()(const ListAdapter& list) const 22 | { 23 | boost::json::array listValue; //(boost::json::kind::array); 24 | 25 | for (auto& v : list) 26 | { 27 | listValue.push_back(Apply(v)); 28 | } 29 | return listValue; 30 | } 31 | 32 | boost::json::value operator()(const MapAdapter& map) const 33 | { 34 | boost::json::object mapNode; //(boost::json::kind::object); 35 | 36 | const auto& keys = map.GetKeys(); 37 | for (auto& k : keys) 38 | { 39 | mapNode.emplace(k.c_str(), Apply(map.GetValueByName(k))); 40 | } 41 | 42 | return mapNode; 43 | } 44 | 45 | boost::json::value operator()(const KeyValuePair& kwPair) const 46 | { 47 | boost::json::object pairNode; //(boost::json::kind::object); 48 | pairNode.emplace(kwPair.key.c_str(), Apply(kwPair.value)); 49 | 50 | return pairNode; 51 | } 52 | 53 | boost::json::value operator()(const std::string& str) const { return boost::json::value(str.c_str()); } 54 | 55 | boost::json::value operator()(const nonstd::string_view& str) const 56 | { 57 | return boost::json::value(boost::json::string(str.data(), str.size())); 58 | } 59 | 60 | boost::json::value operator()(const std::wstring& str) const 61 | { 62 | auto s = ConvertString(str); 63 | return boost::json::value(s.c_str()); 64 | } 65 | 66 | boost::json::value operator()(const nonstd::wstring_view& str) const 67 | { 68 | auto s = ConvertString(str); 69 | return boost::json::value(s.c_str()); 70 | } 71 | 72 | boost::json::value operator()(bool val) const { return boost::json::value(val); } 73 | 74 | boost::json::value operator()(EmptyValue) const { return boost::json::value(); } 75 | 76 | boost::json::value operator()(const Callable&) const { return boost::json::value(""); } 77 | 78 | boost::json::value operator()(double val) const { return boost::json::value(val); } 79 | 80 | boost::json::value operator()(int64_t val) const { return boost::json::value(val); } 81 | 82 | }; 83 | } // namespace 84 | 85 | DocumentWrapper::DocumentWrapper() 86 | { 87 | } 88 | 89 | ValueWrapper DocumentWrapper::CreateValue(const InternalValue& value) const 90 | { 91 | auto v = Apply(value); 92 | return ValueWrapper(std::move(v)); 93 | } 94 | 95 | ValueWrapper::ValueWrapper(boost::json::value&& value) 96 | : m_value(std::move(value)) 97 | { 98 | } 99 | 100 | void PrettyPrint(fmt::basic_memory_buffer& os, const boost::json::value& jv, uint8_t indent = 4, int level = 0) 101 | { 102 | switch (jv.kind()) 103 | { 104 | case boost::json::kind::object: 105 | { 106 | fmt::format_to(std::back_inserter(os), "{}", '{'); 107 | if (indent != 0) 108 | { 109 | fmt::format_to(std::back_inserter(os), "{}", "\n"); 110 | } 111 | const auto& obj = jv.get_object(); 112 | if (!obj.empty()) 113 | { 114 | auto it = obj.begin(); 115 | for (;;) 116 | { 117 | auto key = boost::json::serialize(it->key()); 118 | fmt::format_to( 119 | std::back_inserter(os), 120 | "{: >{}}{: <{}}", 121 | key, 122 | key.size() + indent * (level + 1), 123 | ":", 124 | (indent == 0) ? 0 : 2 125 | ); 126 | PrettyPrint(os, it->value(), indent, level + 1); 127 | if (++it == obj.end()) 128 | break; 129 | fmt::format_to(std::back_inserter(os), "{: <{}}", ",", (indent == 0) ? 0 : 2); 130 | } 131 | } 132 | if (indent != 0) 133 | { 134 | fmt::format_to(std::back_inserter(os), "{}", "\n"); 135 | } 136 | fmt::format_to(std::back_inserter(os), "{: >{}}", "}", (indent * level) + 1); 137 | break; 138 | } 139 | 140 | case boost::json::kind::array: 141 | { 142 | fmt::format_to(std::back_inserter(os), "["); 143 | auto const& arr = jv.get_array(); 144 | if (!arr.empty()) 145 | { 146 | auto it = arr.begin(); 147 | for (;;) 148 | { 149 | PrettyPrint(os, *it, indent, level + 1); 150 | if (++it == arr.end()) 151 | break; 152 | fmt::format_to(std::back_inserter(os), "{: <{}}", ",", (indent == 0) ? 0 : 2); 153 | } 154 | } 155 | fmt::format_to(std::back_inserter(os), "]"); 156 | break; 157 | } 158 | 159 | case boost::json::kind::string: 160 | { 161 | fmt::format_to(std::back_inserter(os), "{}", boost::json::serialize(jv.get_string())); 162 | break; 163 | } 164 | 165 | case boost::json::kind::uint64: 166 | case boost::json::kind::int64: 167 | case boost::json::kind::double_: 168 | { 169 | fmt::format_to(std::back_inserter(os), "{}", jv); 170 | break; 171 | } 172 | case boost::json::kind::bool_: 173 | { 174 | fmt::format_to(std::back_inserter(os), "{}", jv.get_bool()); 175 | break; 176 | } 177 | 178 | case boost::json::kind::null: 179 | { 180 | fmt::format_to(std::back_inserter(os), "null"); 181 | break; 182 | } 183 | } 184 | } 185 | 186 | std::string ValueWrapper::AsString(const uint8_t indent) const 187 | { 188 | fmt::memory_buffer out; 189 | PrettyPrint(out, m_value, indent); 190 | return fmt::to_string(out); 191 | } 192 | 193 | } // namespace boost_json_serializer 194 | } // namespace jinja2 195 | -------------------------------------------------------------------------------- /src/binding/boost_json_serializer.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H 2 | #define JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H 3 | 4 | #include "../internal_value.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace jinja2 11 | { 12 | namespace boost_json_serializer 13 | { 14 | 15 | class ValueWrapper 16 | { 17 | friend class DocumentWrapper; 18 | 19 | public: 20 | ValueWrapper(ValueWrapper&&) = default; 21 | ValueWrapper& operator=(ValueWrapper&&) = default; 22 | 23 | std::string AsString(uint8_t indent = 0) const; 24 | 25 | private: 26 | ValueWrapper(boost::json::value&& value); 27 | 28 | boost::json::value m_value; 29 | }; 30 | 31 | class DocumentWrapper 32 | { 33 | public: 34 | DocumentWrapper(); 35 | 36 | DocumentWrapper(DocumentWrapper&&) = default; 37 | DocumentWrapper& operator=(DocumentWrapper&&) = default; 38 | 39 | ValueWrapper CreateValue(const InternalValue& value) const; 40 | 41 | private: 42 | }; 43 | 44 | } // namespace boost_json_serializer 45 | } // namespace jinja2 46 | 47 | #endif // JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H 48 | -------------------------------------------------------------------------------- /src/binding/rapid_json_serializer.cpp: -------------------------------------------------------------------------------- 1 | #include "rapid_json_serializer.h" 2 | 3 | #include "../value_visitors.h" 4 | 5 | #include 6 | 7 | namespace jinja2 8 | { 9 | namespace rapidjson_serializer 10 | { 11 | namespace 12 | { 13 | struct JsonInserter : visitors::BaseVisitor 14 | { 15 | using BaseVisitor::operator(); 16 | 17 | explicit JsonInserter(rapidjson::Document::AllocatorType& allocator) 18 | : m_allocator(allocator) 19 | { 20 | } 21 | 22 | rapidjson::Value operator()(const ListAdapter& list) const 23 | { 24 | rapidjson::Value listValue(rapidjson::kArrayType); 25 | 26 | for (auto& v : list) 27 | { 28 | listValue.PushBack(Apply(v, m_allocator), m_allocator); 29 | } 30 | return listValue; 31 | } 32 | 33 | rapidjson::Value operator()(const MapAdapter& map) const 34 | { 35 | rapidjson::Value mapNode(rapidjson::kObjectType); 36 | 37 | const auto& keys = map.GetKeys(); 38 | for (auto& k : keys) 39 | { 40 | mapNode.AddMember(rapidjson::Value(k.c_str(), m_allocator), Apply(map.GetValueByName(k), m_allocator), m_allocator); 41 | } 42 | 43 | return mapNode; 44 | } 45 | 46 | rapidjson::Value operator()(const KeyValuePair& kwPair) const 47 | { 48 | rapidjson::Value pairNode(rapidjson::kObjectType); 49 | pairNode.AddMember(rapidjson::Value(kwPair.key.c_str(), m_allocator), Apply(kwPair.value, m_allocator), m_allocator); 50 | 51 | return pairNode; 52 | } 53 | 54 | rapidjson::Value operator()(const std::string& str) const { return rapidjson::Value(str.c_str(), m_allocator); } 55 | 56 | rapidjson::Value operator()(const nonstd::string_view& str) const 57 | { 58 | return rapidjson::Value(str.data(), static_cast(str.size()), m_allocator); 59 | } 60 | 61 | rapidjson::Value operator()(const std::wstring& str) const 62 | { 63 | auto s = ConvertString(str); 64 | return rapidjson::Value(s.c_str(), m_allocator); 65 | } 66 | 67 | rapidjson::Value operator()(const nonstd::wstring_view& str) const 68 | { 69 | auto s = ConvertString(str); 70 | return rapidjson::Value(s.c_str(), m_allocator); 71 | } 72 | 73 | rapidjson::Value operator()(bool val) const { return rapidjson::Value(val); } 74 | 75 | rapidjson::Value operator()(EmptyValue) const { return rapidjson::Value(); } 76 | 77 | rapidjson::Value operator()(const Callable&) const { return rapidjson::Value(""); } 78 | 79 | rapidjson::Value operator()(double val) const { return rapidjson::Value(val); } 80 | 81 | rapidjson::Value operator()(int64_t val) const { return rapidjson::Value(val); } 82 | 83 | rapidjson::Document::AllocatorType& m_allocator; 84 | }; 85 | } // namespace 86 | 87 | DocumentWrapper::DocumentWrapper() 88 | : m_document(std::make_shared()) 89 | { 90 | } 91 | 92 | ValueWrapper DocumentWrapper::CreateValue(const InternalValue& value) const 93 | { 94 | auto v = Apply(value, m_document->GetAllocator()); 95 | return ValueWrapper(std::move(v), m_document); 96 | } 97 | 98 | ValueWrapper::ValueWrapper(rapidjson::Value&& value, std::shared_ptr document) 99 | : m_value(std::move(value)) 100 | , m_document(document) 101 | { 102 | } 103 | 104 | std::string ValueWrapper::AsString(const uint8_t indent) const 105 | { 106 | using Writer = rapidjson::Writer>; 107 | using PrettyWriter = rapidjson::PrettyWriter>; 108 | 109 | rapidjson::StringBuffer buffer; 110 | if (indent == 0) 111 | { 112 | Writer writer(buffer); 113 | m_value.Accept(writer); 114 | } 115 | else 116 | { 117 | PrettyWriter writer(buffer); 118 | writer.SetIndent(' ', indent); 119 | writer.SetFormatOptions(rapidjson::kFormatSingleLineArray); 120 | m_value.Accept(writer); 121 | } 122 | 123 | return buffer.GetString(); 124 | } 125 | 126 | } // namespace rapidjson_serializer 127 | } // namespace jinja2 128 | -------------------------------------------------------------------------------- /src/binding/rapid_json_serializer.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H 2 | #define JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H 3 | 4 | #include "../internal_value.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace jinja2 12 | { 13 | namespace rapidjson_serializer 14 | { 15 | 16 | class ValueWrapper 17 | { 18 | friend class DocumentWrapper; 19 | 20 | public: 21 | ValueWrapper(ValueWrapper&&) = default; 22 | ValueWrapper& operator=(ValueWrapper&&) = default; 23 | 24 | std::string AsString(uint8_t indent = 0) const; 25 | 26 | private: 27 | ValueWrapper(rapidjson::Value&& value, std::shared_ptr document); 28 | 29 | rapidjson::Value m_value; 30 | std::shared_ptr m_document; 31 | }; 32 | 33 | class DocumentWrapper 34 | { 35 | public: 36 | DocumentWrapper(); 37 | 38 | DocumentWrapper(DocumentWrapper&&) = default; 39 | DocumentWrapper& operator=(DocumentWrapper&&) = default; 40 | 41 | ValueWrapper CreateValue(const InternalValue& value) const; 42 | 43 | private: 44 | std::shared_ptr m_document; 45 | }; 46 | 47 | } // namespace rapidjson_serializer 48 | } // namespace jinja2 49 | 50 | #endif // JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H 51 | -------------------------------------------------------------------------------- /src/error_handling.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_ERROR_HANDLING_H 2 | #define JINJA2CPP_SRC_ERROR_HANDLING_H 3 | 4 | #include "lexer.h" 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace jinja2 12 | { 13 | 14 | struct ParseError 15 | { 16 | ParseError() = default; 17 | ParseError(ErrorCode code, Token tok) 18 | : errorCode(code) 19 | , errorToken(tok) 20 | {} 21 | 22 | ParseError(ErrorCode code, Token tok, std::initializer_list toks) 23 | : errorCode(code) 24 | , errorToken(tok) 25 | , relatedTokens(toks) 26 | {} 27 | ParseError(const ParseError&) = default; 28 | ParseError(ParseError&& other) noexcept(true) 29 | : errorCode(std::move(other.errorCode)) 30 | , errorToken(std::move(other.errorToken)) 31 | , relatedTokens(std::move(other.relatedTokens)) 32 | {} 33 | 34 | ParseError& operator =(const ParseError&) = default; 35 | ParseError& operator =(ParseError&& error) noexcept 36 | { 37 | if (this == &error) 38 | return *this; 39 | 40 | std::swap(errorCode, error.errorCode); 41 | std::swap(errorToken, error.errorToken); 42 | std::swap(relatedTokens, error.relatedTokens); 43 | 44 | return *this; 45 | } 46 | 47 | ErrorCode errorCode; 48 | Token errorToken; 49 | std::vector relatedTokens; 50 | }; 51 | 52 | inline auto MakeParseError(ErrorCode code, Token tok) 53 | { 54 | return nonstd::make_unexpected(ParseError{code, tok}); 55 | } 56 | 57 | inline auto MakeParseError(ErrorCode code, Token tok, std::initializer_list toks) 58 | { 59 | return nonstd::make_unexpected(ParseError{code, tok, toks}); 60 | } 61 | 62 | } // namespace jinja2 63 | #endif // JINJA2CPP_SRC_ERROR_HANDLING_H 64 | -------------------------------------------------------------------------------- /src/expression_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_EXPRESSION_PARSER_H 2 | #define JINJA2CPP_SRC_EXPRESSION_PARSER_H 3 | 4 | #include "lexer.h" 5 | #include "error_handling.h" 6 | #include "expression_evaluator.h" 7 | #include "renderer.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace jinja2 13 | { 14 | class ExpressionParser 15 | { 16 | public: 17 | template 18 | using ParseResult = nonstd::expected; 19 | 20 | explicit ExpressionParser(const Settings& settings, TemplateEnv* env = nullptr); 21 | ParseResult Parse(LexScanner& lexer); 22 | ParseResult> ParseFullExpression(LexScanner& lexer, bool includeIfPart = true); 23 | ParseResult ParseCallParams(LexScanner& lexer); 24 | ParseResult> ParseFilterExpression(LexScanner& lexer); 25 | private: 26 | ParseResult> ParseLogicalNot(LexScanner& lexer); 27 | ParseResult> ParseLogicalOr(LexScanner& lexer); 28 | ParseResult> ParseLogicalAnd(LexScanner& lexer); 29 | ParseResult> ParseLogicalCompare(LexScanner& lexer); 30 | ParseResult> ParseStringConcat(LexScanner& lexer); 31 | ParseResult> ParseMathPow(LexScanner& lexer); 32 | ParseResult> ParseMathMulDiv(LexScanner& lexer); 33 | ParseResult> ParseMathPlusMinus(LexScanner& lexer); 34 | ParseResult> ParseUnaryPlusMinus(LexScanner& lexer); 35 | ParseResult> ParseValueExpression(LexScanner& lexer); 36 | ParseResult> ParseBracedExpressionOrTuple(LexScanner& lexer); 37 | ParseResult> ParseDictionary(LexScanner& lexer); 38 | ParseResult> ParseTuple(LexScanner& lexer); 39 | ParseResult> ParseCall(LexScanner& lexer, ExpressionEvaluatorPtr valueRef); 40 | ParseResult> ParseSubscript(LexScanner& lexer, ExpressionEvaluatorPtr valueRef); 41 | ParseResult> ParseIfExpression(LexScanner& lexer); 42 | 43 | private: 44 | ComposedRenderer* m_topLevelRenderer = nullptr; 45 | }; 46 | 47 | } // namespace jinja2 48 | 49 | #endif // JINJA2CPP_SRC_EXPRESSION_PARSER_H 50 | -------------------------------------------------------------------------------- /src/filesystem_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace jinja2 11 | { 12 | 13 | using TargetFileStream = nonstd::variant; 14 | 15 | struct FileContentConverter 16 | { 17 | void operator() (const std::string& content, CharFileStreamPtr* sPtr) const 18 | { 19 | sPtr->reset(new std::istringstream(content)); 20 | } 21 | 22 | void operator() (const std::wstring& content, WCharFileStreamPtr* sPtr) const 23 | { 24 | sPtr->reset(new std::wistringstream(content)); 25 | } 26 | void operator() (const std::wstring&, CharFileStreamPtr*) const 27 | { 28 | // CharFileStreamPtr stream(new std::istringstream(content), [](std::istream* s) {delete static_cast(s);}); 29 | // std::swap(*sPtr, stream); 30 | } 31 | 32 | void operator() (const std::string&, WCharFileStreamPtr*) const 33 | { 34 | // WCharFileStreamPtr stream(new std::wistringstream(content), [](std::wistream* s) {delete static_cast(s);}); 35 | // std::swap(*sPtr, stream); 36 | } 37 | }; 38 | 39 | void MemoryFileSystem::AddFile(std::string fileName, std::string fileContent) 40 | { 41 | m_filesMap[std::move(fileName)] = FileContent{std::move(fileContent), {}}; 42 | } 43 | 44 | void MemoryFileSystem::AddFile(std::string fileName, std::wstring fileContent) 45 | { 46 | m_filesMap[std::move(fileName)] = FileContent{ {}, std::move(fileContent) }; 47 | } 48 | 49 | CharFileStreamPtr MemoryFileSystem::OpenStream(const std::string& name) const 50 | { 51 | CharFileStreamPtr result(nullptr, [](std::istream* s) {delete static_cast(s);}); 52 | auto p = m_filesMap.find(name); 53 | if (p == m_filesMap.end()) 54 | return result; 55 | 56 | auto& content = p->second; 57 | 58 | if (!content.narrowContent && !content.wideContent) 59 | return result; 60 | 61 | if (!content.narrowContent) 62 | content.narrowContent = ConvertString(content.wideContent.value()); 63 | 64 | result.reset(new std::istringstream(content.narrowContent.value())); 65 | 66 | return result; 67 | } 68 | 69 | WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const 70 | { 71 | WCharFileStreamPtr result(nullptr, [](std::wistream* s) {delete static_cast(s);}); 72 | auto p = m_filesMap.find(name); 73 | if (p == m_filesMap.end()) 74 | return result; 75 | 76 | auto& content = p->second; 77 | 78 | if (!content.narrowContent && !content.wideContent) 79 | return result; 80 | 81 | if (!content.wideContent) 82 | content.wideContent = ConvertString(content.narrowContent.value()); 83 | 84 | result.reset(new std::wistringstream(content.wideContent.value())); 85 | 86 | return result; 87 | } 88 | nonstd::optional MemoryFileSystem::GetLastModificationDate(const std::string&) const 89 | { 90 | return nonstd::optional(); 91 | } 92 | 93 | bool MemoryFileSystem::IsEqual(const IComparable& other) const 94 | { 95 | auto* ptr = dynamic_cast(&other); 96 | if (!ptr) 97 | return false; 98 | return m_filesMap == ptr->m_filesMap; 99 | } 100 | 101 | RealFileSystem::RealFileSystem(std::string rootFolder) 102 | : m_rootFolder(std::move(rootFolder)) 103 | { 104 | 105 | } 106 | 107 | std::string RealFileSystem::GetFullFilePath(const std::string& name) const 108 | { 109 | boost::filesystem::path root(m_rootFolder); 110 | root /= name; 111 | return root.string(); 112 | } 113 | 114 | CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const 115 | { 116 | auto filePath = GetFullFilePath(name); 117 | 118 | CharFileStreamPtr result(new std::ifstream(filePath), [](std::istream* s) {delete static_cast(s);}); 119 | if (result->good()) 120 | return result; 121 | 122 | return CharFileStreamPtr(nullptr, [](std::istream*){}); 123 | } 124 | 125 | WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const 126 | { 127 | auto filePath = GetFullFilePath(name); 128 | 129 | WCharFileStreamPtr result(new std::wifstream(filePath), [](std::wistream* s) {delete static_cast(s);}); 130 | if (result->good()) 131 | return result; 132 | 133 | return WCharFileStreamPtr(nullptr, [](std::wistream*){;}); 134 | } 135 | nonstd::optional RealFileSystem::GetLastModificationDate(const std::string& name) const 136 | { 137 | boost::filesystem::path root(m_rootFolder); 138 | root /= name; 139 | 140 | auto modify_time = boost::filesystem::last_write_time(root); 141 | 142 | return std::chrono::system_clock::from_time_t(modify_time); 143 | } 144 | CharFileStreamPtr RealFileSystem::OpenByteStream(const std::string& name) const 145 | { 146 | auto filePath = GetFullFilePath(name); 147 | 148 | CharFileStreamPtr result(new std::ifstream(filePath, std::ios_base::binary), [](std::istream* s) {delete static_cast(s);}); 149 | if (result->good()) 150 | return result; 151 | 152 | return CharFileStreamPtr(nullptr, [](std::istream*){}); 153 | } 154 | 155 | bool RealFileSystem::IsEqual(const IComparable& other) const 156 | { 157 | auto* ptr = dynamic_cast(&other); 158 | if (!ptr) 159 | return false; 160 | return m_rootFolder == ptr->m_rootFolder; 161 | } 162 | 163 | } // namespace jinja2 164 | -------------------------------------------------------------------------------- /src/function_base.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_FUNCTION_BASE_H 2 | #define JINJA2CPP_SRC_FUNCTION_BASE_H 3 | 4 | #include "expression_evaluator.h" 5 | #include "internal_value.h" 6 | 7 | namespace jinja2 8 | { 9 | class FunctionBase 10 | { 11 | public: 12 | bool operator==(const FunctionBase& other) const 13 | { 14 | return m_args == other.m_args; 15 | } 16 | bool operator!=(const FunctionBase& other) const 17 | { 18 | return !(*this == other); 19 | } 20 | protected: 21 | bool ParseParams(const std::initializer_list& argsInfo, const CallParamsInfo& params); 22 | InternalValue GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal = InternalValue()); 23 | 24 | protected: 25 | ParsedArgumentsInfo m_args; 26 | }; 27 | 28 | //bool operator==(const FunctionBase& lhs, const FunctionBase& rhs) 29 | //{ 30 | // return 31 | //} 32 | 33 | inline bool FunctionBase::ParseParams(const std::initializer_list& argsInfo, const CallParamsInfo& params) 34 | { 35 | bool result = true; 36 | m_args = helpers::ParseCallParamsInfo(argsInfo, params, result); 37 | 38 | return result; 39 | } 40 | 41 | inline InternalValue FunctionBase::GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal) 42 | { 43 | auto argExpr = m_args[argName]; 44 | return argExpr ? argExpr->Evaluate(context) : std::move(defVal); 45 | } 46 | 47 | } // namespace jinja2 48 | 49 | #endif // JINJA2CPP_SRC_FUNCTION_BASE_H 50 | -------------------------------------------------------------------------------- /src/generic_adapters.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_GENERIC_ADAPTERS_H 2 | #define JINJA2CPP_SRC_GENERIC_ADAPTERS_H 3 | 4 | #include 5 | #include "internal_value.h" 6 | 7 | namespace jinja2 8 | { 9 | 10 | template 11 | class IndexedEnumeratorImpl : public Base 12 | { 13 | public: 14 | using ValueType = ValType; 15 | using ThisType = IndexedEnumeratorImpl; 16 | 17 | IndexedEnumeratorImpl(const List* list) 18 | : m_list(list) 19 | , m_maxItems(list->GetSize().value()) 20 | { } 21 | 22 | void Reset() override 23 | { 24 | m_curItem = m_invalidIndex; 25 | } 26 | 27 | bool MoveNext() override 28 | { 29 | if (m_curItem == m_invalidIndex) 30 | m_curItem = 0; 31 | else 32 | ++ m_curItem; 33 | 34 | return m_curItem < m_maxItems; 35 | } 36 | 37 | bool IsEqual(const IComparable& other) const override 38 | { 39 | auto* val = dynamic_cast(&other); 40 | if (!val) 41 | return false; 42 | if (m_list && val->m_list && !m_list->IsEqual(*val->m_list)) 43 | return false; 44 | if ((m_list && !val->m_list) || (!m_list && val->m_list)) 45 | return false; 46 | if (m_curItem != val->m_curItem) 47 | return false; 48 | if (m_maxItems != val->m_maxItems) 49 | return false; 50 | return true; 51 | } 52 | 53 | protected: 54 | constexpr static auto m_invalidIndex = std::numeric_limits::max(); 55 | const List* m_list{}; 56 | size_t m_curItem = m_invalidIndex; 57 | size_t m_maxItems{}; 58 | }; 59 | 60 | 61 | template 62 | class IndexedListItemAccessorImpl : public IListItemAccessor, public IIndexBasedAccessor 63 | { 64 | public: 65 | using ThisType = IndexedListItemAccessorImpl; 66 | class Enumerator : public IndexedEnumeratorImpl 67 | { 68 | public: 69 | using BaseClass = IndexedEnumeratorImpl; 70 | #if defined(_MSC_VER) 71 | using IndexedEnumeratorImpl::IndexedEnumeratorImpl; 72 | #else 73 | using BaseClass::BaseClass; 74 | #endif 75 | 76 | typename BaseClass::ValueType GetCurrent() const override 77 | { 78 | auto indexer = this->m_list->GetIndexer(); 79 | if (!indexer) 80 | return Value(); 81 | 82 | return indexer->GetItemByIndex(this->m_curItem); 83 | } 84 | ListEnumeratorPtr Clone() const override 85 | { 86 | auto result = MakeEnumerator(this->m_list); 87 | auto base = static_cast(&(*result)); 88 | base->m_curItem = this->m_curItem; 89 | return result; 90 | } 91 | 92 | ListEnumeratorPtr Move() override 93 | { 94 | auto result = MakeEnumerator(this->m_list); 95 | auto base = static_cast(&(*result)); 96 | base->m_curItem = this->m_curItem; 97 | this->m_list = nullptr; 98 | this->m_curItem = this->m_invalidIndex; 99 | this->m_maxItems = 0; 100 | return result; 101 | } 102 | }; 103 | 104 | Value GetItemByIndex(int64_t idx) const override 105 | { 106 | return IntValue2Value(std::move(static_cast(this)->GetItem(idx).value())); 107 | } 108 | 109 | nonstd::optional GetSize() const override 110 | { 111 | return static_cast(this)->GetItemsCountImpl(); 112 | } 113 | 114 | const IIndexBasedAccessor* GetIndexer() const override 115 | { 116 | return this; 117 | } 118 | 119 | ListEnumeratorPtr CreateEnumerator() const override; 120 | 121 | bool IsEqual(const IComparable& other) const override 122 | { 123 | auto* val = dynamic_cast(&other); 124 | if (!val) 125 | return false; 126 | auto enumerator = CreateEnumerator(); 127 | auto otherEnum = val->CreateEnumerator(); 128 | if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum)) 129 | return false; 130 | return true; 131 | } 132 | }; 133 | 134 | template 135 | class IndexedListAccessorImpl : public IListAccessor, public IndexedListItemAccessorImpl 136 | { 137 | public: 138 | using ThisType = IndexedListAccessorImpl; 139 | class Enumerator : public IndexedEnumeratorImpl 140 | { 141 | public: 142 | using BaseClass = IndexedEnumeratorImpl; 143 | #if defined(_MSC_VER) 144 | using IndexedEnumeratorImpl::IndexedEnumeratorImpl; 145 | #else 146 | using BaseClass::BaseClass; 147 | #endif 148 | 149 | typename BaseClass::ValueType GetCurrent() const override 150 | { 151 | const auto& result = this->m_list->GetItem(this->m_curItem); 152 | if (!result) 153 | return InternalValue(); 154 | 155 | return result.value(); 156 | } 157 | 158 | IListAccessorEnumerator* Clone() const override 159 | { 160 | auto result = new Enumerator(this->m_list); 161 | auto base = result; 162 | base->m_curItem = this->m_curItem; 163 | return result; 164 | } 165 | 166 | IListAccessorEnumerator* Transfer() override 167 | { 168 | auto result = new Enumerator(std::move(*this)); 169 | auto base = result; 170 | base->m_curItem = this->m_curItem; 171 | this->m_list = nullptr; 172 | this->m_curItem = this->m_invalidIndex; 173 | this->m_maxItems = 0; 174 | return result; 175 | } 176 | }; 177 | 178 | nonstd::optional GetSize() const override 179 | { 180 | return static_cast(this)->GetItemsCountImpl(); 181 | } 182 | ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override; 183 | }; 184 | 185 | template 186 | class MapItemAccessorImpl : public IMapItemAccessor 187 | { 188 | public: 189 | Value GetValueByName(const std::string& name) const 190 | { 191 | return IntValue2Value(static_cast(this)->GetItem(name)); 192 | } 193 | }; 194 | 195 | template 196 | class MapAccessorImpl : public IMapAccessor, public MapItemAccessorImpl 197 | { 198 | public: 199 | }; 200 | 201 | template 202 | inline ListAccessorEnumeratorPtr IndexedListAccessorImpl::CreateListAccessorEnumerator() const 203 | { 204 | return ListAccessorEnumeratorPtr(new Enumerator(this)); 205 | } 206 | 207 | template 208 | inline ListEnumeratorPtr IndexedListItemAccessorImpl::CreateEnumerator() const 209 | { 210 | return MakeEnumerator(this); 211 | } 212 | 213 | } // namespace jinja2 214 | 215 | #endif // JINJA2CPP_SRC_GENERIC_ADAPTERS_H 216 | -------------------------------------------------------------------------------- /src/generic_list.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace jinja2 { 5 | 6 | detail::GenericListIterator GenericList::begin() const 7 | { 8 | return m_accessor && m_accessor() ? detail::GenericListIterator(m_accessor()->CreateEnumerator()) : detail::GenericListIterator(); 9 | } 10 | 11 | detail::GenericListIterator GenericList::end() const 12 | { 13 | return detail::GenericListIterator(); 14 | } 15 | 16 | auto GenericList::cbegin() const {return begin();} 17 | auto GenericList::cend() const {return end();} 18 | 19 | bool GenericList::IsEqual(const GenericList& rhs) const 20 | { 21 | if (IsValid() && rhs.IsValid() && !GetAccessor()->IsEqual(*rhs.GetAccessor())) 22 | return false; 23 | if ((IsValid() && !rhs.IsValid()) || (!IsValid() && rhs.IsValid())) 24 | return false; 25 | return true; 26 | } 27 | 28 | bool operator==(const GenericList& lhs, const GenericList& rhs) 29 | { 30 | return lhs.IsEqual(rhs); 31 | } 32 | 33 | bool operator!=(const GenericList& lhs, const GenericList& rhs) 34 | { 35 | return !(lhs == rhs); 36 | } 37 | 38 | } // namespace jinja2 39 | -------------------------------------------------------------------------------- /src/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_HELPERS_H 2 | #define JINJA2CPP_SRC_HELPERS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace jinja2 12 | { 13 | struct MultiStringLiteral 14 | { 15 | const char* charValue; 16 | const wchar_t* wcharValue; 17 | 18 | constexpr MultiStringLiteral(const char* val, const wchar_t* wval) 19 | : charValue(val) 20 | , wcharValue(wval) 21 | { 22 | } 23 | 24 | template 25 | constexpr auto GetValue() const 26 | { 27 | #if __cplusplus < 202002L 28 | return GetValueStr(); 29 | #else 30 | constexpr auto memPtr = SelectMemberPtr::GetPtr(); 31 | return nonstd::basic_string_view(this->*memPtr); 32 | #endif 33 | } 34 | 35 | template 36 | constexpr auto GetValueStr() const 37 | { 38 | constexpr auto memPtr = SelectMemberPtr::GetPtr(); 39 | return std::basic_string(this->*memPtr); 40 | } 41 | 42 | template 43 | struct SelectMemberPtr; 44 | 45 | template 46 | struct SelectMemberPtr 47 | { 48 | static constexpr auto GetPtr() {return charMemPtr;} 49 | }; 50 | 51 | template 52 | struct SelectMemberPtr 53 | { 54 | static constexpr auto GetPtr() {return wcharMemPtr;} 55 | }; 56 | 57 | template 58 | friend std::basic_ostream& operator << (std::basic_ostream& os, const MultiStringLiteral& lit) 59 | { 60 | os << lit.GetValue(); 61 | return os; 62 | } 63 | }; 64 | 65 | #define UNIVERSAL_STR(Str) \ 66 | ::jinja2::MultiStringLiteral { Str, L##Str } 67 | 68 | //! CompileEscapes replaces escape characters by their meanings. 69 | /** 70 | * @param[in] s Characters sequence with zero or more escape characters. 71 | * @return Characters sequence copy where replaced all escape characters by 72 | * their meanings. 73 | */ 74 | template 75 | Sequence CompileEscapes(Sequence s) 76 | { 77 | auto itr1 = s.begin(); 78 | auto itr2 = s.begin(); 79 | const auto end = s.cend(); 80 | 81 | auto removalCount = 0; 82 | 83 | while (end != itr1) 84 | { 85 | if ('\\' == *itr1) 86 | { 87 | ++removalCount; 88 | 89 | if (end == ++itr1) 90 | break; 91 | if ('\\' != *itr1) 92 | { 93 | switch (*itr1) 94 | { 95 | case 'n': *itr1 = '\n'; break; 96 | case 'r': *itr1 = '\r'; break; 97 | case 't': *itr1 = '\t'; break; 98 | default: break; 99 | } 100 | 101 | continue; 102 | } 103 | } 104 | 105 | if (itr1 != itr2) 106 | *itr2 = *itr1; 107 | 108 | ++itr1; 109 | ++itr2; 110 | } 111 | 112 | s.resize(s.size() - removalCount); 113 | 114 | return s; 115 | } 116 | 117 | } // namespace jinja2 118 | 119 | #endif // JINJA2CPP_SRC_HELPERS_H 120 | -------------------------------------------------------------------------------- /src/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.h" 2 | 3 | #include 4 | 5 | namespace jinja2 6 | { 7 | 8 | bool Lexer::Preprocess() 9 | { 10 | bool result = true; 11 | while (1) 12 | { 13 | lexertk::token token = m_tokenizer(); 14 | if (token.is_error()) 15 | { 16 | result = false; 17 | break; 18 | } 19 | 20 | Token newToken; 21 | newToken.range.startOffset = token.position; 22 | newToken.range.endOffset = newToken.range.startOffset + token.length; 23 | 24 | if (token.type == lexertk::token::e_eof) 25 | { 26 | newToken.type = Token::Eof; 27 | m_tokens.push_back(std::move(newToken)); 28 | break; 29 | } 30 | 31 | switch (token.type) 32 | { 33 | case lexertk::token::e_number: 34 | result = ProcessNumber(token, newToken); 35 | break; 36 | case lexertk::token::e_symbol: 37 | result = ProcessSymbolOrKeyword(token, newToken); 38 | break; 39 | case lexertk::token::e_string: 40 | result = ProcessString(token, newToken); 41 | break; 42 | case lexertk::token::e_lte: 43 | newToken.type = Token::LessEqual; 44 | break; 45 | case lexertk::token::e_ne: 46 | newToken.type = Token::NotEqual; 47 | break; 48 | case lexertk::token::e_gte: 49 | newToken.type = Token::GreaterEqual; 50 | break; 51 | case lexertk::token::e_eq: 52 | newToken.type = Token::Equal; 53 | break; 54 | case lexertk::token::e_mulmul: 55 | newToken.type = Token::MulMul; 56 | break; 57 | case lexertk::token::e_divdiv: 58 | newToken.type = Token::DivDiv; 59 | break; 60 | default: 61 | newToken.type = static_cast(token.type); 62 | break; 63 | } 64 | 65 | if (result) 66 | m_tokens.push_back(std::move(newToken)); 67 | else 68 | break; 69 | } 70 | 71 | return result; 72 | } 73 | 74 | bool Lexer::ProcessNumber(const lexertk::token&, Token& newToken) 75 | { 76 | newToken.type = Token::FloatNum; 77 | newToken.value = m_helper->GetAsValue(newToken.range, newToken.type); 78 | return true; 79 | } 80 | 81 | bool Lexer::ProcessSymbolOrKeyword(const lexertk::token&, Token& newToken) 82 | { 83 | Keyword kwType = m_helper->GetKeyword(newToken.range); 84 | Token::Type tokType = Token::Unknown; 85 | 86 | switch (kwType) 87 | { 88 | case Keyword::None: 89 | tokType = Token::None; 90 | break; 91 | case Keyword::True: 92 | tokType = Token::True; 93 | break; 94 | case Keyword::False: 95 | tokType = Token::False; 96 | break; 97 | default: 98 | tokType = Token::Unknown; 99 | break; 100 | } 101 | 102 | if (tokType == Token::Unknown) 103 | { 104 | newToken.type = Token::Identifier; 105 | auto id = m_helper->GetAsString(newToken.range); 106 | newToken.value = InternalValue(id); 107 | } 108 | else 109 | { 110 | newToken.type = tokType; 111 | } 112 | return true; 113 | } 114 | 115 | bool Lexer::ProcessString(const lexertk::token&, Token& newToken) 116 | { 117 | newToken.type = Token::String; 118 | newToken.value = m_helper->GetAsValue(newToken.range, newToken.type); 119 | return true; 120 | } 121 | 122 | } // namespace jinja2 123 | -------------------------------------------------------------------------------- /src/out_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_OUT_STREAM_H 2 | #define JINJA2CPP_SRC_OUT_STREAM_H 3 | 4 | #include "internal_value.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace jinja2 11 | { 12 | class OutStream 13 | { 14 | public: 15 | struct StreamWriter 16 | { 17 | virtual ~StreamWriter() {} 18 | 19 | virtual void WriteBuffer(const void* ptr, size_t length) = 0; 20 | virtual void WriteValue(const InternalValue &val) = 0; 21 | }; 22 | 23 | OutStream(std::function writerGetter) 24 | : m_writerGetter(std::move(writerGetter)) 25 | {} 26 | 27 | void WriteBuffer(const void* ptr, size_t length) 28 | { 29 | m_writerGetter()->WriteBuffer(ptr, length); 30 | } 31 | 32 | void WriteValue(const InternalValue& val) 33 | { 34 | m_writerGetter()->WriteValue(val); 35 | } 36 | 37 | private: 38 | std::function m_writerGetter; 39 | }; 40 | 41 | } // namespace jinja2 42 | 43 | #endif // JINJA2CPP_SRC_OUT_STREAM_H 44 | -------------------------------------------------------------------------------- /src/render_context.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_RENDER_CONTEXT_H 2 | #define JINJA2CPP_SRC_RENDER_CONTEXT_H 3 | 4 | #include "internal_value.h" 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace jinja2 14 | { 15 | template 16 | class TemplateImpl; 17 | 18 | struct IRendererCallback : IComparable 19 | { 20 | virtual ~IRendererCallback() {} 21 | virtual TargetString GetAsTargetString(const InternalValue& val) = 0; 22 | virtual OutStream GetStreamOnString(TargetString& str) = 0; 23 | virtual nonstd::variant>, ErrorInfo>, 25 | nonstd::expected>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0; 26 | virtual nonstd::variant>, ErrorInfo>, 28 | nonstd::expected>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const = 0; 29 | virtual void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) = 0; 30 | }; 31 | 32 | class RenderContext 33 | { 34 | public: 35 | RenderContext(const InternalValueMap& extValues, const InternalValueMap& globalValues, IRendererCallback* rendererCallback) 36 | : m_rendererCallback(rendererCallback) 37 | { 38 | m_externalScope = &extValues; 39 | m_globalScope = &globalValues; 40 | EnterScope(); 41 | (*m_currentScope)["self"] = CreateMapAdapter(InternalValueMap()); 42 | } 43 | 44 | RenderContext(const RenderContext& other) 45 | : m_rendererCallback(other.m_rendererCallback) 46 | , m_externalScope(other.m_externalScope) 47 | , m_globalScope(other.m_globalScope) 48 | , m_boundScope(other.m_boundScope) 49 | , m_scopes(other.m_scopes) 50 | { 51 | m_currentScope = &m_scopes.back(); 52 | } 53 | 54 | InternalValueMap& EnterScope() 55 | { 56 | m_scopes.push_back(InternalValueMap()); 57 | m_currentScope = &m_scopes.back(); 58 | return *m_currentScope; 59 | } 60 | 61 | void ExitScope() 62 | { 63 | m_scopes.pop_back(); 64 | if (!m_scopes.empty()) 65 | m_currentScope = &m_scopes.back(); 66 | else 67 | m_currentScope = nullptr; 68 | } 69 | 70 | auto FindValue(const std::string& val, bool& found) const 71 | { 72 | auto finder = [&val, &found](auto& map) mutable 73 | { 74 | auto p = map.find(val); 75 | if (p != map.end()) 76 | found = true; 77 | 78 | return p; 79 | }; 80 | 81 | if (m_boundScope) 82 | { 83 | auto valP = finder(*m_boundScope); 84 | if (found) 85 | return valP; 86 | } 87 | 88 | for (auto p = m_scopes.rbegin(); p != m_scopes.rend(); ++ p) 89 | { 90 | auto valP = finder(*p); 91 | if (found) 92 | return valP; 93 | } 94 | 95 | auto valP = finder(*m_externalScope); 96 | if (found) 97 | return valP; 98 | 99 | return finder(*m_globalScope); 100 | } 101 | 102 | auto& GetCurrentScope() const 103 | { 104 | return *m_currentScope; 105 | } 106 | 107 | auto& GetCurrentScope() 108 | { 109 | return *m_currentScope; 110 | } 111 | auto& GetGlobalScope() 112 | { 113 | return m_scopes.front(); 114 | } 115 | auto GetRendererCallback() 116 | { 117 | return m_rendererCallback; 118 | } 119 | RenderContext Clone(bool includeCurrentContext) const 120 | { 121 | if (!includeCurrentContext) 122 | return RenderContext(m_emptyScope, *m_globalScope, m_rendererCallback); 123 | 124 | return RenderContext(*this); 125 | } 126 | 127 | void BindScope(InternalValueMap* scope) 128 | { 129 | m_boundScope = scope; 130 | } 131 | 132 | bool IsEqual(const RenderContext& other) const 133 | { 134 | if (!IsEqual(m_rendererCallback, other.m_rendererCallback)) 135 | return false; 136 | if (!IsEqual(this->m_currentScope, other.m_currentScope)) 137 | return false; 138 | if (!IsEqual(m_externalScope, other.m_externalScope)) 139 | return false; 140 | if (!IsEqual(m_globalScope, other.m_globalScope)) 141 | return false; 142 | if (!IsEqual(m_boundScope, other.m_boundScope)) 143 | return false; 144 | if (m_emptyScope != other.m_emptyScope) 145 | return false; 146 | if (m_scopes != other.m_scopes) 147 | return false; 148 | return true; 149 | } 150 | 151 | private: 152 | 153 | bool IsEqual(const IRendererCallback* lhs, const IRendererCallback* rhs) const 154 | { 155 | if (lhs && rhs) 156 | return lhs->IsEqual(*rhs); 157 | if ((!lhs && rhs) || (lhs && !rhs)) 158 | return false; 159 | return true; 160 | } 161 | 162 | bool IsEqual(const InternalValueMap* lhs, const InternalValueMap* rhs) const 163 | { 164 | if (lhs && rhs) 165 | return *lhs == *rhs; 166 | if ((!lhs && rhs) || (lhs && !rhs)) 167 | return false; 168 | return true; 169 | } 170 | 171 | private: 172 | IRendererCallback* m_rendererCallback{}; 173 | InternalValueMap* m_currentScope{}; 174 | const InternalValueMap* m_externalScope{}; 175 | const InternalValueMap* m_globalScope{}; 176 | const InternalValueMap* m_boundScope{}; 177 | InternalValueMap m_emptyScope; 178 | std::deque m_scopes; 179 | }; 180 | } // namespace jinja2 181 | 182 | #endif // JINJA2CPP_SRC_RENDER_CONTEXT_H 183 | -------------------------------------------------------------------------------- /src/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_RENDERER_H 2 | #define JINJA2CPP_SRC_RENDERER_H 3 | 4 | #include "out_stream.h" 5 | #include "lexertk.h" 6 | #include "expression_evaluator.h" 7 | #include "render_context.h" 8 | #include "ast_visitor.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace jinja2 19 | { 20 | class IRendererBase : public virtual IComparable 21 | { 22 | public: 23 | virtual ~IRendererBase() = default; 24 | virtual void Render(OutStream& os, RenderContext& values) = 0; 25 | }; 26 | 27 | class VisitableRendererBase : public IRendererBase, public VisitableStatement 28 | { 29 | }; 30 | 31 | using RendererPtr = std::shared_ptr; 32 | 33 | inline bool operator==(const RendererPtr& lhs, const RendererPtr& rhs) 34 | { 35 | if (lhs && rhs && !lhs->IsEqual(*rhs)) 36 | return false; 37 | if ((lhs && !rhs) || (!lhs && rhs)) 38 | return false; 39 | return true; 40 | } 41 | 42 | inline bool operator!=(const RendererPtr& lhs, const RendererPtr& rhs) 43 | { 44 | return !(lhs == rhs); 45 | } 46 | 47 | class ComposedRenderer : public VisitableRendererBase 48 | { 49 | public: 50 | VISITABLE_STATEMENT(); 51 | 52 | void AddRenderer(RendererPtr r) 53 | { 54 | m_renderers.push_back(std::move(r)); 55 | } 56 | void Render(OutStream& os, RenderContext& values) override 57 | { 58 | for (auto& r : m_renderers) 59 | r->Render(os, values); 60 | } 61 | 62 | bool IsEqual(const IComparable& other) const override 63 | { 64 | auto* val = dynamic_cast(&other); 65 | if (!val) 66 | return false; 67 | return m_renderers == val->m_renderers; 68 | } 69 | 70 | private: 71 | std::vector m_renderers; 72 | }; 73 | 74 | class RawTextRenderer : public VisitableRendererBase 75 | { 76 | public: 77 | VISITABLE_STATEMENT(); 78 | 79 | RawTextRenderer(const void* ptr, size_t len) 80 | : m_ptr(ptr) 81 | , m_length(len) 82 | { 83 | } 84 | 85 | void Render(OutStream& os, RenderContext&) override 86 | { 87 | os.WriteBuffer(m_ptr, m_length); 88 | } 89 | 90 | bool IsEqual(const IComparable& other) const override 91 | { 92 | auto* val = dynamic_cast(&other); 93 | if (!val) 94 | return false; 95 | if (m_ptr != val->m_ptr) 96 | return false; 97 | return m_length == val->m_length; 98 | } 99 | private: 100 | const void* m_ptr{}; 101 | size_t m_length{}; 102 | }; 103 | 104 | class ExpressionRenderer : public VisitableRendererBase 105 | { 106 | public: 107 | VISITABLE_STATEMENT(); 108 | 109 | explicit ExpressionRenderer(ExpressionEvaluatorPtr<> expr) 110 | : m_expression(std::move(expr)) 111 | { 112 | } 113 | 114 | void Render(OutStream& os, RenderContext& values) override 115 | { 116 | m_expression->Render(os, values); 117 | } 118 | 119 | bool IsEqual(const IComparable& other) const override 120 | { 121 | auto* val = dynamic_cast(&other); 122 | if (!val) 123 | return false; 124 | return m_expression == val->m_expression; 125 | } 126 | private: 127 | ExpressionEvaluatorPtr<> m_expression; 128 | }; 129 | } // namespace jinja2 130 | 131 | #endif // JINJA2CPP_SRC_RENDERER_H 132 | -------------------------------------------------------------------------------- /src/template.cpp: -------------------------------------------------------------------------------- 1 | #include "jinja2cpp/template.h" 2 | #include "template_impl.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace jinja2 10 | { 11 | bool operator==(const Template& lhs, const Template& rhs) 12 | { 13 | return lhs.IsEqual(rhs); 14 | } 15 | 16 | bool operator==(const TemplateW& lhs, const TemplateW& rhs) 17 | { 18 | return lhs.IsEqual(rhs); 19 | } 20 | 21 | template 22 | auto GetImpl(std::shared_ptr impl) 23 | { 24 | return static_cast*>(impl.get()); 25 | } 26 | 27 | Template::Template(TemplateEnv* env) 28 | : m_impl(new TemplateImpl(env)) 29 | { 30 | 31 | } 32 | 33 | Template::~Template() = default; 34 | 35 | Result Template::Load(const char* tpl, std::string tplName) 36 | { 37 | std::string t(tpl); 38 | auto result = GetImpl(m_impl)->Load(std::move(t), std::move(tplName)); 39 | return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); 40 | } 41 | 42 | Result Template::Load(const std::string& str, std::string tplName) 43 | { 44 | auto result = GetImpl(m_impl)->Load(str, std::move(tplName)); 45 | return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); 46 | } 47 | 48 | Result Template::Load(std::istream& stream, std::string tplName) 49 | { 50 | std::string t; 51 | 52 | while (stream.good() && !stream.eof()) 53 | { 54 | char buff[0x10000]; 55 | stream.read(buff, sizeof(buff)); 56 | auto read = stream.gcount(); 57 | if (read) 58 | t.append(buff, buff + read); 59 | } 60 | 61 | auto result = GetImpl(m_impl)->Load(std::move(t), std::move(tplName)); 62 | return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); 63 | } 64 | 65 | Result Template::LoadFromFile(const std::string& fileName) 66 | { 67 | std::ifstream file(fileName); 68 | 69 | if (!file.good()) 70 | return Result(); 71 | 72 | return Load(file, fileName); 73 | } 74 | 75 | Result Template::Render(std::ostream& os, const jinja2::ValuesMap& params) 76 | { 77 | std::string buffer; 78 | auto result = GetImpl(m_impl)->Render(buffer, params); 79 | 80 | if (!result) 81 | os.write(buffer.data(), buffer.size()); 82 | 83 | return !result ? Result() : nonstd::make_unexpected(std::move(result.get())); 84 | } 85 | 86 | Result Template::RenderAsString(const jinja2::ValuesMap& params) 87 | { 88 | std::string buffer; 89 | auto result = GetImpl(m_impl)->Render(buffer, params); 90 | return !result ? Result(std::move(buffer)) : Result(nonstd::make_unexpected(std::move(result.get())));; 91 | } 92 | 93 | Result Template::GetMetadata() 94 | { 95 | return GetImpl(m_impl)->GetMetadata(); 96 | } 97 | 98 | Result> Template::GetMetadataRaw() 99 | { 100 | return GetImpl(m_impl)->GetMetadataRaw(); 101 | } 102 | 103 | bool Template::IsEqual(const Template& other) const 104 | { 105 | return m_impl == other.m_impl; 106 | } 107 | 108 | TemplateW::TemplateW(TemplateEnv* env) 109 | : m_impl(new TemplateImpl(env)) 110 | { 111 | 112 | } 113 | 114 | TemplateW::~TemplateW() = default; 115 | 116 | ResultW TemplateW::Load(const wchar_t* tpl, std::string tplName) 117 | { 118 | std::wstring t(tpl); 119 | auto result = GetImpl(m_impl)->Load(t, std::move(tplName)); 120 | return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); 121 | } 122 | 123 | ResultW TemplateW::Load(const std::wstring& str, std::string tplName) 124 | { 125 | auto result = GetImpl(m_impl)->Load(str, std::move(tplName)); 126 | return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); 127 | } 128 | 129 | ResultW TemplateW::Load(std::wistream& stream, std::string tplName) 130 | { 131 | std::wstring t; 132 | 133 | while (stream.good() && !stream.eof()) 134 | { 135 | wchar_t buff[0x10000]; 136 | stream.read(buff, sizeof(buff)); 137 | auto read = stream.gcount(); 138 | if (read) 139 | t.append(buff, buff + read); 140 | } 141 | 142 | auto result = GetImpl(m_impl)->Load(t, std::move(tplName)); 143 | return !result ? ResultW() : nonstd::make_unexpected(std::move(result.get())); 144 | } 145 | 146 | ResultW TemplateW::LoadFromFile(const std::string& fileName) 147 | { 148 | std::wifstream file(fileName); 149 | 150 | if (!file.good()) 151 | return ResultW(); 152 | 153 | return Load(file, fileName); 154 | } 155 | 156 | ResultW TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params) 157 | { 158 | std::wstring buffer; 159 | auto result = GetImpl(m_impl)->Render(buffer, params); 160 | if (!result) 161 | os.write(buffer.data(), buffer.size()); 162 | return !result ? ResultW() : ResultW(nonstd::make_unexpected(std::move(result.get()))); 163 | } 164 | 165 | ResultW TemplateW::RenderAsString(const jinja2::ValuesMap& params) 166 | { 167 | std::wstring buffer; 168 | auto result = GetImpl(m_impl)->Render(buffer, params); 169 | 170 | return !result ? buffer : ResultW(nonstd::make_unexpected(std::move(result.get()))); 171 | } 172 | 173 | ResultW TemplateW::GetMetadata() 174 | { 175 | return GenericMap(); 176 | // GetImpl(m_impl)->GetMetadata(); 177 | } 178 | 179 | ResultW> TemplateW::GetMetadataRaw() 180 | { 181 | return MetadataInfo(); 182 | // GetImpl(m_impl)->GetMetadataRaw(); 183 | ; 184 | } 185 | bool TemplateW::IsEqual(const TemplateW& other) const 186 | { 187 | return m_impl == other.m_impl; 188 | } 189 | 190 | } // namespace jinja2 191 | -------------------------------------------------------------------------------- /src/template_env.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace jinja2 5 | { 6 | template 7 | struct TemplateFunctions; 8 | 9 | template<> 10 | struct TemplateFunctions 11 | { 12 | using ResultType = nonstd::expected; 13 | static Template CreateTemplate(TemplateEnv* env) { return Template(env); } 14 | static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenStream(fileName); } 15 | }; 16 | 17 | template<> 18 | struct TemplateFunctions 19 | { 20 | using ResultType = nonstd::expected; 21 | static TemplateW CreateTemplate(TemplateEnv* env) { return TemplateW(env); } 22 | static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenWStream(fileName); } 23 | }; 24 | 25 | template 26 | auto TemplateEnv::LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache) 27 | { 28 | using Functions = TemplateFunctions; 29 | using ResultType = typename Functions::ResultType; 30 | using ErrorType = typename ResultType::error_type; 31 | auto tpl = Functions::CreateTemplate(env); 32 | 33 | { 34 | std::shared_lock l(m_guard); 35 | auto p = cache.find(fileName); 36 | if (p != cache.end()) 37 | { 38 | if (m_settings.autoReload) 39 | { 40 | auto lastModified = p->second.handler->GetLastModificationDate(fileName); 41 | if (!lastModified || (p->second.lastModification && lastModified.value() <= p->second.lastModification.value())) 42 | return ResultType(p->second.tpl); 43 | } 44 | else 45 | return ResultType(p->second.tpl); 46 | } 47 | } 48 | 49 | for (auto& fh : filesystemHandlers) 50 | { 51 | if (!fh.prefix.empty() && fileName.find(fh.prefix) != 0) 52 | continue; 53 | 54 | auto stream = Functions::LoadFile(fileName, fh.handler.get()); 55 | if (stream) 56 | { 57 | auto res = tpl.Load(*stream, fileName); 58 | if (!res) 59 | return ResultType(res.get_unexpected()); 60 | 61 | if (m_settings.cacheSize != 0) 62 | { 63 | auto lastModified = fh.handler->GetLastModificationDate(fileName); 64 | std::unique_lock l(m_guard); 65 | auto& cacheEntry = cache[fileName]; 66 | cacheEntry.tpl = tpl; 67 | cacheEntry.handler = fh.handler; 68 | cacheEntry.lastModification = lastModified; 69 | } 70 | 71 | return ResultType(tpl); 72 | } 73 | } 74 | 75 | typename ErrorType::Data errorData; 76 | errorData.code = ErrorCode::FileNotFound; 77 | errorData.srcLoc.col = 1; 78 | errorData.srcLoc.line = 1; 79 | errorData.srcLoc.fileName = ""; 80 | errorData.extraParams.push_back(Value(fileName)); 81 | 82 | return ResultType(nonstd::make_unexpected(ErrorType(errorData))); 83 | } 84 | 85 | nonstd::expected TemplateEnv::LoadTemplate(std::string fileName) 86 | { 87 | return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers, m_templateCache); 88 | } 89 | 90 | nonstd::expected TemplateEnv::LoadTemplateW(std::string fileName) 91 | { 92 | return LoadTemplateImpl(this, std::move(fileName), m_filesystemHandlers, m_templateWCache); 93 | } 94 | 95 | } // namespace jinja2 96 | -------------------------------------------------------------------------------- /src/testers.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_TESTERS_H 2 | #define JINJA2CPP_SRC_TESTERS_H 3 | 4 | #include "expression_evaluator.h" 5 | #include "function_base.h" 6 | #include "jinja2cpp/value.h" 7 | #include "render_context.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace jinja2 13 | { 14 | using TesterPtr = std::shared_ptr; 15 | using TesterParams = CallParamsInfo; 16 | 17 | extern TesterPtr CreateTester(std::string testerName, CallParamsInfo params); 18 | 19 | namespace testers 20 | { 21 | 22 | class TesterBase : public FunctionBase, public IsExpression::ITester 23 | { 24 | }; 25 | 26 | class Comparator : public TesterBase 27 | { 28 | public: 29 | Comparator(TesterParams params, BinaryExpression::Operation op); 30 | 31 | bool Test(const InternalValue& baseVal, RenderContext& context) override; 32 | bool IsEqual(const IComparable& other) const override 33 | { 34 | auto* val = dynamic_cast(&other); 35 | if (!val) 36 | return false; 37 | return m_op == val->m_op; 38 | } 39 | private: 40 | BinaryExpression::Operation m_op; 41 | }; 42 | 43 | class StartsWith : public IsExpression::ITester 44 | { 45 | public: 46 | StartsWith(TesterParams); 47 | 48 | bool Test(const InternalValue& baseVal, RenderContext& context) override; 49 | 50 | bool IsEqual(const IComparable& other) const override 51 | { 52 | auto* val = dynamic_cast(&other); 53 | if (!val) 54 | return false; 55 | return m_stringEval == val->m_stringEval; 56 | } 57 | private: 58 | ExpressionEvaluatorPtr<> m_stringEval; 59 | }; 60 | 61 | class ValueTester : public TesterBase 62 | { 63 | public: 64 | enum Mode 65 | { 66 | IsDefinedMode, 67 | IsEvenMode, 68 | IsInMode, 69 | IsIterableMode, 70 | IsLowerMode, 71 | IsMappingMode, 72 | IsNumberMode, 73 | IsOddMode, 74 | IsSequenceMode, 75 | IsStringMode, 76 | IsUndefinedMode, 77 | IsUpperMode 78 | }; 79 | 80 | ValueTester(TesterParams params, Mode mode); 81 | 82 | bool Test(const InternalValue& baseVal, RenderContext& context) override; 83 | 84 | bool IsEqual(const IComparable& other) const override 85 | { 86 | auto* val = dynamic_cast(&other); 87 | if (!val) 88 | return false; 89 | return m_mode == val->m_mode; 90 | } 91 | private: 92 | Mode m_mode; 93 | }; 94 | 95 | class UserDefinedTester : public TesterBase 96 | { 97 | public: 98 | UserDefinedTester(std::string filterName, TesterParams params); 99 | 100 | bool Test(const InternalValue& baseVal, RenderContext& context) override; 101 | 102 | bool IsEqual(const IComparable& other) const override 103 | { 104 | auto* val = dynamic_cast(&other); 105 | if (!val) 106 | return false; 107 | return m_testerName == val->m_testerName && m_callParams == val->m_callParams; 108 | } 109 | private: 110 | std::string m_testerName; 111 | TesterParams m_callParams; 112 | }; 113 | } // namespace testers 114 | } // namespace jinja2 115 | 116 | #endif // JINJA2CPP_SRC_TESTERS_H 117 | -------------------------------------------------------------------------------- /src/value.cpp: -------------------------------------------------------------------------------- 1 | #if 0 2 | #include "jinja2cpp/value.h" 3 | #include 4 | 5 | namespace jinja2 6 | { 7 | template 8 | std::string toString(T val) 9 | { 10 | std::ostringstream os; 11 | os << val; 12 | return os.str(); 13 | } 14 | 15 | namespace 16 | { 17 | struct ValueRenderer : boost::static_visitor 18 | { 19 | std::string operator() (bool val) const 20 | { 21 | return val ? "True" : "False"; 22 | } 23 | std::string operator() (const EmptyValue&) const 24 | { 25 | return std::string(); 26 | } 27 | std::string operator() (const std::wstring&) const 28 | { 29 | return std::string(); 30 | } 31 | 32 | std::string operator() (const ValuesList& vals) const 33 | { 34 | std::string result = "{"; 35 | bool isFirst = true; 36 | for (auto& val : vals) 37 | { 38 | if (isFirst) 39 | isFirst = false; 40 | else 41 | result += ", "; 42 | 43 | result += boost::apply_visitor(ValueRenderer(), val.data()); 44 | } 45 | result += "}"; 46 | return result; 47 | } 48 | 49 | std::string operator() (const ValuesMap& vals) const 50 | { 51 | std::string result = "{"; 52 | bool isFirst = true; 53 | for (auto& val : vals) 54 | { 55 | if (isFirst) 56 | isFirst = false; 57 | else 58 | result += ", "; 59 | 60 | result += "{\"" + val.first + "\","; 61 | result += boost::apply_visitor(ValueRenderer(), val.second.data()); 62 | result += "}"; 63 | } 64 | result += "}"; 65 | return result; 66 | } 67 | 68 | std::string operator() (const GenericMap& /*val*/) const 69 | { 70 | return ""; 71 | } 72 | 73 | std::string operator() (const GenericList& /*val*/) const 74 | { 75 | return ""; 76 | } 77 | 78 | template 79 | std::string operator() (const T& val) const 80 | { 81 | return toString(val); 82 | } 83 | }; 84 | 85 | struct SubscriptionVisitor : public boost::static_visitor 86 | { 87 | InternalValue operator() (const ValuesMap& values, const std::string& field) const 88 | { 89 | auto p = values.find(field); 90 | if (p == values.end()) 91 | return InternalValue(); 92 | 93 | return p->second; 94 | } 95 | 96 | InternalValue operator() (const GenericMap& values, const std::string& field) const 97 | { 98 | if (!values.HasValue(field)) 99 | return InternalValue(); 100 | 101 | return values.GetValueByName(field); 102 | } 103 | 104 | InternalValue operator() (const GenericMap& values, const int64_t index) const 105 | { 106 | if (index < 0 || static_cast(index) >= values.GetSize()) 107 | return InternalValue(); 108 | 109 | return values.GetValueByIndex(index); 110 | } 111 | 112 | InternalValue operator() (const ValuesList& values, int64_t index) const 113 | { 114 | if (index < 0 || static_cast(index) >= values.size()) 115 | return InternalValue(); 116 | 117 | return values[static_cast(index)]; 118 | } 119 | 120 | InternalValue operator() (const GenericList& values, const int64_t index) const 121 | { 122 | if (index < 0 || static_cast(index) >= values.GetSize()) 123 | return InternalValue(); 124 | 125 | return values.GetValueByIndex(index); 126 | } 127 | 128 | template 129 | InternalValue operator() (T&& /*first*/, U&& /*second*/) const 130 | { 131 | return InternalValue(); 132 | } 133 | }; 134 | 135 | } // 136 | 137 | InternalValue InternalValue::subscript(const InternalValue& index) const 138 | { 139 | return boost::apply_visitor(SubscriptionVisitor(), m_data, index.m_data); 140 | } 141 | 142 | } // jinja2 143 | #endif 144 | -------------------------------------------------------------------------------- /src/value_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef JINJA2CPP_SRC_VALUE_HELPERS_H 2 | #define JINJA2CPP_SRC_VALUE_HELPERS_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace jinja2 9 | { 10 | #if 0 11 | class GenericListIterator 12 | : public boost::iterator_facade< 13 | GenericListIterator, 14 | const InternalValue, 15 | boost::random_access_traversal_tag> 16 | { 17 | public: 18 | GenericListIterator() 19 | : m_current(0) 20 | , m_list(nullptr) 21 | {} 22 | 23 | explicit GenericListIterator(GenericList& list) 24 | : m_current(0) 25 | , m_list(&list) 26 | {} 27 | 28 | private: 29 | friend class boost::iterator_core_access; 30 | 31 | void increment() 32 | { 33 | ++ m_current; 34 | m_valueIdx = m_current; 35 | m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast(m_current)); 36 | } 37 | 38 | int distance_to(const GenericListIterator& other) const 39 | { 40 | if (m_list == nullptr) 41 | return other.m_list == nullptr ? 0 : -other.distance_to(*this); 42 | 43 | if (other.m_list == nullptr) 44 | return m_list->GetSize() - m_current; 45 | 46 | return other.m_current - m_current; 47 | } 48 | 49 | void advance(int distance) 50 | { 51 | m_current += distance; 52 | if (distance != 0) 53 | { 54 | m_valueIdx = m_current; 55 | m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast(m_current)); 56 | 57 | } 58 | } 59 | 60 | bool equal(const GenericListIterator& other) const 61 | { 62 | if (m_list == nullptr) 63 | return other.m_list == nullptr ? true : other.equal(*this); 64 | 65 | if (other.m_list == nullptr) 66 | return m_current == m_list->GetSize(); 67 | 68 | return this->m_list == other.m_list && this->m_current == other.m_current; 69 | } 70 | 71 | const InternalValue& dereference() const 72 | { 73 | if (m_current != m_valueIdx) 74 | m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast(m_current)); 75 | return m_currentVal; 76 | } 77 | 78 | int64_t m_current = 0; 79 | mutable int64_t m_valueIdx = -1; 80 | mutable InternalValue m_currentVal; 81 | GenericList* m_list; 82 | }; 83 | 84 | class ConstGenericListIterator 85 | : public boost::iterator_facade< 86 | GenericListIterator, 87 | const InternalValue, 88 | boost::random_access_traversal_tag> 89 | { 90 | public: 91 | ConstGenericListIterator() 92 | : m_current(0) 93 | , m_list(nullptr) 94 | {} 95 | 96 | explicit ConstGenericListIterator(const GenericList& list) 97 | : m_current(0) 98 | , m_list(&list) 99 | {} 100 | 101 | private: 102 | friend class boost::iterator_core_access; 103 | 104 | void increment() 105 | { 106 | ++ m_current; 107 | } 108 | 109 | int distance_to(const ConstGenericListIterator& other) const 110 | { 111 | if (m_list == nullptr) 112 | return other.m_list == nullptr ? 0 : -other.distance_to(*this); 113 | 114 | if (other.m_list == nullptr) 115 | return m_list->GetSize() - m_current; 116 | 117 | return other.m_current - m_current; 118 | } 119 | 120 | void advance(int distance) 121 | { 122 | m_current += distance; 123 | } 124 | 125 | bool equal(const ConstGenericListIterator& other) const 126 | { 127 | if (m_list == nullptr) 128 | return other.m_list == nullptr ? true : other.equal(*this); 129 | 130 | if (other.m_list == nullptr) 131 | return m_current == m_list->GetSize(); 132 | 133 | return this->m_list == other.m_list && this->m_current == other.m_current; 134 | } 135 | 136 | const InternalValue& dereference() const 137 | { 138 | return m_list->GetValueByIndex(static_cast(m_current)); 139 | } 140 | 141 | size_t m_current; 142 | const GenericList* m_list; 143 | }; 144 | 145 | inline auto begin(GenericList& list) 146 | { 147 | return GenericListIterator(list); 148 | } 149 | 150 | inline auto end(GenericList& list) 151 | { 152 | return GenericListIterator(); 153 | } 154 | 155 | inline auto begin(const GenericList& list) 156 | { 157 | return ConstGenericListIterator(list); 158 | } 159 | 160 | inline auto end(const GenericList& list) 161 | { 162 | return ConstGenericListIterator(); 163 | } 164 | #endif 165 | } // namespace jinja2 166 | 167 | #endif // JINJA2CPP_SRC_VALUE_HELPERS_H 168 | -------------------------------------------------------------------------------- /test/binding/boost_json_binding_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "../test_tools.h" 8 | #include 9 | 10 | using BoostJsonTest = BasicTemplateRenderer; 11 | 12 | MULTISTR_TEST(BoostJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") 13 | { 14 | boost::json::value values = { 15 | {"message", "Hello World from Parser!"} 16 | }; 17 | 18 | params["json"] = jinja2::Reflect(std::move(values)); 19 | } 20 | 21 | MULTISTR_TEST(BoostJsonTest, BasicTypesReflection, R"( 22 | {{ json.bool | pprint }} 23 | {{ json.small_int | pprint }} 24 | {{ json.big_int | pprint }} 25 | {{ json.double | pprint }} 26 | {{ json.string | pprint }} 27 | )", 28 | R"( 29 | true 30 | 100500 31 | 100500100500100 32 | 100.5 33 | 'Hello World!' 34 | )") 35 | { 36 | boost::json::value values = { 37 | {"bool", true}, 38 | {"small_int", 100500}, 39 | {"big_int", 100500100500100LL}, 40 | {"double", 100.5}, 41 | {"string", "Hello World!"}, 42 | }; 43 | 44 | params["json"] = jinja2::Reflect(std::move(values)); 45 | } 46 | 47 | MULTISTR_TEST(BoostJsonTest, BasicValuesReflection, R"( 48 | {{ bool_val | pprint }} 49 | {{ small_int_val | pprint }} 50 | {{ big_int_val | pprint }} 51 | {{ double_val | pprint }} 52 | {{ string_val | pprint }} 53 | {{ array_val | pprint }} 54 | {{ object_val | pprint }} 55 | {{ empty_val | pprint }} 56 | )", 57 | R"( 58 | true 59 | 100500 60 | 100500100500100 61 | 100.5 62 | 'Hello World!' 63 | [1, 2, 3, 4] 64 | {'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} 65 | none 66 | )") 67 | { 68 | boost::json::value values = { 69 | {"bool", true}, 70 | {"small_int", 100500}, 71 | {"big_int", 100500100500100LL}, 72 | {"double", 100.5}, 73 | {"string", "Hello World!"}, 74 | {"array", {1, 2, 3, 4}}, 75 | {"object", { 76 | {"message", "Hello World from Parser!"}, 77 | {"message2", "Hello World from Parser-123!"} 78 | }}}; 79 | 80 | auto boolVal = values.at("bool"); 81 | auto smallIntVal = values.at("small_int"); 82 | auto bigIntVal = values.at("big_int"); 83 | auto doubleVal = values.at("double"); 84 | auto stringVal = values.at("string"); 85 | auto arrayVal = values.at("array"); 86 | auto objectVal = values.at("object"); 87 | boost::json::value emptyVal; 88 | 89 | params["bool_val"] = jinja2::Reflect(std::move(boolVal)); 90 | params["small_int_val"] = jinja2::Reflect(std::move(smallIntVal)); 91 | params["big_int_val"] = jinja2::Reflect(std::move(bigIntVal)); 92 | params["double_val"] = jinja2::Reflect(std::move(doubleVal)); 93 | params["string_val"] = jinja2::Reflect(std::move(stringVal)); 94 | params["array_val"] = jinja2::Reflect(std::move(arrayVal)); 95 | params["object_val"] = jinja2::Reflect(std::move(objectVal)); 96 | params["empty_val"] = jinja2::Reflect(std::move(emptyVal)); 97 | } 98 | 99 | MULTISTR_TEST(BoostJsonTest, SubobjectReflection, 100 | R"( 101 | {{ json.object.message }} 102 | {{ json.object.message3 }} 103 | {{ json.object | list | join(', ') }} 104 | )", 105 | R"( 106 | Hello World from Parser! 107 | 108 | message, message2 109 | )") 110 | { 111 | boost::json::value values = { 112 | {"object", { 113 | {"message", "Hello World from Parser!"}, 114 | {"message2", "Hello World from Parser-123!"} 115 | }} 116 | }; 117 | 118 | params["json"] = jinja2::Reflect(std::move(values)); 119 | } 120 | 121 | MULTISTR_TEST(BoostJsonTest, ArrayReflection, 122 | R"( 123 | {{ json.array | sort | pprint }} 124 | {{ json.array | length }} 125 | {{ json.array | first }} 126 | {{ json.array | last }} 127 | {{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} 128 | )", 129 | R"( 130 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 131 | 9 132 | 9 133 | 1 134 | 1-3-5 135 | )") 136 | { 137 | boost::json::value values = { 138 | {"array", {9, 8, 7, 6, 5, 4, 3, 2, 1}} 139 | }; 140 | 141 | params["json"] = jinja2::Reflect(std::move(values)); 142 | } 143 | 144 | MULTISTR_TEST(BoostJsonTest, ParsedTypesReflection, R"( 145 | {{ json.bool | pprint }} 146 | {{ json.small_int | pprint }} 147 | {{ json.big_int | pprint }} 148 | {{ json.double | pprint }} 149 | {{ json.string | pprint }} 150 | {{ json.object.message | pprint }} 151 | {{ json.array | sort | pprint }} 152 | )", 153 | R"( 154 | true 155 | 100500 156 | 100500100500100 157 | 100.5 158 | 'Hello World!' 159 | 'Hello World from Parser!' 160 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 161 | )") 162 | { 163 | boost::json::value values = boost::json::parse(R"( 164 | { 165 | "big_int": 100500100500100, 166 | "bool": true, 167 | "double": 100.5, 168 | "small_int": 100500, 169 | "string": "Hello World!", 170 | "object": {"message": "Hello World from Parser!"}, 171 | "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] 172 | } 173 | )");; 174 | 175 | params["json"] = jinja2::Reflect(std::move(values)); 176 | } 177 | 178 | -------------------------------------------------------------------------------- /test/binding/nlohmann_json_binding_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../test_tools.h" 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | using NlohmannJsonTest = BasicTemplateRenderer; 12 | 13 | MULTISTR_TEST(NlohmannJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") 14 | { 15 | nlohmann::json values = { 16 | {"message", "Hello World from Parser!"} 17 | }; 18 | 19 | params["json"] = jinja2::Reflect(std::move(values)); 20 | } 21 | 22 | MULTISTR_TEST(NlohmannJsonTest, BasicTypesReflection, R"( 23 | {{ json.bool | pprint }} 24 | {{ json.small_int | pprint }} 25 | {{ json.big_int | pprint }} 26 | {{ json.double | pprint }} 27 | {{ json.string | pprint }} 28 | )", 29 | R"( 30 | true 31 | 100500 32 | 100500100500100 33 | 100.5 34 | 'Hello World!' 35 | )") 36 | { 37 | nlohmann::json values = { 38 | {"bool", true}, 39 | {"small_int", 100500}, 40 | {"big_int", 100500100500100LL}, 41 | {"double", 100.5}, 42 | {"string", "Hello World!"}, 43 | }; 44 | 45 | params["json"] = jinja2::Reflect(std::move(values)); 46 | } 47 | 48 | MULTISTR_TEST(NlohmannJsonTest, BasicValuesReflection, R"( 49 | {{ bool_val | pprint }} 50 | {{ small_int_val | pprint }} 51 | {{ big_int_val | pprint }} 52 | {{ double_val | pprint }} 53 | {{ string_val | pprint }} 54 | {{ array_val | pprint }} 55 | {{ object_val | pprint }} 56 | {{ empty_val | pprint }} 57 | )", 58 | R"( 59 | true 60 | 100500 61 | 100500100500100 62 | 100.5 63 | 'Hello World!' 64 | [1, 2, 3, 4] 65 | {'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} 66 | none 67 | )") 68 | { 69 | nlohmann::json values = { 70 | {"bool", true}, 71 | {"small_int", 100500}, 72 | {"big_int", 100500100500100LL}, 73 | {"double", 100.5}, 74 | {"string", "Hello World!"}, 75 | {"array", {1, 2, 3, 4}}, 76 | {"object", { 77 | {"message", "Hello World from Parser!"}, 78 | {"message2", "Hello World from Parser-123!"} 79 | }}}; 80 | 81 | auto boolVal = values["bool"]; 82 | auto smallIntVal = values["small_int"]; 83 | auto bigIntVal = values["big_int"]; 84 | auto doubleVal = values["double"]; 85 | auto stringVal = values["string"]; 86 | auto arrayVal = values["array"]; 87 | auto objectVal = values["object"]; 88 | nlohmann::json emptyVal; 89 | 90 | params["bool_val"] = jinja2::Reflect(std::move(boolVal)); 91 | params["small_int_val"] = jinja2::Reflect(std::move(smallIntVal)); 92 | params["big_int_val"] = jinja2::Reflect(std::move(bigIntVal)); 93 | params["double_val"] = jinja2::Reflect(std::move(doubleVal)); 94 | params["string_val"] = jinja2::Reflect(std::move(stringVal)); 95 | params["array_val"] = jinja2::Reflect(std::move(arrayVal)); 96 | params["object_val"] = jinja2::Reflect(std::move(objectVal)); 97 | params["empty_val"] = jinja2::Reflect(std::move(emptyVal)); 98 | } 99 | 100 | MULTISTR_TEST(NlohmannJsonTest, SubobjectReflection, 101 | R"( 102 | {{ json.object.message }} 103 | {{ json.object.message3 }} 104 | {{ json.object | list | join(', ') }} 105 | )", 106 | R"( 107 | Hello World from Parser! 108 | 109 | message, message2 110 | )") 111 | { 112 | nlohmann::json values = { 113 | {"object", { 114 | {"message", "Hello World from Parser!"}, 115 | {"message2", "Hello World from Parser-123!"} 116 | }} 117 | }; 118 | 119 | params["json"] = jinja2::Reflect(std::move(values)); 120 | } 121 | 122 | MULTISTR_TEST(NlohmannJsonTest, ArrayReflection, 123 | R"( 124 | {{ json.array | sort | pprint }} 125 | {{ json.array | length }} 126 | {{ json.array | first }} 127 | {{ json.array | last }} 128 | {{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} 129 | )", 130 | R"( 131 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 132 | 9 133 | 9 134 | 1 135 | 1-3-5 136 | )") 137 | { 138 | nlohmann::json values = { 139 | {"array", {9, 8, 7, 6, 5, 4, 3, 2, 1}} 140 | }; 141 | 142 | params["json"] = jinja2::Reflect(std::move(values)); 143 | } 144 | 145 | MULTISTR_TEST(NlohmannJsonTest, ParsedTypesReflection, R"( 146 | {{ json.bool | pprint }} 147 | {{ json.small_int | pprint }} 148 | {{ json.big_int | pprint }} 149 | {{ json.double | pprint }} 150 | {{ json.string | pprint }} 151 | {{ json.object.message | pprint }} 152 | {{ json.array | sort | pprint }} 153 | )", 154 | R"( 155 | true 156 | 100500 157 | 100500100500100 158 | 100.5 159 | 'Hello World!' 160 | 'Hello World from Parser!' 161 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 162 | )") 163 | { 164 | nlohmann::json values = nlohmann::json::parse(R"( 165 | { 166 | "big_int": 100500100500100, 167 | "bool": true, 168 | "double": 100.5, 169 | "small_int": 100500, 170 | "string": "Hello World!", 171 | "object": {"message": "Hello World from Parser!"}, 172 | "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] 173 | } 174 | )");; 175 | 176 | params["json"] = jinja2::Reflect(std::move(values)); 177 | } 178 | 179 | -------------------------------------------------------------------------------- /test/binding/rapid_json_binding_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../test_tools.h" 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | class RapidJsonTest : public BasicTemplateRenderer 12 | { 13 | public: 14 | const auto& GetJson() const {return m_json;} 15 | const auto& GetEmptyVal() const {return m_emptyVal;} 16 | protected: 17 | void SetUp() override 18 | { 19 | const char *json = R"( 20 | { 21 | "message": "Hello World from Parser!", 22 | "big_int": 100500100500100, 23 | "bool_true": true, 24 | "bool_false": false, 25 | "double": 100.5, 26 | "small_int": 100500, 27 | "string": "Hello World!", 28 | "object": {"message": "Hello World from Parser!", "message2": "Hello World from Parser-123!"}, 29 | "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] 30 | } 31 | )"; 32 | m_json.Parse(json); 33 | } 34 | protected: 35 | rapidjson::Document m_json; 36 | rapidjson::Value m_emptyVal; 37 | }; 38 | 39 | MULTISTR_TEST(RapidJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") 40 | { 41 | params["json"] = jinja2::Reflect(test.GetJson()); 42 | } 43 | 44 | MULTISTR_TEST(RapidJsonTest, BasicTypesReflection, R"( 45 | {{ json.bool_true | pprint }} 46 | {{ json.bool_false | pprint }} 47 | {{ json.small_int | pprint }} 48 | {{ json.big_int | pprint }} 49 | {{ json.double | pprint }} 50 | {{ json.string | pprint }} 51 | )", 52 | R"( 53 | true 54 | false 55 | 100500 56 | 100500100500100 57 | 100.5 58 | 'Hello World!' 59 | )") 60 | { 61 | params["json"] = jinja2::Reflect(test.GetJson()); 62 | } 63 | 64 | MULTISTR_TEST(RapidJsonTest, BasicValuesReflection, R"( 65 | {{ bool_val | pprint }} 66 | {{ small_int_val | pprint }} 67 | {{ big_int_val | pprint }} 68 | {{ double_val | pprint }} 69 | {{ string_val | pprint }} 70 | {{ array_val | pprint }} 71 | {{ object_val | pprint }} 72 | {{ empty_val | pprint }} 73 | )", 74 | R"( 75 | true 76 | 100500 77 | 100500100500100 78 | 100.5 79 | 'Hello World!' 80 | [9, 8, 7, 6, 5, 4, 3, 2, 1] 81 | {'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} 82 | none 83 | )") 84 | { 85 | auto& values = test.GetJson(); 86 | 87 | auto& boolVal = values["bool_true"]; 88 | auto& smallIntVal = values["small_int"]; 89 | auto& bigIntVal = values["big_int"]; 90 | auto& doubleVal = values["double"]; 91 | auto& stringVal = values["string"]; 92 | auto& arrayVal = values["array"]; 93 | auto& objectVal = values["object"]; 94 | auto& emptyVal = test.GetEmptyVal(); 95 | 96 | params["bool_val"] = jinja2::Reflect(boolVal); 97 | params["small_int_val"] = jinja2::Reflect(smallIntVal); 98 | params["big_int_val"] = jinja2::Reflect(bigIntVal); 99 | params["double_val"] = jinja2::Reflect(doubleVal); 100 | params["string_val"] = jinja2::Reflect(stringVal); 101 | params["array_val"] = jinja2::Reflect(arrayVal); 102 | params["object_val"] = jinja2::Reflect(objectVal); 103 | params["empty_val"] = jinja2::Reflect(emptyVal); 104 | } 105 | 106 | MULTISTR_TEST(RapidJsonTest, SubobjectReflection, 107 | R"( 108 | {{ json.object.message }} 109 | {{ json.object.message3 }} 110 | {{ json.object | list | join(', ') }} 111 | )", 112 | R"( 113 | Hello World from Parser! 114 | 115 | message, message2 116 | )") 117 | { 118 | params["json"] = jinja2::Reflect(test.GetJson()); 119 | } 120 | 121 | MULTISTR_TEST(RapidJsonTest, ArrayReflection, 122 | R"( 123 | {{ json.array | sort | pprint }} 124 | {{ json.array | length }} 125 | {{ json.array | first }} 126 | {{ json.array | last }} 127 | {{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} 128 | )", 129 | R"( 130 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 131 | 9 132 | 9 133 | 1 134 | 1-3-5 135 | )") 136 | { 137 | params["json"] = jinja2::Reflect(test.GetJson()); 138 | } 139 | -------------------------------------------------------------------------------- /test/binding/rapid_json_serializer_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef JINJA2CPP_SHARED_LIB 4 | 5 | #include "../../src/binding/rapid_json_serializer.h" 6 | #include 7 | 8 | namespace 9 | { 10 | template 11 | jinja2::InternalValue MakeInternalValue(T&& v) 12 | { 13 | return jinja2::InternalValue(std::forward(v)); 14 | } 15 | } // namespace 16 | TEST(RapidJsonSerializerTest, SerializeTrivialTypes) 17 | { 18 | const jinja2::rapidjson_serializer::DocumentWrapper document; 19 | 20 | const auto stringValue = document.CreateValue(MakeInternalValue("string")); 21 | const auto intValue = document.CreateValue(MakeInternalValue(123)); 22 | const auto doubleValue = document.CreateValue(MakeInternalValue(12.34)); 23 | 24 | EXPECT_EQ("\"string\"", stringValue.AsString()); 25 | EXPECT_EQ("123", intValue.AsString()); 26 | EXPECT_EQ("12.34", doubleValue.AsString()); 27 | } 28 | 29 | TEST(RapidJsonSerializerTest, SerializeComplexTypes) 30 | { 31 | jinja2::rapidjson_serializer::DocumentWrapper document; 32 | { 33 | jinja2::InternalValueMap params = { { "string", MakeInternalValue("hello") } }; 34 | const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); 35 | EXPECT_EQ("{\"string\":\"hello\"}", jsonValue.AsString()); 36 | } 37 | 38 | { 39 | jinja2::InternalValueMap params = { { "int", MakeInternalValue(123) } }; 40 | const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); 41 | EXPECT_EQ("{\"int\":123}", jsonValue.AsString()); 42 | } 43 | 44 | { 45 | jinja2::InternalValueList array{ MakeInternalValue(1), MakeInternalValue(2), MakeInternalValue(3) }; 46 | jinja2::InternalValueMap map{ { "array", jinja2::ListAdapter::CreateAdapter(std::move(array)) } }; 47 | jinja2::InternalValueMap params = { { "map", CreateMapAdapter(std::move(map)) } }; 48 | const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); 49 | EXPECT_EQ("{\"map\":{\"array\":[1,2,3]}}", jsonValue.AsString()); 50 | } 51 | } 52 | 53 | TEST(RapidJsonSerializerTest, SerializeComplexTypesWithIndention) 54 | { 55 | const jinja2::rapidjson_serializer::DocumentWrapper document; 56 | 57 | jinja2::InternalValueList array{ MakeInternalValue(1), MakeInternalValue(2), MakeInternalValue(3) }; 58 | jinja2::InternalValueMap map{ { "array", jinja2::ListAdapter::CreateAdapter(std::move(array)) } }; 59 | jinja2::InternalValueMap params = { { "map", CreateMapAdapter(std::move(map)) } }; 60 | const auto jsonValue = document.CreateValue(CreateMapAdapter(std::move(params))); 61 | 62 | auto indentedDocument = 63 | R"({ 64 | "map": { 65 | "array": [1, 2, 3] 66 | } 67 | })"; 68 | 69 | EXPECT_EQ(indentedDocument, jsonValue.AsString(4)); 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /test/filesystem_handler_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | class FilesystemHandlerTest : public testing::Test 10 | { 11 | public: 12 | template 13 | std::basic_string ReadFile(jinja2::FileStreamPtr& stream) 14 | { 15 | std::basic_string result; 16 | constexpr size_t buffSize = 0x10000; 17 | CharT buff[buffSize]; 18 | 19 | if (!stream) 20 | return result; 21 | 22 | while (stream->good() && !stream->eof()) 23 | { 24 | stream->read(buff, buffSize); 25 | auto readSize = stream->gcount(); 26 | result.append(buff, buff + readSize); 27 | if (readSize < buffSize) 28 | break; 29 | } 30 | 31 | return result; 32 | } 33 | }; 34 | 35 | TEST_F(FilesystemHandlerTest, MemoryFS_Narrow2NarrowReading) 36 | { 37 | const std::string test1Content = R"( 38 | Line1 39 | Line2 40 | Line3 41 | )"; 42 | const std::string test2Content = R"( 43 | Line6 44 | Line7 45 | Line8 46 | )"; 47 | jinja2::MemoryFileSystem fs; 48 | fs.AddFile("test1.j2tpl", test1Content); 49 | fs.AddFile("test2.j2tpl", test2Content); 50 | 51 | auto testStream = fs.OpenStream("test.j2tpl"); 52 | EXPECT_FALSE((bool)testStream); 53 | auto test1Stream = fs.OpenStream("test1.j2tpl"); 54 | EXPECT_TRUE((bool)test1Stream); 55 | EXPECT_EQ(test1Content, ReadFile(test1Stream)); 56 | auto test2Stream = fs.OpenStream("test2.j2tpl"); 57 | EXPECT_TRUE((bool)test2Stream); 58 | EXPECT_EQ(test2Content, ReadFile(test2Stream)); 59 | } 60 | 61 | TEST_F(FilesystemHandlerTest, MemoryFS_Wide2WideReading) 62 | { 63 | const std::wstring test1Content = LR"( 64 | Line1 65 | Line2 66 | Line3 67 | )"; 68 | const std::wstring test2Content = LR"( 69 | Line6 70 | Line7 71 | Line8 72 | )"; 73 | jinja2::MemoryFileSystem fs; 74 | fs.AddFile("test1.j2tpl", test1Content); 75 | fs.AddFile("test2.j2tpl", test2Content); 76 | 77 | auto testStream = fs.OpenWStream("test.j2tpl"); 78 | EXPECT_FALSE((bool)testStream); 79 | auto test1Stream = fs.OpenWStream("test1.j2tpl"); 80 | EXPECT_TRUE((bool)test1Stream); 81 | EXPECT_EQ(test1Content, ReadFile(test1Stream)); 82 | auto test2Stream = fs.OpenWStream("test2.j2tpl"); 83 | EXPECT_TRUE((bool)test2Stream); 84 | EXPECT_EQ(test2Content, ReadFile(test2Stream)); 85 | } 86 | 87 | TEST_F(FilesystemHandlerTest, RealFS_NarrowReading) 88 | { 89 | const std::string test1Content = 90 | R"(Hello World! 91 | )"; 92 | jinja2::RealFileSystem fs; 93 | auto testStream = fs.OpenStream("===incorrect====.j2tpl"); 94 | EXPECT_FALSE((bool)testStream); 95 | auto test1Stream = fs.OpenStream("test_data/simple_template1.j2tpl"); 96 | EXPECT_TRUE((bool)test1Stream); 97 | EXPECT_EQ(test1Content, ReadFile(test1Stream)); 98 | } 99 | 100 | TEST_F(FilesystemHandlerTest, RealFS_RootHandling) 101 | { 102 | const std::string test1Content = 103 | R"(Hello World! 104 | )"; 105 | jinja2::RealFileSystem fs; 106 | 107 | auto test1Stream = fs.OpenStream("test_data/simple_template1.j2tpl"); 108 | EXPECT_TRUE((bool)test1Stream); 109 | EXPECT_EQ(test1Content, ReadFile(test1Stream)); 110 | fs.SetRootFolder("./test_data"); 111 | auto test2Stream = fs.OpenStream("simple_template1.j2tpl"); 112 | EXPECT_TRUE((bool)test2Stream); 113 | EXPECT_EQ(test1Content, ReadFile(test2Stream)); 114 | fs.SetRootFolder("./test_data/"); 115 | auto test3Stream = fs.OpenStream("simple_template1.j2tpl"); 116 | EXPECT_TRUE((bool)test3Stream); 117 | EXPECT_EQ(test1Content, ReadFile(test3Stream)); 118 | } 119 | 120 | TEST_F(FilesystemHandlerTest, RealFS_WideReading) 121 | { 122 | const std::wstring test1Content = 123 | LR"(Hello World! 124 | )"; 125 | jinja2::RealFileSystem fs; 126 | auto testStream = fs.OpenWStream("===incorrect====.j2tpl"); 127 | EXPECT_FALSE((bool)testStream); 128 | auto test1Stream = fs.OpenWStream("test_data/simple_template1.j2tpl"); 129 | EXPECT_TRUE((bool)test1Stream); 130 | EXPECT_EQ(test1Content, ReadFile(test1Stream)); 131 | } 132 | 133 | TEST_F(FilesystemHandlerTest, TestDefaultCaching) 134 | { 135 | const std::string test1Content = R"( 136 | Line1 137 | Line2 138 | Line3 139 | )"; 140 | const std::string test2Content = R"( 141 | Line6 142 | Line7 143 | Line8 144 | )"; 145 | jinja2::MemoryFileSystem fs; 146 | fs.AddFile("test1.j2tpl", test1Content); 147 | 148 | jinja2::TemplateEnv env; 149 | 150 | env.AddFilesystemHandler("", fs); 151 | auto tpl1 = env.LoadTemplate("test1.j2tpl").value(); 152 | EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); 153 | 154 | fs.AddFile("test1.j2tpl", test2Content); 155 | auto tpl2 = env.LoadTemplate("test1.j2tpl").value(); 156 | EXPECT_EQ(test1Content, tpl2.RenderAsString({}).value()); 157 | } 158 | 159 | TEST_F(FilesystemHandlerTest, TestNoCaching) 160 | { 161 | const std::string test1Content = R"( 162 | Line1 163 | Line2 164 | Line3 165 | )"; 166 | const std::string test2Content = R"( 167 | Line6 168 | Line7 169 | Line8 170 | )"; 171 | jinja2::MemoryFileSystem fs; 172 | fs.AddFile("test1.j2tpl", test1Content); 173 | 174 | jinja2::TemplateEnv env; 175 | env.GetSettings().cacheSize = 0; 176 | 177 | env.AddFilesystemHandler("", fs); 178 | auto tpl1 = env.LoadTemplate("test1.j2tpl").value(); 179 | EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); 180 | 181 | fs.AddFile("test1.j2tpl", test2Content); 182 | auto tpl2 = env.LoadTemplate("test1.j2tpl").value(); 183 | EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); 184 | } 185 | 186 | TEST_F(FilesystemHandlerTest, TestDefaultRFSCaching) 187 | { 188 | const std::string test1Content = R"( 189 | Line1 190 | Line2 191 | Line3 192 | )"; 193 | const std::string test2Content = R"( 194 | Line6 195 | Line7 196 | Line8 197 | )"; 198 | const std::string fileName = "test_data/cached_content.j2tpl"; 199 | 200 | jinja2::RealFileSystem fs; 201 | { 202 | std::ofstream os(fileName); 203 | os << test1Content; 204 | } 205 | 206 | jinja2::TemplateEnv env; 207 | env.GetSettings().autoReload = false; 208 | 209 | env.AddFilesystemHandler("", fs); 210 | auto tpl1 = env.LoadTemplate(fileName).value(); 211 | EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); 212 | 213 | { 214 | std::ofstream os(fileName); 215 | os << test2Content; 216 | } 217 | 218 | auto tpl2 = env.LoadTemplate(fileName).value(); 219 | EXPECT_EQ(test1Content, tpl2.RenderAsString({}).value()); 220 | } 221 | 222 | TEST_F(FilesystemHandlerTest, TestRFSCachingReload) 223 | { 224 | const std::string test1Content = R"( 225 | Line1 226 | Line2 227 | Line3 228 | )"; 229 | const std::string test2Content = R"( 230 | Line6 231 | Line7 232 | Line8 233 | )"; 234 | const std::string fileName = "test_data/cached_content.j2tpl"; 235 | 236 | jinja2::RealFileSystem fs; 237 | { 238 | std::ofstream os(fileName); 239 | os << test1Content; 240 | } 241 | 242 | jinja2::TemplateEnv env; 243 | 244 | env.AddFilesystemHandler("", fs); 245 | auto tpl1 = env.LoadTemplate(fileName).value(); 246 | EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); 247 | 248 | std::this_thread::sleep_for(std::chrono::seconds(2)); 249 | 250 | { 251 | std::ofstream os(fileName); 252 | os << test2Content; 253 | } 254 | 255 | auto tpl2 = env.LoadTemplate(fileName).value(); 256 | EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); 257 | } 258 | 259 | TEST_F(FilesystemHandlerTest, TestNoRFSCaching) 260 | { 261 | const std::string test1Content = R"( 262 | Line1 263 | Line2 264 | Line3 265 | )"; 266 | const std::string test2Content = R"( 267 | Line6 268 | Line7 269 | Line8 270 | )"; 271 | const std::string fileName = "test_data/cached_content.j2tpl"; 272 | 273 | jinja2::RealFileSystem fs; 274 | { 275 | std::ofstream os(fileName); 276 | os << test1Content; 277 | } 278 | 279 | jinja2::TemplateEnv env; 280 | env.GetSettings().cacheSize = 0; 281 | 282 | env.AddFilesystemHandler("", fs); 283 | auto tpl1 = env.LoadTemplate(fileName).value(); 284 | EXPECT_EQ(test1Content, tpl1.RenderAsString({}).value()); 285 | 286 | { 287 | std::ofstream os(fileName); 288 | os << test2Content; 289 | } 290 | 291 | auto tpl2 = env.LoadTemplate(fileName).value(); 292 | EXPECT_EQ(test2Content, tpl2.RenderAsString({}).value()); 293 | } 294 | 295 | -------------------------------------------------------------------------------- /test/helpers_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "../src/helpers.h" 4 | 5 | using namespace jinja2; 6 | 7 | TEST(Helpers, CompileEscapes) 8 | { 9 | EXPECT_STREQ("\n", CompileEscapes(std::string{"\\n"}).c_str()); 10 | EXPECT_STREQ("\t", CompileEscapes(std::string{"\\t"}).c_str()); 11 | EXPECT_STREQ("\r", CompileEscapes(std::string{"\\r"}).c_str()); 12 | EXPECT_STREQ("\r\n\t", CompileEscapes(std::string{R"(\r\n\t)"}).c_str()); 13 | EXPECT_STREQ( 14 | "aa\rbb\ncc\tdd", 15 | CompileEscapes(std::string{R"(aa\rbb\ncc\tdd)"}).c_str()); 16 | EXPECT_STREQ("", CompileEscapes(std::string{""}).c_str()); 17 | EXPECT_STREQ( 18 | "aa bb cc dd", 19 | CompileEscapes(std::string{"aa bb cc dd"}).c_str()); 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/if_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include "jinja2cpp/template.h" 7 | 8 | using namespace jinja2; 9 | 10 | TEST(IfTest, IfTrueTest) 11 | { 12 | std::string source = R"( 13 | {% if TrueVal %} 14 | Hello from Jinja template! 15 | {% endif %} 16 | )"; 17 | 18 | Template tpl; 19 | ASSERT_TRUE(tpl.Load(source)); 20 | 21 | ValuesMap params = { 22 | {"TrueVal", true}, 23 | {"FalseVal", true}, 24 | }; 25 | 26 | std::string result = tpl.RenderAsString(params).value(); 27 | std::cout << result << std::endl; 28 | std::string expectedResult = R"( 29 | 30 | Hello from Jinja template! 31 | 32 | )"; 33 | EXPECT_EQ(expectedResult, result); 34 | } 35 | 36 | TEST(IfTest, IfFalseTest) 37 | { 38 | std::string source = R"( 39 | {% if FalseVal %} 40 | Hello from Jinja template! 41 | {% else %} 42 | Else branch triggered! 43 | {% endif %} 44 | )"; 45 | 46 | Template tpl; 47 | ASSERT_TRUE(tpl.Load(source)); 48 | 49 | ValuesMap params = { 50 | {"TrueVal", true}, 51 | {"FalseVal", false}, 52 | }; 53 | 54 | std::string result = tpl.RenderAsString(params).value(); 55 | std::cout << result << std::endl; 56 | std::string expectedResult = R"( 57 | 58 | Else branch triggered! 59 | 60 | )"; 61 | EXPECT_EQ(expectedResult, result); 62 | } 63 | 64 | TEST(IfTest, ElseIfTrueTest) 65 | { 66 | std::string source = R"( 67 | {% if FalseVal %} 68 | Hello from Jinja template! 69 | {% elif FalseVal %} 70 | ElseIf 1 branch triggered! 71 | {% elif TrueVal %} 72 | ElseIf 2 branch triggered! 73 | {% else %} 74 | ElseIf branch triggered! 75 | {% endif %} 76 | )"; 77 | 78 | Template tpl; 79 | ASSERT_TRUE(tpl.Load(source)); 80 | 81 | ValuesMap params = { 82 | {"TrueVal", true}, 83 | {"FalseVal", false}, 84 | }; 85 | 86 | std::string result = tpl.RenderAsString(params).value(); 87 | std::cout << result << std::endl; 88 | std::string expectedResult = R"( 89 | 90 | ElseIf 2 branch triggered! 91 | 92 | )"; 93 | EXPECT_EQ(expectedResult, result); 94 | } 95 | -------------------------------------------------------------------------------- /test/import_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "test_tools.h" 5 | 6 | // Test cases are taken from the pandor/Jinja2 tests 7 | 8 | class ImportTest : public TemplateEnvFixture 9 | { 10 | protected: 11 | void SetUp() override 12 | { 13 | TemplateEnvFixture::SetUp(); 14 | 15 | AddFile("module", R"( 16 | {% macro test(extra='') %}[{{ foo }}{{ extra }}|{{ 23 }}]{% endmacro %} 17 | {% set sbar=56 %} 18 | {% macro __inner() %}77{% endmacro %} 19 | {% macro test_set() %}[{{ foo }}|{{ sbar }}]{% endmacro %} 20 | {% macro test_inner() %}[{{ foo }}|{{ __inner() }}]{% endmacro %} 21 | )"); 22 | AddFile("header", "[{{ foo }}|{{ 23 }}]"); 23 | AddFile("o_printer", "({{ o }})"); 24 | } 25 | }; 26 | 27 | TEST_F(ImportTest, TestContextImports) 28 | { 29 | jinja2::ValuesMap params{{"foo", 42}}; 30 | 31 | auto result = Render(R"({% import "module" as m %}{{ m.test() }}{{ m.test_set() }})", params); 32 | EXPECT_EQ("[|23][|56]", result); 33 | result = Render(R"({% import "module" as m %}{{ m.test(foo) }}{{ m.test_set() }})", params); 34 | EXPECT_EQ("[42|23][|56]", result); 35 | result = Render(R"({% import "module" as m without context %}{{ m.test() }}{{ m.test_set() }})", params); 36 | EXPECT_EQ("[|23][|56]", result); 37 | result = Render(R"({% import "module" as m without context %}{{ m.test(foo) }}{{ m.test_set() }})", params); 38 | EXPECT_EQ("[42|23][|56]", result); 39 | result = Render(R"({% import "module" as m with context %}{{ m.test() }}{{ m.test_set() }})", params); 40 | EXPECT_EQ("[42|23][42|56]", result); 41 | result = Render(R"({% import "module" as m with context %}{{ m.test(foo) }}{{ m.test_set() }})", params); 42 | EXPECT_EQ("[4242|23][42|56]", result); 43 | result = Render(R"({% import "module" as m without context %}{% set sbar=88 %}{{ m.test() }}{{ m.test_set() }})", params); 44 | EXPECT_EQ("[|23][|56]", result); 45 | result = Render(R"({% import "module" as m with context %}{% set sbar=88 %}{{ m.test() }}{{ m.test_set() }})", params); 46 | EXPECT_EQ("[42|23][42|56]", result); 47 | result = Render(R"({% import "module" as m without context %}{{ m.test() }}{{ m.test_inner() }})", params); 48 | EXPECT_EQ("[|23][|77]", result); 49 | result = Render(R"({% import "module" as m with context %}{{ m.test() }}{{ m.test_inner() }})", params); 50 | EXPECT_EQ("[42|23][42|77]", result); 51 | result = Render(R"({% from "module" import test %}{{ test() }})", params); 52 | EXPECT_EQ("[|23]", result); 53 | result = Render(R"({% from "module" import test without context %}{{ test() }})", params); 54 | EXPECT_EQ("[|23]", result); 55 | result = Render(R"({% from "module" import test with context %}{{ test() }})", params); 56 | EXPECT_EQ("[42|23]", result); 57 | } 58 | 59 | TEST_F(ImportTest, TestImportSyntax) 60 | { 61 | Load(R"({% from "foo" import bar %})"); 62 | Load(R"({% from "foo" import bar, baz %})"); 63 | Load(R"({% from "foo" import bar, baz with context %})"); 64 | Load(R"({% from "foo" import bar, baz, with context %})"); 65 | Load(R"({% from "foo" import bar, with context %})"); 66 | Load(R"({% from "foo" import bar, with, context %})"); 67 | Load(R"({% from "foo" import bar, with with context %})"); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /test/includes_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "test_tools.h" 5 | #include 6 | 7 | // Test cases are taken from the pandor/Jinja2 tests 8 | 9 | class IncludeTest : public TemplateEnvFixture 10 | { 11 | protected: 12 | void SetUp() override 13 | { 14 | TemplateEnvFixture::SetUp(); 15 | 16 | AddFile("header", "[{{ foo }}|{{ bar }}]"); 17 | AddFile("header1", "{% set inner_foo = 10 %}[{{ foo }}|{{ bar }}]{{inner_foo}}"); 18 | AddFile("o_printer", "({{ o }})"); 19 | AddFile("missing_inner_header", "{% include 'missing' %}"); 20 | 21 | m_env.AddGlobal("bar", 23); 22 | } 23 | }; 24 | 25 | TEST_F(IncludeTest, TestContextInclude) 26 | { 27 | jinja2::ValuesMap params{{"foo", 42}}; 28 | 29 | auto result = Render(R"({% include "header1" with context %})", params); 30 | EXPECT_EQ("[42|23]10", result); 31 | result = Render(R"({% include "header" %})", params); 32 | EXPECT_EQ("[42|23]", result); 33 | result = Render(R"({% include "header" with context %})", params); 34 | EXPECT_EQ("[42|23]", result); 35 | result = Render(R"({% include "header" without context %})", params); 36 | EXPECT_EQ("[|23]", result); 37 | result = Render(R"({% include "header" ignore missing with context %})", params); 38 | EXPECT_EQ("[42|23]", result); 39 | result = Render(R"({% include "header" ignore missing without context %})", params); 40 | EXPECT_EQ("[|23]", result); 41 | } 42 | 43 | TEST_F(IncludeTest, TestChoiceIncludes) 44 | { 45 | jinja2::ValuesMap params{{"foo", 42}}; 46 | 47 | auto result = Render(R"({% include ["missing", "header"] %})", params); 48 | EXPECT_EQ("[42|23]", result); 49 | 50 | result = Render(R"({% include ["missing", "missing2"] ignore missing %})", params); 51 | EXPECT_EQ("", result); 52 | 53 | auto testInclude = [&, this](std::string tpl, jinja2::ValuesMap params) 54 | { 55 | params["foo"] = 42; 56 | return Render(std::move(tpl), params); 57 | }; 58 | 59 | EXPECT_EQ("[42|23]", testInclude(R"({% include ["missing", "header"] %})", {})); 60 | EXPECT_EQ("[42|23]", testInclude(R"({% include x %})", {{"x", jinja2::ValuesList{jinja2::Value("missing"), jinja2::Value("header")}}})); 61 | EXPECT_EQ("[42|23]", testInclude(R"({% include [x, "header"] %})", {{"x", jinja2::Value("header")}})); 62 | EXPECT_EQ("[42|23]", testInclude(R"({% include x %})", {{"x", jinja2::Value("header")}})); 63 | EXPECT_EQ("[42|23]", testInclude(R"({% include [x] %})", {{"x", jinja2::Value("header")}})); 64 | EXPECT_EQ("[42|23]", testInclude(R"({% include "head" ~ x %})", {{"x", jinja2::Value("er")}})); 65 | } 66 | 67 | TEST_F(IncludeTest, TestMissingIncludesError1) 68 | { 69 | jinja2::ValuesMap params{}; 70 | 71 | jinja2::Template tpl(&m_env); 72 | auto loadResult = tpl.Load(R"({% include "missing" %})"); 73 | EXPECT_FALSE(!loadResult); 74 | 75 | auto renderResult = tpl.RenderAsString(params); 76 | EXPECT_TRUE(!renderResult); 77 | auto error = renderResult.error(); 78 | EXPECT_EQ(jinja2::ErrorCode::TemplateNotFound, error.GetCode()); 79 | auto& extraParams = error.GetExtraParams(); 80 | ASSERT_EQ(1ull, extraParams.size()); 81 | auto filesList = nonstd::get_if(&extraParams[0].data()); 82 | EXPECT_NE(nullptr, filesList); 83 | EXPECT_EQ(1ull, filesList->GetSize().value()); 84 | EXPECT_EQ("missing", (*filesList->begin()).asString()); 85 | } 86 | 87 | TEST_F(IncludeTest, TestMissingInnerIncludesError) 88 | { 89 | jinja2::ValuesMap params{}; 90 | 91 | jinja2::Template tpl(&m_env); 92 | auto loadResult = tpl.Load(R"({% include "missing_inner_header" %})"); 93 | EXPECT_FALSE(!loadResult); 94 | 95 | auto renderResult = tpl.RenderAsString(params); 96 | EXPECT_TRUE(!renderResult); 97 | auto error = renderResult.error(); 98 | EXPECT_EQ(jinja2::ErrorCode::TemplateNotFound, error.GetCode()); 99 | auto& extraParams = error.GetExtraParams(); 100 | ASSERT_EQ(1ull, extraParams.size()); 101 | auto filesList = nonstd::get_if(&extraParams[0].data()); 102 | EXPECT_NE(nullptr, filesList); 103 | EXPECT_EQ(1ull, filesList->GetSize().value()); 104 | EXPECT_EQ("missing", (*filesList->begin()).asString()); 105 | } 106 | 107 | TEST_F(IncludeTest, TestMissingIncludesError2) 108 | { 109 | jinja2::ValuesMap params{}; 110 | 111 | jinja2::Template tpl(&m_env); 112 | auto loadResult = tpl.Load(R"({% include ["missing", "missing2"] %})"); 113 | EXPECT_FALSE(!loadResult); 114 | 115 | auto renderResult = tpl.RenderAsString(params); 116 | EXPECT_TRUE(!renderResult); 117 | auto error = renderResult.error(); 118 | EXPECT_EQ(jinja2::ErrorCode::TemplateNotFound, error.GetCode()); 119 | auto& extraParams = error.GetExtraParams(); 120 | ASSERT_EQ(1ull, extraParams.size()); 121 | auto filesList = nonstd::get_if(&extraParams[0].data()); 122 | EXPECT_NE(nullptr, filesList); 123 | EXPECT_EQ(2ull, filesList->GetSize().value()); 124 | auto params_iter = filesList->begin(); 125 | EXPECT_EQ("missing", (*params_iter++).asString()); 126 | EXPECT_EQ("missing2", (*params_iter++).asString()); 127 | } 128 | 129 | TEST_F(IncludeTest, TestContextIncludeWithOverrides) 130 | { 131 | AddFile("item", "{{ item }}"); 132 | EXPECT_EQ("123", Render(R"({% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %})")); 133 | } 134 | 135 | TEST_F(IncludeTest, TestUnoptimizedScopes) 136 | { 137 | auto result = Render( 138 | R"({% macro outer(o) %} 139 | {% macro inner() %} 140 | {% include "o_printer" %} 141 | {% endmacro %} 142 | {{ inner() }} 143 | {%- endmacro %} 144 | {{ outer("FOO") }})"); 145 | 146 | EXPECT_EQ("\n\n\n\n(FOO)\n", result); 147 | } 148 | -------------------------------------------------------------------------------- /test/macro_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include "jinja2cpp/template.h" 7 | #include "test_tools.h" 8 | 9 | using namespace jinja2; 10 | 11 | using MacroTest = BasicTemplateRenderer; 12 | 13 | MULTISTR_TEST(MacroTest, SimpleMacro, 14 | R"( 15 | {% macro test %} 16 | Hello World! 17 | {% endmacro %} 18 | {{ test() }}{{ test() }} 19 | )", 20 | //------------- 21 | R"( 22 | 23 | 24 | Hello World! 25 | 26 | Hello World! 27 | 28 | )" 29 | ) 30 | { 31 | params = PrepareTestData(); 32 | } 33 | 34 | MULTISTR_TEST(MacroTest, OneParamMacro, 35 | R"( 36 | {% macro test(param) %} 37 | -->{{ param }}<-- 38 | {% endmacro %} 39 | {{ test('Hello') }}{{ test(param='World!') }} 40 | )", 41 | //----------- 42 | R"( 43 | 44 | 45 | -->Hello<-- 46 | 47 | -->World!<-- 48 | 49 | )" 50 | ) 51 | { 52 | params = PrepareTestData(); 53 | } 54 | 55 | MULTISTR_TEST(MacroTest, 56 | OneParamRecursiveMacro, 57 | R"( 58 | {% macro fib(param) %}{{ 1 if param == 1 else (fib(param - 1) | int + param) }}{% endmacro %} 59 | {{ fib(10) }} 60 | )", 61 | //----------- 62 | R"( 63 | 64 | 55 65 | )") 66 | { 67 | params = PrepareTestData(); 68 | } 69 | 70 | MULTISTR_TEST(MacroTest, OneDefaultParamMacro, 71 | R"( 72 | {% macro test(param='Hello') %} 73 | -->{{ param }}<-- 74 | {% endmacro %} 75 | {{ test() }}{{ test('World!') }} 76 | )", 77 | //-------------- 78 | R"( 79 | 80 | 81 | -->Hello<-- 82 | 83 | -->World!<-- 84 | 85 | )" 86 | ) 87 | { 88 | params = PrepareTestData(); 89 | } 90 | 91 | MULTISTR_TEST(MacroTest, ClosureMacro, 92 | R"( 93 | {% macro test1(param) %}-->{{ param('Hello World') }}<--{% endmacro %} 94 | {% macro test(param1) %} 95 | {% set var='Some Value' %} 96 | {% macro inner1(msg) %}{{var ~ param1}} -> {{msg}}{% endmacro %} 97 | {% macro inner2(msg) %}{{msg | upper}}{% endmacro %} 98 | -->{{ test1(inner1) }}<-- 99 | -->{{ test1(inner2) }}<-- 100 | {% endmacro %} 101 | {{ test() }}{{ test('World!') }} 102 | )", 103 | //----------- 104 | R"( 105 | 106 | 107 | 108 | 109 | 110 | 111 | -->-->Some Value -> Hello World<--<-- 112 | -->-->HELLO WORLD<--<-- 113 | 114 | 115 | 116 | 117 | -->-->Some ValueWorld! -> Hello World<--<-- 118 | -->-->HELLO WORLD<--<-- 119 | 120 | )" 121 | ) 122 | { 123 | params = PrepareTestData(); 124 | } 125 | 126 | MULTISTR_TEST(MacroTest, MacroVariables, 127 | R"( 128 | {% macro test(param1='Hello', param2, param3='World') %} 129 | name: {{ name }} 130 | arguments: {{ arguments | pprint }} 131 | defaults: {{ defaults | pprint }} 132 | varargs: {{ varargs | pprint }} 133 | kwargs: {{ kwargs | pprint }} 134 | {% endmacro %} 135 | {{ test(1, 2, param3=3, 4, extraValue=5, 6) }} 136 | )", 137 | //----------- 138 | R"( 139 | 140 | 141 | name: test 142 | arguments: ['param1', 'param2', 'param3'] 143 | defaults: ['Hello', none, 'World'] 144 | varargs: [4, 6] 145 | kwargs: {'extraValue': 5} 146 | 147 | )" 148 | ) 149 | { 150 | params = PrepareTestData(); 151 | } 152 | 153 | MULTISTR_TEST(MacroTest, SimpleCallMacro, 154 | R"( 155 | {% macro test %} 156 | Hello World! -> {{ caller() }} <- 157 | {% endmacro %} 158 | {% call test %}Message from caller{% endcall %} 159 | )", 160 | //----------------- 161 | R"( 162 | 163 | 164 | Hello World! -> Message from caller <- 165 | 166 | )" 167 | ) 168 | { 169 | params = PrepareTestData(); 170 | } 171 | 172 | MULTISTR_TEST(MacroTest, CallWithParamsAndSimpleMacro, 173 | R"( 174 | {% macro test %} 175 | -> {{ caller('Hello World' | upper) }} <- 176 | {% endmacro %} 177 | {% call(message) test %}{{ message }}{% endcall %} 178 | )", 179 | //------------ 180 | R"( 181 | 182 | 183 | -> HELLO WORLD <- 184 | 185 | )" 186 | ) 187 | { 188 | params = PrepareTestData(); 189 | } 190 | 191 | MULTISTR_TEST(MacroTest, CallWithParamsAndMacro, 192 | R"( 193 | {% macro test(msg) %} 194 | {{ msg }} >>> -> {{ caller([msg]) }} <--> {{ caller([msg], 'upper') }} <- 195 | {% endmacro %} 196 | {% call(message, fName='lower') test('Hello World') %}{{ message | map(fName) | first }}{% endcall %} 197 | )", 198 | //------------- 199 | R"( 200 | 201 | 202 | Hello World >>> -> hello world <--> HELLO WORLD <- 203 | 204 | )" 205 | ) 206 | { 207 | params = PrepareTestData(); 208 | } 209 | 210 | MULTISTR_TEST(MacroTest, MacroCallVariables, 211 | R"( 212 | {% macro invoke() %}{{ caller(1, 2, param3=3, 4, extraValue=5, 6) }}{% endmacro %} 213 | {% call (param1='Hello', param2, param3='World') invoke %} 214 | name: {{ name }} 215 | arguments: {{ arguments | pprint }} 216 | defaults: {{ defaults | pprint }} 217 | varargs: {{ varargs | pprint }} 218 | kwargs: {{ kwargs | pprint }} 219 | {% endcall %} 220 | )", 221 | //-------------- 222 | R"( 223 | 224 | 225 | name: $call$ 226 | arguments: ['param1', 'param2', 'param3'] 227 | defaults: ['Hello', none, 'World'] 228 | varargs: [4, 6] 229 | kwargs: {'extraValue': 5} 230 | 231 | )" 232 | ) 233 | { 234 | params = PrepareTestData(); 235 | } 236 | -------------------------------------------------------------------------------- /test/metadata_test.cpp: -------------------------------------------------------------------------------- 1 | #include "jinja2cpp/template.h" 2 | #include "test_tools.h" 3 | 4 | #include 5 | 6 | using namespace jinja2; 7 | 8 | TEST(MetadataTest, NoMetadata_EmtpyData) 9 | { 10 | constexpr auto source = "Hello World!"; 11 | 12 | Template tpl; 13 | auto parse_result = tpl.Load(source); 14 | EXPECT_FALSE(!parse_result); 15 | EXPECT_EQ(0, tpl.GetMetadata().value().GetSize()); 16 | EXPECT_TRUE(tpl.GetMetadataRaw().value().metadata.empty()); 17 | auto renderResult = tpl.RenderAsString({}); 18 | EXPECT_FALSE(!renderResult); 19 | EXPECT_EQ("Hello World!", renderResult.value()); 20 | } 21 | 22 | TEST(MetadataTest, Metadata_EmptyTag) 23 | { 24 | constexpr auto source = R"({% meta %}{% endmeta %}Hello World!)"; 25 | 26 | Template tpl; 27 | auto parse_result = tpl.Load(source); 28 | EXPECT_FALSE(!parse_result); 29 | auto metadataValue = tpl.GetMetadata(); 30 | EXPECT_FALSE(!metadataValue); 31 | EXPECT_EQ(0, metadataValue.value().GetSize()); 32 | EXPECT_TRUE(tpl.GetMetadataRaw().value().metadata.empty()); 33 | std::cout << tpl.GetMetadataRaw().value().metadata << std::endl; 34 | auto renderResult = tpl.RenderAsString({}); 35 | EXPECT_FALSE(!renderResult); 36 | EXPECT_EQ("Hello World!", renderResult.value()); 37 | } 38 | 39 | TEST(MetadataTest, Metadata_TagWithSpaces) 40 | { 41 | constexpr auto source = R"({% meta %} 42 | {% endmeta %}Hello World!)"; 43 | 44 | Template tpl; 45 | auto parse_result = tpl.Load(source); 46 | EXPECT_FALSE(!parse_result); 47 | auto metadataValue = tpl.GetMetadata(); 48 | EXPECT_FALSE(!metadataValue); 49 | EXPECT_EQ(0, metadataValue.value().GetSize()); 50 | EXPECT_TRUE(tpl.GetMetadataRaw().value().metadata.empty()); 51 | auto renderResult = tpl.RenderAsString({}); 52 | EXPECT_FALSE(!renderResult); 53 | EXPECT_EQ("Hello World!", renderResult.value()); 54 | } 55 | 56 | TEST(MetadataTest, Metadata_Invalid) 57 | { 58 | constexpr auto source = R"( 59 | {% meta %}INVALID JSON 60 | {% endmeta %}Hello World!)"; 61 | 62 | Template tpl; 63 | auto parse_result = tpl.Load(source); 64 | EXPECT_FALSE(!parse_result); 65 | auto metadataValue = tpl.GetMetadata(); 66 | EXPECT_TRUE(!metadataValue); 67 | auto error = metadataValue.error().ToString(); 68 | std::cout << error << std::endl; 69 | EXPECT_EQ("noname.j2tpl:2:1: error: Error occurred during template metadata parsing. Error: Invalid value.\n", error); 70 | } 71 | 72 | TEST(MetadataTest, Metadata_JsonData_Narrow) 73 | { 74 | std::string json = R"({ 75 | "stringValue": "Hello!", 76 | "subobject": { 77 | "intValue": 10, 78 | "array": [1, 2, 3, 4, 5] 79 | } 80 | })"; 81 | 82 | auto source = "{% meta %}" + json + "{% endmeta %}Hello World!"; 83 | 84 | Template tpl; 85 | auto parse_result = tpl.Load(source); 86 | EXPECT_FALSE(!parse_result); 87 | auto metadataValue = tpl.GetMetadata(); 88 | EXPECT_FALSE(!metadataValue); 89 | EXPECT_EQ(2, metadataValue.value().GetSize()); 90 | 91 | auto& metadata = metadataValue.value(); 92 | EXPECT_TRUE(metadata.HasValue("stringValue")); 93 | EXPECT_TRUE(metadata.HasValue("subobject")); 94 | EXPECT_EQ("Hello!", AsString(metadata["stringValue"])); 95 | auto subobjectVal = metadata["subobject"]; 96 | const auto& subobject = subobjectVal.get(); 97 | EXPECT_EQ(10, subobject["intValue"].get()); 98 | EXPECT_EQ(5, subobject["array"].get().GetSize().value()); 99 | 100 | auto metadataRaw = tpl.GetMetadataRaw().value(); 101 | EXPECT_FALSE(metadataRaw.metadata.empty()); 102 | EXPECT_EQ("json", metadataRaw.metadataType); 103 | EXPECT_EQ(nonstd::string_view(json.data(), json.size()), metadataRaw.metadata); 104 | std::cout << metadataRaw.metadata << std::endl; 105 | auto renderResult = tpl.RenderAsString({}); 106 | EXPECT_FALSE(!renderResult); 107 | EXPECT_EQ("Hello World!", renderResult.value()); 108 | } 109 | 110 | TEST(MetadataTest, DISABLED_Metadata_JsonData_Wide) 111 | { 112 | std::wstring json = LR"({ 113 | "stringValue": "Hello!", 114 | "subobject": { 115 | "intValue": 10, 116 | "array": [1, 2, 3, 4, 5] 117 | } 118 | })"; 119 | 120 | auto source = L"{% meta %}" + json + L"{% endmeta %}Hello World!"; 121 | 122 | TemplateW tpl; 123 | auto parse_result = tpl.Load(source); 124 | EXPECT_FALSE(!parse_result); 125 | auto metadataValue = tpl.GetMetadata(); 126 | EXPECT_FALSE(!metadataValue); 127 | EXPECT_EQ(2, metadataValue.value().GetSize()); 128 | 129 | auto& metadata = metadataValue.value(); 130 | EXPECT_TRUE(metadata.HasValue("stringValue")); 131 | EXPECT_TRUE(metadata.HasValue("subobject")); 132 | EXPECT_EQ("Hello!", AsString(metadata["stringValue"])); 133 | auto subobjectVal = metadata["subobject"]; 134 | const auto& subobject = subobjectVal.get(); 135 | EXPECT_EQ(10, subobject["intValue"].get()); 136 | EXPECT_EQ(5, subobject["array"].get().GetSize().value()); 137 | 138 | auto metadataRaw = tpl.GetMetadataRaw().value(); 139 | EXPECT_FALSE(metadataRaw.metadata.empty()); 140 | EXPECT_EQ("json", metadataRaw.metadataType); 141 | EXPECT_EQ(nonstd::wstring_view(json.data(), json.size()), metadataRaw.metadata); 142 | std::wcout << metadataRaw.metadata << std::endl; 143 | auto renderResult = tpl.RenderAsString({}); 144 | EXPECT_FALSE(!renderResult); 145 | EXPECT_EQ(L"Hello World!", renderResult.value()); 146 | } 147 | -------------------------------------------------------------------------------- /test/perf_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include "jinja2cpp/template.h" 7 | 8 | using namespace jinja2; 9 | 10 | constexpr int Iterations = 10000; 11 | 12 | #if !defined(NDEBUG) || defined(_DEBUG) 13 | #define PerfTests DISABLED_PerfTests 14 | #else 15 | #define PerfTests PerfTests 16 | #endif 17 | 18 | TEST(PerfTests, PlainText) 19 | { 20 | std::string source = "Hello World from Parser!"; 21 | 22 | Template tpl; 23 | ASSERT_TRUE(tpl.Load(source)); 24 | 25 | jinja2::ValuesMap params; 26 | 27 | auto renderResult = tpl.RenderAsString(params); 28 | if (!renderResult) 29 | { 30 | std::cout << "Render error: " << renderResult.error() << std::endl; 31 | ASSERT_FALSE(true); 32 | } 33 | std::cout << renderResult.value() << std::endl; 34 | std::string result; 35 | for (int n = 0; n < Iterations * 100; ++ n) 36 | result = tpl.RenderAsString(params).value(); 37 | 38 | std::cout << result << std::endl; 39 | } 40 | 41 | TEST(PerfTests, SimpleSubstituteText) 42 | { 43 | std::string source = "{{ message }} from Parser!"; 44 | 45 | Template tpl; 46 | ASSERT_TRUE(tpl.Load(source)); 47 | 48 | jinja2::ValuesMap params = {{"message", "Hello World!"}}; 49 | 50 | std::cout << tpl.RenderAsString(params).value() << std::endl; 51 | std::string result; 52 | for (int n = 0; n < Iterations * 100; ++ n) 53 | result = tpl.RenderAsString(params).value(); 54 | 55 | std::cout << result << std::endl; 56 | } 57 | 58 | TEST(PerfTests, ValueSubstituteText) 59 | { 60 | std::string source = "{{ message }} from Parser!"; 61 | 62 | Template tpl; 63 | ASSERT_TRUE(tpl.Load(source)); 64 | 65 | jinja2::ValuesMap params = {{"message", 100500}}; 66 | 67 | std::cout << tpl.RenderAsString(params).value() << std::endl; 68 | std::string result; 69 | for (int n = 0; n < Iterations * 100; ++ n) 70 | result = tpl.RenderAsString(params).value(); 71 | 72 | std::cout << result << std::endl; 73 | } 74 | 75 | TEST(PerfTests, SimpleSubstituteFilterText) 76 | { 77 | std::string source = "{{ message | upper }} from Parser!"; 78 | 79 | Template tpl; 80 | ASSERT_TRUE(tpl.Load(source)); 81 | 82 | jinja2::ValuesMap params = {{"message", "Hello World!"}}; 83 | 84 | std::cout << tpl.RenderAsString(params).value() << std::endl; 85 | std::string result; 86 | for (int n = 0; n < Iterations * 100; ++ n) 87 | result = tpl.RenderAsString(params).value(); 88 | 89 | std::cout << result << std::endl; 90 | } 91 | 92 | TEST(PerfTests, DoubleSubstituteText) 93 | { 94 | std::string source = "{{ message }} from Parser! - {{number}}"; 95 | 96 | Template tpl; 97 | ASSERT_TRUE(tpl.Load(source)); 98 | 99 | jinja2::ValuesMap params = {{"message", "Hello World!"}, 100 | {"number", 10}}; 101 | 102 | std::cout << tpl.RenderAsString(params).value() << std::endl; 103 | std::string result; 104 | for (int n = 0; n < Iterations * 100; ++ n) 105 | result = tpl.RenderAsString(params).value(); 106 | 107 | std::cout << result << std::endl; 108 | } 109 | 110 | TEST(PerfTests, ForLoopText) 111 | { 112 | std::string source = "{% for i in range(20)%} {{i}} {%endfor%}"; 113 | 114 | Template tpl; 115 | ASSERT_TRUE(tpl.Load(source)); 116 | 117 | jinja2::ValuesMap params = {}; 118 | 119 | auto renderResult = tpl.RenderAsString(params); 120 | if (!renderResult) 121 | { 122 | std::cout << "Render error: " << renderResult.error() << std::endl; 123 | ASSERT_FALSE(true); 124 | } 125 | std::cout << renderResult.value() << std::endl; 126 | std::string result; 127 | for (int n = 0; n < Iterations * 20; ++ n) 128 | tpl.RenderAsString(params); 129 | 130 | std::cout << result << std::endl; 131 | } 132 | 133 | TEST(PerfTests, ForLoopParamText) 134 | { 135 | std::string source = "{% for i in range(num)%} {{i}} {%endfor%}"; 136 | 137 | Template tpl; 138 | ASSERT_TRUE(tpl.Load(source)); 139 | 140 | jinja2::ValuesMap params = {{"num", 20}}; 141 | 142 | std::cout << tpl.RenderAsString(params).value() << std::endl; 143 | std::string result; 144 | for (int n = 0; n < Iterations * 20; ++ n) 145 | result = tpl.RenderAsString(params).value(); 146 | 147 | std::cout << result << std::endl; 148 | } 149 | 150 | TEST(PerfTests, ForLoopIndexText) 151 | { 152 | std::string source = "{% for i in range(20)%} {{i ~ '-' ~ loop.index}} {%endfor%}"; 153 | 154 | Template tpl; 155 | ASSERT_TRUE(tpl.Load(source)); 156 | 157 | jinja2::ValuesMap params = {}; 158 | 159 | std::cout << tpl.RenderAsString(params).value() << std::endl; 160 | std::string result; 161 | for (int n = 0; n < Iterations * 20; ++ n) 162 | result = tpl.RenderAsString(params).value(); 163 | 164 | std::cout << result << std::endl; 165 | } 166 | 167 | TEST(PerfTests, ForLoopIfText) 168 | { 169 | std::string source = "{% for i in range(20) if i is odd %} {{i}} {%endfor%}"; 170 | 171 | Template tpl; 172 | ASSERT_TRUE(tpl.Load(source)); 173 | 174 | jinja2::ValuesMap params = {}; 175 | 176 | std::cout << tpl.RenderAsString(params).value() << std::endl; 177 | std::string result; 178 | for (int n = 0; n < Iterations * 20; ++ n) 179 | result = tpl.RenderAsString(params).value(); 180 | 181 | std::cout << result << std::endl; 182 | } 183 | 184 | TEST(PerfTests, LoadTemplate) 185 | { 186 | std::string source = "{{ title }}"; 187 | jinja2::Template tpl; 188 | 189 | const int N = 100000; 190 | 191 | for (int i = 0; i < N; i++) 192 | { 193 | tpl.Load(source); 194 | } 195 | 196 | ValuesMap data; 197 | data["title"] = "My title"; 198 | data["t"] = "My List"; 199 | std::string result = tpl.RenderAsString(data).value();; 200 | 201 | std::cout << result << std::endl; 202 | } 203 | 204 | TEST(PerfTests, DISABLED_TestMatsuhiko) 205 | { 206 | std::string source = R"( 207 | 208 | 209 | 210 | {{ page_title }} 211 | 212 | 213 |
214 |

{{ page_title }}

215 |
216 | 225 |
226 | 227 | {% for row in table %} 228 | 229 | {% for cell in row|list %} 230 | 231 | {% endfor %} 232 | 233 | {% endfor %} 234 |
{{ cell }}
235 |
236 | 237 | 238 | )"; 239 | 240 | Template tpl; 241 | auto parseRes = tpl.Load(source); 242 | EXPECT_TRUE(parseRes.has_value()); 243 | if (!parseRes) 244 | { 245 | std::cout << parseRes.error() << std::endl; 246 | return; 247 | } 248 | 249 | jinja2::ValuesMap params = {}; 250 | params["page_title"] = "mitsuhiko's benchmark"; 251 | // jinja2::ValuesMap dictEntry = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}, {"f", 6}, {"g",7}, {"h", 8}, {"i", 9}, {"j", 10}}; 252 | jinja2::ValuesList dictEntry = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}; 253 | jinja2::ValuesList table; 254 | for (int n = 0; n < 1000; ++ n) 255 | table.push_back(jinja2::Value(dictEntry)); 256 | params["table"] = std::move(table); 257 | 258 | // std::cout << tpl.RenderAsString(params).value() << std::endl; 259 | std::string result; 260 | for (int n = 0; n < 5000; ++ n) 261 | tpl.RenderAsString(params).value(); 262 | 263 | // std::cout << result << std::endl; 264 | } 265 | -------------------------------------------------------------------------------- /test/test_data/simple_template1.j2tpl: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /test/tojson_filter_test.cpp: -------------------------------------------------------------------------------- 1 | #include "jinja2cpp/template.h" 2 | #include "test_tools.h" 3 | 4 | #include 5 | 6 | using namespace jinja2; 7 | 8 | SUBSTITUTION_TEST_P(JsonFilterSubstitutionTest) 9 | 10 | INSTANTIATE_TEST_SUITE_P(ToJson, 11 | JsonFilterSubstitutionTest, 12 | ::testing::Values(InputOutputPair{ "(1, 2, 3) | tojson", "[1,2,3]" }, 13 | InputOutputPair{ "(1, 2, 3) | tojson(indent = 1)", "[1, 2, 3]" }, 14 | InputOutputPair{ "'\"ba&r\\'' | tojson", "\"\\\"ba\\u0026r\\u0027\"" }, 15 | InputOutputPair{ "'' | tojson", "\"\\u003cbar\\u003e\"" })); 16 | 17 | struct ToJson : ::testing::Test 18 | { 19 | ValuesMap GetObjectParam() const 20 | { 21 | const ValuesMap object{ { "intValue", 3 }, 22 | { "doubleValue", 12.123f }, 23 | { "stringValue", "rain" }, 24 | { "wstringValue", std::wstring(L"rain") }, 25 | { "boolFalseValue", false }, 26 | { "boolTrueValue", true }, 27 | { "listValue", ValuesList{ 1, 2, 3 } }, 28 | { "map", ValuesMap{ { "str1", 1 } } } }; 29 | 30 | return ValuesMap{ { "obj", object } }; 31 | } 32 | 33 | ValuesMap GetKeyValuePairParam() const 34 | { 35 | const ValuesMap pair{ { "foo", "bar" } }; 36 | return ValuesMap{ { "obj", pair } }; 37 | } 38 | 39 | template 40 | void PerformJsonTest(const StringType& source, const StringType& expectedResult, const jinja2::ValuesMap& params) 41 | { 42 | TemplateType tpl; 43 | ASSERT_TRUE(tpl.Load(source)); 44 | const auto result = tpl.RenderAsString(params).value(); 45 | 46 | const auto expectedJson = nlohmann::json::parse(expectedResult); 47 | const auto resultJson = nlohmann::json::parse(result); 48 | 49 | EXPECT_EQ(expectedJson, resultJson); 50 | } 51 | 52 | void PerformBothJsonTests(const std::string& source, const std::string& expectedResult, const jinja2::ValuesMap& params) 53 | { 54 | PerformJsonTest