InquirerPrompt 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- InquirerPy/__init__.py +4 -0
- InquirerPy/base/__init__.py +24 -0
- InquirerPy/base/complex.py +295 -0
- InquirerPy/base/control.py +230 -0
- InquirerPy/base/list.py +239 -0
- InquirerPy/base/simple.py +379 -0
- InquirerPy/containers/__init__.py +3 -0
- InquirerPy/containers/instruction.py +38 -0
- InquirerPy/containers/message.py +42 -0
- InquirerPy/containers/spinner.py +109 -0
- InquirerPy/containers/validation.py +60 -0
- InquirerPy/enum.py +8 -0
- InquirerPy/exceptions.py +25 -0
- InquirerPy/inquirer.py +31 -0
- InquirerPy/prompts/__init__.py +25 -0
- InquirerPy/prompts/checkbox.py +249 -0
- InquirerPy/prompts/confirm.py +202 -0
- InquirerPy/prompts/expand.py +459 -0
- InquirerPy/prompts/filepath.py +193 -0
- InquirerPy/prompts/fuzzy.py +685 -0
- InquirerPy/prompts/input.py +256 -0
- InquirerPy/prompts/list.py +371 -0
- InquirerPy/prompts/number.py +621 -0
- InquirerPy/prompts/rawlist.py +286 -0
- InquirerPy/prompts/secret.py +137 -0
- InquirerPy/py.typed +1 -0
- InquirerPy/resolver.py +220 -0
- InquirerPy/separator.py +23 -0
- InquirerPy/utils.py +291 -0
- InquirerPy/validator.py +166 -0
- inquirerprompt-0.3.0.dist-info/METADATA +217 -0
- inquirerprompt-0.3.0.dist-info/RECORD +34 -0
- inquirerprompt-0.3.0.dist-info/WHEEL +4 -0
- inquirerprompt-0.3.0.dist-info/licenses/LICENSE +21 -0
InquirerPy/base/list.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Contains the base class :class:`.BaseListPrompt` which can be used to create a prompt involving choices."""
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import Any, Callable, List, Optional
|
|
5
|
+
|
|
6
|
+
from prompt_toolkit.filters.base import Condition
|
|
7
|
+
from prompt_toolkit.keys import Keys
|
|
8
|
+
|
|
9
|
+
from InquirerPy.base.complex import BaseComplexPrompt
|
|
10
|
+
from InquirerPy.base.control import InquirerPyUIListControl
|
|
11
|
+
from InquirerPy.separator import Separator
|
|
12
|
+
from InquirerPy.utils import (
|
|
13
|
+
InquirerPyKeybindings,
|
|
14
|
+
InquirerPyMessage,
|
|
15
|
+
InquirerPySessionResult,
|
|
16
|
+
InquirerPyStyle,
|
|
17
|
+
InquirerPyValidate,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseListPrompt(BaseComplexPrompt):
|
|
22
|
+
"""A base class to create a complex prompt involving choice selections (i.e. list) using `prompt_toolkit` Application.
|
|
23
|
+
|
|
24
|
+
Note:
|
|
25
|
+
This class does not create :class:`~prompt_toolkit.layout.Layout` nor :class:`~prompt_toolkit.application.Application`,
|
|
26
|
+
it only contains the necessary attributes and helper functions to be consumed.
|
|
27
|
+
|
|
28
|
+
See Also:
|
|
29
|
+
:class:`~InquirerPy.prompts.list.ListPrompt`
|
|
30
|
+
:class:`~InquirerPy.prompts.fuzzy.FuzzyPrompt`
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
message: InquirerPyMessage,
|
|
36
|
+
style: Optional[InquirerPyStyle] = None,
|
|
37
|
+
vi_mode: bool = False,
|
|
38
|
+
qmark: str = "?",
|
|
39
|
+
amark: str = "?",
|
|
40
|
+
instruction: str = "",
|
|
41
|
+
long_instruction: str = "",
|
|
42
|
+
border: bool = False,
|
|
43
|
+
transformer: Optional[Callable[[Any], Any]] = None,
|
|
44
|
+
filter: Optional[Callable[[Any], Any]] = None,
|
|
45
|
+
validate: Optional[InquirerPyValidate] = None,
|
|
46
|
+
invalid_message: str = "Invalid input",
|
|
47
|
+
multiselect: bool = False,
|
|
48
|
+
keybindings: Optional[InquirerPyKeybindings] = None,
|
|
49
|
+
cycle: bool = True,
|
|
50
|
+
wrap_lines: bool = True,
|
|
51
|
+
raise_keyboard_interrupt: bool = True,
|
|
52
|
+
mandatory: bool = True,
|
|
53
|
+
mandatory_message: str = "Mandatory prompt",
|
|
54
|
+
session_result: Optional[InquirerPySessionResult] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
super().__init__(
|
|
57
|
+
message=message,
|
|
58
|
+
style=style,
|
|
59
|
+
border=border,
|
|
60
|
+
vi_mode=vi_mode,
|
|
61
|
+
qmark=qmark,
|
|
62
|
+
amark=amark,
|
|
63
|
+
transformer=transformer,
|
|
64
|
+
filter=filter,
|
|
65
|
+
invalid_message=invalid_message,
|
|
66
|
+
validate=validate,
|
|
67
|
+
instruction=instruction,
|
|
68
|
+
long_instruction=long_instruction,
|
|
69
|
+
wrap_lines=wrap_lines,
|
|
70
|
+
raise_keyboard_interrupt=raise_keyboard_interrupt,
|
|
71
|
+
mandatory=mandatory,
|
|
72
|
+
mandatory_message=mandatory_message,
|
|
73
|
+
session_result=session_result,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self._content_control: InquirerPyUIListControl
|
|
77
|
+
self._multiselect = multiselect
|
|
78
|
+
self._is_multiselect = Condition(lambda: self._multiselect)
|
|
79
|
+
self._cycle = cycle
|
|
80
|
+
|
|
81
|
+
if not keybindings:
|
|
82
|
+
keybindings = {}
|
|
83
|
+
|
|
84
|
+
self.kb_maps = {
|
|
85
|
+
"down": [
|
|
86
|
+
{"key": "down"},
|
|
87
|
+
{"key": "c-n", "filter": ~self._is_vim_edit},
|
|
88
|
+
{"key": "j", "filter": self._is_vim_edit},
|
|
89
|
+
],
|
|
90
|
+
"up": [
|
|
91
|
+
{"key": "up"},
|
|
92
|
+
{"key": "c-p", "filter": ~self._is_vim_edit},
|
|
93
|
+
{"key": "k", "filter": self._is_vim_edit},
|
|
94
|
+
],
|
|
95
|
+
"toggle": [
|
|
96
|
+
{"key": "space"},
|
|
97
|
+
],
|
|
98
|
+
"toggle-down": [
|
|
99
|
+
{"key": Keys.Tab},
|
|
100
|
+
],
|
|
101
|
+
"toggle-up": [
|
|
102
|
+
{"key": Keys.BackTab},
|
|
103
|
+
],
|
|
104
|
+
"toggle-all": [
|
|
105
|
+
{"key": "alt-r"},
|
|
106
|
+
{"key": "c-r"},
|
|
107
|
+
],
|
|
108
|
+
"toggle-all-true": [
|
|
109
|
+
{"key": "alt-a"},
|
|
110
|
+
{"key": "c-a"},
|
|
111
|
+
],
|
|
112
|
+
"toggle-all-false": [],
|
|
113
|
+
**keybindings,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
self.kb_func_lookup = {
|
|
117
|
+
"down": [{"func": self._handle_down}],
|
|
118
|
+
"up": [{"func": self._handle_up}],
|
|
119
|
+
"toggle": [{"func": self._handle_toggle_choice}],
|
|
120
|
+
"toggle-down": [
|
|
121
|
+
{"func": self._handle_toggle_choice},
|
|
122
|
+
{"func": self._handle_down},
|
|
123
|
+
],
|
|
124
|
+
"toggle-up": [
|
|
125
|
+
{"func": self._handle_toggle_choice},
|
|
126
|
+
{"func": self._handle_up},
|
|
127
|
+
],
|
|
128
|
+
"toggle-all": [{"func": self._handle_toggle_all}],
|
|
129
|
+
"toggle-all-true": [{"func": self._handle_toggle_all, "args": [True]}],
|
|
130
|
+
"toggle-all-false": [{"func": self._handle_toggle_all, "args": [False]}],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def content_control(self) -> InquirerPyUIListControl:
|
|
135
|
+
"""Get the content controller object.
|
|
136
|
+
|
|
137
|
+
Needs to be an instance of :class:`~InquirerPy.base.control.InquirerPyUIListControl`.
|
|
138
|
+
|
|
139
|
+
Each :class:`.BaseComplexPrompt` requires a `content_control` to display custom
|
|
140
|
+
contents for the prompt.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
NotImplementedError: When `self._content_control` is not found.
|
|
144
|
+
"""
|
|
145
|
+
if not self._content_control:
|
|
146
|
+
raise NotImplementedError
|
|
147
|
+
return self._content_control
|
|
148
|
+
|
|
149
|
+
@content_control.setter
|
|
150
|
+
def content_control(self, value: InquirerPyUIListControl) -> None:
|
|
151
|
+
self._content_control = value
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def result_name(self) -> Any:
|
|
155
|
+
"""Get the result value that should be printed to the terminal.
|
|
156
|
+
|
|
157
|
+
In multiselect scenario, return result as a list.
|
|
158
|
+
"""
|
|
159
|
+
if self._multiselect:
|
|
160
|
+
return [choice["name"] for choice in self.selected_choices]
|
|
161
|
+
else:
|
|
162
|
+
try:
|
|
163
|
+
return self.content_control.selection["name"]
|
|
164
|
+
except IndexError:
|
|
165
|
+
return ""
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def result_value(self) -> Any:
|
|
169
|
+
"""Get the result value that should return to the user.
|
|
170
|
+
|
|
171
|
+
In multiselect scenario, return result as a list.
|
|
172
|
+
"""
|
|
173
|
+
if self._multiselect:
|
|
174
|
+
return [choice["value"] for choice in self.selected_choices]
|
|
175
|
+
else:
|
|
176
|
+
try:
|
|
177
|
+
return self.content_control.selection["value"]
|
|
178
|
+
except IndexError:
|
|
179
|
+
return ""
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def selected_choices(self) -> List[Any]:
|
|
183
|
+
"""List[Any]: Get all user selected choices."""
|
|
184
|
+
|
|
185
|
+
def filter_choice(choice):
|
|
186
|
+
return not isinstance(choice, Separator) and choice["enabled"]
|
|
187
|
+
|
|
188
|
+
return list(filter(filter_choice, self.content_control.choices))
|
|
189
|
+
|
|
190
|
+
def _handle_down(self, _) -> bool:
|
|
191
|
+
"""Handle event when user attempts to move down.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Boolean indicating if the action hits the cap.
|
|
195
|
+
"""
|
|
196
|
+
if self._cycle:
|
|
197
|
+
self.content_control.selected_choice_index = (
|
|
198
|
+
self.content_control.selected_choice_index + 1
|
|
199
|
+
) % self.content_control.choice_count
|
|
200
|
+
return False
|
|
201
|
+
else:
|
|
202
|
+
self.content_control.selected_choice_index += 1
|
|
203
|
+
if (
|
|
204
|
+
self.content_control.selected_choice_index
|
|
205
|
+
>= self.content_control.choice_count
|
|
206
|
+
):
|
|
207
|
+
self.content_control.selected_choice_index = (
|
|
208
|
+
self.content_control.choice_count - 1
|
|
209
|
+
)
|
|
210
|
+
return True
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
def _handle_up(self, _) -> bool:
|
|
214
|
+
"""Handle event when user attempts to move up.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Boolean indicating if the action hits the cap.
|
|
218
|
+
"""
|
|
219
|
+
if self._cycle:
|
|
220
|
+
self.content_control.selected_choice_index = (
|
|
221
|
+
self.content_control.selected_choice_index - 1
|
|
222
|
+
) % self.content_control.choice_count
|
|
223
|
+
return False
|
|
224
|
+
else:
|
|
225
|
+
self.content_control.selected_choice_index -= 1
|
|
226
|
+
if self.content_control.selected_choice_index < 0:
|
|
227
|
+
self.content_control.selected_choice_index = 0
|
|
228
|
+
return True
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def _handle_toggle_choice(self, event) -> None:
|
|
233
|
+
"""Handle event when user attempting to toggle the state of the chocie."""
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
@abstractmethod
|
|
237
|
+
def _handle_toggle_all(self, event, value: bool) -> None:
|
|
238
|
+
"""Handle event when user attempting to alter the state of all choices."""
|
|
239
|
+
pass
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Contains the base class :class:`.BaseSimplePrompt`."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
List,
|
|
12
|
+
Optional,
|
|
13
|
+
Tuple,
|
|
14
|
+
Union,
|
|
15
|
+
cast,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from prompt_toolkit.enums import EditingMode
|
|
19
|
+
from prompt_toolkit.filters.base import Condition, FilterOrBool
|
|
20
|
+
from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyHandlerCallable
|
|
21
|
+
from prompt_toolkit.keys import Keys
|
|
22
|
+
from prompt_toolkit.styles.style import Style
|
|
23
|
+
from prompt_toolkit.validation import Validator
|
|
24
|
+
|
|
25
|
+
from InquirerPy.enum import INQUIRERPY_KEYBOARD_INTERRUPT
|
|
26
|
+
from InquirerPy.exceptions import RequiredKeyNotFound
|
|
27
|
+
from InquirerPy.utils import (
|
|
28
|
+
InquirerPyMessage,
|
|
29
|
+
InquirerPySessionResult,
|
|
30
|
+
InquirerPyStyle,
|
|
31
|
+
InquirerPyValidate,
|
|
32
|
+
get_style,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BaseSimplePrompt(ABC):
|
|
40
|
+
"""The base class to create a simple terminal input prompt.
|
|
41
|
+
|
|
42
|
+
Note:
|
|
43
|
+
No actual :class:`~prompt_toolkit.application.Application` is created by this class.
|
|
44
|
+
This class only creates some common interface and attributes that can be easily used
|
|
45
|
+
by `prompt_toolkit`.
|
|
46
|
+
|
|
47
|
+
To have a functional prompt, you'll at least have to implement the :meth:`.BaseSimplePrompt._run`
|
|
48
|
+
and :meth:`.BaseSimplePrompt._get_prompt_message`.
|
|
49
|
+
|
|
50
|
+
See Also:
|
|
51
|
+
:class:`~InquirerPy.prompts.input.InputPrompt`
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
message: InquirerPyMessage,
|
|
57
|
+
style: Optional[InquirerPyStyle] = None,
|
|
58
|
+
vi_mode: bool = False,
|
|
59
|
+
qmark: str = "?",
|
|
60
|
+
amark: str = "?",
|
|
61
|
+
instruction: str = "",
|
|
62
|
+
validate: Optional[InquirerPyValidate] = None,
|
|
63
|
+
invalid_message: str = "Invalid input",
|
|
64
|
+
transformer: Optional[Callable[[Any], Any]] = None,
|
|
65
|
+
filter: Optional[Callable[[Any], Any]] = None,
|
|
66
|
+
default: Any = "",
|
|
67
|
+
wrap_lines: bool = True,
|
|
68
|
+
raise_keyboard_interrupt: bool = True,
|
|
69
|
+
mandatory: bool = True,
|
|
70
|
+
mandatory_message: str = "Mandatory prompt",
|
|
71
|
+
session_result: Optional[InquirerPySessionResult] = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
self._mandatory = mandatory
|
|
74
|
+
self._mandatory_message = mandatory_message
|
|
75
|
+
self._result = session_result or {}
|
|
76
|
+
self._message = (
|
|
77
|
+
message
|
|
78
|
+
if not isinstance(message, Callable)
|
|
79
|
+
else cast(Callable, message)(self._result)
|
|
80
|
+
)
|
|
81
|
+
self._instruction = instruction
|
|
82
|
+
self._default = (
|
|
83
|
+
default if not isinstance(default, Callable) else default(self._result)
|
|
84
|
+
)
|
|
85
|
+
self._style = Style.from_dict(style.dict if style else get_style().dict)
|
|
86
|
+
self._qmark = qmark
|
|
87
|
+
self._amark = amark
|
|
88
|
+
self._status = {"answered": False, "result": None, "skipped": False}
|
|
89
|
+
self._kb = KeyBindings()
|
|
90
|
+
self._lexer = "class:input"
|
|
91
|
+
self._transformer = transformer
|
|
92
|
+
self._filter = filter
|
|
93
|
+
self._wrap_lines = wrap_lines
|
|
94
|
+
self._editing_mode = (
|
|
95
|
+
EditingMode.VI
|
|
96
|
+
if vi_mode or bool(os.getenv("INQUIRERPY_VI_MODE", False))
|
|
97
|
+
else EditingMode.EMACS
|
|
98
|
+
)
|
|
99
|
+
if isinstance(validate, Validator):
|
|
100
|
+
self._validator = validate
|
|
101
|
+
else:
|
|
102
|
+
self._validator = Validator.from_callable(
|
|
103
|
+
validate if validate else lambda _: True,
|
|
104
|
+
invalid_message,
|
|
105
|
+
move_cursor_to_end=True,
|
|
106
|
+
)
|
|
107
|
+
self._raise_kbi = not os.getenv(
|
|
108
|
+
"INQUIRERPY_NO_RAISE_KBI", not raise_keyboard_interrupt
|
|
109
|
+
)
|
|
110
|
+
self._is_rasing_kbi = Condition(lambda: self._raise_kbi)
|
|
111
|
+
|
|
112
|
+
self._kb_maps = {
|
|
113
|
+
"answer": [{"key": Keys.Enter}],
|
|
114
|
+
"interrupt": [
|
|
115
|
+
{"key": "c-c", "filter": self._is_rasing_kbi},
|
|
116
|
+
{"key": "c-d", "filter": ~self._is_rasing_kbi},
|
|
117
|
+
],
|
|
118
|
+
"skip": [{"key": "c-z"}, {"key": "c-c", "filter": ~self._is_rasing_kbi}],
|
|
119
|
+
}
|
|
120
|
+
self._kb_func_lookup = {
|
|
121
|
+
"answer": [{"func": self._handle_enter}],
|
|
122
|
+
"interrupt": [{"func": self._handle_interrupt}],
|
|
123
|
+
"skip": [{"func": self._handle_skip}],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def _keybinding_factory(self):
|
|
127
|
+
"""Register all keybindings in `self._kb_maps`.
|
|
128
|
+
|
|
129
|
+
It's required to call this function at the end of prompt constructor if
|
|
130
|
+
it inherits from :class:`~InquirerPy.base.simple.BaseSimplePrompt` or
|
|
131
|
+
:class:`~InquirerPy.base.complex.BaseComplexPrompt`.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def _factory(keys, filter, action):
|
|
135
|
+
if action not in self.kb_func_lookup:
|
|
136
|
+
raise RequiredKeyNotFound(f"keybinding action {action} not found")
|
|
137
|
+
if not isinstance(keys, list):
|
|
138
|
+
keys = [keys]
|
|
139
|
+
|
|
140
|
+
@self.register_kb(*keys, filter=filter)
|
|
141
|
+
def _(event):
|
|
142
|
+
for method in self.kb_func_lookup[action]:
|
|
143
|
+
method["func"](event, *method.get("args", []))
|
|
144
|
+
|
|
145
|
+
for key, item in self.kb_maps.items():
|
|
146
|
+
if not isinstance(item, list):
|
|
147
|
+
item = [item]
|
|
148
|
+
for kb in item:
|
|
149
|
+
_factory(kb["key"], kb.get("filter", Condition(lambda: True)), key)
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def _set_error(self, message: str) -> None:
|
|
153
|
+
"""Set the error message for the prompt.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
message: Error message to set.
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def _handle_skip(self, event: Optional["KeyPressEvent"]) -> None:
|
|
161
|
+
"""Handle the event when attempting to skip a prompt.
|
|
162
|
+
|
|
163
|
+
Skip the prompt if the `_mandatory` field is False, otherwise
|
|
164
|
+
show an error message that the prompt cannot be skipped.
|
|
165
|
+
"""
|
|
166
|
+
if not self._mandatory:
|
|
167
|
+
self.status["answered"] = True
|
|
168
|
+
self.status["skipped"] = True
|
|
169
|
+
self.status["result"] = None
|
|
170
|
+
if event:
|
|
171
|
+
event.app.exit(result=None)
|
|
172
|
+
else:
|
|
173
|
+
self._set_error(message=self._mandatory_message)
|
|
174
|
+
|
|
175
|
+
def _handle_interrupt(self, event: Optional["KeyPressEvent"]) -> None:
|
|
176
|
+
"""Handle the event when a KeyboardInterrupt signal is sent."""
|
|
177
|
+
self.status["answered"] = True
|
|
178
|
+
self.status["result"] = INQUIRERPY_KEYBOARD_INTERRUPT
|
|
179
|
+
self.status["skipped"] = True
|
|
180
|
+
if event:
|
|
181
|
+
event.app.exit(result=INQUIRERPY_KEYBOARD_INTERRUPT)
|
|
182
|
+
|
|
183
|
+
@abstractmethod
|
|
184
|
+
def _handle_enter(self, event: Optional["KeyPressEvent"]) -> None:
|
|
185
|
+
"""Handle the event when user attempt to answer the question."""
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def status(self) -> Dict[str, Any]:
|
|
190
|
+
"""Dict[str, Any]: Get current prompt status.
|
|
191
|
+
|
|
192
|
+
The status contains 3 keys: "answered" and "result".
|
|
193
|
+
answered: If the current prompt is answered.
|
|
194
|
+
result: The result of the user answer.
|
|
195
|
+
skipped: If the prompt is skipped.
|
|
196
|
+
"""
|
|
197
|
+
return self._status
|
|
198
|
+
|
|
199
|
+
@status.setter
|
|
200
|
+
def status(self, value) -> None:
|
|
201
|
+
self._status = value
|
|
202
|
+
|
|
203
|
+
def register_kb(
|
|
204
|
+
self, *keys: Union[Keys, str], filter: FilterOrBool = True, **kwargs
|
|
205
|
+
) -> Callable[[KeyHandlerCallable], KeyHandlerCallable]:
|
|
206
|
+
"""Keybinding registration decorator.
|
|
207
|
+
|
|
208
|
+
This decorator wraps around the :meth:`prompt_toolkit.key_binding.KeyBindings.add` with
|
|
209
|
+
added feature to process `alt` realted keybindings.
|
|
210
|
+
|
|
211
|
+
By default, `prompt_toolkit` doesn't process `alt` related keybindings,
|
|
212
|
+
it requires `alt-ANY` to `escape` + `ANY`.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
keys: The keys to bind that can trigger the function.
|
|
216
|
+
filter: :class:`~prompt_toolkit.filter.Condition` to indicate if this keybinding should be active.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A decorator that should be applied to the function thats intended to be active when the keys
|
|
220
|
+
are pressed.
|
|
221
|
+
|
|
222
|
+
Examples:
|
|
223
|
+
>>> @self.register_kb("alt-j")
|
|
224
|
+
... def test(event):
|
|
225
|
+
... pass
|
|
226
|
+
"""
|
|
227
|
+
alt_pattern = re.compile(r"^alt-(.*)")
|
|
228
|
+
|
|
229
|
+
def decorator(func: KeyHandlerCallable) -> KeyHandlerCallable:
|
|
230
|
+
formatted_keys = []
|
|
231
|
+
for key in keys:
|
|
232
|
+
match = alt_pattern.match(key)
|
|
233
|
+
if match:
|
|
234
|
+
formatted_keys.append("escape")
|
|
235
|
+
formatted_keys.append(match.group(1))
|
|
236
|
+
else:
|
|
237
|
+
formatted_keys.append(key)
|
|
238
|
+
|
|
239
|
+
@self._kb.add(*formatted_keys, filter=filter, **kwargs)
|
|
240
|
+
def executable(event) -> None:
|
|
241
|
+
func(event)
|
|
242
|
+
|
|
243
|
+
return executable
|
|
244
|
+
|
|
245
|
+
return decorator
|
|
246
|
+
|
|
247
|
+
@abstractmethod
|
|
248
|
+
def _get_prompt_message(
|
|
249
|
+
self, pre_answer: Tuple[str, str], post_answer: Tuple[str, str]
|
|
250
|
+
) -> List[Tuple[str, str]]:
|
|
251
|
+
"""Get the question message in formatted text form to display in the prompt.
|
|
252
|
+
|
|
253
|
+
This function is mainly used to render the question message dynamically based
|
|
254
|
+
on the current status (answered or not answered) of the prompt.
|
|
255
|
+
|
|
256
|
+
Note:
|
|
257
|
+
The function requires implementation when inheriting :class:`.BaseSimplePrompt`.
|
|
258
|
+
You should call `super()._get_prompt_message(pre_answer, post_answer)` in
|
|
259
|
+
the implemented `_get_prompt_message`.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
pre_answer: The message to display before the question is answered.
|
|
263
|
+
post_answer: The information to display after the question is answered.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Formatted text in list of tuple format.
|
|
267
|
+
"""
|
|
268
|
+
display_message = []
|
|
269
|
+
if self.status["skipped"]:
|
|
270
|
+
display_message.append(("class:skipped", self._qmark))
|
|
271
|
+
display_message.append(
|
|
272
|
+
("class:skipped", "%s%s " % (" " if self._qmark else "", self._message))
|
|
273
|
+
)
|
|
274
|
+
elif self.status["answered"]:
|
|
275
|
+
display_message.append(("class:answermark", self._amark))
|
|
276
|
+
display_message.append(
|
|
277
|
+
(
|
|
278
|
+
"class:answered_question",
|
|
279
|
+
"%s%s" % (" " if self._amark else "", self._message),
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
display_message.append(
|
|
283
|
+
post_answer
|
|
284
|
+
if not self._transformer
|
|
285
|
+
else (
|
|
286
|
+
"class:answer",
|
|
287
|
+
" %s" % self._transformer(self.status["result"]),
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
display_message.append(("class:questionmark", self._qmark))
|
|
292
|
+
display_message.append(
|
|
293
|
+
(
|
|
294
|
+
"class:question",
|
|
295
|
+
"%s%s" % (" " if self._qmark else "", self._message),
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
display_message.append(pre_answer)
|
|
299
|
+
return display_message
|
|
300
|
+
|
|
301
|
+
@abstractmethod
|
|
302
|
+
def _run(self) -> Any:
|
|
303
|
+
"""Abstractmethod to enforce a run function is implemented.
|
|
304
|
+
|
|
305
|
+
All prompt instance requires a `_run` call to initialise and run an instance of
|
|
306
|
+
`PromptSession` or `Application`.
|
|
307
|
+
"""
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
@abstractmethod
|
|
311
|
+
async def _run_async(self) -> Any:
|
|
312
|
+
"""Abstractmethod to enforce a run function is implemented.
|
|
313
|
+
|
|
314
|
+
All prompt instance requires a `_run_async` call to initialise and run an instance of
|
|
315
|
+
`PromptSession` or `Application`.
|
|
316
|
+
"""
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
def execute(self, raise_keyboard_interrupt: Optional[bool] = None) -> Any:
|
|
320
|
+
"""Run the prompt and get the result.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
raise_keyboard_interrupt: **Deprecated**. Set this parameter on the prompt initialisation instead.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Value of the user answer. Types varies depending on the prompt.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
KeyboardInterrupt: When `ctrl-c` is pressed and `raise_keyboard_interrupt` is True.
|
|
330
|
+
"""
|
|
331
|
+
result = self._run()
|
|
332
|
+
if raise_keyboard_interrupt is not None:
|
|
333
|
+
self._raise_kbi = not os.getenv(
|
|
334
|
+
"INQUIRERPY_NO_RAISE_KBI", not raise_keyboard_interrupt
|
|
335
|
+
)
|
|
336
|
+
if result == INQUIRERPY_KEYBOARD_INTERRUPT:
|
|
337
|
+
raise KeyboardInterrupt
|
|
338
|
+
if not self._filter:
|
|
339
|
+
return result
|
|
340
|
+
return self._filter(result)
|
|
341
|
+
|
|
342
|
+
async def execute_async(self) -> Any:
|
|
343
|
+
"""Run the prompt asynchronously and get the result.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Value of the user answer. Types varies depending on the prompt.
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
KeyboardInterrupt: When `ctrl-c` is pressed and `raise_keyboard_interrupt` is True.
|
|
350
|
+
"""
|
|
351
|
+
result = await self._run_async()
|
|
352
|
+
if result == INQUIRERPY_KEYBOARD_INTERRUPT:
|
|
353
|
+
raise KeyboardInterrupt
|
|
354
|
+
if not self._filter:
|
|
355
|
+
return result
|
|
356
|
+
return self._filter(result)
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def instruction(self) -> str:
|
|
360
|
+
"""str: Instruction to display next to question."""
|
|
361
|
+
return self._instruction
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def kb_maps(self) -> Dict[str, Any]:
|
|
365
|
+
"""Dict[str, Any]: Keybinding mappings."""
|
|
366
|
+
return self._kb_maps
|
|
367
|
+
|
|
368
|
+
@kb_maps.setter
|
|
369
|
+
def kb_maps(self, value: Dict[str, Any]) -> None:
|
|
370
|
+
self._kb_maps = {**self._kb_maps, **value}
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def kb_func_lookup(self) -> Dict[str, Any]:
|
|
374
|
+
"""Dict[str, Any]: Keybinding function lookup mappings.."""
|
|
375
|
+
return self._kb_func_lookup
|
|
376
|
+
|
|
377
|
+
@kb_func_lookup.setter
|
|
378
|
+
def kb_func_lookup(self, value: Dict[str, Any]) -> None:
|
|
379
|
+
self._kb_func_lookup = {**self._kb_func_lookup, **value}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Module contains :class:`.InstructionWindow` which can be used to display long instructions."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit.layout.containers import ConditionalContainer, Window
|
|
6
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from prompt_toolkit.filters.base import FilterOrBool
|
|
10
|
+
from prompt_toolkit.formatted_text.base import AnyFormattedText
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InstructionWindow(ConditionalContainer):
|
|
14
|
+
"""Conditional `prompt_toolkit` :class:`~prompt_toolkit.layout.Window` that displays long instructions.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
message: Long instructions to display.
|
|
18
|
+
filter: Condition to display the instruction window.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, filter: "FilterOrBool", **kwargs) -> None:
|
|
22
|
+
self._message = message
|
|
23
|
+
super().__init__(
|
|
24
|
+
Window(
|
|
25
|
+
FormattedTextControl(text=self._get_message),
|
|
26
|
+
dont_extend_height=True,
|
|
27
|
+
**kwargs
|
|
28
|
+
),
|
|
29
|
+
filter=filter,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _get_message(self) -> "AnyFormattedText":
|
|
33
|
+
"""Get long instruction to display.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
FormattedText in list of tuple format.
|
|
37
|
+
"""
|
|
38
|
+
return [("class:long_instruction", self._message)]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Module contains the main message window :class:`~prompt_toolkit.container.Container`."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit.layout.containers import ConditionalContainer, Window
|
|
6
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
7
|
+
from prompt_toolkit.layout.dimension import LayoutDimension
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from prompt_toolkit.filters.base import FilterOrBool
|
|
11
|
+
from prompt_toolkit.formatted_text.base import AnyFormattedText
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MessageWindow(ConditionalContainer):
|
|
15
|
+
"""Main window to display question to the user.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
message: The message to display in the terminal.
|
|
19
|
+
filter: Condition that this message window should be displayed.
|
|
20
|
+
Use a loading condition to only display this window while its not loading.
|
|
21
|
+
wrap_lines: Enable line wrapping if the message is too long.
|
|
22
|
+
show_cursor: Display cursor.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
message: "AnyFormattedText",
|
|
28
|
+
filter: "FilterOrBool",
|
|
29
|
+
wrap_lines: bool = True,
|
|
30
|
+
show_cursor: bool = True,
|
|
31
|
+
**kwargs
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(
|
|
34
|
+
content=Window(
|
|
35
|
+
height=LayoutDimension.exact(1) if not wrap_lines else None,
|
|
36
|
+
content=FormattedTextControl(message, show_cursor=show_cursor),
|
|
37
|
+
wrap_lines=wrap_lines,
|
|
38
|
+
dont_extend_height=True,
|
|
39
|
+
**kwargs
|
|
40
|
+
),
|
|
41
|
+
filter=filter,
|
|
42
|
+
)
|