Skip to content

Commit 4b65199

Browse files
committed
refactor(commit): address PR review feedback
- Use internal out module for colored prompts instead of ANSI codes - Extract all duplicated questionary prompt handling into helper functions - Remove outdated backslash continuation reference from commit message - Add comprehensive tests for edge cases and error scenarios - Fix type annotations and import organization Addresses feedback from @bearomorphism in PR #1630: - Lines 95-102: Replace ANSI colors with out.info/out.error - Lines 88-97, 200-209: Extract duplicated try/catch blocks - Clean up breaking change references
1 parent f3ffccf commit 4b65199

File tree

3 files changed

+63
-38
lines changed

3 files changed

+63
-38
lines changed

commitizen/commands/commit.py

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import subprocess
77
import tempfile
88
from pathlib import Path
9-
from typing import TypedDict
9+
from typing import Any, TypedDict
1010

1111
import questionary
1212
import questionary.prompts.text
@@ -30,6 +30,7 @@
3030
NothingToCommitError,
3131
)
3232
from commitizen.git import smart_open
33+
from commitizen.question import CzQuestion, InputQuestion
3334

3435

3536
class CommitArgs(TypedDict, total=False):
@@ -44,6 +45,25 @@ class CommitArgs(TypedDict, total=False):
4445
retry: bool
4546

4647

48+
def _handle_questionary_prompt(question: CzQuestion, cz_style: Any) -> dict[str, Any]:
49+
"""Handle questionary prompt with error handling."""
50+
try:
51+
answer = questionary.prompt([question], style=cz_style)
52+
if not answer:
53+
raise NoAnswersError()
54+
return answer
55+
except ValueError as err:
56+
root_err = err.__context__
57+
if isinstance(root_err, CzException):
58+
raise CustomError(root_err.__str__())
59+
raise err
60+
61+
62+
def _handle_multiline_fallback(multiline_question: InputQuestion, cz_style: Any) -> dict[str, Any]:
63+
"""Handle fallback to standard behavior if custom multiline approach fails."""
64+
return _handle_questionary_prompt(multiline_question, cz_style)
65+
66+
4767
class Commit:
4868
"""Show prompt for the user to create a guided commit."""
4969

@@ -76,29 +96,21 @@ def _prompt_commit_questions(self) -> str:
7696
for question in questions:
7797
if question["type"] == "list":
7898
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
79-
try:
80-
answer = questionary.prompt([question], style=cz.style)
81-
if not answer:
82-
raise NoAnswersError()
83-
answers.update(answer)
84-
except ValueError as err:
85-
root_err = err.__context__
86-
if isinstance(root_err, CzException):
87-
raise CustomError(root_err.__str__())
88-
raise err
99+
answer = _handle_questionary_prompt(question, cz.style)
100+
answers.update(answer)
89101
elif question["type"] == "input" and question.get("multiline", False):
90102
is_optional = (
91103
question.get("default") == ""
92104
or "skip" in question.get("message", "").lower()
93105
)
94106

95107
if is_optional:
96-
print(
97-
"\033[90m💡 Multiline input:\n Press Enter on empty line to skip, Enter after text for new lines, Alt+Enter to finish\033[0m"
108+
out.info(
109+
"💡 Multiline input:\n Press Enter on empty line to skip, Enter after text for new lines, Alt+Enter to finish"
98110
)
99111
else:
100-
print(
101-
"\033[90m💡 Multiline input:\n Press Enter for new lines and Alt+Enter to finish\033[0m"
112+
out.info(
113+
"💡 Multiline input:\n Press Enter for new lines and Alt+Enter to finish"
102114
)
103115

104116
# Create custom multiline input with Enter-on-empty behavior for optional fields
@@ -142,11 +154,7 @@ def _(event: KeyPressEvent) -> None:
142154

143155
except Exception:
144156
# Fallback to standard behavior if custom approach fails
145-
answer = questionary.prompt(
146-
[multiline_question], style=cz.style
147-
)
148-
if not answer:
149-
raise NoAnswersError()
157+
answer = _handle_multiline_fallback(multiline_question, cz.style)
150158
answers.update(answer)
151159
else:
152160
# Required fields - don't allow newline on empty first line and show error
@@ -158,8 +166,8 @@ def _(event: KeyPressEvent) -> None:
158166
# If buffer is completely empty (no content at all), show error and don't allow newline
159167
if not buffer.text.strip():
160168
# Show error message with prompt
161-
print(
162-
"\n\033[91m⚠ This field is required. Please enter some content or press Ctrl+C to abort.\033[0m"
169+
out.error(
170+
"\n⚠ This field is required. Please enter some content or press Ctrl+C to abort."
163171
)
164172
print("> ", end="", flush=True)
165173
# Don't do anything - require content first
@@ -189,23 +197,11 @@ def _(event: KeyPressEvent) -> None:
189197

190198
except Exception:
191199
# Fallback to standard behavior if custom approach fails
192-
answer = questionary.prompt(
193-
[multiline_question], style=cz.style
194-
)
195-
if not answer:
196-
raise NoAnswersError()
200+
answer = _handle_multiline_fallback(multiline_question, cz.style)
197201
answers.update(answer)
198202
else:
199-
try:
200-
answer = questionary.prompt([question], style=cz.style)
201-
if not answer:
202-
raise NoAnswersError()
203-
answers.update(answer)
204-
except ValueError as err:
205-
root_err = err.__context__
206-
if isinstance(root_err, CzException):
207-
raise CustomError(root_err.__str__())
208-
raise err
203+
answer = _handle_questionary_prompt(question, cz.style)
204+
answers.update(answer)
209205

210206
message = cz.message(answers)
211207
message_len = len(message.partition("\n")[0].strip())

commitizen/cz/conventional_commits/conventional_commits.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def questions(self) -> list[CzQuestion]:
123123
{
124124
"type": "input",
125125
"name": "body",
126-
"message": "Provide additional contextual information about the code changes:\n(Type backslash and press Enter for line continuation, or use multiline input, or [Enter] to skip)",
126+
"message": "Provide additional contextual information about the code changes:\n(Use multiline input or [Enter] to skip)",
127127
"multiline": True,
128128
"default": "",
129129
},

tests/commands/test_commit_multiline.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,32 @@ def test_answer_dictionary_structure(self):
295295
assert isinstance(answer, dict)
296296
assert field_name in answer
297297
assert answer[field_name] == result
298+
299+
def test_handle_multiline_fallback_success(self):
300+
"""Test the _handle_multiline_fallback helper function with successful response."""
301+
from commitizen.commands.commit import _handle_multiline_fallback
302+
303+
with patch("questionary.prompt") as mock_prompt:
304+
mock_prompt.return_value = {"test_field": "test_value"}
305+
306+
question = {"name": "test_field", "message": "Test"}
307+
style = None
308+
309+
result = _handle_multiline_fallback(question, style)
310+
311+
assert result == {"test_field": "test_value"}
312+
mock_prompt.assert_called_once_with([question], style=style)
313+
314+
def test_handle_multiline_fallback_no_answers_error(self):
315+
"""Test the _handle_multiline_fallback helper function raises NoAnswersError."""
316+
from commitizen.commands.commit import _handle_multiline_fallback
317+
from commitizen.exceptions import NoAnswersError
318+
319+
with patch("questionary.prompt") as mock_prompt:
320+
mock_prompt.return_value = None
321+
322+
question = {"name": "test_field", "message": "Test"}
323+
style = None
324+
325+
with pytest.raises(NoAnswersError):
326+
_handle_multiline_fallback(question, style)

0 commit comments

Comments
 (0)