Skip to content
This repository was archived by the owner on Feb 20, 2026. It is now read-only.

Commit 41571ff

Browse files
committed
Fix #15: ask_* functions prompt can be colorized
1 parent e1f4f44 commit 41571ff

3 files changed

Lines changed: 33 additions & 15 deletions

File tree

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,6 @@ Example:
6161
with_sugar = cli_ui.ask_yes_no("With sugar?", default=False)
6262
6363
fruits = ["apple", "orange", "banana"]
64-
selected_fruit = cli_ui.ask_choice("Choose a fruit", fruits)
64+
selected_fruit = cli_ui.ask_choice("Choose a fruit", choices=fruits)
6565
6666
# ... and more!

cli_ui/__init__.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -449,22 +449,28 @@ def read_password() -> str:
449449
return getpass.getpass(prompt="")
450450

451451

452-
def ask_string(question: str, default: Optional[str] = None) -> Optional[str]:
452+
def get_ask_tokens(tokens: Sequence[Token]) -> List[Token]:
453+
return [green, "::", reset] + list(tokens) + [reset] # type: ignore
454+
455+
456+
def ask_string(*question: Token, default: Optional[str] = None) -> Optional[str]:
453457
"""Ask the user to enter a string.
454458
"""
459+
tokens = get_ask_tokens(question)
455460
if default:
456-
question += " (%s)" % default
457-
info(green, "::", reset, question)
461+
tokens.append("(%s)" % default)
462+
info(*tokens)
458463
answer = read_input()
459464
if not answer:
460465
return default
461466
return answer
462467

463468

464-
def ask_password(question: str) -> str:
469+
def ask_password(*question: Token) -> str:
465470
"""Ask the user to enter a password.
466471
"""
467-
info(green, "::", reset, question)
472+
tokens = get_ask_tokens(question)
473+
info(*tokens)
468474
answer = read_password()
469475
return answer
470476

@@ -473,7 +479,7 @@ def ask_password(question: str) -> str:
473479

474480

475481
def ask_choice(
476-
input_text: str, choices: List[Any], *, func_desc: Optional[FuncDesc] = None
482+
*prompt: Token, choices: List[Any], func_desc: Optional[FuncDesc] = None
477483
) -> Any:
478484
"""Ask the user to choose from a list of choices.
479485
@@ -492,7 +498,8 @@ def ask_choice(
492498
"""
493499
if func_desc is None:
494500
func_desc = lambda x: str(x)
495-
info(green, "::", reset, input_text)
501+
tokens = get_ask_tokens(prompt)
502+
info(*tokens)
496503
choices.sort(key=func_desc)
497504
for i, choice in enumerate(choices, start=1):
498505
choice_desc = func_desc(choice)
@@ -517,13 +524,15 @@ def ask_choice(
517524
return res
518525

519526

520-
def ask_yes_no(question: str, default: bool = False) -> bool:
527+
def ask_yes_no(*question: Token, default: bool = False) -> bool:
521528
"""Ask the user to answer by yes or no"""
522529
while True:
530+
tokens = [green, "::", reset] + list(question) + [reset]
523531
if default:
524-
info(green, "::", reset, question, "(Y/n)")
532+
tokens.append("(Y/n)")
525533
else:
526-
info(green, "::", reset, question, "(y/N)")
534+
tokens.append("(y/N)")
535+
info(*tokens) # type: ignore
527536
answer = read_input()
528537
if answer.lower() in ["y", "yes"]:
529538
return True
@@ -639,7 +648,7 @@ def new_message(*args: Any, **kwargs: Any) -> None:
639648
# stop monkey patching
640649
message = old_message
641650
fruits = ["apple", "orange", "banana"]
642-
answer = ask_choice("Choose a fruit", fruits)
651+
answer = ask_choice("Choose a fruit", choices=fruits)
643652
info("You chose:", answer)
644653

645654

cli_ui/tests/test_cli_ui.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ def test_ask_string() -> None:
159159
assert res == "milk"
160160

161161

162+
def test_ask_colored_message() -> None:
163+
with mock.patch("builtins.input") as m:
164+
m.side_effect = ["y"]
165+
res = cli_ui.ask_yes_no(
166+
"Deploy to", cli_ui.bold, "prod", cli_ui.reset, "?", default=False
167+
)
168+
assert res
169+
170+
162171
def test_ask_password() -> None:
163172
with mock.patch("getpass.getpass") as m:
164173
m.side_effect = ["chocolate!", ""]
@@ -214,7 +223,7 @@ def func_desc(fruit: Fruit) -> str:
214223
with mock.patch("builtins.input") as m:
215224
m.side_effect = ["nan", "5", "2"]
216225
actual = cli_ui.ask_choice(
217-
"Select a fruit", fruits, func_desc=operator.attrgetter("name")
226+
"Select a fruit", choices=fruits, func_desc=operator.attrgetter("name")
218227
)
219228
assert actual.name == "banana"
220229
assert actual.price == 10
@@ -224,15 +233,15 @@ def func_desc(fruit: Fruit) -> str:
224233
def test_ask_choice_empty_input() -> None:
225234
with mock.patch("builtins.input") as m:
226235
m.side_effect = [""]
227-
res = cli_ui.ask_choice("Select a animal", ["cat", "dog", "cow"])
236+
res = cli_ui.ask_choice("Select a animal", choices=["cat", "dog", "cow"])
228237
assert res is None
229238

230239

231240
def test_ask_choice_ctrl_c() -> None:
232241
with pytest.raises(KeyboardInterrupt):
233242
with mock.patch("builtins.input") as m:
234243
m.side_effect = KeyboardInterrupt
235-
cli_ui.ask_choice("Select a animal", ["cat", "dog", "cow"])
244+
cli_ui.ask_choice("Select a animal", choices=["cat", "dog", "cow"])
236245

237246

238247
def test_quiet(message_recorder: MessageRecorder) -> None:

0 commit comments

Comments
 (0)