Skip to content

Commit c6793de

Browse files
committed
feat: add extra and template_name fields to generator configs
1 parent 2f976e7 commit c6793de

File tree

5 files changed

+123
-11
lines changed

5 files changed

+123
-11
lines changed

schemas/corsair-build-schema.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
"additionalProperties": false,
1616
"description": "Configuration for the Markdown generator.",
1717
"properties": {
18+
"extra": {
19+
"additionalProperties": true,
20+
"description": "Extra configuration parameters for the generator.",
21+
"title": "Extra",
22+
"type": "object"
23+
},
1824
"kind": {
1925
"const": "markdown",
2026
"default": "markdown",
@@ -34,6 +40,12 @@
3440
"title": "Title",
3541
"type": "string"
3642
},
43+
"template_name": {
44+
"default": "regmap.md.j2",
45+
"description": "Name of the Jinja2 template to use.",
46+
"title": "Template Name",
47+
"type": "string"
48+
},
3749
"print_images": {
3850
"default": false,
3951
"description": "Enable generating images for bit fields of a register.",
@@ -56,6 +68,7 @@
5668
"wavedrom": {
5769
"$ref": "#/$defs/corsair___generators__wavedrom__WaveDromGenerator__Config",
5870
"default": {
71+
"extra": {},
5972
"kind": "wavedrom",
6073
"vspace": 80,
6174
"hspace": 800,
@@ -79,6 +92,12 @@
7992
"additionalProperties": false,
8093
"description": "Configuration for the Verilog generator.",
8194
"properties": {
95+
"extra": {
96+
"additionalProperties": true,
97+
"description": "Extra configuration parameters for the generator.",
98+
"title": "Extra",
99+
"type": "object"
100+
},
82101
"kind": {
83102
"const": "verilog",
84103
"default": "verilog",
@@ -98,6 +117,12 @@
98117
"additionalProperties": false,
99118
"description": "Configuration for the VHDL generator.",
100119
"properties": {
120+
"extra": {
121+
"additionalProperties": true,
122+
"description": "Extra configuration parameters for the generator.",
123+
"title": "Extra",
124+
"type": "object"
125+
},
101126
"kind": {
102127
"const": "vhdl",
103128
"default": "vhdl",
@@ -117,6 +142,12 @@
117142
"additionalProperties": false,
118143
"description": "Configuration for the WaveDrom generator.",
119144
"properties": {
145+
"extra": {
146+
"additionalProperties": true,
147+
"description": "Extra configuration parameters for the generator.",
148+
"title": "Extra",
149+
"type": "object"
150+
},
120151
"kind": {
121152
"const": "wavedrom",
122153
"default": "wavedrom",

src/corsair/_generators/base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from corsair._model import Map
1616

1717
import jinja2
18-
from pydantic import BaseModel, ConfigDict
18+
from pydantic import BaseModel, ConfigDict, Field
1919

2020
from corsair._templates import TemplateEnvironment
2121

@@ -81,6 +81,9 @@ def __str__(self) -> str:
8181
class GeneratorConfig(BaseModel, ABC):
8282
"""Base configuration for a generator."""
8383

84+
extra: dict[str, Any] = Field(default_factory=dict)
85+
"""Extra configuration parameters for the generator."""
86+
8487
model_config = ConfigDict(
8588
extra="forbid",
8689
use_attribute_docstrings=True,
@@ -136,7 +139,7 @@ def __call__(self) -> TypeGenerator[Path, None, None]:
136139

137140
def _render_to_text(self, template_name: str, context: dict[str, Any]) -> str:
138141
"""Render text with Jinja2."""
139-
env = TemplateEnvironment(searchpaths=self.template_searchpaths)
142+
env = TemplateEnvironment(searchpath=self.template_searchpaths)
140143
template = env.get_template(template_name)
141144
return template.render(context)
142145

src/corsair/_generators/markdown.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
class MarkdownGenerator(Generator):
1616
"""Markdown file generator for a register map."""
1717

18-
template_name: str = "regmap.md.j2"
19-
2018
class Config(GeneratorConfig):
2119
"""Configuration for the Markdown generator."""
2220

@@ -29,6 +27,9 @@ class Config(GeneratorConfig):
2927
title: str = "Register Map"
3028
"""Document title."""
3129

30+
template_name: str = "regmap.md.j2"
31+
"""Name of the Jinja2 template to use."""
32+
3233
print_images: bool = False
3334
"""Enable generating images for bit fields of a register."""
3435

@@ -65,7 +66,7 @@ def _generate(self) -> TypeGenerator[Path, None, None]:
6566
}
6667

6768
yield self._render_to_file(
68-
template_name=self.template_name,
69+
template_name=self.config.template_name,
6970
context=context,
7071
file_name=self.config.file_name,
7172
)

src/corsair/_templates/env.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,47 @@
55
from pathlib import Path
66
from typing import TYPE_CHECKING, Any
77

8-
from jinja2 import Environment, FileSystemLoader, StrictUndefined
8+
from jinja2 import Environment, FileSystemLoader, StrictUndefined, TemplateNotFound
99

1010
from corsair._version import VERSION
1111

1212
if TYPE_CHECKING:
13+
from collections.abc import Callable
14+
1315
from typing_extensions import Self
1416

1517

18+
class _EnhancedFileSystemLoader(FileSystemLoader):
19+
"""Custom Jinja2 FileSystemLoader that supports both relative and absolute paths."""
20+
21+
def __init__(self, searchpath: list[Path], **kwargs: Any) -> None:
22+
super().__init__(searchpath=searchpath, **kwargs)
23+
24+
def get_source(
25+
self,
26+
environment: Environment,
27+
template: str,
28+
) -> tuple[str, str, Callable[[], bool]]:
29+
"""Get the template source, filename and reload helper for a template.
30+
31+
Checks for absolute paths first, otherwise delegates to the parent FileSystemLoader.
32+
"""
33+
template_path = Path(template)
34+
if template_path.is_absolute():
35+
if not template_path.is_file():
36+
raise TemplateNotFound(template)
37+
38+
mtime = template_path.stat().st_mtime
39+
with template_path.open("r", encoding="utf-8") as f:
40+
source = f.read()
41+
42+
# Return the source, filename (absolute path), and a mtime check function
43+
return source, str(template_path), lambda: template_path.stat().st_mtime == mtime
44+
45+
# If it's not an absolute path, delegate to the parent class method
46+
return super().get_source(environment, template)
47+
48+
1649
class TemplateEnvironment(Environment):
1750
"""Singleton environment for managing Jinja2 templates."""
1851

@@ -24,14 +57,16 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Self: # noqa: ARG003
2457
cls._instance = super().__new__(cls)
2558
return cls._instance
2659

27-
def __init__(self, searchpaths: list[Path] | None = None) -> None:
60+
def __init__(self, searchpath: list[Path] | None = None) -> None:
2861
"""Initialize the template environment."""
29-
if not searchpaths:
30-
searchpaths = []
31-
searchpaths.append(Path(__file__).parent)
62+
if not searchpath:
63+
searchpath = []
64+
# Always include the directory containing the built-in templates
65+
if Path(__file__).parent not in searchpath:
66+
searchpath.append(Path(__file__).parent)
3267

3368
super().__init__(
34-
loader=FileSystemLoader(searchpath=searchpaths),
69+
loader=_EnhancedFileSystemLoader(searchpath=searchpath),
3570
trim_blocks=True,
3671
lstrip_blocks=True,
3772
undefined=StrictUndefined, # to throw exception on any undefined variable within template

tests/generators/test_markdown.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,45 @@ def test_wavedrom_dump_json(tmp_path: Path, simple_regmap: csr.Map) -> None:
213213
assert expected_data_dir.is_dir()
214214
assert {f for f in expected_data_dir.iterdir() if f.suffix == ".svg"} == expected_svg_files
215215
assert {f for f in expected_data_dir.iterdir() if f.suffix == ".json"} == expected_json_files
216+
217+
218+
def test_custom_template_and_extra_param(tmp_path: Path, simple_regmap: csr.Map) -> None:
219+
"""Test generation with a custom template and extra config parameters."""
220+
custom_template_name = "custom_template.md.j2"
221+
custom_template_content = """
222+
# Custom Template Test
223+
224+
Registers:
225+
{% for item in regmap.items %}
226+
- {{ item.name }}
227+
{% endfor %}
228+
229+
Custom Param: {{ cfg.extra['my_param'] }}
230+
"""
231+
custom_template_file = tmp_path / custom_template_name
232+
custom_template_file.write_text(custom_template_content)
233+
234+
custom_key = "my_param"
235+
custom_value = "hello_world"
236+
config = csr.MarkdownGenerator.Config(template_name=str(custom_template_file), extra={custom_key: custom_value})
237+
238+
gen = csr.MarkdownGenerator(
239+
label="test_md_gen_custom_tmpl",
240+
register_map=simple_regmap,
241+
config=config,
242+
output_dir=tmp_path,
243+
)
244+
generated_files = list(gen())
245+
246+
# Check that the default file name is used if not specified
247+
expected_file = tmp_path / "regmap.md"
248+
assert generated_files == [expected_file]
249+
assert expected_file.is_file()
250+
251+
# Check the content of the generated file
252+
content = expected_file.read_text()
253+
assert "# Custom Template Test" in content
254+
assert "Registers:" in content
255+
assert "- reg1" in content
256+
assert "- reg2" in content
257+
assert f"Custom Param: {custom_value}" in content

0 commit comments

Comments
 (0)