Skip to content

Commit 9550971

Browse files
authored
Add space trimming control and global environment settings
* Add space trimming control and global environment settings * [skip ci] Update ReadMe.md * [skip ci] Update CMakeLists.txt install script
1 parent 0bd1418 commit 9550971

File tree

6 files changed

+540
-50
lines changed

6 files changed

+540
-50
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ install(TARGETS ${LIB_TARGET_NAME}
172172
ARCHIVE DESTINATION lib/static)
173173

174174
install (DIRECTORY include/ DESTINATION include)
175+
install (DIRECTORY thirdparty/nonstd/expected-light/include/ DESTINATION include)
175176
install (FILES cmake/public/FindJinja2Cpp.cmake DESTINATION cmake)
176177

177178
add_test(NAME jinja2cpp_tests COMMAND jinja2cpp_tests)

README.md

+63-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar
2525
- [Reflection](#reflection)
2626
- ['set' statement](#set-statement)
2727
- ['extends' statement](#extends-statement)
28+
- [Macros](#macros)
2829
- [Error reporting](#error-reporting)
2930
- [Other features](#other-features)
3031
- [Current Jinja2 support](#current-jinja2-support)
@@ -45,8 +46,9 @@ Main features of Jinja2Cpp:
4546
- Partial support for both narrow- and wide-character strings both for templates and parameters.
4647
- Built-in reflection for C++ types.
4748
- Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions.
48-
- Basic control statements (set, for, if).
49+
- Control statements (set, for, if).
4950
- Templates extention.
51+
- Macros
5052
- Rich error reporting.
5153

5254
For instance, this simple code:
@@ -381,6 +383,54 @@ private:
381383

382384
'**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).
383385

386+
## Macros
387+
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:
388+
```c++
389+
{% macro MethodsDecl(class, access) %}
390+
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', access) %}
391+
{{ method.fullPrototype }};
392+
{% endfor %}
393+
{% endmacro %}
394+
395+
class {{ class.name }}
396+
{
397+
public:
398+
{{ MethodsDecl(class, ['Public']) }}
399+
protected:
400+
{{ MethodsDecl(class, ['Protected']) }}
401+
private:
402+
{{ MethodsDecl(class, ['Private', 'Undefined']) }}
403+
};
404+
405+
{% endfor %}
406+
```
407+
408+
`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.
409+
410+
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:
411+
412+
```c++
413+
{% macro InlineMethod(resultType='void', methodName, methodParams=[]) %}
414+
inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} )
415+
{
416+
{{ caller() }}
417+
}
418+
{% endmacro %}
419+
420+
{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' enum.enumName ~ ' e']) %}
421+
switch (e)
422+
{
423+
{% for item in enum.items %}
424+
case {{enum.nsScope}}::{{item}}:
425+
return "{{item}}";
426+
{% endfor %}
427+
}
428+
return "Unknown Item";
429+
{% endcall %}
430+
```
431+
432+
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).
433+
384434
## Error reporting
385435
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:
386436
- Error code
@@ -463,8 +513,10 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way,
463513
- 'for' statement (with 'else' branch and 'if' part support)
464514
- 'extends' statement
465515
- 'set' statement
466-
- 'extends' statement
516+
- 'extends'/'block' statements
517+
- 'macro'/'call' statements
467518
- recursive loops
519+
- space control
468520

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

478530
# Build and install
479-
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.
531+
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.
480532

481533
In order to compile Jinja2Cpp you need:
482534

@@ -562,6 +614,14 @@ target_link_libraries(YourTarget
562614
```
563615

564616
# Changelog
617+
## Version 0.9
618+
* Support of 'extents'/'block' statements
619+
* Support of 'macro'/'call' statements
620+
* Rich error reporting
621+
* Support for recursive loops
622+
* Support for space control before and after control blocks
623+
* Improve reflection
624+
565625
## Version 0.6
566626
* A lot of filters has been implemented. Full set of supported filters listed here: https://github.com/flexferrum/Jinja2Cpp/issues/7
567627
* A lot of testers has been implemented. Full set of supported testers listed here: https://github.com/flexferrum/Jinja2Cpp/issues/8

include/jinja2cpp/template_env.h

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ namespace jinja2
1414
class IErrorHandler;
1515
class IFilesystemHandler;
1616

17+
struct Settings
18+
{
19+
bool useLineStatements = false;
20+
bool trimBlocks = false;
21+
bool lstripBlocks = false;
22+
};
23+
1724
class TemplateEnv
1825
{
1926
public:
@@ -25,6 +32,11 @@ class TemplateEnv
2532
{
2633
return m_errorHandler;
2734
}
35+
36+
const Settings& GetSettings() const {return m_settings;}
37+
Settings& GetSettings() {return m_settings;}
38+
void SetSettings(const Settings& setts) {m_settings = setts;}
39+
2840
void AddFilesystemHandler(std::string prefix, FilesystemHandlerPtr h)
2941
{
3042
m_filesystemHandlers.push_back(FsHandler{std::move(prefix), h});
@@ -44,6 +56,7 @@ class TemplateEnv
4456
FilesystemHandlerPtr handler;
4557
};
4658
std::vector<FsHandler> m_filesystemHandlers;
59+
Settings m_settings;
4760
};
4861

4962
} // jinja2

src/template_impl.h

+27-24
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,16 @@ class TemplateImpl : public ITemplateImpl
5353
TemplateImpl(TemplateEnv* env)
5454
: m_env(env)
5555
{
56+
if (env)
57+
m_settings = env->GetSettings();
5658
}
5759

5860
auto GetRenderer() const {return m_renderer;}
5961

6062
boost::optional<ErrorInfoTpl<CharT>> Load(std::basic_string<CharT> tpl, std::string tplName)
6163
{
6264
m_template = std::move(tpl);
63-
TemplateParser<CharT> parser(&m_template, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName));
65+
TemplateParser<CharT> parser(&m_template, m_settings, tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName));
6466

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

7375
void Render(std::basic_ostream<CharT>& os, const ValuesMap& params)
7476
{
75-
if (m_renderer)
77+
if (!m_renderer)
78+
return;
79+
80+
InternalValueMap intParams;
81+
for (auto& ip : params)
7682
{
77-
InternalValueMap intParams;
78-
for (auto& ip : params)
79-
{
80-
auto valRef = &ip.second.data();
81-
auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef);
82-
if (!newParam)
83-
intParams[ip.first] = std::move(ValueRef(static_cast<const Value&>(*valRef)));
84-
else
85-
intParams[ip.first] = newParam.get();
86-
}
87-
RendererCallback callback(this);
88-
RenderContext context(intParams, &callback);
89-
InitRenderContext(context);
90-
OutStream outStream(
91-
[this, &os](const void* ptr, size_t length) {
92-
os.write(reinterpret_cast<const CharT*>(ptr), length);
93-
},
94-
[this, &os](const InternalValue& val) {
95-
Apply<visitors::ValueRenderer<CharT>>(val, os);
96-
}
97-
);
98-
m_renderer->Render(outStream, context);
83+
auto valRef = &ip.second.data();
84+
auto newParam = boost::apply_visitor(visitors::InputValueConvertor(), *valRef);
85+
if (!newParam)
86+
intParams[ip.first] = std::move(ValueRef(static_cast<const Value&>(*valRef)));
87+
else
88+
intParams[ip.first] = newParam.get();
89+
}
90+
RendererCallback callback(this);
91+
RenderContext context(intParams, &callback);
92+
InitRenderContext(context);
93+
OutStream outStream(
94+
[this, &os](const void* ptr, size_t length) {
95+
os.write(reinterpret_cast<const CharT*>(ptr), length);
96+
},
97+
[this, &os](const InternalValue& val) {
98+
Apply<visitors::ValueRenderer<CharT>>(val, os);
9999
}
100+
);
101+
m_renderer->Render(outStream, context);
100102
}
101103

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

151153
private:
152154
TemplateEnv* m_env;
155+
Settings m_settings;
153156
std::basic_string<CharT> m_template;
154157
RendererPtr m_renderer;
155158
};

0 commit comments

Comments
 (0)