Skip to content

Commit 0813430

Browse files
authored
Error reporting implementation
1 parent 63e6777 commit 0813430

37 files changed

+2239
-517
lines changed

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
[submodule "thirdparty/gtest"]
22
path = thirdparty/gtest
33
url = https://github.com/google/googletest.git
4+
[submodule "thirdparty/nonstd/variant-light"]
5+
path = thirdparty/nonstd/variant-light
6+
url = https://github.com/flexferrum/variant-lite.git
7+
[submodule "thirdparty/nonstd/expected-light"]
8+
path = thirdparty/nonstd/expected-light
9+
url = https://github.com/martinmoene/expected-lite.git

.travis.yml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,24 @@ matrix:
1515

1616
- os: linux
1717
compiler: gcc
18-
env: COMPILER=g++-6
18+
env:
19+
COMPILER=g++-6
1920
addons:
2021
apt:
2122
sources: ['ubuntu-toolchain-r-test']
2223
packages: ['cmake', 'g++-6']
2324

25+
- os: linux
26+
compiler: gcc
27+
env:
28+
COMPILER=g++-6
29+
CMAKE_OPTS="-DCOVERAGE_ENABLED=TRUE"
30+
BUILD_CONFIG=Debug
31+
addons:
32+
apt:
33+
sources: ['ubuntu-toolchain-r-test']
34+
packages: ['cmake', 'g++-6', 'lcov']
35+
2436
- os: linux
2537
compiler: gcc
2638
env: COMPILER=g++-7
@@ -56,9 +68,22 @@ install:
5668

5769
script:
5870
- if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi
71+
- if [[ "${BUILD_CONFIG}" == "" ]]; then export BUILD_CONFIG="Release"; fi
5972
- uname -a
6073
- $CXX --version
6174

6275
- mkdir -p build && cd build
63-
- cmake .. && cmake --build . --config Release -- -j4
64-
- ctest -C Release -V
76+
- cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG .. && cmake --build . --config $BUILD_CONFIG -- -j4
77+
- ctest -C $BUILD_CONFIG -V
78+
79+
80+
after_success:
81+
# Creating report
82+
- echo "Uploading code coverate report"
83+
- cd build
84+
- lcov --directory . --capture --output-file coverage.info # capture coverage info
85+
- lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter out system
86+
- lcov --list coverage.info #debug info
87+
# Uploading report to CodeCov
88+
- bash <(curl -s https://codecov.io/bash) -t "225d6d7a-2b71-4dbe-bf87-fbf75eb7c119" || echo "Codecov did not collect coverage reports"
89+
- fi

CMakeLists.txt

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ project(Jinja2Cpp VERSION 0.5.0)
33

44
list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
55

6+
if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED)
7+
message (STATUS "This is DEBUG build with enabled Code Coverage")
8+
set (CMAKE_BUILD_TYPE Debug)
9+
include(code_coverage)
10+
setup_target_for_coverage(${PROJECT_NAME}_coverage jinja2cpp_tests coverage)
11+
endif()
12+
613
set(GTEST_ROOT $ENV{GTEST_DIR} CACHE PATH "Path to GTest/GMock library root")
714
if (NOT UNIX)
815
set(BOOST_ROOT $ENV{BOOST_DIR} CACHE PATH "Path to boost library root")
@@ -22,6 +29,7 @@ set (CMAKE_CXX_STANDARD_REQUIRED ON)
2229
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
2330
if (NOT UNIX)
2431
set (EXTRA_TEST_LIBS "stdc++")
32+
set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wa,-mbig-obj")
2533
else ()
2634
include(CMakeFindDependencyMacro)
2735
find_dependency(Threads)
@@ -72,17 +80,28 @@ endif ()
7280

7381
if("${GTEST_ROOT}" STREQUAL "" AND WITH_TESTS)
7482
set (GTEST_ROOT ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/gtest/install)
83+
set (GTEST_SELF_BUILD ON)
7584
endif ()
7685

7786
if(NOT "${GTEST_ROOT}" STREQUAL "" AND WITH_TESTS)
7887
list (APPEND CMAKE_PREFIX_PATH ${GTEST_ROOT})
7988
set (Gtest_DIR ${GTEST_ROOT})
8089
message(STATUS "GTest library search path: ${Gtest_DIR}")
81-
find_package(GTest)
82-
if (MSVC AND NOT GTEST_INCLUDE_DIRS)
83-
set (GTEST_MSVC_SEARCH "MT")
90+
if (NOT GTEST_SELF_BUILD)
91+
find_package(GTest)
92+
else ()
93+
set (Gtest_DIR ${GTEST_ROOT})
8494
find_package(GTest)
8595
endif ()
96+
if (NOT GTEST_INCLUDE_DIRS)
97+
if (MSVC AND NOT GTEST_INCLUDE_DIRS)
98+
set (GTEST_MSVC_SEARCH "MT")
99+
find_package(GTest)
100+
else ()
101+
set (GTEST_BOTH_LIBRARIES "optimized;${GTEST_ROOT}/lib/libgtest.a;debug;${GTEST_ROOT}/lib/libgtestd.a;optimized;${GTEST_ROOT}/lib/libgtest_main.a;debug;${GTEST_ROOT}/lib/libgtest_maind.a")
102+
set (GTEST_INCLUDE_DIRS ${GTEST_ROOT}/include)
103+
endif ()
104+
endif ()
86105
endif()
87106

