Used isort to sort imports. Also added to the git pre-commit hook.
[python_utils.git] / input_utils.py
1 #!/usr/bin/env python3
2
3 """Utilities related to user input."""
4
5 import logging
6 import signal
7 import sys
8 from typing import List
9
10 import readchar  # type: ignore
11
12 import exceptions
13
14 logger = logging.getLogger(__file__)
15
16
17 def single_keystroke_response(
18     valid_responses: List[str],
19     *,
20     prompt: str = None,
21     default_response: str = None,
22     timeout_seconds: int = None,
23 ) -> str:
24     """Get a single keystroke response to a prompt."""
25
26     def _handle_timeout(signum, frame) -> None:
27         raise exceptions.TimeoutError()
28
29     def _single_keystroke_response_internal(
30         valid_responses: List[str], timeout_seconds=None
31     ) -> str:
32         os_special_keystrokes = [3, 26]  # ^C, ^Z
33         if timeout_seconds is not None:
34             signal.signal(signal.SIGALRM, _handle_timeout)
35             signal.alarm(timeout_seconds)
36
37         try:
38             while True:
39                 response = readchar.readchar()
40                 logger.debug(f'Keystroke: {ord(response)}')
41                 if response in valid_responses:
42                     break
43                 if ord(response) in os_special_keystrokes:
44                     break
45             return response
46         finally:
47             if timeout_seconds is not None:
48                 signal.alarm(0)
49
50     if prompt is not None:
51         print(prompt, end="")
52         sys.stdout.flush()
53     try:
54         response = _single_keystroke_response_internal(valid_responses, timeout_seconds)
55         if ord(response) == 3:
56             raise KeyboardInterrupt('User pressed ^C in input_utils.')
57
58     except exceptions.TimeoutError:
59         if default_response is not None:
60             response = default_response
61     if prompt is not None:
62         print(response)
63     return response
64
65
66 def yn_response(prompt: str = None, *, timeout_seconds=None) -> str:
67     """Get a Y/N response to a prompt."""
68
69     return single_keystroke_response(
70         ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds
71     ).lower()
72
73
74 def keystroke_helper() -> None:
75     """Misc util to watch keystrokes and report what they were."""
76
77     print("Watching for keystrokes; ^C to quit.")
78     while True:
79         key = readchar.readkey()
80         if len(key) == 1:
81             print(f'That was "{key}" ({ord(key)}).')
82             if ord(key) == 3:
83                 return
84         else:
85             print(f'That was sequence "{key}" (', end="")
86             for _ in key:
87                 print(f" {ord(_)} ", end="")
88             print(")")