AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Jinja2 C++ (and for C++) almost full-conformance template engine implementation
Developer toolsC++ · PythonGitHub @jinja2cppSelf-installOwner unconfirmed
Paste into Claude or ChatGPT for installation instructions suited to your environment.
Have you tried it? One tap is enough. No sign-in needed.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗C++ implementation of the Jinja2 Python template engine. This library brings support of powerful Jinja2 template features into the C++ world, reports dynamic HTML pages and source code generation.
Main features of Jinja2C++:
set, for, if, filter, do, with).For instance, this simple code:
#include <jinja2cpp/template.h>
std::string source = R"(
{{ ("Hello", 'world') | join }}!!!
{{ ("Hello", 'world') | join(', ') }}!!!
{{ ("Hello", 'world') | join(d = '; ') }}!!!
{{ ("Hello", 'world') | join(d = '; ') | lower }}!!!
)";
Template tpl;
if (auto loaded = tpl.Load(source); !loaded)
std::cerr << loaded.error() << std::endl;
std::string result = tpl.RenderAsString({}).value();
produces the result string:
Helloworld!!!
Hello, world!!!
Hello; world!!!
hello; world!!!
To use Jinja2C++ in your project you have to:
Usage of Jinja2C++ in the code is pretty simple:
jinja2::Template tpl;
auto loaded = tpl.Load("{{ 'Hello World' }}!!!");
Load and Render return a jinja2::Result that holds the error when they fail, and
they are [[nodiscard]]: check the result (if (!loaded) std::cerr << loaded.error();)
rather than drop it.
std::cout << tpl.RenderAsString({}).value() << std::endl;
and get:
Hello World!!!
That's all!
More detailed examples and features description can be found in the documentation: https://jinja2cpp.github.io/docs/usage
Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be a fully jinja2 specification-conformant. The current support is limited to:
Full information about Jinja2 specification support and compatibility table can be found here: https://jinja2cpp.github.io/docs/j2_compatibility.html.
Jinja2C++ requires C++17. C++17, C++20 and C++23 are supported and tested; C++26 is built
in CI as a forward-compatibility check. The CMake target requires cxx_std_17 publicly, so
CMake consumers get at least C++17 automatically; others need -std=c++17 (GCC up to 10,
Clang up to 15) or /std:c++17 (MSVC, whose default is still C++14). Version 1.x was the
last to support C++14.
Every pull request is built and tested with:
/std:c++latest until MSVC ships /std:c++23)| Platform | Status |
|---|---|
| Linux | |
| macOS | |
| Windows |
Jinja2C++ has two external dependencies:
nonstd::expected-lite https://github.com/martinmoene/expected-litefmtlib::fmt https://github.com/fmtlib/fmtBoost is used only by opt-in pieces: Boost.Multiprecision (with Boost.Config) for
-DJINJA2CPP_WITH_BIGINT=boost and the jinja2cpp/big_int/boost_cpp_int.h adapter, and
Boost.JSON for the jinja2cpp/binding/boost_json.h binding. The tests fetch what they check;
the library links no Boost and its core package never asks for it.
The JSON bindings and the big-integer adapter are header-only components of the package, each
bringing its own library (find_package(jinja2cpp COMPONENTS nlohmann_json) and
jinja2cpp::nlohmann_json, or pkg-config jinja2cpp-nlohmann_json); also boost_json and
boost_multiprecision. Every component is installed; see MIGRATION.md.
Why each dependency is needed, which are optional, and the CycloneDX and SPDX SBOMs installed with the package: docs/dependencies.md.
Examples of build scripts and different build configurations could be found here: https://github.com/jinja2cpp/examples-build
In simplest case to compile Jinja2C++ you need:
> git clone https://github.com/flexferrum/Jinja2Cpp.git
> cd Jinja2Cpp
> mkdir build
> cd build
> cmake .. -DCMAKE_INSTALL_PREFIX=<path to install folder>
> cmake --build . --target all
"Path to install folder" here is a path to the folder where you want to install Jinja2C++ lib.
> cmake --build . --target install
In this case, Jinja2C++ will be built with internally-shipped dependencies and install them respectively. But Jinja2C++ supports builds with externally-provided deps.
The install carries a CMake package and a pkg-config file. With CMake, point
CMAKE_PREFIX_PATH (or jinja2cpp_DIR) at the install folder and link the namespaced
target:
find_package(jinja2cpp 2 REQUIRED)
target_link_libraries(my_app PRIVATE jinja2cpp::jinja2cpp)
The target carries everything the library was built with: the include directories, the
C++17 floor, JINJA2CPP_LINK_AS_SHARED for a shared build, the expected-lite selection
and, for a static build, the library it depends on (fmt, installed next to it in the
default internal dependencies mode). The un-namespaced jinja2cpp target of
1.x still works. The install layout changed in 2.0: the package lives in
lib/cmake/jinja2cpp (was lib/jinja2cpp), the static library in lib (was
lib/static) and jinja2cpp.pc in lib/pkgconfig (was share/pkgconfig). Without CMake, use pkg-config --cflags --libs jinja2cpp
(--static for a static build).
[[deprecated]];
they are removed in 3.0.jinja2::v2, an inline namespace: you
keep writing jinja2::Value, but the major version is part of every mangled name, so a
program compiled against 1.x headers fails to link with a 2.x library (static or shared)
instead of misbehaving at run time. Never spell v2 yourself. To declare Jinja2C++ types
without their definitions, include <jinja2cpp/fwd.h>: a hand-written
namespace jinja2 { class Value; } declares a different, undefined class and makes later
uses ambiguous. <jinja2cpp/jinja2cpp.h> includes the whole API except the JSON bindings.
If your code says using namespace jinja2; and has its own namespace-scope v2, qualify
one of them. Specialise the extension points inside namespace jinja2 { ... } or with their
qualified name (template<> struct jinja2::Reflector<MyType>). A 1.x-style
detail::Reflector specialisation must use the qualified form
(template<> struct jinja2::detail::Reflector<MyType>): MSVC does not look through the
inline namespace when namespace detail is reopened inside namespace jinja2, and declares
a second jinja2::detail instead.libjinja2cpp.so.2, libjinja2cpp.2.dylib). It is bumped by any release that changes the
layout of a type you allocate (a Settings field, a Value alternative) or the signature
of an exported function, so a shared consumer is never loaded against an incompatible
library. A static consumer has no such guard and rebuilds after a minor upgrade; patch
releases keep the ABI.Jinja2C++ can be used as conan.io package. In this case, you should do the following steps:
jinja2cpp/1.2.1) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with the usage of conan-cmake integration it could be written this way:
cmake_minimum_required(VERSION 3.24)
project(Jinja2CppSampleConan CXX)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})
set(CMAKE_CXX_STANDARD 17)
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/0.18.1/conan.cmake"
"${CMAKE_BINARY_DIR}/conan.cmake"
TLS_VERIFY ON)
endif()
include(${CMAKE_BINARY_DIR}/conan.cmake)
conan_cmake_autodetect(settings)
conan_cmake_run(REQUIRES
jinja2cpp/1.1.0
gtest/1.14.0
BASIC_SETUP
${CONAN_SETTINGS}
OPTIONS
jinja2cpp/*:shared=False
gtest/*:shared=False
BUILD missing)
set (TARGET_NAME jinja2cpp_build_test)
add_executable (${TARGET_NAME} main.cpp)
target_link_libraries (${TARGET_NAME} ${CONAN_LIBS})
set_target_properties (${TARGET_NAME} PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON)
You can define (via -D command-line CMake option) the following build flags:
internal In this mode Jinja2C++ build script uses dependencies shipped as subprojects. Nothing needs to be provided externally.external-boost Deprecated, removed in 3.0: the same as internal, except that the opt-in Boost pieces (big-integer backend, adapter tests) take Boost from find_package.external In this mode all dependencies should be provided externally. Paths to fmt, nonstd-* libs and, for the opt-in Boost pieces, boost should be specified via standard CMake variables (like CMAKE_PREFIX_PATH or libname_DIR)conan-build Special mode for building Jinja2C++ via conan recipe.nlohmann_json) - the JSON bindings the tests check, a list of nlohmann_json and boost_json; it decides which JSON libraries the build fetches or finds. Every binding is installed as a package component whatever this lists.none) - the big-integer backend every TemplateEnv starts with. With none, templates print, compare and convert integers of any size, and arithmetic whose result leaves int64_t fails with IntegerOutOfRange. With boost, that arithmetic is exact, as in Python, through Boost.Multiprecision's cpp_int (header-only, compiled into the library; consumers need nothing). The installed package reports the choice as jinja2cpp_BIGINT (CMake) and bigint (pkg-config).\N{name} escapes in template string literals, as Python does, with a table of the Unicode 15.0 character names and aliases (about 450 KB of read-only data). With FALSE the table is left out and a \N{...} escape is a parse error.Python integers have no size limit. Jinja2C++ computes in int64_t and calls a big-integer backend only when a result leaves that range. A backend is a table of plain functions (jinja2cpp/big_int_backend.h), so one built separately plugs into a prebuilt library:
#include <jinja2cpp/big_int/boost_cpp_int.h> // needs the Boost.Multiprecision and Boost.Config headers
jinja2::TemplateEnv env;
env.SetBigIntBackend(jinja2::BoostCppIntBackend()); // or the library's built-in one, or your own
jinja2::Template tpl(&env);
tpl.Load("{{ 2 ** 100 }}"); // renders 1267650600228229401496703205376
jinja2cpp/big_int/boost_cpp_int.h is also the sample for writing a backend over another library. env.SetBigIntBackend(nullptr) turns big-integer arithmetic off for one environment.
Jinja2C++ tries to use standard versions of std::variant, std::string_view and std::optional if possible.
Thanks to @flexferrum for creating this library, for being one of the brightest minds in software engineering community. Rest in peace, friend.
Thanks to @manu343726 for CMake scripts improvement, bug hunting, and fixing and conan.io packaging.
Thanks to @martinmoene for the perfectly implemented xxx-lite libraries.
Thanks to @vitaut for the amazing text formatting library.
Thanks to @martinus for the fast hash maps implementation.
Values print the way Python Jinja2 prints them (str()): booleans as True/False,
floats in shortest round-trip form with .0 on whole numbers (2.0, 0.30000000000000004,
1e+16), lists, tuples and dicts as [1, 'a'], (1, 2), {'a': 1} instead of nothing,
and join converts non-string items instead of dropping them. Templates that relied on
true/false or on 8-digit floats need updating. Integer % and ** now give integers (10 % 3 is 1, not 1.0).
jinja2::Value stores a C++ float as a float (its last alternative): IsFloat() and
AsFloat() read it, and IsDouble() is false for it, so code that read Value(1.5f) as a
double must test for both. A template sees the Python float its shortest digits name, so
12.123f prints 12.123 (not 12.123000144958496) and compares equal to 12.123; values
coming back from a template are double. Reflect keeps float members float.
A single newline at the end of a template is removed, as Jinja2 does by default; set
Settings::keepTrailingNewline to keep it. \r\n and \r in templates become \n, or
Settings::newlineSequence when set. trim_blocks now removes only a newline directly
after a tag (not spaces before it) and no longer applies after {% raw %}, and -%}
strips all following whitespace, several newlines included.
Tag delimiters and line statement/comment prefixes are configurable (Settings::variableStartString,
blockStartString, commentStartString, their ...EndString pairs, lineStatementPrefix,
lineCommentPrefix); set lineStatementPrefix = "#" for the old useLineStatements.
As in Jinja2, an end delimiter inside a string literal or open brackets does not end a tag
({{ {'a': {'b': 1}} }} parses), and a stray }}, %} or in text is text, not an error.
JINJA2CPP_USE_REGEX CMake option is deprecated and ignored.batch filter addedslice filter addedformat filter addedtojson filter addedstriptags filter addedcenter filter addedxmlattr filter addedraw/endraw tags added'a' * 5 will produce 'aaaaa')meta/endmeta tags) added-fPIC flag added to Linux build configurationblock content if child doesn't override this blockvariant, string_view and optionaldefault attribute added to the map filter (#48)GenericList type (#66)Valuefilter tag support added to the template parser (#44)escape filter support added to the template parser (#140)capitalize filter support added to the template parser (#137)set tag added to the parser (#45)loop.depth and loop.depth0 variables support addedTemplateEnv*context special param|) operator precedence (#47)endblock taginclude and for tagsJINJA2CPP_MSVC_RUNTIME_TYPE CMake variableinclude, import and from statements implemented. Now it's possible to include other templates and use macros from other templates.with statement implementeddo statement implementedRenderAsString method now returns nonstd::expected instead of regular std::stringimport, extends and include generate errors if parsed without TemplateEnv setexternal dependency management mode by defaultapplymacro filter added which allows applying arbitrary macro as a filter#}None and undefined are told apart. A jinja2::Value() (empty) in the context, in
globals or returned by a user callable is Python's None: it prints as None and
is defined is true. A missing name, attribute or item is undefined and still prints
as nothing. undefined == none is now false, and the none test is available.
Undefined values follow Jinja2's Undefined: printing, testing and iterating one is
fine (nope|length is 0), but an attribute, item or call of a missing name, or of a
missing attribute ({{ user.adress.city }}), is an error (ErrorCode::UndefinedError)
instead of rendering empty. Settings::undefinedPolicy selects Strict (any use but
is defined and default fails), Chainable (attributes of undefined stay undefined)
or Debug (prints {{ name }}), as Jinja2's undefined option does.
Settings fields follow the camelBack naming: m_defaultMetadataType is
defaultMetadataType and extensions.Do is extensions.doStatement. useLineStatements
(use lineStatementPrefix = "#") and the unused jinja2CompatMode/Jinja2CompatMode are
removed. TemplateEnv keeps its state behind a pointer: its layout no longer changes when
it gains a member, templates keep that state alive (they may outlive the environment), and
the internal IsEqual, TimePoint and TimeStamp are gone. ApplyGlobals passes the
globals as const ValuesMap&. New: TemplateEnv::FromString (Jinja2 env.from_string),
AddTest/RemoveTest/FindTest for custom tests.
Macro bodies, {% call %} bodies, a recursive loop's loop(...) and super() resolve
names where they are defined, as in Jinja2, not where they are called: a macro no longer
sees its caller's loop variables or set names, and a call body sees the names around the
{% call %}, not the macro's. A macro that read its caller's loop variable should take it
as an argument: {% macro row(item) %}{{ item }}{% endmacro %}{% for item in items %}{{ row(item) }}{% endfor %}.
A macro or call body kept and called after the loop, macro call or include that defined it
has ended reads that frame's names as undefined.
jinja2::ValuesMap keeps insertion order, like a Python dict: it is based on
jinja2::OrderedMap (jinja2cpp/utils/ordered_map.h) instead of std::unordered_map.
Iteration over a context dict, its repr ({{ d }}), items(), first, join and
xmlattr follow the order the C++ code inserted the keys, the same on every standard
library, and **kwargs handed to a user callable follow the call order. Dict reprs no
longer sort their keys; reflected structs still list theirs sorted. The map keeps the
std::unordered_map interface except the bucket API, node handles, hash_function,
key_eq and get_allocator, and no longer converts to std::unordered_map<std::string, Value>&.
The library's SOVERSION is now 2.
jinja2::Reflect accepts nlohmann::ordered_json (nlohmann 3.9 and later), whose objects
keep document order; nlohmann::json objects iterate sorted, as before.
Boost is no longer a dependency of the library (docs/tasks/0230). The installed package
names no Boost in any dependencies mode: the internal mode no longer installs Boost's
headers and libraries next to Jinja2C++, the installed CMake config no longer calls
find_dependency(Boost), and the exported target no longer defines
BOOST_SYSTEM_NO_DEPRECATED and BOOST_ERROR_CODE_HEADER_ONLY. A consumer that used the
Boost installed with Jinja2C++, or relied on those defines, must provide them itself. The
external-boost dependencies mode is deprecated (it now builds as internal), and Conan
builds use a header-only Boost.