#Mustache-ish Template Engine
#define FIO_MUSTACHE
#include "fio-stl.h"A Mustache-ish template parser and renderer with variables, sections, inverted sections, partials, delimiter changes, comments, and optional YAML front matter callbacks. Logic-less-ish, because C callbacks still get a vote. Implemented in ./104 mustache.h.
Defining FIO_MUSTACHE pulls in string / fio_bstr helpers as needed.
#Configuration Macros
#FIO_MUSTACHE_MAX_DEPTH
#ifndef FIO_MUSTACHE_MAX_DEPTH
#define FIO_MUSTACHE_MAX_DEPTH 128
#endifMaximum parser / builder nesting depth for sections and partials.
#FIO_MUSTACHE_PRESERVE_PADDING
#ifndef FIO_MUSTACHE_PRESERVE_PADDING
#define FIO_MUSTACHE_PRESERVE_PADDING 0
#endifWhen enabled, preserves padding for stand-alone variables and partial templates.
#FIO_MUSTACHE_LAMBDA_SUPPORT
#ifndef FIO_MUSTACHE_LAMBDA_SUPPORT
#define FIO_MUSTACHE_LAMBDA_SUPPORT 0
#endifWhen enabled, stores raw section text for lambda-style section handling through is_lambda.
#FIO_MUSTACHE_ISOLATE_PARTIALS
#ifndef FIO_MUSTACHE_ISOLATE_PARTIALS
#define FIO_MUSTACHE_ISOLATE_PARTIALS 1
#endifWhen enabled, limits partial lookup scope to the context of the partial's section.
#FIO_MUSTACHE_SECURE_PATH
#ifndef FIO_MUSTACHE_SECURE_PATH
#define FIO_MUSTACHE_SECURE_PATH 1
#endifWhen enabled, skips partial names that attempt ../ path traversal.
#Syntax Quick Map
| Syntax | Meaning |
|---|---|
{{name}} |
escaped variable |
{{{name}}} |
raw variable |
{{&name}} |
raw variable |
{{#items}}...{{/items}} |
truthy section / array iteration |
{{^items}}...{{/items}} |
inverted section |
{{>partial}} |
partial template include |
{{! comment}} |
comment |
{{=<% %>=}} |
delimiter change |
{{.}} |
current context |
Dot lookup such as person.name is supported by repeatedly calling get_var for path segments.
#Types
#fio_mustache_s
typedef struct fio_mustache_s fio_mustache_s;Opaque parsed template object. Internally it is stored as a fio_bstr instruction stream.
Ownership: fio_mustache_load returns an owned template. Free it with fio_mustache_free. fio_mustache_dup creates a copy; it is not a shared reference counter in this header.
#fio_mustache_bargs_s
typedef struct fio_mustache_bargs_s fio_mustache_bargs_s;Forward declaration for build arguments. The full struct is listed below.
#fio_mustache_load_args_s
typedef struct {
fio_buf_info_s data;
fio_buf_info_s filename;
fio_buf_info_s (*load_file_data)(fio_buf_info_s filename, void *udata);
void (*free_file_data)(fio_buf_info_s file_data, void *udata);
void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata);
void *udata;
} fio_mustache_load_args_s;Load / parse settings.
Members:
data- preloaded template bytesfilename- file name, also used as base path for partialsload_file_data- callback that loads file contentsfree_file_data- callback that frees loaded file contentson_yaml_front_matter- called when front matter is foundudata- user data for load callbacks
If both load_file_data and free_file_data are missing, defaults use fio_bstr_readfile and fio_bstr_free. If one is custom, provide the matching cleanup. Either filename or data is required.
#fio_mustache_bargs_s
struct fio_mustache_bargs_s {
void *(*write_text)(void *udata, fio_buf_info_s txt);
void *(*write_text_escaped)(void *udata, fio_buf_info_s raw);
void *(*get_var)(void *ctx, fio_buf_info_s name);
size_t (*array_length)(void *ctx);
void *(*get_var_index)(void *ctx, size_t index);
fio_buf_info_s (*var2str)(void *var);
int (*var_is_truthful)(void *ctx);
void (*release_var)(void *ctx);
int (*is_lambda)(void **udata,
void *ctx,
fio_buf_info_s raw_template_section);
void *ctx;
void *udata;
};Build / render callbacks.
Members:
write_text- writes raw template textwrite_text_escaped- writes escaped variable textget_var- returns a value fornamefromctxarray_length- returns array length, or0for non-array valuesget_var_index- returns an item context by indexvar2str- returns a string view for a valuevar_is_truthful- returns non-zero for truthy valuesrelease_var- releases values returned by callbacksis_lambda- handles lambda sections when lambda support is enabledctx- root render contextudata- output / user pointer; the final value is returned byfio_mustache_build
If both writer callbacks are missing, defaults append to a fio_bstr in udata. The returned pointer should be freed with fio_bstr_free.
#Loading
#fio_mustache_load
SFUNC fio_mustache_s *fio_mustache_load(fio_mustache_load_args_s settings);
#define fio_mustache_load(...) \
fio_mustache_load((fio_mustache_load_args_s){__VA_ARGS__})Loads and parses a template from settings.data or settings.filename.
If filename is provided and data is empty, the loader callback is used. If both are provided, data is parsed and filename is kept for partial path resolution.
Returns: a parsed template object, or NULL on error.
#fio_mustache_free
SFUNC void fio_mustache_free(fio_mustache_s *m);Frees a parsed template. Accepts NULL.
#fio_mustache_dup
SFUNC fio_mustache_s *fio_mustache_dup(fio_mustache_s *m);Copies a parsed template using fio_bstr_copy.
Returns: the copied template, or NULL if m == NULL.
#Rendering
#fio_mustache_build
SFUNC void *fio_mustache_build(fio_mustache_s *m, fio_mustache_bargs_s);
#define fio_mustache_build(m, ...) \
fio_mustache_build((m), ((fio_mustache_bargs_s){__VA_ARGS__}))Renders m with the supplied context and callbacks.
Returns: the final udata value. If m == NULL, returns args.udata unchanged.
During rendering:
- escaped variables call
write_text_escaped - raw variables call
write_text - sections render once for truthy non-arrays, or once per array item
- inverted sections render when
var_is_truthfulreturns zero - values returned from lookup / array callbacks are released with
release_var
#Partials and Files
Partials are loaded by name through load_file_data. The implementation tries each name with these suffixes:
.mustache.html- no suffix
Relative partials are searched against the including template's path chain, then as provided. With FIO_MUSTACHE_SECURE_PATH, ../ partials are skipped.
#YAML Front Matter
If a template begins with --- followed by a line break, the parser scans until a closing --- line and calls:
void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata);The front matter bytes are not rendered. The engine does not parse YAML for you. It has enough hobbies.
#Thread-Safety
Parsed templates are immutable during fio_mustache_build, so separate builds can use the same template as long as your callbacks and contexts are safe. Loading and freeing are caller-owned operations.
#Example
#define FIO_MUSTACHE
#include "fio-stl.h"
#include <stdio.h>
#include <string.h>
typedef struct {
const char *name;
} data_s;
static void *get_var(void *ctx, fio_buf_info_s name) {
data_s *d = (data_s *)ctx;
if (name.len == 4 && !memcmp(name.buf, "name", 4))
return (void *)d->name;
return NULL;
}
static fio_buf_info_s var2str(void *var) {
char *s = (char *)var;
return FIO_BUF_INFO2(s, strlen(s));
}
int main(void) {
char template_text[] = "Hello, {{name}}!\n";
data_s data = {.name = "World"};
fio_mustache_s *m = fio_mustache_load(
.data = FIO_BUF_INFO1(template_text));
if (!m)
return 1;
char *out = fio_mustache_build(m,
.ctx = &data,
.get_var = get_var,
.var2str = var2str);
if (out) {
fwrite(out, 1, fio_bstr_len(out), stdout);
fio_bstr_free(out);
}
fio_mustache_free(m);
return 0;
}