Source code for runcommands.util.prompt
from rich.prompt import Confirm, Prompt, InvalidResponse
from .misc import abort
from .printer import printer
def prompt(message, password=False, choices=None, default=None, color=True):
if color is True:
color = "warning"
if color:
message = printer.colorize(message, color=color)
try:
return Prompt.ask(message, password=password, choices=choices, default=default)
except KeyboardInterrupt:
print()
abort()
[docs]def confirm(
prompt="Really?",
color="warning",
yes_value="y",
abort_on_unconfirmed=False,
abort_options=None,
):
"""Prompt for confirmation.
Confirmation can be aborted by typing in a no value instead of one
of the yes values or with Ctrl-C.
Args:
prompt (str): Prompt to present user ["Really?"]
color (string|Color|bool) Color to print prompt string; can be
``False`` or ``None`` to print without color ["yellow"]
yes_value (str): Value user must type in to confirm; note that
this will be case sensitive *if* it contains any upper case
letters ["y"]
abort_on_unconfirmed (bool|int|str): When user does *not*
confirm:
- If this is an integer, print "Aborted" to stdout if
it's 0 or to stderr if it's not 0 and then exit with
this code
- If this is a string, print it to stdout and exit with
code 0
- If this is ``True`` (or any other truthy value), print
"Aborted" to stdout and exit with code 0
abort_options (dict): Options to pass to :func:`abort` when not
confirmed (these options will override any options set via
``abort_on_unconfirmed``)
"""
if color:
prompt = printer.colorize(prompt, color=color)
choices = [yes_value, "n"]
try:
confirmed = Confirm.ask(prompt, choices=choices, default=False)
except KeyboardInterrupt:
print()
confirmed = False
# NOTE: The abort-on-unconfirmed logic is somewhat convoluted
# because of the special case for return code 0.
do_abort_on_unconfirmed = not confirmed and (
# True, non-zero return code, non-empty string, or any other
# truthy value (in the manner of typical Python duck-typing)
bool(abort_on_unconfirmed)
or
# Zero return code (special case)
(abort_on_unconfirmed == 0 and abort_on_unconfirmed is not False)
)
if do_abort_on_unconfirmed:
if abort_options is None:
abort_options = {}
if abort_on_unconfirmed is True:
abort_options.setdefault("return_code", 0)
elif isinstance(abort_on_unconfirmed, int):
abort_options.setdefault("return_code", abort_on_unconfirmed)
elif isinstance(abort_on_unconfirmed, str):
abort_options.setdefault("message", abort_on_unconfirmed)
else:
abort_options.setdefault("return_code", 0)
abort(**abort_options)
return confirmed
class Confirm(Confirm):
@property
def validate_error_message(self):
yes, no = self.choices
return f"[prompt.invalid]Please enter {yes} or {no}"
def render_default(self, default):
"""Default is *always* no."""
return "(n)"
def process_response(self, value):
value = value.strip()
# Hitting enter without a value -> "n" -> unconfirmed
if not value:
return False
yes, no = self.choices
# If yes value contains *any* upper case characters, do case-
# sensitive yes value comparison
all_lower = all(c.islower() for c in yes)
if all_lower:
value = value.lower()
if value == yes:
return True
value = value.lower()
# Allow "yes" when yes value is lower case "y"
if yes == "y" and value == "yes":
return True
# Always allow "no"
if value in ("n", "no"):
return False
raise InvalidResponse(self.validate_error_message)