Writing Templates¶
Templates are Jinja2 files that define the output format for your generated code. embgen automatically discovers templates based on their naming convention.
Template Naming Convention¶
Templates must follow this naming pattern:
Examples:
| Template File | Output Extension | CLI Flag |
|---|---|---|
template.h.j2 |
.h |
--h |
template.py.j2 |
.py |
--py |
template.md.j2 |
.md |
--md |
template.c.j2 |
.c |
--c |
template.rs.j2 |
.rs |
--rs |
Template Location¶
Templates must be in a templates/ subdirectory of your domain:
mydomain/
├── __init__.py
├── generator.py
├── models.py
└── templates/
├── template.h.j2
├── template.py.j2
└── template.md.j2
Available Context¶
Your generator's render() method determines what variables are available in templates:
def render(self, config: Any, template: Template) -> str:
return template.render(
config=cfg, # The full config object
name=cfg.name, # Individual fields
items=cfg.items,
generated_on="...", # Extra context
)
Common Variables¶
Most generators provide:
| Variable | Type | Description |
|---|---|---|
config |
Your config class | Full configuration object |
name |
str |
Configuration name |
generated_on |
str |
Timestamp |
Jinja2 Basics¶
Variables¶
Loops¶
Conditionals¶
{% if item.description %}
// {{ item.description }}
{% endif %}
{% if item.optional %}
{{ item.name }}?
{% else %}
{{ item.name }}
{% endif %}
Filters¶
{{ name | upper }} {# MYNAME #}
{{ name | lower }} {# myname #}
{{ name | title }} {# Myname #}
{{ items | length }} {# 5 #}
{{ items | first }} {# First item #}
{{ items | last }} {# Last item #}
{{ value | default(0) }} {# Use 0 if value is undefined #}
Template Examples¶
C Header Template¶
// {{ name }} - Generated by embgen
// DO NOT EDIT - Generated on {{ generated_on }}
#ifndef {{ name | upper }}_H
#define {{ name | upper }}_H
#include <stdint.h>
#include <stdbool.h>
{% for item in config.items %}
{% if item.description %}
/** {{ item.description }} */
{% endif %}
#define {{ name | upper }}_{{ item.name | upper }} {{ item.value }}
{% endfor %}
{% for item in config.items %}
{% if item.enums %}
typedef enum {
{% for e in item.enums %}
{{ item.name | upper }}_{{ e.name | upper }} = {{ e.value }},{% if e.description %} /**< {{ e.description }} */{% endif %}
{% endfor %}
} {{ item.name | title }}Enum;
{% endif %}
{% endfor %}
#endif // {{ name | upper }}_H
Python Template¶
"""{{ name }} - Generated by embgen.
DO NOT EDIT - Generated on {{ generated_on }}.
"""
from dataclasses import dataclass
from enum import IntEnum
{% if config.items | selectattr("enums") | list %}
{% for item in config.items %}
{% if item.enums %}
class {{ item.name | title }}(IntEnum):
"""{{ item.description or item.name }}"""
{% for e in item.enums %}
{{ e.name }} = {{ e.value }}{% if e.description %} # {{ e.description }}{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% for item in config.items %}
@dataclass
class {{ item.name | title }}:
"""{{ item.description or item.name }}"""
value: int = {{ item.value }}
{% if item.enums %}
enum: {{ item.name | title }} = {{ item.name | title }}.{{ item.enums[0].name }}
{% endif %}
{% endfor %}
Markdown Template¶
# {{ name }}
> Generated by embgen on {{ generated_on }}
## Overview
{{ config.description or "No description provided." }}
## Items
| Name | Value | Description |
| ---- | ----- | ----------- |
{% for item in config.items %}
| `{{ item.name }}` | {{ item.value }} | {{ item.description or "-" }} |
{% endfor %}
{% for item in config.items %}
{% if item.enums %}
### {{ item.name }} Values
| Name | Value | Description |
| ---- | ----- | ----------- |
{% for e in item.enums %}
| `{{ e.name }}` | {{ e.value }} | {{ e.description or "-" }} |
{% endfor %}
{% endif %}
{% endfor %}
Advanced Jinja2 Features¶
Whitespace Control¶
Use - to strip whitespace:
embgen configures Jinja2 with trim_blocks=True and lstrip_blocks=True by default.
Macros¶
Define reusable template functions:
{% macro type_to_c(type_name) -%}
{% if type_name == "uint8" %}uint8_t
{% elif type_name == "uint16" %}uint16_t
{% elif type_name == "uint32" %}uint32_t
{% elif type_name == "string" %}char*
{% else %}void*
{% endif %}
{%- endmacro %}
typedef struct {
{% for field in item.fields %}
{{ type_to_c(field.type) }} {{ field.name }};
{% endfor %}
} {{ item.name }};
Includes¶
Split templates into reusable parts:
templates/
├── template.h.j2
├── _macros.h.j2 # Shared macros (not discovered as output)
└── _types.h.j2 # Shared type definitions
Note
Files starting with _ are not discovered as output templates.
Set Variables¶
{% set sorted_items = config.items | sort(attribute="value") %}
{% for item in sorted_items %}
...
{% endfor %}
Loop Variables¶
{% for item in items %}
{{ loop.index }} {# 1-indexed counter #}
{{ loop.index0 }} {# 0-indexed counter #}
{{ loop.first }} {# True if first iteration #}
{{ loop.last }} {# True if last iteration #}
{{ loop.length }} {# Total number of items #}
{% endfor %}
Custom Filters¶
You can add custom filters in your generator:
def render(self, config: Any, template: Template) -> str:
# Add custom filter to environment
template.environment.filters["to_snake_case"] = self._to_snake_case
return template.render(config=cfg)
@staticmethod
def _to_snake_case(s: str) -> str:
"""Convert CamelCase to snake_case."""
import re
return re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()
Usage in template: