Template development¶
Learn how to create and customise templates for Griffonner.
Template basics¶
Griffonner templates use Jinja2 syntax with rich context from Griffe’s Python code analysis.
Template structure¶
Templates are Jinja2 files that generate documentation from Python code analysis:
# {{ obj.name }}
{% if obj.docstring -%}
{{ obj.docstring.description }}
{% endif %}
## Classes
{% for class_obj in obj.classes.values() %}
### {{ class_obj.name }}
{{ class_obj.docstring.summary if class_obj.docstring else "No description" }}
{% endfor %}
Template context¶
Every template receives this context:
obj
- The Griffe object (module, class, function) being documentedgriffe_target
- The target module/object path- Custom variables from frontmatter
custom_vars
Template discovery¶
Griffonner searches for templates in this order:
- Custom directories (specified with
--template-dir
) docs/templates/
in your projecttemplates/
in current directory- Built-in templates
Template organisation¶
Organise templates by language and style:
templates/
├── python/
│ ├── default/
│ │ ├── module.md.jinja2
│ │ ├── class.md.jinja2
│ │ └── function.md.jinja2
│ ├── sphinx-style/
│ │ └── module.md.jinja2
│ └── gitlab-wiki/
│ └── module.md.jinja2
└── rust/
└── default/
└── module.md.jinja2
Creating templates¶
1. Understanding the Griffe object¶
The obj
context variable is a Griffe object with rich metadata:
{# Module information #}
{{ obj.name }} {# Module name #}
{{ obj.filepath }} {# Source file path #}
{{ obj.docstring.description }} {# Module docstring #}
{# Members #}
{% for name, member in obj.members.items() %}
{{ member.kind.value }} {# "class", "function", "attribute" #}
{{ member.name }}
{% endfor %}
2. Working with classes¶
{% for class_obj in obj.classes.values() %}
# {{ class_obj.name }}
{# Inheritance #}
{% if class_obj.bases %}
Inherits from: {{ class_obj.bases | join(', ') }}
{% endif %}
{# Methods #}
{% for method in class_obj.methods.values() %}
## {{ method.name }}
{{ method.docstring.summary if method.docstring }}
```python
{{ method.signature }}
```
{% endfor %}
{% endfor %}
3. Working with functions¶
{% for func in obj.functions.values() %}
# {{ func.name }}
{{ func.docstring.summary if func.docstring }}
## Signature
```python
{{ func.signature }}
```
{% if func.docstring and func.docstring.parameters %}
## Parameters
{% for param in func.docstring.parameters %}
- **{{ param.name }}**: {{ param.description }}
{% endfor %}
{% endif %}
{% if func.docstring and func.docstring.returns %}
## Returns
{{ func.docstring.returns.description }}
{% endif %}
{% endfor %}
4. Using custom variables¶
Access custom variables from frontmatter:
{# From frontmatter custom_vars #}
{{ emoji | default("📚") }} {{ title | default(obj.name) }}
Version: {{ version | default("Unknown") }}
Category: {{ category | default("General") }}
Corresponding frontmatter:
---
template: "python/custom/module.md.jinja2"
custom_vars:
emoji: "🔧"
title: "Utilities module"
version: "2.1.0"
category: "Core"
---
Advanced techniques¶
Conditional sections¶
Show sections only when content exists:
{% if obj.classes %}
## Classes
{% for class_obj in obj.classes.values() %}
### {{ class_obj.name }}
{{ class_obj.docstring.summary if class_obj.docstring }}
{% endfor %}
{% endif %}
{% if obj.functions %}
## Functions
{# ... #}
{% endif %}
Filtering members¶
Hide private members and filter by type:
{# Public functions only #}
{% for func in obj.functions.values() %}
{% if not func.name.startswith('_') %}
### {{ func.name }}
{{ func.docstring.summary if func.docstring }}
{% endif %}
{% endfor %}
{# Filter by decorator #}
{% for func in obj.functions.values() %}
{% if func.decorators and 'property' in func.decorators | map(attribute='value') %}
### {{ func.name }} (property)
{% endif %}
{% endfor %}
Cross-references¶
Create links between documentation:
{# Link to other modules #}
See also: [{{ related_module }}]({{ related_module }}.md)
{# Generate table of contents #}
## Contents
{% for class_obj in obj.classes.values() %}
- [{{ class_obj.name }}](#{{ class_obj.name | lower }})
{% endfor %}
Multiple output formats¶
Templates can generate any text format:
Markdown template:
reStructuredText template:
{{ obj.name }}
{{ "=" * obj.name | length }}
Overview
--------
{{ obj.docstring.description if obj.docstring }}
JSON template:
{
"module": "{{ obj.name }}",
"description": "{{ obj.docstring.summary if obj.docstring }}",
"classes": [
{% for class_obj in obj.classes.values() -%}
{
"name": "{{ class_obj.name }}",
"methods": {{ class_obj.methods.keys() | list | tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
Template validation¶
Validate your templates before using them:
# Check syntax
griffonner validate python/custom/module.md.jinja2
# Test with actual data
griffonner generate test-file.md --output test-output/
Built-in template reference¶
Griffonner ships with these built-in templates:
python/default/module.md.jinja2
¶
- Comprehensive module documentation
- Classes with method tables
- Functions with signatures and parameters
- Attributes with type information
python/default/class.md.jinja2
¶
- Detailed class documentation
- Inheritance information
- Method documentation with examples
- Property and attribute listing
python/default/function.md.jinja2
¶
- Function signature and parameters
- Return type documentation
- Docstring parsing with sections
- Example usage if available
Best practices¶
1. Template organisation¶
- Use consistent naming:
<language>/<style>/<type>.md.jinja2
- Group related templates in directories
- Include README files explaining template sets
2. Content structure¶
- Start with object name and summary
- Group related information in sections
- Use consistent heading levels
- Include metadata (source file, generation time)
3. Error handling¶
Handle missing information gracefully:
{# Safe docstring access #}
{{ obj.docstring.summary if obj.docstring else "No description available" }}
{# Check for empty collections #}
{% if obj.functions %}
## Functions
{% for func in obj.functions.values() %}
{# ... #}
{% endfor %}
{% else %}
*No public functions.*
{% endif %}
4. Performance¶
- Avoid complex logic in templates
- Use filters efficiently
- Cache expensive operations in custom variables
5. Maintainability¶
- Comment complex template logic
- Use descriptive variable names
- Keep templates focused on single purposes
Sharing templates¶
Template packages¶
Create reusable template packages:
my-templates/
├── README.md
├── python/
│ └── sphinx-style/
│ ├── module.md.jinja2
│ ├── class.md.jinja2
│ └── function.md.jinja2
└── examples/
└── usage.md
Usage¶
Distribution¶
- Share via Git repositories
- Package as Python distributions
- Include example frontmatter
- Document template variables and context
Troubleshooting¶
Common errors¶
Template not found:
- Check template path spelling
- Verify template directory exists
- Use griffonner templates
to list available templates
Syntax errors:
- Check Jinja2 syntax - Balance{% %}
and {{ }}
tags
- Use griffonner validate
to check syntax
Missing variables:
- Check frontmatter custom_vars
section
- Use default filters: {{ variable | default("fallback") }}
- Test with minimal frontmatter first
Debugging templates¶
Add debug output to templates:
{# Debug: Show available variables #}
<!--
Object type: {{ obj.__class__.__name__ }}
Object name: {{ obj.name }}
Available attrs: {{ obj.__dict__.keys() | list }}
-->
{# Continue with template... #}
Next steps¶
- Explore the template reference
- Learn about watch mode for development
- Check the CLI reference for validation commands