Created
March 20, 2018 19:31
-
-
Save seblin/7b77cfa1285b093b3c9e632d01f3f78b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Copyright (c) 2018, Sebastian Linke | |
# Released under the Simplified BSD license | |
""" | |
Simple interface to get user input. | |
Examples: | |
>>> import easyinput | |
>>> easyinput.ask_input('Give me a number: ', int) | |
Give me a number: foo | |
Invalid value | |
Give me a number: 42 | |
42 | |
>>> easyinput.ask_input('Give me a number (1-10): ', int, 1, 10) | |
Give me a number (1-10): 42 | |
Invalid value | |
Give me a number (1-10): 10 | |
10 | |
>>> easyinput.ask_choice(['Apple', 'Banana', 'Cherry']) | |
Choose an item: | |
[1] Apple | |
[2] Banana | |
[3] Cherry | |
Choice: 5 | |
Invalid value | |
Choice: 2 | |
2 | |
>>> easyinput.ask_input('Want to proceed (Yes/No)? ', easyinput.yesno) | |
Want to proceed (Yes/No)? spam | |
Invalid value | |
Want to proceed (Yes/No)? yes | |
True | |
""" | |
from __future__ import print_function | |
__author__ = 'Sebastian Linke' | |
__version__ = '0.1' | |
__all__ = ['ask_choice', 'ask_input', 'yesno', 'yesno_false', 'yn', 'yn_false'] | |
try: | |
# Python 2 | |
input_func = raw_input | |
except NameError: | |
# Python 3 | |
input_func = input | |
# Default messages | |
CHOOSE_TEXT = 'Choose an item:' | |
CHOICE_TEXT = 'Choice: ' | |
ERROR_TEXT = 'Invalid value' | |
def ask_input( | |
prompt='', val_type=str, val_min=None, val_max=None, error=ERROR_TEXT, | |
error_prompt='' | |
): | |
""" | |
Print *prompt* and then read from STDIN. Return the result converted to | |
*val_type*. | |
*val_min* and *val_max* define the limits within which the input must be | |
given. Using ``None`` means unlimited input. | |
This function asks for input until a correct value is given. If the input | |
is invalid then it will print the given *error* instead of returning the | |
value and it will ask again by printing *error_prompt*. If *error_prompt* | |
is not given (e.g. empty string or ``None``) then it will reuse *prompt* | |
instead. | |
""" | |
while True: | |
try: | |
value = val_type(input_func(prompt)) | |
return get_validated(value, val_min, val_max) | |
except Exception: | |
print(error + '\n') | |
if error_prompt: | |
prompt = error_prompt | |
def ask_choice( | |
items, text_before=CHOOSE_TEXT, text_after=CHOICE_TEXT, error=ERROR_TEXT | |
): | |
""" | |
Print numbered choices for *items*. Then read user's choice (an integer | |
between 1 and len(items)) from STDIN and return the result. | |
*text_before* and *text_after* are set around the numbered choices. | |
This function asks for input until a valid choice is given. If the input | |
is invalid then it will print the given *error* instead of returning the | |
choice and it will ask again by printing *text_after*. If *text_after* is | |
not given (e.g. empty string or ``None``) then it will reuse the numbered | |
choices instead. | |
""" | |
if not items: | |
raise ValueError('Need at least one item') | |
choices = '\n'.join( | |
'[{}] {}'.format(number, text) | |
for number, text in enumerate(items, 1) | |
) | |
prompt = '{}\n{}\n{}'.format(text_before, choices, text_after) | |
return ask_input(prompt, int, 1, len(items), error, text_after) | |
def get_yesno_converter(yes_values, no_values, default=True, lower=True): | |
""" | |
Return a converter meant to be used as ``val_type`` in ``ask_input()``. | |
The converter returns ``True`` if the input matches one of the *yes_values* | |
or ``False`` if the input matches one of the *no_values*. If the input is | |
an empty string then *default* is returned. Any other string will result in | |
a ``ValueError``. | |
The input is lower-cased by default. Setting *lower* to ``False`` means to | |
use the original input instead. | |
""" | |
def converter(value): | |
if not value: | |
return default | |
if lower: | |
value = value.lower() | |
if value in yes_values: | |
return True | |
if value in no_values: | |
return False | |
raise ValueError(ERROR_TEXT) | |
return converter | |
# Matches "yes" / "y" and "no" / "n" (default: True) | |
yesno = get_yesno_converter(['yes', 'y'], ['no', 'n']) | |
# Matches "yes" / "y" and "no" / "n" (default: False) | |
yesno_false = get_yesno_converter(['yes', 'y'], ['no', 'n'], False) | |
# Matches "y" and "n" (default: True) | |
yn = get_yesno_converter(['y'], ['n']) | |
# Matches "y" and "n" (default: False) | |
yn_false = get_yesno_converter(['y'], ['n'], False) | |
def get_validated(value, val_min, val_max): | |
""" | |
Return *value* if ``val_min <= value <= val_max``. Otherwise, raise | |
``ValueError`` instead of returning the value. | |
Use ``None`` to omit an upper or lower limit. | |
""" | |
if ( | |
val_min is not None and value < val_min | |
or val_max is not None and value > val_max | |
): | |
raise ValueError(ERROR_TEXT) | |
return value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment