diff --git a/project_forge/configurations/pattern.py b/project_forge/configurations/pattern.py index 978742d..344c1b7 100644 --- a/project_forge/configurations/pattern.py +++ b/project_forge/configurations/pattern.py @@ -155,7 +155,8 @@ class Pattern(BaseModel): """The configuration of a pattern.""" questions: List[Question] = Field( - description="A list of question objects that define the available context variables for project generation." + default_factory=list, + description="A list of question objects that define the available context variables for project generation.", ) template_location: Union[str, Location] = Field( description=( diff --git a/project_forge/rendering/render.py b/project_forge/rendering/render.py new file mode 100644 index 0000000..a6ce1bf --- /dev/null +++ b/project_forge/rendering/render.py @@ -0,0 +1,38 @@ +"""Functions to render a composition using answered questions.""" + +import logging +import pprint +from io import StringIO +from pathlib import Path + +from jinja2 import Environment + +from project_forge.rendering.environment import InheritanceMap +from project_forge.rendering.expressions import render_expression + +logger = logging.getLogger(__name__) + + +def render_env(env: Environment, path_list: InheritanceMap, context: dict, destination_path: Path): + """Render the templates in path_list using context.""" + context_stream = StringIO() + pprint.pprint(context, context_stream) + logger.debug(f"Rendering templates using context:\n{context_stream.getvalue()}") + + for path, val in path_list.items(): + dst_rel_path = render_expression(path, context) + full_path = destination_path / dst_rel_path + + if not val.exists(): + raise ValueError(f"Path {path} does not exist") + + if val.is_file(): + logger.debug(f"Writing file {dst_rel_path}") + template = env.get_template(path) + full_path.parent.mkdir(parents=True, exist_ok=True) + full_path.write_text(template.render(context)) + elif val.is_dir(): + logger.debug(f"Writing directory {dst_rel_path}") + full_path.mkdir(parents=True, exist_ok=True) + else: + raise ValueError(f"Path {val} does not exist") diff --git a/tests/test_rendering/test_render.py b/tests/test_rendering/test_render.py new file mode 100644 index 0000000..3e135e3 --- /dev/null +++ b/tests/test_rendering/test_render.py @@ -0,0 +1,66 @@ +"""Tests for project_forge.rendering.render.py.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from project_forge.configurations.composition import read_composition_file +from project_forge.rendering.environment import load_environment +from project_forge.rendering.render import render_env +from project_forge.context_builder.context import build_context +from project_forge.rendering.templates import catalog_inheritance +from project_forge.tui import ask_question + + +@pytest.fixture +def template(tmp_path: Path): + """A simple template structure.""" + template_dir = tmp_path / "template" + template_dir.mkdir() + template_dir.joinpath("{{ repo_name }}").mkdir() + template_dir.joinpath("{{ repo_name }}", "file.txt").write_text("{{ key }}") + pattern_content = 'template_location = "{{ repo_name }}"\n[extra_context]\nkey = "value"\n' + template_dir.joinpath("pattern.toml").write_text(pattern_content) + composition_content = ( + "overlays = [\n" + '{ pattern_location = "pattern.toml" }\n' + "]\n" + "[extra_context]\n" + 'repo_name = "my-project"\n' + ) + template_dir.joinpath("composition.toml").write_text(composition_content) + return template_dir + + +def test_render_env_for_file(tmp_path: Path, template: Path): + # Assemble + composition = read_composition_file(template / "composition.toml") + context = build_context(composition, ask_question) + template_paths = [overlay.pattern.template_location.resolve() for overlay in composition.overlays] + inheritance = catalog_inheritance(template_paths) + env = load_environment(inheritance) + + # Act + render_env(env, inheritance, context, tmp_path) + + # Assert + file_path = tmp_path / "my-project" / "file.txt" + assert file_path.exists() + assert file_path.read_text() == "value" + + +def test_render_env_for_directory(tmp_path: Path, template: Path): + # Assemble + composition = read_composition_file(template / "composition.toml") + context = build_context(composition, ask_question) + template_paths = [overlay.pattern.template_location.resolve() for overlay in composition.overlays] + inheritance = catalog_inheritance(template_paths) + env = load_environment(inheritance) + + # Act + render_env(env, inheritance, context, tmp_path) + + # Assert + dir_path = tmp_path / "my-project" + assert dir_path.exists()