diff --git a/commitizen/changelog.py b/commitizen/changelog.py index 3492852aa5..13c0597c28 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -37,6 +37,7 @@ Environment, FileSystemLoader, PackageLoader, + Template, ) from commitizen import defaults @@ -147,17 +148,26 @@ def order_changelog_tree(tree: Iterable, change_type_order: List[str]) -> Iterab return sorted_tree +def get_changelog_template( + loader: Optional[BaseLoader] = None, template: Optional[str] = None +) -> Template: + loader = ChoiceLoader( + [ + FileSystemLoader("."), + loader or PackageLoader("commitizen", "templates"), + ] + ) + env = Environment(loader=loader, trim_blocks=True) + return env.get_template(template or DEFAULT_TEMPLATE) + + def render_changelog( tree: Iterable, loader: Optional[BaseLoader] = None, template: Optional[str] = None, **kwargs, ) -> str: - loader = ChoiceLoader( - [FileSystemLoader("."), loader or PackageLoader("commitizen", "templates")] - ) - env = Environment(loader=loader, trim_blocks=True) - jinja_template = env.get_template(template or DEFAULT_TEMPLATE) + jinja_template = get_changelog_template(loader, template or DEFAULT_TEMPLATE) changelog: str = jinja_template.render(tree=tree, **kwargs) return changelog diff --git a/commitizen/cli.py b/commitizen/cli.py index 5369a11050..f1a99a46c9 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -293,6 +293,11 @@ def __call__(self, parser, namespace, kwarg, option_string=None): "If not set, it will generate changelog from the start" ), }, + { + "name": "--export-template", + "default": None, + "help": "Export the changelog template into this file instead of rendering it", + }, *deepcopy(tpl_arguments), ], }, diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 8a43699a6e..9fe1470aa9 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -1,6 +1,7 @@ import os.path from difflib import SequenceMatcher from operator import itemgetter +from pathlib import Path from typing import Callable, Dict, List, Optional from commitizen import bump, changelog, defaults, factory, git, out @@ -49,8 +50,13 @@ def __init__(self, config: BaseConfig, args): self.tag_format = args.get("tag_format") or self.config.settings.get( "tag_format" ) - self.template = args.get("template") or self.config.settings.get("template") + self.template = ( + args.get("template") + or self.config.settings.get("template") + or self.cz.template + ) self.extras = args.get("extras") or {} + self.export_template_to = args.get("export_template") def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str: """Try to find the 'start_rev'. @@ -102,6 +108,11 @@ def write_changelog( changelog_out = changelog_hook(changelog_out, partial_changelog) changelog_file.write(changelog_out) + def export_template(self): + tpl = changelog.get_changelog_template(self.cz.template_loader, self.template) + src = Path(tpl.filename) + Path(self.export_template_to).write_text(src.read_text()) + def __call__(self): commit_parser = self.cz.commit_parser changelog_pattern = self.cz.changelog_pattern @@ -113,6 +124,9 @@ def __call__(self): Callable ] = self.cz.changelog_message_builder_hook + if self.export_template_to: + return self.export_template() + if not changelog_pattern or not commit_parser: raise NoPatternMapError( f"'{self.config.settings['name']}' rule does not support changelog" diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index da95085987..adf577e309 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -4,8 +4,10 @@ from textwrap import dedent import pytest +from jinja2 import FileSystemLoader from pytest_mock import MockFixture +from commitizen import __file__ as commitizen_init from commitizen import cli, git from commitizen.changelog import DEFAULT_TEMPLATE from commitizen.commands.changelog import Changelog @@ -1089,3 +1091,44 @@ def test_changelog_template_extra_quotes( changelog = project_root / "CHANGELOG.md" assert changelog.read_text() == "no-quote - single quotes - double quotes" + + +def test_export_changelog_template_from_default( + mocker: MockFixture, + tmp_commitizen_project: Path, +): + project_root = Path(tmp_commitizen_project) + target = project_root / "changelog.jinja" + src = Path(commitizen_init).parent / "templates" / DEFAULT_TEMPLATE + + args = ["cz", "changelog", "--export-template", str(target)] + + mocker.patch.object(sys, "argv", args) + cli.main() + + assert target.exists() + assert target.read_text() == src.read_text() + + +def test_export_changelog_template_from_plugin( + mocker: MockFixture, + tmp_commitizen_project: Path, + mock_plugin: BaseCommitizen, + tmp_path: Path, +): + project_root = Path(tmp_commitizen_project) + target = project_root / "changelog.jinja" + filename = "template.jinja" + src = tmp_path / filename + tpl = "I am a custom template" + src.write_text(tpl) + mock_plugin.template_loader = FileSystemLoader(tmp_path) + mock_plugin.template = filename + + args = ["cz", "changelog", "--export-template", str(target)] + + mocker.patch.object(sys, "argv", args) + cli.main() + + assert target.exists() + assert target.read_text() == tpl