88107
if (WITH_TESTS)
@@ -91,32 +110,47 @@ else ()
91110
find_package(Boost)
92111
endif ()
93112

113+
add_subdirectory (thirdparty/nonstd)
114+
94115
include(collect_sources)
95116

96117
include_directories(
97118
${CMAKE_CURRENT_SOURCE_DIR}/include
98-
${Boost_INCLUDE_DIRS}
99-
${GTEST_INCLUDE_DIRS}
100119
)
101120

102121
set (LIB_TARGET_NAME jinja2cpp)
103122

104123
CollectSources(Sources Headers ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src)
105124
CollectSources(PublicSources PublicHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include)
106125

126+
if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE_ENABLED)
127+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
128+
endif()
129+
107130
add_library(${LIB_TARGET_NAME} STATIC
108131
${Sources}
109132
${Headers}
110133
${PublicHeaders}
111134
)
112135

113-
target_include_directories(${LIB_TARGET_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
136+
target_link_libraries(${LIB_TARGET_NAME} PUBLIC ThirdParty::nonstd Boost::boost Boost::system Boost::filesystem)
137+
138+
target_include_directories(${LIB_TARGET_NAME}
139+
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
114140

115141
if (WITH_TESTS)
116142
enable_testing()
143+
144+
message (STATUS "############# GTEST_INCLUDE_DIRS=${GTEST_INCLUDE_DIRS}")
145+
message (STATUS "############# GTEST_BOTH_LIBRARIES=${GTEST_BOTH_LIBRARIES}")
146+
147+
include_directories(
148+
${GTEST_INCLUDE_DIRS}
149+
)
150+
117151
CollectSources(TestSources TestHeaders ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/test)
118152
add_executable(jinja2cpp_tests ${TestSources} ${TestHeaders})
119-
target_link_libraries(jinja2cpp_tests ${GTEST_BOTH_LIBRARIES} ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS} Boost::system Boost::filesystem)
153+
target_link_libraries(jinja2cpp_tests ${GTEST_BOTH_LIBRARIES} ${LIB_TARGET_NAME} ${EXTRA_TEST_LIBS})
120154

121155
add_custom_command(
122156
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl

README.md

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
55
[![Build Status](https://travis-ci.org/flexferrum/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/flexferrum/Jinja2Cpp)
66
[![Build status](https://ci.appveyor.com/api/projects/status/19v2k3bl63jxl42f/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/Jinja2Cpp)
7+
[![Coverage Status](https://codecov.io/gh/flexferrum/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/flexferrum/Jinja2Cpp)
78
[![Github Releases](https://img.shields.io/github/release/flexferrum/Jinja2Cpp.svg)](https://github.com/flexferrum/Jinja2Cpp/releases)
89
[![Github Issues](https://img.shields.io/github/issues/flexferrum/Jinja2Cpp.svg)](http://github.com/flexferrum/Jinja2Cpp/issues)
910
[![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/flexferrum/Jinja2Cpp/master/LICENSE)
@@ -22,6 +23,8 @@ C++ implementation of big subset of Jinja2 template engine features. This librar
2223
- [The simplest case](#the-simplest-case)
2324
- [Reflection](#reflection)
2425
- ['set' statement](#set-statement)
26+
- ['extends' statement](#extends-statement)
27+
- [Error reporting](#error-reporting)
2528
- [Other features](#other-features)
2629
- [Current Jinja2 support](#current-jinja2-support)
2730
- [Supported compilers](#supported-compilers)
@@ -43,6 +46,7 @@ Main features of Jinja2Cpp:
4346
- Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions.
4447
- Basic control statements (set, for, if).
4548
- Templates extention.
49+
- Rich error reporting.
4650

4751
For instance, this simple code:
4852

@@ -246,7 +250,7 @@ inline const char* {{enum.enumName}}ToString({{enum.enumName}} e)
246250
};
247251
// ...
248252
```
249-
Every specified field will be reflected into Jinja2Cpp internal data structures and can be accessed from the template without additional efforts. Quite simple! As you can see, you can use 'dot' notation to access named members of some parameter as well, as index notation like this: `enum['enumName']`. With index notation you can access to the particular item of a list: `enum.items[3]` or `enum.items[itemIndex]` or `enum['items'][itemIndex]`.
253+
Every specified field will be reflected into Jinja2Cpp internal data structures and can be accessed from the template without additional efforts. Quite simply! As you can see, you can use 'dot' notation to access named members of some parameter as well, as index notation like this: `enum['enumName']`. With index notation you can access to the particular item of a list: `enum.items[3]` or `enum.items[itemIndex]` or `enum['items'][itemIndex]`.
250254

251255
### 'set' statement
252256
But what if enum `Animals` will be in the namespace?
@@ -301,6 +305,99 @@ std::string prefix = !descr.nsScope.empty() ? descr.nsScope + "::" : "";
301305
```
302306
I.e. left part of this expression (before 'if') is a true-branch of the statement. Right part (after 'else') - false-branch, which can be omitted. As a condition you can use any expression convertible to bool.
303307

308+
## 'extends' statement
309+
In general, C++ header files look similar to each other. Almost every header file has got header guard, block of 'include' directives and then block of declarations wrapped into namespaces. So, if you have several different Jinja2 templates for header files production it can be a good idea to extract the common header structure into separate template. Like this:
310+
```c++
311+
{% if headerGuard is defined %}
312+
#ifndef {{headerGuard}}
313+
#define {{headerGuard}}
314+
{% else %}
315+
#pragma once
316+
{% endif %}
317+
318+
{% for fileName in inputFiles | sort %}
319+
#include "{{fileName}}"
320+
{% endfor %}
321+
322+
{% for fileName in extraHeaders | sort %}
323+
{% if fileName is startsWith('<') %}
324+
#include {{fileName}}
325+
{% else %}
326+
#include "{{fileName}}"
327+
{% endif %}
328+
{% endfor %}
329+
330+
{% block generator_headers %}{% endblock %}
331+
332+
{% block namespaced_decls %}
333+
{% set ns = rootNamespace %}
334+
{#ns | pprint}
335+
{{rootNamespace | pprint} #}
336+
{% block namespace_content scoped %}{%endblock%}
337+
{% for ns in rootNamespace.innerNamespaces recursive %}namespace {{ns.name}}
338+
{
339+
{{self.namespace_content()}}
340+
{{ loop(ns.innerNamespaces) }}
341+
}
342+
{% endfor %}
343+
{% endblock %}
344+
345+
{% block global_decls %}{% endblock %}
346+
347+
{% if headerGuard is defined %}
348+
#endif // {{headerGuard}}
349+
{% endif %}
350+
```
351+
352+
In this sample you can see the '**block**' statements. They are placeholders. Each block is a part of generic template which can be replaced by more specific template which 'extends' generic:
353+
```c++
354+
{% extends "header_skeleton.j2tpl" %}
355+
356+
{% block namespaced_decls %}{{super()}}{% endblock %}
357+
358+
{% block namespace_content %}
359+
{% for class in ns.classes | sort(attribute="name") %}
360+
361+
class {{ class.name }}
362+
{
363+
public:
364+
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'equalto', 'Public') %}
365+
{{ method.fullPrototype }};
366+
{% endfor %}
367+
protected:
368+
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'equalto', 'Protected') %}
369+
{{ method.fullPrototype }};
370+
{% endfor %}
371+
private:
372+
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', ['Private', 'Undefined']) %}
373+
{{ method.fullPrototype }};
374+
{% endfor %}
375+
};
376+
377+
{% endfor %}
378+
{% endblock %}
379+
```
380+
381+
'**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).
382+
383+
## Error reporting
384+
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:
385+
- Error code
386+
- Error description
387+
- File name and position (1-based line, col) of the error
388+
- Location description
389+
390+
For example, this template:
391+
```
392+
{{ {'key'=,} }}
393+
```
394+
produces the following error message:
395+
```
396+
noname.j2tpl:1:11: error: Expected expression, got: ','
397+
{{ {'key'=,} }}
398+
---^-------
399+
```
400+
304401
## Other features
305402
The render procedure is stateless, so you can perform several renderings simultaneously in different threads. Even if you pass parameters:
306403

@@ -365,6 +462,7 @@ Currently, Jinja2Cpp supports the limited number of Jinja2 features. By the way,
365462
- 'for' statement (with 'else' branch and 'if' part support)
366463
- 'extends' statement
367464
- 'set' statement
465+
- 'extends' statement
368466
- recursive loops
369467

370468
# Supported compilers

0 commit comments

Comments
 (0)