From d6b07457de2c6a79a94f5c35350cc517b8012cfe Mon Sep 17 00:00:00 2001 From: grahamhar Date: Mon, 1 Apr 2024 18:38:40 +0100 Subject: [PATCH] fix(changelog): Handle tag format without version pattern --- commitizen/changelog.py | 4 +- commitizen/changelog_formats/base.py | 40 +++++++-------- commitizen/changelog_formats/markdown.py | 16 +++++- tests/test_changelog_format_markdown.py | 62 ++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/commitizen/changelog.py b/commitizen/changelog.py index 7da4c7a4df..fc9d567002 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -98,13 +98,13 @@ def get_version_tags( ) -> list[GitTag]: valid_tags: list[GitTag] = [] TAG_FORMAT_REGEXS = { - "$version": str(scheme.parser.pattern), + "$version": scheme.parser.pattern, "$major": r"(?P\d+)", "$minor": r"(?P\d+)", "$patch": r"(?P\d+)", "$prerelease": r"(?P\w+\d+)?", "$devrelease": r"(?P\.dev\d+)?", - "${version}": str(scheme.parser.pattern), + "${version}": scheme.parser.pattern, "${major}": r"(?P\d+)", "${minor}": r"(?P\d+)", "${patch}": r"(?P\d+)", diff --git a/commitizen/changelog_formats/base.py b/commitizen/changelog_formats/base.py index ac237fc3e0..ebf795c440 100644 --- a/commitizen/changelog_formats/base.py +++ b/commitizen/changelog_formats/base.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re from abc import ABCMeta from re import Pattern from typing import IO, Any, ClassVar @@ -24,30 +25,29 @@ def __init__(self, config: BaseConfig): # Constructor needs to be redefined because `Protocol` prevent instantiation by default # See: https://bugs.python.org/issue44807 self.config = config - self.tag_format = self.config.settings.get("tag_format") + self.tag_format = self.config.settings["tag_format"] @property def version_parser(self) -> Pattern: + tag_regex: str = self.tag_format version_regex = get_version_scheme(self.config).parser.pattern - if self.tag_format != "$version": - TAG_FORMAT_REGEXS = { - "$version": version_regex, - "$major": "(?P\d+)", - "$minor": "(?P\d+)", - "$patch": "(?P\d+)", - "$prerelease": "(?P\w+\d+)?", - "$devrelease": "(?P\.dev\d+)?", - "${version}": version_regex, - "${major}": "(?P\d+)", - "${minor}": "(?P\d+)", - "${patch}": "(?P\d+)", - "${prerelease}": "(?P\w+\d+)?", - "${devrelease}": "(?P\.dev\d+)?", - } - version_regex = self.tag_format - for pattern, regex in TAG_FORMAT_REGEXS.items(): - version_regex = version_regex.replace(pattern, regex) - return rf"{version_regex}" + TAG_FORMAT_REGEXS = { + "$version": version_regex, + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + "${version}": version_regex, + "${major}": r"(?P\d+)", + "${minor}": r"(?P\d+)", + "${patch}": r"(?P\d+)", + "${prerelease}": r"(?P\w+\d+)?", + "${devrelease}": r"(?P\.dev\d+)?", + } + for pattern, regex in TAG_FORMAT_REGEXS.items(): + tag_regex = tag_regex.replace(pattern, regex) + return re.compile(tag_regex) def get_metadata(self, filepath: str) -> Metadata: if not os.path.isfile(filepath): diff --git a/commitizen/changelog_formats/markdown.py b/commitizen/changelog_formats/markdown.py index a5a0f42de3..5c38ce8042 100644 --- a/commitizen/changelog_formats/markdown.py +++ b/commitizen/changelog_formats/markdown.py @@ -19,7 +19,21 @@ def parse_version_from_title(self, line: str) -> str | None: m = re.search(self.version_parser, m.group("title")) if not m: return None - return m.group("version") + if "version" in m.groupdict(): + return m.group("version") + matches = m.groupdict() + try: + partial_version = ( + f"{matches['major']}.{matches['minor']}.{matches['patch']}" + ) + except KeyError: + return None + + if matches.get("prerelease"): + partial_version += f"-{matches['prerelease']}" + if matches.get("devrelease"): + partial_version += f"+{matches['devrelease']}" + return partial_version def parse_title_level(self, line: str) -> int | None: m = self.RE_TITLE.match(line) diff --git a/tests/test_changelog_format_markdown.py b/tests/test_changelog_format_markdown.py index 2e1ee69977..0a82a5b67e 100644 --- a/tests/test_changelog_format_markdown.py +++ b/tests/test_changelog_format_markdown.py @@ -73,12 +73,42 @@ unreleased_start=1, ) +CHANGELOG_E = """ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- Start using "changelog" over "change log" since it's the common usage. + +## {tag_formatted_version} - 2017-06-20 +### Added +- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8). +- Version navigation. +""".strip() + +EXPECTED_E = Metadata( + latest_version="1.0.0", + latest_version_position=10, + unreleased_end=10, + unreleased_start=7, +) + @pytest.fixture def format(config: BaseConfig) -> Markdown: return Markdown(config) +@pytest.fixture +def format_with_tags(config: BaseConfig, request) -> Markdown: + config.settings["tag_format"] = request.param + return Markdown(config) + + VERSIONS_EXAMPLES = [ ("## [1.0.0] - 2017-06-20", "1.0.0"), ( @@ -136,3 +166,35 @@ def test_get_matadata( changelog.write_text(content) assert format.get_metadata(str(changelog)) == expected + + +@pytest.mark.parametrize( + "format_with_tags, tag_string, expected, ", + ( + pytest.param("${version}-example", "1.0.0-example", "1.0.0"), + pytest.param("${version}example", "1.0.0example", "1.0.0"), + pytest.param("example${version}", "example1.0.0", "1.0.0"), + pytest.param("example-${version}", "example-1.0.0", "1.0.0"), + pytest.param("example-${major}-${minor}-${patch}", "example-1-0-0", "1.0.0"), + pytest.param("example-${major}-${minor}-${patch}", "example-1-0-0", "1.0.0"), + pytest.param( + "${major}-${minor}-${patch}-${prerelease}-example", + "1-0-0-rc1-example", + "1.0.0-rc1", + ), + pytest.param( + "${major}-${minor}-${patch}-${prerelease}-example", + "1-0-0-a1-example", + "1.0.0-a1", + ), + ), + indirect=["format_with_tags"], +) +def test_get_metadata_custom_tag_format( + tmp_path: Path, format_with_tags: Markdown, tag_string: str, expected: Metadata +): + content = CHANGELOG_E.format(tag_formatted_version=tag_string) + changelog = tmp_path / format_with_tags.default_changelog_file + changelog.write_text(content) + + assert format_with_tags.get_metadata(str(changelog)).latest_version == expected