Type safe "Keep it simple, stupid" text templates for C++
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples
include
src
syntax-files
tests
.appveyor.yml
.clang-format
.travis.yml
CMakeLists.txt
LICENSE
README.md
pre-commit

README.md

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 Build Status Build status
Develop Build Status Build status

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:

kiste2cpp hello_world.kiste > hello_world.h

Using the generated code

And now we use it in our C++ project like this

#include <iostream>
#include <hello_world.h>
#include <kiste/raw.h>

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:

$ ./examples/0_hello_world/hello_world
    Hello World!

Yeah!

Short Reference

  • %<whatever> C++ code
  • $class <name> starts a template class
  • $class <name> : <base> starts a template class, which inherits from a base class
  • $member <class> <name> adds a template as a member
  • $endclass ends a template class
  • ${<expression>} send expression to serializer (which takes care of encoding, quoting, escaping, etc)
  • $raw{<expression>} send expression to the ostream directly (no escaping)
  • $call{<function>} 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<string>
%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 <name> [: <base>] and end with $endclass.

This is a stand-alone class:

$class base
% // Some stuff
$endclass

And this is a derived class

%#include <base.h>
$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 <Helper.h>
$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:

struct ratio
{
  int num;
  int den;
};

struct ratio_policy
{
  template <typename SerializerT>
  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):

struct html : kiste::html
{
  html(std::ostream& os) : kiste::html(os)
  {
  }

  template <typename SerializerT, typename T>
  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.