https://github.com/rbock/kiss-templates
Type safe "Keep it simple, stupid" text templates for C++
https://github.com/rbock/kiss-templates
Last synced: over 1 year ago
JSON representation
Type safe "Keep it simple, stupid" text templates for C++
- Host: GitHub
- URL: https://github.com/rbock/kiss-templates
- Owner: rbock
- License: bsd-2-clause
- Created: 2015-08-14T20:34:23.000Z (almost 11 years ago)
- Default Branch: develop
- Last Pushed: 2017-03-16T05:57:26.000Z (over 9 years ago)
- Last Synced: 2025-03-21T08:51:16.464Z (over 1 year ago)
- Language: C++
- Size: 172 KB
- Stars: 39
- Watchers: 7
- Forks: 9
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# kiss-templates
Type safe _"Keep it simple, stupid"_ text templates for C++. If you are familiar with the idea of text templates and with C++, you can learn how to use it in just a few minutes.
Branch / Compiler | clang-3.4, gcc-4.8 | VS14 2015
------------------| ----------------------|-------------
Master | [](https://travis-ci.org/rbock/kiss-templates) | [](https://ci.appveyor.com/project/rbock/kiss-templates/branch/master)
Develop | [](https://travis-ci.org/rbock/kiss-templates) | [](https://ci.appveyor.com/project/rbock/kiss-templates/branch/develop)
## How it works
Use kiste2cpp to turn text templates into type- and name-safe C++ code. Use this code to generate text from your data and the serializer of choice.
### What templates look like
Template are a mix of
- kiss template commands (there are VERY few)
- C++
- text
```
%namespace test
%{
$class Hello
%auto render() -> void
%{
Hello ${data.name}!
%}
$endclass
%}
```
### Generating C++ code
I bet you can guess what this all means (and it is documented below), so let's compile this into a C++ header file:
```sh
kiste2cpp hello_world.kiste > hello_world.h
```
### Using the generated code
And now we use it in our C++ project like this
```C++
#include
#include
#include
struct Data
{
std::string name;
};
int main()
{
const auto data = Data{"World"};
auto& os = std::cout;
auto serializer = kiste::raw{os};
auto hello = test::Hello(data, serializer);
hello.render();
}
```
### Output
Compile and run:
```sh
$ ./examples/0_hello_world/hello_world
Hello World!
```
Yeah!
## Short Reference
- `%` C++ code
- `$class ` starts a template class
- `$class : ` starts a template class, which inherits from a base class
- `$member ` adds a template as a member
- `$endclass` ends a template class
- `${}` send expression to serializer (which takes care of encoding, quoting, escaping, etc)
- `$raw{}` send expression to the ostream directly (no escaping)
- `$call{}` call a function (do not serialize result)
- `$|` trim left/right
- `$$` and `$%` escape `$` and `%` respectively
- Anything else inside a function of a template class is text
## The kiss template syntax
### C++
Any line that starts with zero or more spaces and a `%` is interpreted as C++.
For example
```
%#include
%namespace
%{
%auto foo() -> std::string
%{
return "bar";
%}
%}
```
There is really nothing to it, just a `%` at the beginning of the line
### Template classes
All text of the template is located in functions of template classes. Template classes start with `$class [: ]` and end with `$endclass`.
This is a stand-alone class:
```
$class base
% // Some stuff
$endclass
```
And this is a derived class
```
%#include
$class derived : base
% // Some other stuff
$endclass
```
In the generated code, the parent will also be the base of the child. They are linked in such a way that
- you can access the direct child via a `child` member variable in the parent
- you can access anything inherited from parent, grandparent, etc via a `parent` member
### Member templates
If you want to reuse some template elements or just want to organize your templates into smaller units and use composition.
A helper class
```
$class Helper
% // Some stuff
$endclass
```
And this is a composite class
```
%#include
$class composite
$member Helper helper
% // Some other stuff
$endclass
```
In the generated code, the member template will also be a member of the composite. They are linked in such a way that
- you can access the member via its name in the composite
- you can access the composite as `child` from the member template
### Serializing data
As you saw in the initial example, the generated template code is initialized with data and a serializer. You can serialize members of that data or in fact any C++ expression by enclosing it in `${}`. For instance
```
%for (const auto& entry : data.entries)
%{
First name: ${entry.first}
Last name: ${entry.last}
Size of names: ${entry.first.size() + entry.last.size()}
%}
```
The serializer takes care of the required escaping, quoting, encoding, etc.
### Raw data
Sometimes you need to actually output some text as is. Then use `$raw{expression}`. It will just pipe whatever you give it to the `ostream` directly.
### Calling functions
If you want to call a function without serializing the result (e.g. because the function returns `void`), you can enclose the call in `$call{}`.
### Trimming
- left-trim of a line: Zero or more spaces/tabs followed by `$|`
- right-trim of a line (including the trailing return): `$|` at the end of the line
For example:
```
%auto title() -> void
%{
$| Hello ${data.name}! $|
%}
```
This will get rid of the leading spaces and the trailing return, yielding something like
```
Hello Mr. Wolf!
```
### Escape sequences
- `$$` -> `$`
- `$%` -> `%`
### Text
Text is everything else, as long as it is inside a function of a template class.
## Serializer classes
The interface of a serializer has to have
- `auto text(const char*) -> void;` This function is called by the kiss templates to serialize their texts.
- `auto escape(...) -> void;` This function is called with expressions from `${whatever}`. Make it accept whatever you need and like.
Optionally, the serializer might offer
- `auto raw(...) -> void;` This function is called with expressions from `$raw{whatever}`. Make it accept whatever you need and like.
- `auto report_exception(long lineNo, const std::string& expression, std::exception_ptr e);` This function gets called if kiste2cpp is called with --report-exceptions. Handle reported exceptions here in any way you seem fit.
## Serializer policies
At some point you will probably want to serialize your types.
If extending of `kiste::html` for one or two types works,
extending for more types is not flexible, especially if you want to customize your serializer.
Then this is a moment when _serializer policies_ may help.
It allows you to implement a serializer for your output format in one class and
implement policies (how to serialize specific types) as separate classes.
Policies should know nothing about serializers, only how to convert their types to string (or even other types).
Have a look at example `ratio_policy` for our type `ratio`:
```C++
struct ratio
{
int num;
int den;
};
struct ratio_policy
{
template
void escape(SerializerT& serializer, const ratio& value)
{
serializer.escape(value.num);
if (value.den != 1)
{
serializer.escape('/');
serializer.escape(value.den);
}
}
};
```
Then we need to extend `kiste::html` (or your serializer) with one template method `escape(SerializerT&, const T& t)`:
```C++
struct html : kiste::html
{
html(std::ostream& os) : kiste::html(os)
{
}
template
void escape(SerializerT&, const T& t)
{
kiste::html::escape(t);
}
};
```
Finally we can build a serializer as `kiste::build_serializer(kiste::html{os}, ratio_policy{})`.
`kiste::build_serializer` accepts an arbitary number of policies and builds one serializer that uses them all.
This approach allows to keep knowledge about types in policies,
provide arguments to policies and even reuse them for different serializers.
Check out examples for more complex usages.
## Further education
This is pretty much it.
There are several examples in the `examples` folder. If you have questions, please do not hesitate to open an issue.