Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2759cab

Browse files
committed
feat(commit): implement questions 'filter' support with handlers
Supported APIs: - multiple_line_breaker - required_validator - required_validator_scope - required_validator_subject_strip - required_validator_title_strip Example YAML configurations: --- commitizen: name: cz_customize customize: questions: - ... - type: input name: scope message: 'Scope of the change :' filter: 'required_validator_scope' default: '' - type: input name: subject message: 'Title of the commit (starting in lower case and without period) :' filter: 'required_validator_subject_strip' default: '' - type: input name: body message: 'Additional contextual message (Empty to skip) :' default: 'Issue: #...' filter: 'multiple_line_breaker' --- Signed-off-by: Adrian DC <radian.dc@gmail.com>
1 parent 20ca2fb commit 2759cab

File tree

4 files changed

+196
-18
lines changed

4 files changed

+196
-18
lines changed

‎commitizen/commands/commit.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
from commitizen import factory, git, out
1212
from commitizen.config import BaseConfig
1313
from commitizen.cz.exceptions import CzException
14-
from commitizen.cz.utils import get_backup_file_path
14+
from commitizen.cz.utils import (
15+
get_backup_file_path,
16+
multiple_line_breaker,
17+
required_validator,
18+
required_validator_scope,
19+
required_validator_subject_strip,
20+
required_validator_title_strip,
21+
)
1522
from commitizen.exceptions import (
1623
CommitError,
1724
CommitMessageLengthExceededError,
@@ -55,6 +62,23 @@ def prompt_commit_questions(self) -> str:
5562

5663
for question in filter(lambda q: q["type"] == "list", questions):
5764
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
65+
66+
for question in filter(
67+
lambda q: isinstance(q.get("filter", None), str), questions
68+
):
69+
if question["filter"] == "multiple_line_breaker":
70+
question["filter"] = multiple_line_breaker
71+
elif question["filter"] == "required_validator":
72+
question["filter"] = required_validator
73+
elif question["filter"] == "required_validator_scope":
74+
question["filter"] = required_validator_scope
75+
elif question["filter"] == "required_validator_subject_strip":
76+
question["filter"] = required_validator_subject_strip
77+
elif question["filter"] == "required_validator_title_strip":
78+
question["filter"] = required_validator_title_strip
79+
else:
80+
raise NotAllowed(f"Unknown value filter: {question['filter']}")
81+
5882
try:
5983
answers = questionary.prompt(questions, style=cz.style)
6084
except ValueError as err:

‎commitizen/cz/utils.py‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,34 @@
66
from commitizen.cz import exceptions
77

88

9-
def required_validator(answer, msg=None):
9+
def required_validator(answer: str, msg=None)->str:
1010
if not answer:
1111
raise exceptions.AnswerRequiredError(msg)
1212
return answer
1313

1414

15-
def multiple_line_breaker(answer, sep="|"):
15+
def required_validator_scope(
16+
answer: str,
17+
msg: str = "! Error: Scope is required",
18+
) -> str:
19+
return required_validator(answer, msg)
20+
21+
22+
def required_validator_subject_strip(
23+
answer: str,
24+
msg: str = "! Error: Subject is required",
25+
) -> str:
26+
return required_validator(answer.strip(".").strip(), msg)
27+
28+
29+
def required_validator_title_strip(
30+
answer: str,
31+
msg: str = "! Error: Title is required",
32+
) -> str:
33+
return required_validator(answer.strip(".").strip(), msg)
34+
35+
36+
def multiple_line_breaker(answer: str, sep: str = "|") -> str:
1637
return "\n".join(line.strip() for line in answer.split(sep) if line)
1738

1839

‎docs/customization.md‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,18 @@ commitizen:
168168

169169
#### Detailed `questions` content
170170

171-
| Parameter | Type | Default | Description |
172-
| ----------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
173-
| `type` | `str` | `None` | The type of questions. Valid types: `list`, `select`, `input`, etc. The `select` type provides an interactive searchable list interface. [See More][different-question-types] |
174-
| `name` | `str` | `None` | The key for the value answered by user. It's used in `message_template` |
175-
| `message` | `str` | `None` | Detail description for the question. |
176-
| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = list` or `type = select`. 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. |
177-
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
178-
| `filter` | `str` | `None` | (OPTIONAL) Validator for user's answer. **(Work in Progress)** |
179-
| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. |
180-
| `use_search_filter` | `bool` | `False` | (OPTIONAL) Enable search/filter functionality for list/select type questions. This allows users to type and filter through the choices. |
181-
| `use_jk_keys` | `bool` | `True` | (OPTIONAL) Enable/disable j/k keys for navigation in list/select type questions. Set to false if you prefer arrow keys only. |
171+
| Parameter | Type | Default | Description |
172+
| ------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
173+
| `type` | `str` | `None` | The type of questions. Valid types: `list`, `select`, `input`, etc. The `select` type provides an interactive searchable list interface. [See More][different-question-types] |
174+
| `name` | `str` | `None` | The key for the value answered by user. It's used in `message_template` |
175+
| `message` | `str` | `None` | Detail description for the question. |
176+
| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = list` or `type = select`. 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. |
177+
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
178+
| `filter` | `str` | `None` | (OPTIONAL) Validator for user's answer. The string is the name of a `commitizen.cz.utils.NAME(answer...)` function like `multiple_line_breaker` |
179+
| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. |
180+
| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. |
181+
| `use_search_filter` | `bool` | `False` | (OPTIONAL) Enable search/filter functionality for list/select type questions. This allows users to type and filter through the choices. |
182+
| `use_jk_keys` | `bool` | `True` | (OPTIONAL) Enable/disable j/k keys for navigation in list/select type questions. Set to false if you prefer arrow keys only. |
182183

183184
[different-question-types]: https://github.com/tmbo/questionary#different-question-types
184185

‎tests/test_cz_customize.py‎

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import pytest
2+
from pytest_mock import MockFixture
23

4+
from commitizen import cmd, commands
35
from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig
46
from commitizen.cz.customize import CustomizeCommitsCz
5-
from commitizen.exceptions import MissingCzCustomizeConfigError
7+
from commitizen.cz.utils import (
8+
multiple_line_breaker,
9+
required_validator,
10+
required_validator_scope,
11+
required_validator_subject_strip,
12+
required_validator_title_strip,
13+
)
14+
from commitizen.exceptions import MissingCzCustomizeConfigError, NotAllowed
615

716
TOML_STR = r"""
817
[tool.commitizen]
@@ -36,10 +45,17 @@
3645
]
3746
message = "Select the type of change you are committing"
3847
48+
[[tool.commitizen.customize.questions]]
49+
type = "input"
50+
name = "subject"
51+
message = "Subject."
52+
filter = "required_validator_subject_strip"
53+
3954
[[tool.commitizen.customize.questions]]
4055
type = "input"
4156
name = "message"
4257
message = "Body."
58+
filter = "multiple_line_breaker"
4359
4460
[[tool.commitizen.customize.questions]]
4561
type = "confirm"
@@ -89,10 +105,17 @@
89105
],
90106
"message": "Select the type of change you are committing"
91107
},
108+
{
109+
"type": "input",
110+
"name": "subject",
111+
"message": "Subject.",
112+
"filter": "required_validator_subject_strip"
113+
},
92114
{
93115
"type": "input",
94116
"name": "message",
95-
"message": "Body."
117+
"message": "Body.",
118+
"filter": "multiple_line_breaker"
96119
},
97120
{
98121
"type": "confirm",
@@ -139,9 +162,14 @@
139162
- value: bug fix
140163
name: 'bug fix: A bug fix.'
141164
message: Select the type of change you are committing
165+
- type: input
166+
name: subject
167+
message: Subject.
168+
filter: required_validator_subject_strip
142169
- type: input
143170
name: message
144171
message: Body.
172+
filter: multiple_line_breaker
145173
- type: confirm
146174
name: show_message
147175
message: Do you want to add body message in commit?
@@ -330,6 +358,13 @@
330358
"""
331359

332360

361+
@pytest.fixture
362+
def staging_is_clean(mocker: MockFixture, tmp_git_project):
363+
is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean")
364+
is_staging_clean_mock.return_value = False
365+
return tmp_git_project
366+
367+
333368
@pytest.fixture(
334369
params=[
335370
TomlConfig(data=TOML_STR, path="not_exist.toml"),
@@ -346,6 +381,15 @@ def config(request):
346381
return request.param
347382

348383

384+
@pytest.fixture(
385+
params=[
386+
YAMLConfig(data=YAML_STR, path="not_exist.yaml"),
387+
]
388+
)
389+
def config_filters(request):
390+
return request.param
391+
392+
349393
@pytest.fixture(
350394
params=[
351395
TomlConfig(data=TOML_STR_INFO_PATH, path="not_exist.toml"),
@@ -437,7 +481,7 @@ def test_change_type_order_unicode(config_with_unicode):
437481
]
438482

439483

440-
def test_questions(config):
484+
def test_questions_default(config):
441485
cz = CustomizeCommitsCz(config)
442486
questions = cz.questions()
443487
expected_questions = [
@@ -450,7 +494,18 @@ def test_questions(config):
450494
],
451495
"message": "Select the type of change you are committing",
452496
},
453-
{"type": "input", "name": "message", "message": "Body."},
497+
{
498+
"type": "input",
499+
"name": "subject",
500+
"message": "Subject.",
501+
"filter": "required_validator_subject_strip",
502+
},
503+
{
504+
"type": "input",
505+
"name": "message",
506+
"message": "Body.",
507+
"filter": "multiple_line_breaker",
508+
},
454509
{
455510
"type": "confirm",
456511
"name": "show_message",
@@ -460,6 +515,83 @@ def test_questions(config):
460515
assert list(questions) == expected_questions
461516

462517

518+
@pytest.mark.usefixtures("staging_is_clean")
519+
def test_questions_filter_default(config, mocker: MockFixture):
520+
is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean")
521+
is_staging_clean_mock.return_value = False
522+
523+
prompt_mock = mocker.patch("questionary.prompt")
524+
prompt_mock.return_value = {
525+
"change_type": "feature",
526+
"subject": "user created",
527+
"message": "body of the commit",
528+
"show_message": True,
529+
}
530+
531+
commit_mock = mocker.patch("commitizen.git.commit")
532+
commit_mock.return_value = cmd.Command("success", "", b"", b"", 0)
533+
534+
commands.Commit(config, {})()
535+
536+
prompts_questions = prompt_mock.call_args[0][0]
537+
assert prompts_questions[0]["type"] == "list"
538+
assert prompts_questions[0]["name"] == "change_type"
539+
assert prompts_questions[0]["use_shortcuts"] is False
540+
assert prompts_questions[1]["type"] == "input"
541+
assert prompts_questions[1]["name"] == "subject"
542+
assert prompts_questions[1]["filter"] == required_validator_subject_strip
543+
assert prompts_questions[2]["type"] == "input"
544+
assert prompts_questions[2]["name"] == "message"
545+
assert prompts_questions[2]["filter"] == multiple_line_breaker
546+
assert prompts_questions[3]["type"] == "confirm"
547+
assert prompts_questions[3]["name"] == "show_message"
548+
549+
550+
@pytest.mark.usefixtures("staging_is_clean")
551+
def test_questions_filter_values(config_filters, mocker: MockFixture):
552+
is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean")
553+
is_staging_clean_mock.return_value = False
554+
555+
prompt_mock = mocker.patch("questionary.prompt")
556+
prompt_mock.return_value = {
557+
"change_type": "feature",
558+
"subject": "user created",
559+
"message": "body of the commit",
560+
"show_message": True,
561+
}
562+
563+
commit_mock = mocker.patch("commitizen.git.commit")
564+
commit_mock.return_value = cmd.Command("success", "", b"", b"", 0)
565+
566+
commit_cmd = commands.Commit(config_filters, {})
567+
568+
assert isinstance(commit_cmd.cz, CustomizeCommitsCz)
569+
570+
for filter_desc in [
571+
("multiple_line_breaker", multiple_line_breaker),
572+
("required_validator", required_validator),
573+
("required_validator_scope", required_validator_scope),
574+
("required_validator_subject_strip", required_validator_subject_strip),
575+
("required_validator_title_strip", required_validator_title_strip),
576+
]:
577+
commit_cmd.cz.custom_settings["questions"][1]["filter"] = filter_desc[0] # type: ignore[index]
578+
commit_cmd()
579+
580+
assert filter_desc[1]("input")
581+
582+
prompts_questions = prompt_mock.call_args[0][0]
583+
assert prompts_questions[1]["filter"] == filter_desc[1]
584+
585+
for filter_name in [
586+
"",
587+
"faulty_value",
588+
]:
589+
commit_cmd.cz.custom_settings["questions"][1]["filter"] = filter_name # type: ignore[index]
590+
591+
with pytest.raises(NotAllowed):
592+
commit_cmd()
593+
594+
463595
def test_questions_unicode(config_with_unicode):
464596
cz = CustomizeCommitsCz(config_with_unicode)
465597
questions = cz.questions()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /