Skip to content

Commit 5011a91

Browse files
committed
feat(commit): implement questions 'filter' support with evaluations
Supported APIs: Common Python, commitizen.cz.utils.* functions Example YAML configurations: --- commitizen: name: cz_customize customize: questions: - ... - type: input name: scope message: 'Scope of the change :' filter: 'lambda text: commitizen.cz.utils.required_validator(text, msg="! Error: Scope is required")' default: '' - type: input name: subject message: 'Title of the commit (starting in lower case and without period) :' filter: 'lambda text: commitizen.cz.utils.required_validator(text.strip(".").strip(), msg="! Error: Title is required")' default: '' - type: input name: body message: 'Additional contextual message (Empty to skip) :' default: 'Issue: #...' filter: 'commitizen.cz.utils.multiple_line_breaker' --- Signed-off-by: Adrian DC <[email protected]>
1 parent 7258073 commit 5011a91

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

commitizen/commands/commit.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
from commitizen import factory, git, out
99
from commitizen.config import BaseConfig
1010
from commitizen.cz.exceptions import CzException
11-
from commitizen.cz.utils import get_backup_file_path
11+
from commitizen.cz.utils import (
12+
get_backup_file_path,
13+
multiple_line_breaker,
14+
)
1215
from commitizen.exceptions import (
1316
CommitError,
1417
CommitMessageLengthExceededError,
@@ -52,6 +55,19 @@ def prompt_commit_questions(self) -> str:
5255

5356
for question in filter(lambda q: q["type"] == "list", questions):
5457
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
58+
59+
# Import allowed modules for 'filter'
60+
global commitizen
61+
import commitizen.cz.utils
62+
63+
for question in filter(
64+
lambda q: isinstance(q.get("filter", None), str), questions
65+
):
66+
question_filter = [
67+
multiple_line_breaker(question["filter"].replace("\\n", "\n"))
68+
]
69+
question["filter"] = eval("\n".join(question_filter))
70+
5571
try:
5672
answers = questionary.prompt(questions, style=cz.style)
5773
except ValueError as err:

docs/customization.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ commitizen:
175175
| `message` | `str` | `None` | Detail description for the question. |
176176
| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = list`. Either use a list of values or a list of dictionaries with `name` and `value` keys. Keyboard shortcuts can be defined via `key`. See examples above. |
177177
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
178-
| `filter` | `str` | `None` | (Optional) Validator for user's answer. **(Work in Progress)** |
178+
| `filter` | `str` | `None` | (OPTIONAL) Validator for user's answer. The string is evaluated into a Python function, either use `commitizen.cz.utils.*` or lambda functions like `lambda text: text.strip(".").strip()` |
179179
[different-question-types]: https://github.com/tmbo/questionary#different-question-types
180180

181181
#### Shortcut keys

tests/test_cz_customize.py

+77-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
from types import LambdaType
2+
13
import pytest
4+
from pytest_mock import MockFixture
25

6+
from commitizen import cmd, commands
37
from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig
48
from commitizen.cz.customize import CustomizeCommitsCz
9+
from commitizen.cz.utils import multiple_line_breaker
510
from commitizen.exceptions import MissingCzCustomizeConfigError
611

712
TOML_STR = r"""
@@ -36,10 +41,17 @@
3641
]
3742
message = "Select the type of change you are committing"
3843
44+
[[tool.commitizen.customize.questions]]
45+
type = "input"
46+
name = "subject"
47+
message = "Subject."
48+
filter = "lambda text: commitizen.cz.utils.required_validator(text.strip(\".\").strip(), msg=\"! Error: Subject is required\")"
49+
3950
[[tool.commitizen.customize.questions]]
4051
type = "input"
4152
name = "message"
4253
message = "Body."
54+
filter = "commitizen.cz.utils.multiple_line_breaker"
4355
4456
[[tool.commitizen.customize.questions]]
4557
type = "confirm"
@@ -89,10 +101,17 @@
89101
],
90102
"message": "Select the type of change you are committing"
91103
},
104+
{
105+
"type": "input",
106+
"name": "subject",
107+
"message": "Subject.",
108+
"filter": "lambda text: commitizen.cz.utils.required_validator(text.strip(\".\").strip(), msg=\"! Error: Subject is required\")"
109+
},
92110
{
93111
"type": "input",
94112
"name": "message",
95-
"message": "Body."
113+
"message": "Body.",
114+
"filter": "commitizen.cz.utils.multiple_line_breaker"
96115
},
97116
{
98117
"type": "confirm",
@@ -139,9 +158,14 @@
139158
- value: bug fix
140159
name: 'bug fix: A bug fix.'
141160
message: Select the type of change you are committing
161+
- type: input
162+
name: subject
163+
message: Subject.
164+
filter: 'lambda text: commitizen.cz.utils.required_validator(text.strip(".").strip(), msg="! Error: Subject is required")'
142165
- type: input
143166
name: message
144167
message: Body.
168+
filter: 'commitizen.cz.utils.multiple_line_breaker'
145169
- type: confirm
146170
name: show_message
147171
message: Do you want to add body message in commit?
@@ -330,6 +354,13 @@
330354
"""
331355

332356

357+
@pytest.fixture
358+
def staging_is_clean(mocker: MockFixture, tmp_git_project):
359+
is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean")
360+
is_staging_clean_mock.return_value = False
361+
return tmp_git_project
362+
363+
333364
@pytest.fixture(
334365
params=[
335366
TomlConfig(data=TOML_STR, path="not_exist.toml"),
@@ -437,7 +468,7 @@ def test_change_type_order_unicode(config_with_unicode):
437468
]
438469

439470

440-
def test_questions(config):
471+
def test_questions_default(config):
441472
cz = CustomizeCommitsCz(config)
442473
questions = cz.questions()
443474
expected_questions = [
@@ -450,7 +481,18 @@ def test_questions(config):
450481
],
451482
"message": "Select the type of change you are committing",
452483
},
453-
{"type": "input", "name": "message", "message": "Body."},
484+
{
485+
"type": "input",
486+
"name": "subject",
487+
"message": "Subject.",
488+
"filter": 'lambda text: commitizen.cz.utils.required_validator(text.strip(".").strip(), msg="! Error: Subject is required")',
489+
},
490+
{
491+
"type": "input",
492+
"name": "message",
493+
"message": "Body.",
494+
"filter": "commitizen.cz.utils.multiple_line_breaker",
495+
},
454496
{
455497
"type": "confirm",
456498
"name": "show_message",
@@ -460,6 +502,38 @@ def test_questions(config):
460502
assert list(questions) == expected_questions
461503

462504

505+
@pytest.mark.usefixtures("staging_is_clean")
506+
def test_questions_filter(config, mocker: MockFixture):
507+
is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean")
508+
is_staging_clean_mock.return_value = False
509+
510+
prompt_mock = mocker.patch("questionary.prompt")
511+
prompt_mock.return_value = {
512+
"change_type": "feature",
513+
"subject": "user created",
514+
"message": "body of the commit",
515+
"show_message": True,
516+
}
517+
518+
commit_mock = mocker.patch("commitizen.git.commit")
519+
commit_mock.return_value = cmd.Command("success", "", b"", b"", 0)
520+
521+
commands.Commit(config, {})()
522+
523+
prompts_questions = prompt_mock.call_args[0][0]
524+
assert prompts_questions[0]["type"] == "list"
525+
assert prompts_questions[0]["name"] == "change_type"
526+
assert prompts_questions[0]["use_shortcuts"] is False
527+
assert prompts_questions[1]["type"] == "input"
528+
assert prompts_questions[1]["name"] == "subject"
529+
assert type(prompts_questions[1]["filter"]) is LambdaType
530+
assert prompts_questions[2]["type"] == "input"
531+
assert prompts_questions[2]["name"] == "message"
532+
assert prompts_questions[2]["filter"] == multiple_line_breaker
533+
assert prompts_questions[3]["type"] == "confirm"
534+
assert prompts_questions[3]["name"] == "show_message"
535+
536+
463537
def test_questions_unicode(config_with_unicode):
464538
cz = CustomizeCommitsCz(config_with_unicode)
465539
questions = cz.questions()

0 commit comments

Comments
 (0)