C99 golang-style template engine
Be careful with untrusted input!
cgotpl comes with a simple CLI.
cgotpl ([TEMPLATE] | -f [FILENAME]) [DATA]Here TEMPLATE refers to a golang-style template string and DATA to a serialized JSON string.
The -f flag can be used to read the template from a file.
For instance:
cgotpl '{{ range . -}} {{.}} {{- end }}' '["h", "e", "ll", "o"]'Will print hello on stdout.
There are two central function defined in template.h:
// in is a pointer to a stream, which may be read to the end. dot is
// the inital dot value. out will be filled with the result of templating
// and needs to be freed by the caller. Returns 0 on success.
int template_eval_stream(stream* in, json_value* dot, char** out);
// tpl is a pointer to a template string from which up to n bytes are read.
// dot is the inital dot value. out will be filled with the result of
// templating and needs to be freed by the caller. Returns 0 on success.
int template_eval_mem(const char* tpl, size_t n, json_value* dot, char** out);An initalized json_value can be obtained from:
// Consumes an abitrary amount of bytes from st to parse a single JSON value
// into val. Returns 0 on success.
int json_parse(stream* st, json_value* val);A json_value needs to be freed with:
void json_value_free(json_value* val);A stream can be created with:
// Opens stream backed by data up to len bytes.
void stream_open_memory(stream* stream, const void* data, size_t len);
// Opens stream on the file referenced by filename. Returns 0 on success.
int stream_open_file(stream* stream, const char* filename);A stream needs be closed with:
// Closes stream. Returns 0 on success.
int stream_close(stream* stream);See cli/main.c for a complete example.
The template and JSON passed to cgotpl need to be utf-8 encoded, which is validated during consumption.
cmake version 3.16 or greater is required. Initialize cmake with:
cmake -S . -B build -DCMAKE_BUILD_TYPE=ReleaseThe following will produce the cgotpl CLI in build/cli/cgotpl:
cmake --build build --target cliThe library can be build with:
cmake --build build --target cgotplFor development a check (requires a go compiler) and fuzz (requires CC=clang) target exist.
Proper handling of non-ASCII characters in CLI arguments on Windows requires building with cosmocc. The CLI expects valid arguments to be utf-8 encoded, which cosmopolitan ensures on Windows.
The GitHub releases contain a binary built with cosmopolitan for x86_64. This binary is compatible with all major operating systems.
Most templating features, besides sub-templates, are implemented.
| Feature | State |
|---|---|
Trim whitespace {{- and -}} |
✅ |
{{/* comments */}} |
✅ |
Default Textual Representation {{pipeline}} |
✅ |
| Literals | 🚧 (Some escape sequences are missing) |
{{if pipeline}} T1 {{end}} |
✅ |
{{if pipeline}} T1 {{else}} T0 {{end}} |
✅ |
{{if p}} T1 {{else if p}} T0 {{end}} |
✅ |
{{range pipeline}} T1 {{end}} |
✅ |
{{range pipeline}} T1 {{else}} T0 {{end}} |
✅ |
{{break}} |
✅ |
{{continue}} |
✅ |
{{define}} |
✅ |
{{template "name" pipeline}} |
✅ |
{{block "name" pipeline}} T1 {{end}} |
✅ |
{{with pipeline}} T1 {{end}} |
✅ |
Field access .a.b.c |
✅ |
Variables $a := 1337 |
✅ |
range with variable initializer |
✅ |
Function invocation {{ func $value }} |
✅ |
Pipes {{ $value | func }} |
✅ |
cgotpl parses non-executed templates sloppy. Syntactical issues in non-executed branches may not lead to an error.
| Function | State |
|---|---|
| and | ✅ |
| call | 💩 (makes no sense for this implementation) |
| html | ✅ |
| index | ✅ |
| slice | ✅ |
| js | 🚧 (no check for printable codepoints) |
| len | ✅ |
| not | ✅ |
| or | ✅ |
| ✅ | |
| printf | 🚧 (many missing features, see below) |
| println | ✅ |
| urlquery | ✅ |
| eq | ✅ |
| ne | ✅ |
| lt | ✅ |
| le | ✅ |
| gt | ✅ |
| ge | ✅ |
This implementation cannot check for the correct upper bound on capacity for the 3-index slice function on arrays as a slice's capacity depends on implementation details of the go runtime.
The following set of plain format specifiers is supported: %e, %E, %f, %F, %g, %q, %s, %t, %v, %x and %X.
cgotpl doesn't implement any support for configuring signs, padding, minimum widths and precision.
All numbers are internally stored as double values, which can leads to corner-cases if mismatched values are passed to a specifier.
In addition the c and go standard library may disagree on certain formatting (printf) corner-cases, e.g. hexadecimal formatting of float exponents.
Sorry, I don't plan to write a full fledged printf implementation, right now.
I wanted to make something in C.