diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index a5abe35f16..37294efe01 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -1,37 +1,14 @@ from abc import ABCMeta, abstractmethod -from typing import Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, List, Optional -from prompt_toolkit.styles import Style, merge_styles +from prompt_toolkit.styles import Style -from commitizen import git +from commitizen import defaults, git from commitizen.config.base_config import BaseConfig from commitizen.defaults import Questions class BaseCommitizen(metaclass=ABCMeta): - bump_pattern: Optional[str] = None - bump_map: Optional[Dict[str, str]] = None - default_style_config: List[Tuple[str, str]] = [ - ("qmark", "fg:#ff9d00 bold"), - ("question", "bold"), - ("answer", "fg:#ff9d00 bold"), - ("pointer", "fg:#ff9d00 bold"), - ("highlighted", "fg:#ff9d00 bold"), - ("selected", "fg:#cc5454"), - ("separator", "fg:#cc5454"), - ("instruction", ""), - ("text", ""), - ("disabled", "fg:#858585 italic"), - ] - - # The whole subject will be parsed as message by default - # This allows supporting changelog for any rule system. - # It can be modified per rule - commit_parser: Optional[str] = r"(?P<message>.*)" - changelog_pattern: Optional[str] = r".*" - change_type_map: Optional[Dict[str, str]] = None - change_type_order: Optional[List[str]] = None - # Executed per message parsed by the commitizen changelog_message_builder_hook: Optional[ Callable[[Dict, git.GitCommit], Dict] @@ -42,8 +19,28 @@ class BaseCommitizen(metaclass=ABCMeta): def __init__(self, config: BaseConfig): self.config = config - if not self.config.settings.get("style"): - self.config.settings.update({"style": BaseCommitizen.default_style_config}) + self.style = Style(self.config.settings.get("style", defaults.style)) + self.bump_pattern: Optional[str] = self.config.settings.get( + "bump_pattern", defaults.bump_pattern + ) + self.bump_map: Optional[Dict[str, str]] = self.config.settings.get( + "bump_map", defaults.bump_map + ) + self.change_type_order: Optional[List[str]] = self.config.settings.get( + "change_type_order", defaults.change_type_order + ) + self.change_type_map: Optional[Dict[str, str]] = self.config.settings.get( + "change_type_map", defaults.change_type_map + ) + self.commit_parser: Optional[str] = self.config.settings.get( + "commit_parser", defaults.commit_parser + ) + self.changelog_pattern: Optional[str] = self.config.settings.get( + "changelog_pattern", defaults.changelog_pattern + ) + self.version_parser = self.config.settings.get( + "version_parser", defaults.version_parser + ) @abstractmethod def questions(self) -> Questions: @@ -53,15 +50,6 @@ def questions(self) -> Questions: def message(self, answers: dict) -> str: """Format your git message.""" - @property - def style(self): - return merge_styles( - [ - Style(BaseCommitizen.default_style_config), - Style(self.config.settings["style"]), - ] - ) - def example(self) -> Optional[str]: """Example of the commit message.""" raise NotImplementedError("Not Implemented yet") diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index 7989a17122..d17f31c514 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -1,7 +1,11 @@ import os import re -from commitizen import defaults +try: + from jinja2 import Template +except ImportError: + from string import Template # type: ignore + from commitizen.cz.base import BaseCommitizen from commitizen.cz.utils import multiple_line_breaker, required_validator from commitizen.defaults import Questions @@ -28,18 +32,6 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): - bump_pattern = defaults.bump_pattern - bump_map = defaults.bump_map - commit_parser = defaults.commit_parser - version_parser = defaults.version_parser - change_type_map = { - "feat": "Feat", - "fix": "Fix", - "refactor": "Refactor", - "perf": "Perf", - } - changelog_pattern = defaults.bump_pattern - def questions(self) -> Questions: questions: Questions = [ { @@ -148,9 +140,20 @@ def questions(self) -> Questions: ), }, ] - return questions + + return self.config.settings.get("questions", questions) def message(self, answers: dict) -> str: + custom_message = self.config.settings.get("message_template") + if custom_message: + message_template = Template( + str(self.config.settings.get("message_template", "")) + ) + if getattr(Template, "substitute", None): + return message_template.substitute(**answers) # type: ignore + else: + return message_template.render(**answers) + prefix = answers["prefix"] scope = answers["scope"] subject = answers["subject"] @@ -172,32 +175,36 @@ def message(self, answers: dict) -> str: return message def example(self) -> str: - return ( - "fix: correct minor typos in code\n" - "\n" - "see the issue for details on the typos fixed\n" - "\n" - "closes issue #12" + return str( + self.config.settings.get( + "example", + "fix: correct minor typos in code\n" + "\n" + "see the issue for details on the typos fixed\n" + "\n" + "closes issue #12", + ) ) def schema(self) -> str: - return ( + return self.config.settings.get( + "schema", "<type>(<scope>): <subject>\n" "<BLANK LINE>\n" "<body>\n" "<BLANK LINE>\n" - "(BREAKING CHANGE: )<footer>" + "(BREAKING CHANGE: )<footer>", ) def schema_pattern(self) -> str: PATTERN = ( - r"(?s)" # To explictly make . match new line + r"(?s)" # To explicitly make . match new line r"(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)" # type r"(\(\S+\))?!?:" # scope r"( [^\n\r]+)" # subject r"((\n\n.*)|(\s*))?$" ) - return PATTERN + return self.config.settings.get("schema_pattern", PATTERN) def info(self) -> str: dir_path = os.path.dirname(os.path.realpath(__file__)) diff --git a/commitizen/cz/jira/jira.py b/commitizen/cz/jira/jira.py index bd3cd3c7ee..b47af0c2ea 100644 --- a/commitizen/cz/jira/jira.py +++ b/commitizen/cz/jira/jira.py @@ -1,5 +1,6 @@ import os +from commitizen.config.base_config import BaseConfig from commitizen.cz.base import BaseCommitizen from commitizen.defaults import Questions @@ -7,6 +8,13 @@ class JiraSmartCz(BaseCommitizen): + def __init__(self, config: BaseConfig): + super().__init__(config) + self.bump_map = None + self.bump_pattern = None + self.commit_parser = r"(?P<message>.*)" + self.changelog_pattern = r".*" + def questions(self) -> Questions: questions = [ { diff --git a/commitizen/defaults.py b/commitizen/defaults.py index f6b6dee1c3..47d0b28e38 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -31,14 +31,23 @@ class Settings(TypedDict, total=False): version_files: List[str] tag_format: Optional[str] bump_message: Optional[str] + bump_pattern: Optional[str] + bump_map: Optional[Dict[str, str]] allow_abort: bool changelog_file: str changelog_incremental: bool changelog_start_rev: Optional[str] + changelog_pattern: Optional[str] update_changelog_on_bump: bool use_shortcuts: bool - style: Optional[List[Tuple[str, str]]] + style: List[Tuple[str, str]] customize: CzSettings + change_type_order: Optional[List[str]] + change_type_map: Optional[Dict[str, str]] + commit_parser: Optional[str] + schema: str + schema_pattern: str + questions: Questions name: str = "cz_conventional_commits" @@ -80,8 +89,31 @@ class Settings(TypedDict, total=False): (r"^perf", PATCH), ) ) -change_type_order = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] +change_type_order: Optional[List[str]] = None + bump_message = "bump: version $current_version → $new_version" commit_parser = r"^(?P<change_type>feat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:\s(?P<message>.*)?" # noqa version_parser = r"(?P<version>([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?(\w+)?)" + +change_type_map = { + "feat": "Feat", + "fix": "Fix", + "refactor": "Refactor", + "perf": "Perf", +} + +changelog_pattern = bump_pattern + +style: List[Tuple[str, str]] = [ + ("qmark", "fg:#ff9d00 bold"), + ("question", "bold"), + ("answer", "fg:#ff9d00 bold"), + ("pointer", "fg:#ff9d00 bold"), + ("highlighted", "fg:#ff9d00 bold"), + ("selected", "fg:#cc5454"), + ("separator", "fg:#cc5454"), + ("instruction", ""), + ("text", ""), + ("disabled", "fg:#858585 italic"), +] diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py index 826490a3ba..27279d7a34 100644 --- a/tests/test_bump_find_increment.py +++ b/tests/test_bump_find_increment.py @@ -5,6 +5,7 @@ import pytest from commitizen import bump +from commitizen.config.base_config import BaseConfig from commitizen.cz import ConventionalCommitsCz from commitizen.git import GitCommit @@ -72,11 +73,12 @@ ), ) def test_find_increment(messages, expected_type): + cz = ConventionalCommitsCz(BaseConfig()) commits = [GitCommit(rev="test", title=message) for message in messages] increment_type = bump.find_increment( commits, - regex=ConventionalCommitsCz.bump_pattern, - increments_map=ConventionalCommitsCz.bump_map, + regex=cz.bump_pattern, + increments_map=cz.bump_map, ) assert increment_type == expected_type diff --git a/tests/test_changelog.py b/tests/test_changelog.py index e68a3abdcf..0af6f8484c 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -1,6 +1,7 @@ import pytest from commitizen import changelog, defaults, git +from commitizen.config.base_config import BaseConfig from commitizen.cz.conventional_commits.conventional_commits import ( ConventionalCommitsCz, ) @@ -844,8 +845,9 @@ def test_order_changelog_tree_raises(): def test_render_changelog(gitcommits, tags, changelog_content): - parser = ConventionalCommitsCz.commit_parser - changelog_pattern = ConventionalCommitsCz.bump_pattern + cz = ConventionalCommitsCz(BaseConfig()) + parser = cz.commit_parser + changelog_pattern = cz.bump_pattern tree = changelog.generate_tree_from_commits( gitcommits, tags, parser, changelog_pattern ) @@ -854,9 +856,10 @@ def test_render_changelog(gitcommits, tags, changelog_content): def test_render_changelog_unreleased(gitcommits): + cz = ConventionalCommitsCz(BaseConfig()) some_commits = gitcommits[:7] - parser = ConventionalCommitsCz.commit_parser - changelog_pattern = ConventionalCommitsCz.bump_pattern + parser = cz.commit_parser + changelog_pattern = cz.bump_pattern tree = changelog.generate_tree_from_commits( some_commits, [], parser, changelog_pattern ) @@ -869,9 +872,10 @@ def test_render_changelog_tag_and_unreleased(gitcommits, tags): single_tag = [ tag for tag in tags if tag.rev == "56c8a8da84e42b526bcbe130bd194306f7c7e813" ] + cz = ConventionalCommitsCz(BaseConfig()) - parser = ConventionalCommitsCz.commit_parser - changelog_pattern = ConventionalCommitsCz.bump_pattern + parser = cz.commit_parser + changelog_pattern = cz.bump_pattern tree = changelog.generate_tree_from_commits( some_commits, single_tag, parser, changelog_pattern ) @@ -882,10 +886,11 @@ def test_render_changelog_tag_and_unreleased(gitcommits, tags): def test_render_changelog_with_change_type(gitcommits, tags): + cz = ConventionalCommitsCz(BaseConfig()) new_title = ":some-emoji: feature" change_type_map = {"feat": new_title} - parser = ConventionalCommitsCz.commit_parser - changelog_pattern = ConventionalCommitsCz.bump_pattern + parser = cz.commit_parser + changelog_pattern = cz.bump_pattern tree = changelog.generate_tree_from_commits( gitcommits, tags, parser, changelog_pattern, change_type_map=change_type_map ) @@ -900,8 +905,9 @@ def changelog_message_builder_hook(message: dict, commit: git.GitCommit) -> dict ] = f"{message['message']} [link](github.com/232323232) {commit.author} {commit.author_email}" return message - parser = ConventionalCommitsCz.commit_parser - changelog_pattern = ConventionalCommitsCz.bump_pattern + cz = ConventionalCommitsCz(BaseConfig()) + parser = cz.commit_parser + changelog_pattern = cz.bump_pattern tree = changelog.generate_tree_from_commits( gitcommits, tags,