Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(commit): implement questions 'filter' support with handlers #1207

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
29 changes: 27 additions & 2 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
from commitizen import factory, git, out
from commitizen.config import BaseConfig
from commitizen.cz.exceptions import CzException
from commitizen.cz.utils import get_backup_file_path
from commitizen.cz.utils import (
get_backup_file_path,
multiple_line_breaker,
required_validator,
required_validator_scope,
required_validator_subject_strip,
required_validator_title_strip,
)
from commitizen.exceptions import (
CommitError,
CommitMessageLengthExceededError,
Expand Down Expand Up @@ -51,9 +58,27 @@ def read_backup_message(self) -> str | None:
def prompt_commit_questions(self) -> str:
# Prompt user for the commit message
cz = self.cz
questions = cz.questions()
questions = [dict(question) for question in cz.questions()]

for question in filter(lambda q: q["type"] == "list", questions):
question["use_shortcuts"] = self.config.settings["use_shortcuts"]

for question in filter(
lambda q: isinstance(q.get("filter", None), str), questions
):
if question["filter"] == "multiple_line_breaker":
question["filter"] = multiple_line_breaker
elif question["filter"] == "required_validator":
question["filter"] = required_validator
elif question["filter"] == "required_validator_scope":
question["filter"] = required_validator_scope
elif question["filter"] == "required_validator_subject_strip":
question["filter"] = required_validator_subject_strip
elif question["filter"] == "required_validator_title_strip":
question["filter"] = required_validator_title_strip
else:
raise NotAllowed(f"Unknown value filter: {question['filter']}")

try:
answers = questionary.prompt(questions, style=cz.style)
except ValueError as err:
Expand Down
25 changes: 23 additions & 2 deletions commitizen/cz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,34 @@
from commitizen.cz import exceptions


def required_validator(answer, msg=None):
def required_validator(answer: str, msg=None) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably would like to add test cases to validate these validators

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we named it as required_validator but would be great if we change them to something like require_validator

if not answer:
raise exceptions.AnswerRequiredError(msg)
return answer


def multiple_line_breaker(answer, sep="|"):
def required_validator_scope(
answer: str,
msg: str = "! Error: Scope is required",
) -> str:
return required_validator(answer, msg)


def required_validator_subject_strip(
answer: str,
msg: str = "! Error: Subject is required",
) -> str:
return required_validator(answer.strip(".").strip(), msg)


def required_validator_title_strip(
answer: str,
msg: str = "! Error: Title is required",
) -> str:
return required_validator(answer.strip(".").strip(), msg)


def multiple_line_breaker(answer: str, sep: str = "|") -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def multiple_line_breaker(answer: str, sep: str = "|") -> str:
def break_multiple_line(answer: str, sep: str = "|") -> str:

return "\n".join(line.strip() for line in answer.split(sep) if line)


Expand Down
17 changes: 9 additions & 8 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ And the correspondent example for a yaml json file:
commitizen:
name: cz_customize
customize:
message_template: "{{change_type}}:{% if show_message %} {{message}}{% endif %}"
message_template: '{{change_type}}:{% if show_message %} {{message}}{% endif %}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I know why do we want to change from " to ' here?

example: 'feature: this feature enable customize through config file'
schema: "<type>: <body>"
schema_pattern: "(feature|bug fix):(\\s.*)"
bump_pattern: "^(break|new|fix|hotfix)"
commit_parser: "^(?P<change_type>feature|bug fix):\\s(?P<message>.*)?",
changelog_pattern: "^(feature|bug fix)?(!)?",
schema: '<type>: <body>'
schema_pattern: '(feature|bug fix):(\\s.*)'
bump_pattern: '^(break|new|fix|hotfix)'
commit_parser: '^(?P<change_type>feature|bug fix):\\s(?P<message>.*)?'
changelog_pattern: '^(feature|bug fix)?(!)?'
change_type_map:
feature: Feat
bug fix: Fix
Expand All @@ -125,7 +125,7 @@ commitizen:
new: MINOR
fix: PATCH
hotfix: PATCH
change_type_order: ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]
change_type_order: ['BREAKING CHANGE', 'feat', 'fix', 'refactor', 'perf']
info_path: cz_customize_info.txt
info: This is customized info
questions:
Expand Down Expand Up @@ -176,7 +176,8 @@ commitizen:
| `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. |
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
| `filter` | `str` | `None` | (OPTIONAL) Validator for user's answer. **(Work in Progress)** |
| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. |
| `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` |
| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. |
[different-question-types]: https://github.com/tmbo/questionary#different-question-types

#### Shortcut keys
Expand Down
2 changes: 1 addition & 1 deletion scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e
export PREFIX='poetry run python -m '
export REGEX='^(?![.]|venv).*'

${PREFIX}pytest -n 3 --dist=loadfile --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen tests/
${PREFIX}pytest -n 3 --dist=loadfile --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen "${@}" tests/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What it this for?

${PREFIX}ruff check commitizen/ tests/ --fix
${PREFIX}mypy commitizen/ tests/
${PREFIX}commitizen -nr 3 check --rev-range origin/master..
62 changes: 62 additions & 0 deletions tests/commands/test_commit_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,68 @@ def test_commit_when_nothing_to_commit(config, mocker: MockFixture):
assert "No files added to staging!" in str(excinfo.value)


@pytest.mark.usefixtures("staging_is_clean")
def test_commit_when_nothing_added_to_commit(config, mocker: MockFixture):
prompt_mock = mocker.patch("questionary.prompt")
prompt_mock.return_value = {
"prefix": "feat",
"subject": "user created",
"scope": "",
"is_breaking_change": False,
"body": "",
"footer": "",
}

commit_mock = mocker.patch("commitizen.git.commit")
commit_mock.return_value = cmd.Command(
'nothing added to commit but untracked files present (use "git add" to track)',
"",
b"",
b"",
0,
)

error_mock = mocker.patch("commitizen.out.error")

commands.Commit(config, {"all": False})()

prompt_mock.assert_called_once()
error_mock.assert_called_once()

assert "nothing added" in error_mock.call_args[0][0]


@pytest.mark.usefixtures("staging_is_clean")
def test_commit_when_no_changes_added_to_commit(config, mocker: MockFixture):
prompt_mock = mocker.patch("questionary.prompt")
prompt_mock.return_value = {
"prefix": "feat",
"subject": "user created",
"scope": "",
"is_breaking_change": False,
"body": "",
"footer": "",
}

commit_mock = mocker.patch("commitizen.git.commit")
commit_mock.return_value = cmd.Command(
'no changes added to commit (use "git add" and/or "git commit -a")',
"",
b"",
b"",
0,
)

error_mock = mocker.patch("commitizen.out.error")

commands.Commit(config, {"all": False})()

prompt_mock.assert_called_once()
error_mock.assert_called_once()

assert "no changes added to commit" in error_mock.call_args[0][0]


@pytest.mark.usefixtures("staging_is_clean")
def test_commit_with_allow_empty(config, mocker: MockFixture):
prompt_mock = mocker.patch("questionary.prompt")
Expand Down
Loading
Loading