Skip to content

Commit

Permalink
Add space trimming control and global environment settings
Browse files Browse the repository at this point in the history
* Add space trimming control and global environment settings

*  [skip ci] Update ReadMe.md

* [skip ci] Update CMakeLists.txt install script
  • Loading branch information
flexferrum authored Aug 31, 2018
1 parent 0bd1418 commit 9550971
Show file tree
Hide file tree
Showing 6 changed files with 540 additions and 50 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ install(TARGETS ${LIB_TARGET_NAME}
ARCHIVE DESTINATION lib/static)

install (DIRECTORY include/ DESTINATION include)
install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include)
install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake)

add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests)
66 changes: 63 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar
- [Reflection](#reflection)
- ['set' statement](#set-statement)
- ['extends' statement](#extends-statement)
- [Macros](#macros)
- [Error reporting](#error-reporting)
- [Other features](#other-features)
- [Current Jinja2 support](#current-jinja2-support)
Expand All @@ -45,8 +46,9 @@ Main features of Jinja2Cpp:
- Partial support for both narrow- and wide-character strings both for templates and parameters.
- Built-in reflection for C++ types.
- Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions.
- Basic control statements (set, for, if).
- Control statements (set, for, if).
- Templates extention.
- Macros
- Rich error reporting.

For instance, this simple code:
Expand Down Expand Up @@ -381,6 +383,54 @@ private:

'**extends**' statement here defines the template to extend. Set of '**block**' statements after defines actual filling of the corresponding blocks from the extended template. If block from the extended template contains something (like ```namespaced_decls``` from the example above), this content can be rendered with help of '**super()**' function. In other case the whole content of the block will be replaced. More detailed description of template inheritance feature can be found in [Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/#template-inheritance).

## Macros
Ths sample above violates 'DRY' rule. It contains the code which could be generalized. And Jinja2 supports features for such kind generalization. This feature called '**macros**'. The sample can be rewritten the following way:
```c++
{% macro MethodsDecl(class, access) %}
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', access) %}
{{ method.fullPrototype }};
{% endfor %}
{% endmacro %}

class {{ class.name }}
{
public:
{{ MethodsDecl(class, ['Public']) }}
protected:
{{ MethodsDecl(class, ['Protected']) }}
private:
{{ MethodsDecl(class, ['Private', 'Undefined']) }}
};

{% endfor %}
```
`MethodsDecl` statement here is a **macro** which takes two arguments. First one is a class with method definitions. The second is a tuple of access specifiers. Macro takes non-implicit methods from the methods collection (`rejectattr('isImplicit')` filter) then select such methods which have right access specifier (`selectattr('accessType', 'in', access)`), then just prints the method full prototype. Finally, the macro is invoked as a regular function call: `MethodsDecl(class, ['Public'])` and replaced with rendered macro body.
There is another way to invoke macro: the **call** statement. Simply put, this is a way to call macro with *callback*. Let's take another sample:
```c++
{% macro InlineMethod(resultType='void', methodName, methodParams=[]) %}
inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} )
{
{{ caller() }}
}
{% endmacro %}
{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' enum.enumName ~ ' e']) %}
switch (e)
{
{% for item in enum.items %}
case {{enum.nsScope}}::{{item}}:
return "{{item}}";
{% endfor %}
}
return "Unknown Item";
{% endcall %}
```

Here is an `InlineMacro` which just describe the inline method definition skeleton. This macro doesn't contain the actual method body. Instead of this it calls `caller` special function. This function invokes the special **callback** macro which is a body of `call` statement. And this macro can have parameters as well. More detailed this mechanics described in the [Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/#macros).

## Error reporting
It's difficult to write complex template completely without errors. Missed braces, wrong characters, incorrect names... Everything is possible. So, it's crucial to be able to get informative error report from the template engine. Jinja2Cpp provides such kind of report. ```Template::Load``` method (and TemplateEnv::LoadTemplate respectively) return instance of ```ErrorInfo``` class which contains details about the error. These details include:
- Error code
Expand Down Expand Up @@ -463,8 +513,10 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way,
- 'for' statement (with 'else' branch and 'if' part support)
- 'extends' statement
- 'set' statement
- 'extends' statement
- 'extends'/'block' statements
- 'macro'/'call' statements
- recursive loops
- space control

# Supported compilers
Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled feature):
Expand All @@ -476,7 +528,7 @@ Compilation of Jinja2Cpp tested on the following compilers (with C++14 enabled f
- Microsoft Visual Studio 2017 x86

# Build and install
Jinja2Cpp has got only one external dependency: boost library (at least version 1.55). Because of types from boost are used inside library, you should compile both your projects and Jinja2Cpp library with similar compiler settings. Otherwise ABI could be broken.
Jinja2Cpp has got only two external dependency: boost library (at least version 1.55) and expected-lite. Because of types from boost are used inside library, you should compile both your projects and Jinja2Cpp library with similar compiler settings. Otherwise ABI could be broken.

In order to compile Jinja2Cpp you need:

Expand Down Expand Up @@ -562,6 +614,14 @@ target_link_libraries(YourTarget
```

# Changelog
## Version 0.9
* Support of 'extents'/'block' statements
* Support of 'macro'/'call' statements
* Rich error reporting
* Support for recursive loops
* Support for space control before and after control blocks
* Improve reflection

## Version 0.6
* A lot of filters has been implemented. Full set of supported filters listed here: https://github.com/flexferrum/Jinja2Cpp/issues/7
* A lot of testers has been implemented. Full set of supported testers listed here: https://github.com/flexferrum/Jinja2Cpp/issues/8
Expand Down
13 changes: 13 additions & 0 deletions include/jinja2cpp/template_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace jinja2
class IErrorHandler;
class IFilesystemHandler;

struct Settings
{
bool useLineStatements = false;
bool trimBlocks = false;
bool lstripBlocks = false;
};

class TemplateEnv
{
public:
Expand All @@ -25,6 +32,11 @@ class TemplateEnv
{
return m_errorHandler;
}

const Settings& GetSettings() const {return m_settings;}
Settings& GetSettings() {return m_settings;}
void SetSettings(const Settings& setts) {m_settings = setts;}

void AddFilesystemHandler(std::string prefix, FilesystemHandlerPtr h)
{
m_filesystemHandlers.push_back(FsHandler{std::move(prefix), h});
Expand All @@ -44,6 +56,7 @@ class TemplateEnv
FilesystemHandlerPtr handler;
};
std::vector<FsHandler> m_filesystemHandlers;
Settings m_settings;
};

} // jinja2
Expand Down
51 changes: 27 additions & 24 deletions src/template_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,16 @@ class TemplateImpl : public ITemplateImpl
TemplateImpl(TemplateEnv* env)
: m_env(env)
{
if (env)
m_settings = env->GetSettings();
}

auto GetRenderer() const {return m_renderer;}

boost::optional<ErrorInfoTpl<CharT>> Load(std::basic_string<CharT> tpl, std::string tplName)
{
m_template = std::move(tpl);
TemplateParser<CharT> parser(&m_template, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName));
TemplateParser<CharT> parser(&m_template, m_settings, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName));

auto parseResult = parser.Parse();
if (!parseResult)
Expand All @@ -72,31 +74,31 @@ class TemplateImpl : public ITemplateImpl

void Render(std::basic_ostream<CharT>& os, const ValuesMap& params)
{
if (m_renderer)
if (!m_renderer)
return;

InternalValueMap intParams;
for (auto& ip : params)
{
InternalValueMap intParams;
for (auto& ip : params)
{
auto valRef = &ip.second.data();
auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef);
if (!newParam)
intParams[ip.first] = std::move(ValueRef(static_cast<const Value&>(*valRef)));
else
intParams[ip.first] = newParam.get();
}
RendererCallback callback(this);
RenderContext context(intParams, &callback);
InitRenderContext(context);
OutStream outStream(
[this, &os](const void* ptr, size_t length) {
os.write(reinterpret_cast<const CharT*>(ptr), length);
},
[this, &os](const InternalValue& val) {
Apply<visitors::ValueRenderer<CharT>>(val, os);
}
);
m_renderer->Render(outStream, context);
auto valRef = &ip.second.data();
auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef);
if (!newParam)
intParams[ip.first] = std::move(ValueRef(static_cast<const Value&>(*valRef)));
else
intParams[ip.first] = newParam.get();
}
RendererCallback callback(this);
RenderContext context(intParams, &callback);
InitRenderContext(context);
OutStream outStream(
[this, &os](const void* ptr, size_t length) {
os.write(reinterpret_cast<const CharT*>(ptr), length);
},
[this, &os](const InternalValue& val) {
Apply<visitors::ValueRenderer<CharT>>(val, os);
}
);
m_renderer->Render(outStream, context);
}

InternalValueMap& InitRenderContext(RenderContext& context)
Expand Down Expand Up @@ -150,6 +152,7 @@ class TemplateImpl : public ITemplateImpl

private:
TemplateEnv* m_env;
Settings m_settings;
std::basic_string<CharT> m_template;
RendererPtr m_renderer;
};
Expand Down
Loading

0 comments on commit 9550971

Please sign in to comment.