Source code for salmagundi.inputs

"""Functions for terminal input.

.. versionadded:: 0.3.0

.. versionchanged:: 0.16.0
   The ``exc_on_cancel`` parameters of the functions ``read``, ``select``,
   ``yesno``, and ``menu`` now default to ``None``. This means that the value
   of the new module level attribute ``exception_on_cancel`` will be used. As
   this defaults to ``True`` the default behavior of the functions has changed.
   To restore the old behavior set ``exception_on_cancel``
   to ``False``.

The terminal must support `ANSI escape sequences
<https://en.wikipedia.org/wiki/ANSI_escape_code>`_.
"""

import math
from getpass import getpass

from .utils import check_type

__all__ = ['exception_on_cancel', 'check_float', 'check_int', 'check_str',
           'menu', 'read', 'select', 'yesno']

exception_on_cancel = True  #: Default value for ``exc_on_cancel`` parameters.


[docs]def read(prompt='', default=None, check=None, exc_on_cancel=None, *, noecho=False): """Read a line of input and check if it is allowed. If the input is not allowed the prompt will be shown again. The input can be cancelled with EOF (``^D``). If the ``check`` parameter is set to ``None`` any input is allowed, else it must be a ``callable`` that takes a string as a parameter and returns the (converted) input value or raises :exc:`ValueError` if the input is not allowed. There are 3 predefined check functions in this module: :func:`check_str`, :func:`check_int` and :func:`check_float`. >>> read('Number: ', default='42') Number: 21 '21' >>> read('Number: ', default='42', check=int) Number: 42 :param str prompt: the prompt :param default: default value that will be used if no input is provided :type default: str or None :param check: the check parameter (see above) :type check: callable(str) or None :param bool exc_on_cancel: if ``True`` an EOF will cause an Exception; if ``None`` the value of ``exception_on_cancel`` will be used :param bool noecho: if set to ``True`` :func:`~getpass.getpass` will be used instead of :func:`input` :return: (converted) input value or ``None`` if input was cancelled and ``exc_on_cancel=False`` :rtype: str or return-type of the ``check`` callable or None :raises EOFError: if input was cancelled and ``exc_on_cancel=True`` :raises TypeError: if ``default`` is not of type ``str`` .. versionchanged:: 0.15.0 Add parameter ``noecho`` """ default is not None and check_type(default, str, 'default') exc_on_cancel = (exception_on_cancel if exc_on_cancel is None else exc_on_cancel) value = None lines = prompt.split('\n') line = lines[-1] if len(lines) > 1: print('\n'.join(lines[:-1])) print('\n\x1b[A\x1b[s', end='', flush=True) # CHANGELOG for version 0.7.2 while True: try: if noecho: a = getpass(prompt) or default else: a = input(line) or default except EOFError: print() if exc_on_cancel: raise break if a is not None: if check: try: value = check(a) break except ValueError: pass else: value = a break print('\x1b[u\x1b[J', end='', flush=True) return value
[docs]def yesno(prompt, yesno, exc_on_cancel=None): """Show yes/no input prompt. If the typed character (case-insensitive) is not allowed (i.e. not in ``yesno``) the prompt will be shown again. The input can be cancelled with EOF (``^D``). >>> yesno('Exit program?', 'yN') Exit program? [yN] Y True :param str prompt: the prompt :param str yesno: the characters for ``yes`` (index 0) and ``no`` (index 1); if one is upper and the other lower case, the upper case character is the default :param bool exc_on_cancel: if ``True`` an EOF will cause an Exception; if ``None`` the value of ``exception_on_cancel`` will be used :return: ``True`` for ``yes``, ``False`` for ``no``, ``None`` if cancelled and ``exc_on_cancel=False`` :rtype: bool or None :raises EOFError: if input was cancelled and ``exc_on_cancel=True`` :raises TypeError: if ``yesno`` is not of type ``str`` :raises ValueError: if ``yesno`` is not of length 2 """ check_type(yesno, str, 'yesno argument') if len(yesno) != 2: raise ValueError('argument yesno must be a string of to 2 characters') if yesno[0].isupper() and yesno[1].islower(): default = yesno[0] elif yesno[0].islower() and yesno[1].isupper(): default = yesno[1] else: default = None def f(s): s1 = s.lower() s2 = yesno.lower() if s1 not in s2: raise ValueError return s1 == s2[0] return read('%s [%s%s] ' % (prompt, *yesno), default=default, check=f, exc_on_cancel=exc_on_cancel)
[docs]def select(prompt, options, default=None, case_sensitive=False, exc_on_cancel=None): """Show an input with selectable options. If the input is not allowed the prompt will be shown again. The input can be cancelled with EOF (``^D``). >>> select('Select: [T]op, [B]ottom, [L]eft, [R]ight > ', 'TBLR') Select: [T]op, [B]ottom, [L]eft, [R]ight > b 1 :param str prompt: the prompt :param options: the options; if all options are only 1 character a string can be used, else a tuple of strings :type options: str or tuple :param default: default value that will be used if no input is provided :type default: str or None :param bool case_sensitive: if ``False`` case of typed characters will be ignored :param bool exc_on_cancel: if ``True`` an EOF will cause an Exception; if ``None`` the value of ``exception_on_cancel`` will be used :return: index of the selected option in ``options`` or None if cancelled and ``exc_on_cancel=False`` :rtype: int or None :raises EOFError: if input was cancelled and ``exc_on_cancel=True`` :raises TypeError: if ``options`` is not str or tuple of strings .. versionadded:: 0.4.0 """ check_type(options, (str, tuple), 'options') if not case_sensitive: options = tuple(map(str.lower, options)) def f(s): if not case_sensitive: s = s.lower() if s not in options: raise ValueError return options.index(s) return read(prompt, default=default, check=f, exc_on_cancel=exc_on_cancel)
[docs]def check_str(min_len=0, max_len=None, chars=None, negate=False): """Return a check function for strings. The returned function can be used as the ``check`` argument in the :func:`read` function. :param int min_len: minimal length of the string :param int max_len: maximal length of the string (``None`` means no limit) :param str chars: allowed characters :param bool negate: if ``True`` only characters not in ``chars`` are allowed .. versionadded:: 0.6.0 """ def f(s): nonlocal min_len if min_len < 0: min_len = 0 if max_len is None: len_ok = min_len <= len(s) else: len_ok = min_len <= len(s) <= max_len if len_ok: if chars: x = (c in chars for c in s) if negate and not any(x) or all(x): return s else: return s raise ValueError return f
[docs]def check_int(predicate): """Return a check function for integers. The returned function can be used as the ``check`` argument in the :func:`read` function. :param predicate: predicate function :type predicate: callable(int) .. versionadded:: 0.6.0 """ def f(s): i = int(s) if predicate(i): return i raise ValueError return f
[docs]def check_float(predicate): """Return a check function for floats. The returned function can be used as the ``check`` argument in the :func:`read` function. :param predicate: predicate function :type predicate: callable(float) .. versionadded:: 0.6.0 """ def f(s): i = float(s) if predicate(i): return i raise ValueError return f