diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 89cec1153a..d017f8a7a6 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -122,7 +122,7 @@ def __call__(self): bump.update_version_in_files(current_version, new_version.public, files) c = git.commit(message, args="-a") if c.err: - out.error("git.commit errror: \"{}\"".format(c.err.strip())) + out.error('git.commit errror: "{}"'.format(c.err.strip())) raise SystemExit(COMMIT_FAILED) c = git.tag(new_tag_version) if c.err: diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index 401e8cdf91..bd0ab35885 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -2,15 +2,11 @@ from commitizen import defaults from commitizen.cz.base import BaseCommitizen -from commitizen.cz.exceptions import CzException +from commitizen.cz.utils import multiple_line_breaker, required_validator __all__ = ["ConventionalCommitsCz"] -class NoSubjectException(CzException): - ... - - def parse_scope(text): if not text: return "" @@ -26,10 +22,7 @@ def parse_subject(text): if isinstance(text, str): text = text.strip(".").strip() - if not text: - raise NoSubjectException("Subject is required.") - - return text + return required_validator(text, msg="Subject is required.") class ConventionalCommitsCz(BaseCommitizen): @@ -124,6 +117,7 @@ def questions(self) -> list: "Body. Motivation for the change and contrast this " "with previous behavior:\n" ), + "filter": multiple_line_breaker, }, { "type": "input", diff --git a/commitizen/cz/customize/customize.py b/commitizen/cz/customize/customize.py index e57caec11e..7120147763 100644 --- a/commitizen/cz/customize/customize.py +++ b/commitizen/cz/customize/customize.py @@ -1,3 +1,8 @@ +try: + from jinja2 import Template +except ImportError: + from string import Template + from commitizen import defaults from commitizen.cz.base import BaseCommitizen @@ -24,8 +29,11 @@ def questions(self) -> list: return self.custom_config.get("questions") def message(self, answers: dict) -> str: - message_template = self.custom_config.get("message_template") - return message_template.format(**answers) + message_template = Template(self.custom_config.get("message_template")) + if getattr(Template, "substitute", None): + return message_template.substitute(**answers) + else: + return message_template.render(**answers) def example(self) -> str: return self.custom_config.get("example") diff --git a/commitizen/cz/exceptions.py b/commitizen/cz/exceptions.py index 2b070ff57d..89b19788b7 100644 --- a/commitizen/cz/exceptions.py +++ b/commitizen/cz/exceptions.py @@ -1,2 +1,6 @@ class CzException(Exception): ... + + +class AnswerRequiredError(Exception): + ... diff --git a/commitizen/cz/utils.py b/commitizen/cz/utils.py new file mode 100644 index 0000000000..0c5aedadaa --- /dev/null +++ b/commitizen/cz/utils.py @@ -0,0 +1,11 @@ +from commitizen.cz import exceptions + + +def required_validator(ans, msg=None): + if not ans: + raise exceptions.AnswerRequiredError(msg) + return ans + + +def multiple_line_breaker(ans, sep="|"): + return "\n".join(line.strip() for line in ans.split(sep) if line) diff --git a/docs/customization.md b/docs/customization.md index 5e0120f0b0..9b6f990f84 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -151,7 +151,7 @@ Example: name = "cz_customize" [tool.commitizen.customize] -message_template = "{change_type}: {message}" +message_template = "{{change_type}}:{% if show_message %} {{message}}{% endif %}" example = "feature: this feature eanable customize through config file" schema = ": " bump_pattern = "^(break|new|fix|hotfix)" @@ -171,6 +171,11 @@ message = "Select the type of change you are committing" type = "input" name = "message" message = "Body." + +[[tool.commitizen.customize.questions]] +type = "confirm" +name = "show_message" +message = "Do you want to add body message in commit?" ``` ### Customize configuration @@ -178,7 +183,7 @@ message = "Body." | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------- | | `question` | `dict` | `None` | Questions regarding the commit message. Detatiled below. | -| `message_template` | `str` | `None` | The template for generating message from the given answers. `message_template` should follow the python string formatting specification, and all the variables in this template should be defined in `name` in `questions`. | +| `message_template` | `str` | `None` | The template for generating message from the given answers. `message_template` should either follow the [string.Template](https://docs.python.org/3/library/string.html#template-strings) or [Jinja2](https://jinja.palletsprojects.com/en/2.10.x/) formatting specification, and all the variables in this template should be defined in `name` in `questions`. Note that `Jinja2` is not installed by default. If not installed, commitizen will use `string.Template` formatting. | | `example` | `str` | `None` | (OPTIONAL) Provide an example to help understand the style. Used by `cz example`. | | `schema` | `str` | `None` | (OPTIONAL) Show the schema used. Used by `cz schema`. | | `info_path` | `str` | `None` | (OPTIONAL) The path to the file that contains explanation of the commit rules. Used by `cz info`. If not provided `cz info`, will load `info` instead. | diff --git a/pyproject.toml b/pyproject.toml index c2bc24ddad..bad79fe89f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ colorama = "^0.4.1" termcolor = "^1.1" packaging = "^19.0" tomlkit = "^0.5.3" +jinja2 = {version = "^2.10.3", optional = true} [tool.poetry.dev-dependencies] ipython = "^7.2" diff --git a/tests/test_cz_conventional_commits.py b/tests/test_cz_conventional_commits.py index 0f6be2d624..f464ba4ae6 100644 --- a/tests/test_cz_conventional_commits.py +++ b/tests/test_cz_conventional_commits.py @@ -3,10 +3,10 @@ from commitizen import defaults from commitizen.cz.conventional_commits.conventional_commits import ( ConventionalCommitsCz, - NoSubjectException, parse_scope, parse_subject, ) +from commitizen.cz.exceptions import AnswerRequiredError config = {"name": defaults.name} @@ -39,7 +39,7 @@ def test_parse_subject_valid_values(): def test_parse_subject_invalid_values(): for valid_subject in invalid_subjects: - with pytest.raises(NoSubjectException): + with pytest.raises(AnswerRequiredError): parse_subject(valid_subject) diff --git a/tests/test_cz_customize.py b/tests/test_cz_customize.py index ba034837c0..21fb1272e5 100644 --- a/tests/test_cz_customize.py +++ b/tests/test_cz_customize.py @@ -8,10 +8,9 @@ @pytest.fixture(scope="module") def config(): _conf = Config() - toml_str = """ + toml_str = r""" [tool.commitizen.customize] - # message_template should follow the python string formatting spec - message_template = "{change_type}: {message}" + message_template = "{{change_type}}:{% if show_message %} {{message}}{% endif %}" example = "feature: this feature eanable customize through config file" schema = ": " bump_pattern = "^(break|new|fix|hotfix)" @@ -31,6 +30,11 @@ def config(): type = "input" name = "message" message = "Body." + + [[tool.commitizen.customize.questions]] + type = "confirm" + name = "show_message" + message = "Do you want to add body message in commit?" """ _conf.update(parse(toml_str)["tool"]["commitizen"]) return _conf.config @@ -65,6 +69,11 @@ def test_questions(config): "message": "Select the type of change you are committing", }, {"type": "input", "name": "message", "message": "Body."}, + { + "type": "confirm", + "name": "show_message", + "message": "Do you want to add body message in commit?", + }, ] assert list(questions) == expected_questions @@ -74,10 +83,20 @@ def test_answer(config): answers = { "change_type": "feature", "message": "this feature eanable customize through config file", + "show_message": True, } message = cz.message(answers) assert message == "feature: this feature eanable customize through config file" + cz = CustomizeCommitsCz(config) + answers = { + "change_type": "feature", + "message": "this feature eanable customize through config file", + "show_message": False, + } + message = cz.message(answers) + assert message == "feature:" + def test_example(config): cz = CustomizeCommitsCz(config)