Initial revision
[python_utils.git] / input_utils.py
1 #!/usr/bin/env python3
2
3 """Utilities related to user input."""
4
5 import readchar  # type: ignore
6 import signal
7 import sys
8 from typing import List
9
10
11 def single_keystroke_response(
12     valid_responses: List[str],
13     *,
14     prompt: str = None,
15     default_response: str = None,
16     timeout_seconds: int = None,
17 ) -> str:
18     class TimeoutError(Exception):
19         pass
20
21     def _handle_timeout(signum, frame) -> None:
22         raise TimeoutError()
23
24     def _single_keystroke_response_internal(
25         valid_responses: List[str], timeout_seconds=None
26     ) -> str:
27         if timeout_seconds is not None:
28             signal.signal(signal.SIGALRM, _handle_timeout)
29             signal.alarm(timeout_seconds)
30
31         try:
32             while True:
33                 response = readchar.readchar()
34                 if response in valid_responses:
35                     break
36             return response
37         finally:
38             if timeout_seconds is not None:
39                 signal.alarm(0)
40
41     if prompt is not None:
42         print(prompt, end="")
43         sys.stdout.flush()
44     try:
45         response = _single_keystroke_response_internal(
46             valid_responses, timeout_seconds
47         )
48     except TimeoutError:
49         if default_response is not None:
50             response = default_response
51     if prompt is not None:
52         print(response)
53     return response
54
55
56 def yn_response(prompt: str = None, *, timeout_seconds=None) -> str:
57     return single_keystroke_response(
58         ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds
59     ).lower()
60
61
62 def keystroke_helper() -> None:
63     print("Watching for keystrokes; ^C to quit.")
64     while True:
65         key = readchar.readkey()
66         if len(key) == 1:
67             print(f'That was "{key}" ({ord(key)}).')
68             if ord(key) == 3:
69                 return
70         else:
71             print(f'That was sequence "{key}" (', end="")
72             for _ in key:
73                 print(f" {ord(_)} ", end="")
74             print(")")