X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=input_utils.py;h=0b32eea6f55c5d2fe01466bda0d21ae04c7b13f3;hb=a9bdfd8fc9f84b7b2c09a57cd12ba32259e84d1c;hp=153641b135cfd7debb9b4e20e60ca400316c4dca;hpb=4c315e387f18010ba0b5661744ad3c792f21d2d1;p=python_utils.git diff --git a/input_utils.py b/input_utils.py index 153641b..0b32eea 100644 --- a/input_utils.py +++ b/input_utils.py @@ -1,29 +1,51 @@ #!/usr/bin/env python3 +# © Copyright 2021-2022, Scott Gasch + """Utilities related to user input.""" -import readchar # type: ignore +import logging import signal import sys -from typing import List +from typing import List, Optional + +import readchar # type: ignore import exceptions +logger = logging.getLogger(__file__) + def single_keystroke_response( - valid_responses: List[str], + valid_responses: Optional[List[str]], # None = accept anything *, prompt: str = None, default_response: str = None, timeout_seconds: int = None, -) -> str: - """Get a single keystroke response to a prompt.""" +) -> Optional[str]: # None if timeout w/o keystroke + """Get a single keystroke response to a prompt and returns it. + + Args: + valid_responses: a list of strings that are considered to be + valid keystrokes to be accepted. If None, we accept + anything. + prompt: the prompt to print before watching keystrokes. If + None, skip this. + default_response: the response to return if the timeout + expires. If None, skip this. + timeout_seconds: number of seconds to wait before timing out + and returning the default_response. If None, wait forever. + + Returns: + The keystroke the user pressed. If the user pressed a special + keystroke like ^C or ^Z, we raise a KeyboardInterrupt exception. + """ def _handle_timeout(signum, frame) -> None: raise exceptions.TimeoutError() def _single_keystroke_response_internal( - valid_responses: List[str], timeout_seconds=None + valid_responses: Optional[List[str]], timeout_seconds: int = None ) -> str: os_special_keystrokes = [3, 26] # ^C, ^Z if timeout_seconds is not None: @@ -33,7 +55,8 @@ def single_keystroke_response( try: while True: response = readchar.readchar() - if response in valid_responses: + logger.debug('Keystroke: 0x%x', ord(response)) + if not valid_responses or response in valid_responses: break if ord(response) in os_special_keystrokes: break @@ -42,27 +65,70 @@ def single_keystroke_response( if timeout_seconds is not None: signal.alarm(0) + response = None if prompt is not None: print(prompt, end="") sys.stdout.flush() try: - response = _single_keystroke_response_internal( - valid_responses, timeout_seconds - ) + response = _single_keystroke_response_internal(valid_responses, timeout_seconds) + if ord(response) == 3: + raise KeyboardInterrupt('User pressed ^C in input_utils.') + except exceptions.TimeoutError: if default_response is not None: response = default_response - if prompt is not None: + if prompt and response: print(response) return response -def yn_response(prompt: str = None, *, timeout_seconds=None) -> str: - """Get a Y/N response to a prompt.""" +def yn_response(prompt: str = None, *, timeout_seconds=None) -> Optional[str]: + """Get a Y/N response to a prompt. + + Args: + prompt: the user prompt or None to skip this + timeout_seconds: the number of seconds to wait for a response or + None to wait forever. - return single_keystroke_response( + Returns: + A lower case 'y' or 'n'. Or None if the timeout expires with + no input from the user. Or raises a KeyboardInterrupt if the + user pressed a special key such as ^C or ^Z. + """ + yn = single_keystroke_response( ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds - ).lower() + ) + if yn: + yn = yn.lower() + return yn + + +def press_any_key( + prompt: str = "Press any key to continue...", *, timeout_seconds=None +) -> Optional[str]: + """Press any key to continue...""" + + return single_keystroke_response(None, prompt=prompt, timeout_seconds=timeout_seconds) + + +def up_down_enter() -> Optional[str]: + """Respond to UP, DOWN or ENTER events for simple menus without + the need for curses.""" + + os_special_keystrokes = [3, 26] # ^C, ^Z + while True: + key = readchar.readkey() + if len(key) == 1: + if ord(key) in os_special_keystrokes: + return None + if ord(key) == 13: + return 'enter' + elif len(key) == 3: + if ord(key[0]) == 27 and ord(key[1]) == 91: + if ord(key[2]) == 65: + return "up" + elif ord(key[2]) == 66: + return "down" def keystroke_helper() -> None: