Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ out
venv*
*.bkp
*.bak
*.profraw

# Eclipse
.metadata
Expand Down
44 changes: 41 additions & 3 deletions src/nunavut/jinja/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,23 @@ def __init__(
env_builder.add_globals(**additional_globals)
env_builder.set_embed_auditing_info(embed_auditing_info)

self._env = env_builder.create(language_context)
self._env = self._create_environment(env_builder, language_context)

def _create_environment(
self, env_builder: CodeGenEnvironmentBuilder, language_context: nunavut.lang.LanguageContext
) -> CodeGenEnvironment:
"""
Create the code generation environment for this generator.

This is a template method that subclasses can override to customize the environment
with generator-specific filters, tests, and globals. The base implementation creates
an environment with language-specific filters only.

:param env_builder: The environment builder with basic configuration already set.
:param language_context: The language context for this generator.
:return: A configured CodeGenEnvironment instance.
"""
return env_builder.create(language_context)

@property
def dsdl_loader(self) -> DSDLTemplateLoader:
Expand Down Expand Up @@ -772,9 +788,31 @@ def is_deprecated(instance: pydsdl.Any) -> bool:

def __init__(self, namespace: nunavut.Namespace, resource_types: int = ResourceType.ANY.value, **kwargs: Any):
super().__init__(namespace, resource_types=resource_types, **kwargs)

def _create_environment(
self, env_builder: CodeGenEnvironmentBuilder, language_context: nunavut.lang.LanguageContext
) -> CodeGenEnvironment:
"""
Create the environment with DSDL-specific filters and tests.

This override adds DSDL-specific functionality like type_to_template, type_to_include_path,
and DSDL tests (is_service_request, is_service_response, etc.) that should only be
available in DSDL type templates, not in support templates.

:param env_builder: The environment builder with basic configuration already set.
:param language_context: The language context for this generator.
:return: A configured CodeGenEnvironment with DSDL-specific additions.
"""
env = super()._create_environment(env_builder, language_context)

# Add DSDL-specific tests (is_service_request, is_service_response, etc.)
for test_name, test in self._create_all_dsdl_tests().items():
self._env.add_test(test_name, test)
self._env.add_conventional_methods_to_environment(self)
env.add_test(test_name, test)

# Add DSDL-specific filters (type_to_template, type_to_include_path, etc.)
env.add_conventional_methods_to_environment(self)

return env

# +-----------------------------------------------------------------------+
# | AbstractGenerator
Expand Down
76 changes: 74 additions & 2 deletions test/gentest_filters/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from nunavut import Namespace
from nunavut._namespace import build_namespace_tree # deprecated
from nunavut.jinja import DSDLCodeGenerator
from nunavut.jinja.jinja2.exceptions import TemplateAssertionError
from nunavut.jinja import DSDLCodeGenerator, SupportGenerator
from nunavut.jinja.jinja2.exceptions import TemplateAssertionError, UndefinedError
from nunavut.lang import Language, LanguageClassLoader, LanguageContextBuilder


Expand Down Expand Up @@ -448,3 +448,75 @@ def test_filter_to_template_unique(gen_paths):
actual = foo_file.read()

assert expected == actual


def test_support_generator_filter_isolation(gen_paths): # type: ignore
"""
Test that DSDL-specific filters are not available in SupportGenerator templates.
This verifies that suggestion #2 (creating different environment objects per generator)
is properly implemented, preventing filter leaking between generators.
"""
root_path = str(gen_paths.dsdl_dir / Path("uavcan"))
output_path = gen_paths.out_dir / "filter_isolation"
compound_types = read_namespace(root_path, [])
language_context = LanguageContextBuilder().set_target_language("c").create()
namespace = build_namespace_tree(compound_types, root_path, output_path, language_context)

# Create a template that tries to use a DSDL-specific filter
template_dir = gen_paths.out_dir / "filter_isolation_templates"
template_dir.mkdir(parents=True, exist_ok=True)
template_file = template_dir / "serialization.j2"

# This template tries to use type_to_template, which should only be available in DSDLCodeGenerator
with open(template_file, "w", encoding="utf-8") as f:
f.write("{{ 'test' | type_to_template }}")

# Create a SupportGenerator with this template
from nunavut._utilities import ResourceType
support_generator = SupportGenerator(
namespace,
resource_types=ResourceType.SERIALIZATION_SUPPORT.value,
templates_dir=template_dir
)

# Attempting to generate should fail because type_to_template filter is not available
# in SupportGenerator's environment
with pytest.raises((TemplateAssertionError, UndefinedError, AttributeError)) as exc_info:
list(support_generator.generate_all())

# Verify the error message mentions the missing filter
error_message = str(exc_info.value)
assert "type_to_template" in error_message.lower() or "no filter" in error_message.lower()


def test_dsdl_generator_has_dsdl_filters(gen_paths): # type: ignore
"""
Test that DSDL-specific filters ARE available in DSDLCodeGenerator templates.
This is the positive test case for filter isolation.
"""
root_path = str(gen_paths.dsdl_dir / Path("uavcan"))
output_path = gen_paths.out_dir / "dsdl_filter_test"
compound_types = read_namespace(root_path, [])
language_context = LanguageContextBuilder().set_target_language("c").create()
namespace = build_namespace_tree(compound_types, root_path, output_path, language_context)

# Create a template that uses DSDL-specific filters
template_dir = gen_paths.out_dir / "dsdl_filter_test_templates"
template_dir.mkdir(parents=True, exist_ok=True)
template_file = template_dir / "Any.j2"

# This template uses type_to_template, which should be available in DSDLCodeGenerator
with open(template_file, "w", encoding="utf-8") as f:
f.write("{{ T | type_to_template }}")

# Create a DSDLCodeGenerator with this template
dsdl_generator = DSDLCodeGenerator(
namespace,
templates_dir=template_dir
)

# This should work without errors
generated_files = list(dsdl_generator.generate_all())

# Verify that files were generated successfully
assert len(generated_files) > 0
Loading