#!/usr/bin/env python3 """Utilities related to user input.""" import logging import signal import sys from typing import List import readchar # type: ignore import exceptions logger = logging.getLogger(__file__) def single_keystroke_response( valid_responses: List[str], *, prompt: str = None, default_response: str = None, timeout_seconds: int = None, ) -> str: """Get a single keystroke response to a prompt.""" def _handle_timeout(signum, frame) -> None: raise exceptions.TimeoutError() def _single_keystroke_response_internal( valid_responses: List[str], timeout_seconds=None ) -> str: os_special_keystrokes = [3, 26] # ^C, ^Z if timeout_seconds is not None: signal.signal(signal.SIGALRM, _handle_timeout) signal.alarm(timeout_seconds) try: while True: response = readchar.readchar() logger.debug(f'Keystroke: {ord(response)}') if response in valid_responses: break if ord(response) in os_special_keystrokes: break return response finally: if timeout_seconds is not None: signal.alarm(0) if prompt is not None: print(prompt, end="") sys.stdout.flush() try: 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: print(response) return response def yn_response(prompt: str = None, *, timeout_seconds=None) -> str: """Get a Y/N response to a prompt.""" return single_keystroke_response( ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds ).lower() def keystroke_helper() -> None: """Misc util to watch keystrokes and report what they were.""" print("Watching for keystrokes; ^C to quit.") while True: key = readchar.readkey() if len(key) == 1: print(f'That was "{key}" ({ord(key)}).') if ord(key) == 3: return else: print(f'That was sequence "{key}" (', end="") for _ in key: print(f" {ord(_)} ", end="") print(")")