zhzhang93 commited on
Commit
113ea10
·
verified ·
1 Parent(s): 30ce399

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env/lib/python3.12/site-packages/InquirerPy/__init__.py +2 -0
  2. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/__init__.cpython-312.pyc +0 -0
  3. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/enum.cpython-312.pyc +0 -0
  4. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/exceptions.cpython-312.pyc +0 -0
  5. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/inquirer.cpython-312.pyc +0 -0
  6. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/resolver.cpython-312.pyc +0 -0
  7. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/separator.cpython-312.pyc +0 -0
  8. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/utils.cpython-312.pyc +0 -0
  9. .env/lib/python3.12/site-packages/InquirerPy/__pycache__/validator.cpython-312.pyc +0 -0
  10. .env/lib/python3.12/site-packages/InquirerPy/base/__init__.py +15 -0
  11. .env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/__init__.cpython-312.pyc +0 -0
  12. .env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/complex.cpython-312.pyc +0 -0
  13. .env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/control.cpython-312.pyc +0 -0
  14. .env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/list.cpython-312.pyc +0 -0
  15. .env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/simple.cpython-312.pyc +0 -0
  16. .env/lib/python3.12/site-packages/InquirerPy/base/complex.py +294 -0
  17. .env/lib/python3.12/site-packages/InquirerPy/base/control.py +227 -0
  18. .env/lib/python3.12/site-packages/InquirerPy/base/list.py +238 -0
  19. .env/lib/python3.12/site-packages/InquirerPy/base/simple.py +378 -0
  20. .env/lib/python3.12/site-packages/InquirerPy/containers/__init__.py +1 -0
  21. .env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/__init__.cpython-312.pyc +0 -0
  22. .env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/instruction.cpython-312.pyc +0 -0
  23. .env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/message.cpython-312.pyc +0 -0
  24. .env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/spinner.cpython-312.pyc +0 -0
  25. .env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/validation.cpython-312.pyc +0 -0
  26. .env/lib/python3.12/site-packages/InquirerPy/containers/instruction.py +38 -0
  27. .env/lib/python3.12/site-packages/InquirerPy/containers/message.py +42 -0
  28. .env/lib/python3.12/site-packages/InquirerPy/containers/spinner.py +108 -0
  29. .env/lib/python3.12/site-packages/InquirerPy/containers/validation.py +60 -0
  30. .env/lib/python3.12/site-packages/InquirerPy/enum.py +7 -0
  31. .env/lib/python3.12/site-packages/InquirerPy/exceptions.py +25 -0
  32. .env/lib/python3.12/site-packages/InquirerPy/inquirer.py +17 -0
  33. .env/lib/python3.12/site-packages/InquirerPy/prompts/__init__.py +11 -0
  34. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/__init__.cpython-312.pyc +0 -0
  35. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/checkbox.cpython-312.pyc +0 -0
  36. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/confirm.cpython-312.pyc +0 -0
  37. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/expand.cpython-312.pyc +0 -0
  38. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/filepath.cpython-312.pyc +0 -0
  39. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/fuzzy.cpython-312.pyc +0 -0
  40. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/input.cpython-312.pyc +0 -0
  41. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/list.cpython-312.pyc +0 -0
  42. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/number.cpython-312.pyc +0 -0
  43. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/rawlist.cpython-312.pyc +0 -0
  44. .env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/secret.cpython-312.pyc +0 -0
  45. .env/lib/python3.12/site-packages/InquirerPy/prompts/checkbox.py +244 -0
  46. .env/lib/python3.12/site-packages/InquirerPy/prompts/confirm.py +196 -0
  47. .env/lib/python3.12/site-packages/InquirerPy/prompts/expand.py +458 -0
  48. .env/lib/python3.12/site-packages/InquirerPy/prompts/filepath.py +192 -0
  49. .env/lib/python3.12/site-packages/InquirerPy/prompts/fuzzy.py +680 -0
  50. .env/lib/python3.12/site-packages/InquirerPy/prompts/input.py +253 -0
.env/lib/python3.12/site-packages/InquirerPy/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from InquirerPy.resolver import prompt, prompt_async
2
+ from InquirerPy.utils import get_style
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (303 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/enum.cpython-312.pyc ADDED
Binary file (575 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/exceptions.cpython-312.pyc ADDED
Binary file (1.43 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/inquirer.cpython-312.pyc ADDED
Binary file (916 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/resolver.cpython-312.pyc ADDED
Binary file (8.38 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/separator.cpython-312.pyc ADDED
Binary file (1.26 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/utils.cpython-312.pyc ADDED
Binary file (12.8 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/__pycache__/validator.cpython-312.pyc ADDED
Binary file (7.48 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains base class for prompts.
2
+
3
+ BaseSimplePrompt ← InputPrompt ← SecretPrompt ...
4
+
5
+ BaseComplexPrompt
6
+
7
+ BaseListPrompt ← FuzzyPrompt
8
+
9
+ ListPrompt ← ExpandPrompt ...
10
+ """
11
+
12
+ from .complex import BaseComplexPrompt, FakeDocument
13
+ from .control import Choice, InquirerPyUIListControl
14
+ from .list import BaseListPrompt
15
+ from .simple import BaseSimplePrompt
.env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (658 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/complex.cpython-312.pyc ADDED
Binary file (13.9 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/control.cpython-312.pyc ADDED
Binary file (10.6 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/list.cpython-312.pyc ADDED
Binary file (9.44 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/__pycache__/simple.cpython-312.pyc ADDED
Binary file (17.9 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/base/complex.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Contains the interface class :class:`.BaseComplexPrompt` for more complex prompts and the mocked document class :class:`.FakeDocument`."""
2
+ import shutil
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, List, Optional, Tuple, Union
5
+
6
+ from prompt_toolkit.application import Application
7
+ from prompt_toolkit.enums import EditingMode
8
+ from prompt_toolkit.filters.base import Condition, FilterOrBool
9
+ from prompt_toolkit.key_binding.key_bindings import KeyHandlerCallable
10
+ from prompt_toolkit.keys import Keys
11
+
12
+ from InquirerPy.base.simple import BaseSimplePrompt
13
+ from InquirerPy.enum import INQUIRERPY_KEYBOARD_INTERRUPT
14
+ from InquirerPy.utils import (
15
+ InquirerPySessionResult,
16
+ InquirerPyStyle,
17
+ InquirerPyValidate,
18
+ )
19
+
20
+
21
+ @dataclass
22
+ class FakeDocument:
23
+ """A fake `prompt_toolkit` document class.
24
+
25
+ Work around to allow non-buffer type :class:`~prompt_toolkit.layout.UIControl` to use
26
+ :class:`~prompt_toolkit.validation.Validator`.
27
+
28
+ Args:
29
+ text: Content to be validated.
30
+ cursor_position: Fake cursor position.
31
+ """
32
+
33
+ text: str
34
+ cursor_position: int = 0
35
+
36
+
37
+ class BaseComplexPrompt(BaseSimplePrompt):
38
+ """A base class to create a more complex prompt that will involve :class:`~prompt_toolkit.application.Application`.
39
+
40
+ Note:
41
+ This class does not create :class:`~prompt_toolkit.layout.Layout` nor :class:`~prompt_toolkit.application.Application`,
42
+ it only contains the necessary attributes and helper functions to be consumed.
43
+
44
+ Note:
45
+ Use :class:`~InquirerPy.base.BaseListPrompt` to create a complex list prompt which involves multiple choices. It has
46
+ more methods and helper function implemented.
47
+
48
+ See Also:
49
+ :class:`~InquirerPy.base.BaseListPrompt`
50
+ :class:`~InquirerPy.prompts.fuzzy.FuzzyPrompt`
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ message: Union[str, Callable[[InquirerPySessionResult], str]],
56
+ style: Optional[InquirerPyStyle] = None,
57
+ border: bool = False,
58
+ vi_mode: bool = False,
59
+ qmark: str = "?",
60
+ amark: str = "?",
61
+ instruction: str = "",
62
+ long_instruction: str = "",
63
+ transformer: Optional[Callable[[Any], Any]] = None,
64
+ filter: Optional[Callable[[Any], Any]] = None,
65
+ validate: Optional[InquirerPyValidate] = None,
66
+ invalid_message: str = "Invalid input",
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
+ super().__init__(
74
+ message=message,
75
+ style=style,
76
+ vi_mode=vi_mode,
77
+ qmark=qmark,
78
+ amark=amark,
79
+ instruction=instruction,
80
+ transformer=transformer,
81
+ filter=filter,
82
+ invalid_message=invalid_message,
83
+ validate=validate,
84
+ wrap_lines=wrap_lines,
85
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
86
+ mandatory=mandatory,
87
+ mandatory_message=mandatory_message,
88
+ session_result=session_result,
89
+ )
90
+ self._invalid_message = invalid_message
91
+ self._rendered = False
92
+ self._invalid = False
93
+ self._loading = False
94
+ self._application: Application
95
+ self._long_instruction = long_instruction
96
+ self._border = border
97
+ self._height_offset = 2 # prev prompt result + current prompt question
98
+ if self._border:
99
+ self._height_offset += 2
100
+ if self._long_instruction:
101
+ self._height_offset += 1
102
+ self._validation_window_bottom_offset = 0 if not self._long_instruction else 1
103
+ if self._wrap_lines:
104
+ self._validation_window_bottom_offset += (
105
+ self.extra_long_instruction_line_count
106
+ )
107
+
108
+ self._is_vim_edit = Condition(lambda: self._editing_mode == EditingMode.VI)
109
+ self._is_invalid = Condition(lambda: self._invalid)
110
+ self._is_displaying_long_instruction = Condition(
111
+ lambda: self._long_instruction != ""
112
+ )
113
+
114
+ def _redraw(self) -> None:
115
+ """Redraw the application UI."""
116
+ self._application.invalidate()
117
+
118
+ def register_kb(
119
+ self, *keys: Union[Keys, str], filter: FilterOrBool = True
120
+ ) -> Callable[[KeyHandlerCallable], KeyHandlerCallable]:
121
+ """Decorate keybinding registration function.
122
+
123
+ Ensure that the `invalid` state is cleared on next keybinding entered.
124
+ """
125
+ kb_dec = super().register_kb(*keys, filter=filter)
126
+
127
+ def decorator(func: KeyHandlerCallable) -> KeyHandlerCallable:
128
+ @kb_dec
129
+ def executable(event):
130
+ if self._invalid:
131
+ self._invalid = False
132
+ func(event)
133
+
134
+ return executable
135
+
136
+ return decorator
137
+
138
+ def _exception_handler(self, _, context) -> None:
139
+ """Set exception handler for the event loop.
140
+
141
+ Skip the question and raise exception.
142
+
143
+ Args:
144
+ loop: Current event loop.
145
+ context: Exception context.
146
+ """
147
+ self._status["answered"] = True
148
+ self._status["result"] = INQUIRERPY_KEYBOARD_INTERRUPT
149
+ self._status["skipped"] = True
150
+ self._application.exit(exception=context["exception"])
151
+
152
+ def _after_render(self, app: Optional[Application]) -> None:
153
+ """Run after the :class:`~prompt_toolkit.application.Application` is rendered/updated.
154
+
155
+ Since this function is fired up on each render, adding a check on `self._rendered` to
156
+ process logics that should only run once.
157
+
158
+ Set event loop exception handler here, since its guaranteed that the event loop is running
159
+ in `_after_render`.
160
+ """
161
+ if not self._rendered:
162
+ self._rendered = True
163
+
164
+ self._keybinding_factory()
165
+ self._on_rendered(app)
166
+
167
+ def _set_error(self, message: str) -> None:
168
+ """Set error message and set invalid state.
169
+
170
+ Args:
171
+ message: Error message to display.
172
+ """
173
+ self._invalid_message = message
174
+ self._invalid = True
175
+
176
+ def _get_error_message(self) -> List[Tuple[str, str]]:
177
+ """Obtain the error message dynamically.
178
+
179
+ Returns:
180
+ FormattedText in list of tuple format.
181
+ """
182
+ return [
183
+ (
184
+ "class:validation-toolbar",
185
+ self._invalid_message,
186
+ )
187
+ ]
188
+
189
+ def _on_rendered(self, _: Optional[Application]) -> None:
190
+ """Run once after the UI is rendered. Acts like `ComponentDidMount`."""
191
+ pass
192
+
193
+ def _get_prompt_message(self) -> List[Tuple[str, str]]:
194
+ """Get the prompt message to display.
195
+
196
+ Returns:
197
+ Formatted text in list of tuple format.
198
+ """
199
+ pre_answer = (
200
+ "class:instruction",
201
+ " %s " % self.instruction if self.instruction else " ",
202
+ )
203
+ post_answer = ("class:answer", " %s" % self.status["result"])
204
+ return super()._get_prompt_message(pre_answer, post_answer)
205
+
206
+ def _run(self) -> Any:
207
+ """Run the application."""
208
+ return self.application.run()
209
+
210
+ async def _run_async(self) -> None:
211
+ """Run the application asynchronously."""
212
+ return await self.application.run_async()
213
+
214
+ @property
215
+ def application(self) -> Application:
216
+ """Get the application.
217
+
218
+ :class:`.BaseComplexPrompt` requires :attr:`.BaseComplexPrompt._application` to be defined since this class
219
+ doesn't implement :class:`~prompt_toolkit.layout.Layout` and :class:`~prompt_toolkit.application.Application`.
220
+
221
+ Raises:
222
+ NotImplementedError: When `self._application` is not defined.
223
+ """
224
+ if not self._application:
225
+ raise NotImplementedError
226
+ return self._application
227
+
228
+ @application.setter
229
+ def application(self, value: Application) -> None:
230
+ self._application = value
231
+
232
+ @property
233
+ def height_offset(self) -> int:
234
+ """int: Height offset to apply."""
235
+ if not self._wrap_lines:
236
+ return self._height_offset
237
+ return self.extra_line_count + self._height_offset
238
+
239
+ @property
240
+ def total_message_length(self) -> int:
241
+ """int: Total length of the message."""
242
+ total_message_length = 0
243
+ if self._qmark:
244
+ total_message_length += len(self._qmark)
245
+ total_message_length += 1 # Extra space if qmark is present
246
+ total_message_length += len(str(self._message))
247
+ total_message_length += 1 # Extra space between message and instruction
248
+ total_message_length += len(str(self._instruction))
249
+ if self._instruction:
250
+ total_message_length += 1 # Extra space behind the instruction
251
+ return total_message_length
252
+
253
+ @property
254
+ def extra_message_line_count(self) -> int:
255
+ """int: Get the extra lines created caused by line wrapping.
256
+
257
+ Minus 1 on the totoal message length as we only want the extra line.
258
+ 24 // 24 will equal to 1 however we only want the value to be 1 when we have 25 char
259
+ which will create an extra line.
260
+ """
261
+ term_width, _ = shutil.get_terminal_size()
262
+ return (self.total_message_length - 1) // term_width
263
+
264
+ @property
265
+ def extra_long_instruction_line_count(self) -> int:
266
+ """int: Get the extra lines created caused by line wrapping.
267
+
268
+ See Also:
269
+ :attr:`.BaseComplexPrompt.extra_message_line_count`
270
+ """
271
+ if self._long_instruction:
272
+ term_width, _ = shutil.get_terminal_size()
273
+ return (len(self._long_instruction) - 1) // term_width
274
+ else:
275
+ return 0
276
+
277
+ @property
278
+ def extra_line_count(self) -> int:
279
+ """Get the extra lines created caused by line wrapping.
280
+
281
+ Used mainly to calculate how much additional offset should be applied when getting
282
+ the height.
283
+
284
+ Returns:
285
+ Total extra lines created due to line wrapping.
286
+ """
287
+ result = 0
288
+
289
+ # message wrap
290
+ result += self.extra_message_line_count
291
+ # long instruction wrap
292
+ result += self.extra_long_instruction_line_count
293
+
294
+ return result
.env/lib/python3.12/site-packages/InquirerPy/base/control.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Contains the content control class :class:`.InquirerPyUIListControl`."""
2
+ from abc import abstractmethod
3
+ from dataclasses import asdict, dataclass
4
+ from typing import Any, Callable, Dict, List, Optional, Tuple, cast
5
+
6
+ from prompt_toolkit.layout.controls import FormattedTextControl
7
+
8
+ from InquirerPy.exceptions import InvalidArgument, RequiredKeyNotFound
9
+ from InquirerPy.separator import Separator
10
+ from InquirerPy.utils import InquirerPyListChoices, InquirerPySessionResult
11
+
12
+ __all__ = ["Choice", "InquirerPyUIListControl"]
13
+
14
+
15
+ @dataclass
16
+ class Choice:
17
+ """Class to create choices for list type prompts.
18
+
19
+ A simple dataclass that can be used as an alternate to using :class:`dict`
20
+ when working with choices.
21
+
22
+ Args:
23
+ value: The value of the choice when user selects this choice.
24
+ name: The value that should be presented to the user prior/after selection of the choice.
25
+ This value is optional, if not provided, it will fallback to the string representation of `value`.
26
+ enabled: Indicates if the choice should be pre-selected.
27
+ This only has effects when the prompt has `multiselect` enabled.
28
+ """
29
+
30
+ value: Any
31
+ name: Optional[str] = None
32
+ enabled: bool = False
33
+
34
+ def __post_init__(self):
35
+ """Assign strinify value to name if not present."""
36
+ if self.name is None:
37
+ self.name = str(self.value)
38
+
39
+
40
+ class InquirerPyUIListControl(FormattedTextControl):
41
+ """A base class to create :class:`~prompt_toolkit.layout.UIControl` to display list type contents.
42
+
43
+ Args:
44
+ choices(InquirerPyListChoices): List of choices to display as the content.
45
+ Can also be a callable or async callable that returns a list of choices.
46
+ default: Default value, this will affect the cursor position.
47
+ multiselect: Indicate if the current prompt has `multiselect` enabled.
48
+ session_result: Current session result.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ choices: InquirerPyListChoices,
54
+ default: Any = None,
55
+ multiselect: bool = False,
56
+ session_result: Optional[InquirerPySessionResult] = None,
57
+ ) -> None:
58
+ self._session_result = session_result or {}
59
+ self._selected_choice_index: int = 0
60
+ self._choice_func = None
61
+ self._multiselect = multiselect
62
+ self._default = (
63
+ default
64
+ if not isinstance(default, Callable)
65
+ else cast(Callable, default)(self._session_result)
66
+ )
67
+ self._raw_choices = (
68
+ choices
69
+ if not isinstance(choices, Callable)
70
+ else cast(Callable, choices)(self._session_result)
71
+ )
72
+ self._choices = self._get_choices(self._raw_choices, self._default)
73
+ self._safety_check()
74
+ self._format_choices()
75
+ super().__init__(self._get_formatted_choices)
76
+
77
+ def _get_choices(self, choices: List[Any], default: Any) -> List[Dict[str, Any]]:
78
+ """Process the raw user input choices and format it into dictionary.
79
+
80
+ Args:
81
+ choices: List of chices to display.
82
+ default: Default value, this will affect the :attr:`.InquirerPyUIListControl.selected_choice_index`
83
+
84
+ Returns:
85
+ List of choices.
86
+
87
+ Raises:
88
+ RequiredKeyNotFound: When the provided choice is missing the `name` or `value` key.
89
+ """
90
+ processed_choices: List[Dict[str, Any]] = []
91
+ try:
92
+ for index, choice in enumerate(choices, start=0):
93
+ if isinstance(choice, dict):
94
+ if choice["value"] == default:
95
+ self.selected_choice_index = index
96
+ processed_choices.append(
97
+ {
98
+ "name": str(choice["name"]),
99
+ "value": choice["value"],
100
+ "enabled": choice.get("enabled", False)
101
+ if self._multiselect
102
+ else False,
103
+ }
104
+ )
105
+ elif isinstance(choice, Separator):
106
+ if self.selected_choice_index == index:
107
+ self.selected_choice_index = (
108
+ self.selected_choice_index + 1
109
+ ) % len(choices)
110
+ processed_choices.append(
111
+ {"name": str(choice), "value": choice, "enabled": False}
112
+ )
113
+ elif isinstance(choice, Choice):
114
+ dict_choice = asdict(choice)
115
+ if dict_choice["value"] == default:
116
+ self.selected_choice_index = index
117
+ if not self._multiselect:
118
+ dict_choice["enabled"] = False
119
+ processed_choices.append(dict_choice)
120
+ else:
121
+ if choice == default:
122
+ self.selected_choice_index = index
123
+ processed_choices.append(
124
+ {"name": str(choice), "value": choice, "enabled": False}
125
+ )
126
+ except KeyError:
127
+ raise RequiredKeyNotFound(
128
+ "dictionary type of choice require a 'name' key and a 'value' key"
129
+ )
130
+ return processed_choices
131
+
132
+ @property
133
+ def selected_choice_index(self) -> int:
134
+ """int: Current highlighted index."""
135
+ return self._selected_choice_index
136
+
137
+ @selected_choice_index.setter
138
+ def selected_choice_index(self, value: int) -> None:
139
+ self._selected_choice_index = value
140
+
141
+ @property
142
+ def choices(self) -> List[Dict[str, Any]]:
143
+ """List[Dict[str, Any]]: Get all processed choices."""
144
+ return self._choices
145
+
146
+ @choices.setter
147
+ def choices(self, value: List[Dict[str, Any]]) -> None:
148
+ self._choices = value
149
+
150
+ def _safety_check(self) -> None:
151
+ """Validate processed choices.
152
+
153
+ Check if the choices are empty or if it only contains :class:`~InquirerPy.separator.Separator`.
154
+ """
155
+ if not self.choices:
156
+ raise InvalidArgument("argument choices cannot be empty")
157
+ should_proceed: bool = False
158
+ for choice in self.choices:
159
+ if not isinstance(choice["value"], Separator):
160
+ should_proceed = True
161
+ break
162
+ if not should_proceed:
163
+ raise InvalidArgument(
164
+ "argument choices should contain choices other than separator"
165
+ )
166
+
167
+ def _get_formatted_choices(self) -> List[Tuple[str, str]]:
168
+ """Get all choices in formatted text format.
169
+
170
+ Returns:
171
+ List of choices in formatted text form.
172
+ """
173
+ display_choices = []
174
+
175
+ for index, choice in enumerate(self.choices):
176
+ if index == self.selected_choice_index:
177
+ display_choices += self._get_hover_text(choice)
178
+ else:
179
+ display_choices += self._get_normal_text(choice)
180
+ display_choices.append(("", "\n"))
181
+ if display_choices:
182
+ display_choices.pop()
183
+ return display_choices
184
+
185
+ def _format_choices(self) -> None:
186
+ """Perform post processing on the choices.
187
+
188
+ Additional customisation to the choices after :meth:`.InquirerPyUIListControl._get_choices` call.
189
+ """
190
+ pass
191
+
192
+ @abstractmethod
193
+ def _get_hover_text(self, choice) -> List[Tuple[str, str]]:
194
+ """Generate the formatted text for hovered choice.
195
+
196
+ Returns:
197
+ Formatted text in list of tuple format.
198
+ """
199
+ pass
200
+
201
+ @abstractmethod
202
+ def _get_normal_text(self, choice) -> List[Tuple[str, str]]:
203
+ """Generate the formatted text for non-hovered choices.
204
+
205
+ Returns:
206
+ Formatted text in list of tuple format.
207
+ """
208
+ pass
209
+
210
+ @property
211
+ def choice_count(self) -> int:
212
+ """int: Total count of choices."""
213
+ return len(self.choices)
214
+
215
+ @property
216
+ def selection(self) -> Dict[str, Any]:
217
+ """Dict[str, Any]: Current selected choice."""
218
+ return self.choices[self.selected_choice_index]
219
+
220
+ @property
221
+ def loading(self) -> bool:
222
+ """bool: Indicate if the content control is loading."""
223
+ return self._loading
224
+
225
+ @loading.setter
226
+ def loading(self, value: bool) -> None:
227
+ self._loading = value
.env/lib/python3.12/site-packages/InquirerPy/base/list.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Contains the base class :class:`.BaseListPrompt` which can be used to create a prompt involving choices."""
2
+ from abc import abstractmethod
3
+ from typing import Any, Callable, List, Optional
4
+
5
+ from prompt_toolkit.filters.base import Condition
6
+ from prompt_toolkit.keys import Keys
7
+
8
+ from InquirerPy.base.complex import BaseComplexPrompt
9
+ from InquirerPy.base.control import InquirerPyUIListControl
10
+ from InquirerPy.separator import Separator
11
+ from InquirerPy.utils import (
12
+ InquirerPyKeybindings,
13
+ InquirerPyMessage,
14
+ InquirerPySessionResult,
15
+ InquirerPyStyle,
16
+ InquirerPyValidate,
17
+ )
18
+
19
+
20
+ class BaseListPrompt(BaseComplexPrompt):
21
+ """A base class to create a complex prompt involving choice selections (i.e. list) using `prompt_toolkit` Application.
22
+
23
+ Note:
24
+ This class does not create :class:`~prompt_toolkit.layout.Layout` nor :class:`~prompt_toolkit.application.Application`,
25
+ it only contains the necessary attributes and helper functions to be consumed.
26
+
27
+ See Also:
28
+ :class:`~InquirerPy.prompts.list.ListPrompt`
29
+ :class:`~InquirerPy.prompts.fuzzy.FuzzyPrompt`
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ message: InquirerPyMessage,
35
+ style: Optional[InquirerPyStyle] = None,
36
+ vi_mode: bool = False,
37
+ qmark: str = "?",
38
+ amark: str = "?",
39
+ instruction: str = "",
40
+ long_instruction: str = "",
41
+ border: bool = False,
42
+ transformer: Optional[Callable[[Any], Any]] = None,
43
+ filter: Optional[Callable[[Any], Any]] = None,
44
+ validate: Optional[InquirerPyValidate] = None,
45
+ invalid_message: str = "Invalid input",
46
+ multiselect: bool = False,
47
+ keybindings: Optional[InquirerPyKeybindings] = None,
48
+ cycle: bool = True,
49
+ wrap_lines: bool = True,
50
+ raise_keyboard_interrupt: bool = True,
51
+ mandatory: bool = True,
52
+ mandatory_message: str = "Mandatory prompt",
53
+ session_result: Optional[InquirerPySessionResult] = None,
54
+ ) -> None:
55
+ super().__init__(
56
+ message=message,
57
+ style=style,
58
+ border=border,
59
+ vi_mode=vi_mode,
60
+ qmark=qmark,
61
+ amark=amark,
62
+ transformer=transformer,
63
+ filter=filter,
64
+ invalid_message=invalid_message,
65
+ validate=validate,
66
+ instruction=instruction,
67
+ long_instruction=long_instruction,
68
+ wrap_lines=wrap_lines,
69
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
70
+ mandatory=mandatory,
71
+ mandatory_message=mandatory_message,
72
+ session_result=session_result,
73
+ )
74
+
75
+ self._content_control: InquirerPyUIListControl
76
+ self._multiselect = multiselect
77
+ self._is_multiselect = Condition(lambda: self._multiselect)
78
+ self._cycle = cycle
79
+
80
+ if not keybindings:
81
+ keybindings = {}
82
+
83
+ self.kb_maps = {
84
+ "down": [
85
+ {"key": "down"},
86
+ {"key": "c-n", "filter": ~self._is_vim_edit},
87
+ {"key": "j", "filter": self._is_vim_edit},
88
+ ],
89
+ "up": [
90
+ {"key": "up"},
91
+ {"key": "c-p", "filter": ~self._is_vim_edit},
92
+ {"key": "k", "filter": self._is_vim_edit},
93
+ ],
94
+ "toggle": [
95
+ {"key": "space"},
96
+ ],
97
+ "toggle-down": [
98
+ {"key": Keys.Tab},
99
+ ],
100
+ "toggle-up": [
101
+ {"key": Keys.BackTab},
102
+ ],
103
+ "toggle-all": [
104
+ {"key": "alt-r"},
105
+ {"key": "c-r"},
106
+ ],
107
+ "toggle-all-true": [
108
+ {"key": "alt-a"},
109
+ {"key": "c-a"},
110
+ ],
111
+ "toggle-all-false": [],
112
+ **keybindings,
113
+ }
114
+
115
+ self.kb_func_lookup = {
116
+ "down": [{"func": self._handle_down}],
117
+ "up": [{"func": self._handle_up}],
118
+ "toggle": [{"func": self._handle_toggle_choice}],
119
+ "toggle-down": [
120
+ {"func": self._handle_toggle_choice},
121
+ {"func": self._handle_down},
122
+ ],
123
+ "toggle-up": [
124
+ {"func": self._handle_toggle_choice},
125
+ {"func": self._handle_up},
126
+ ],
127
+ "toggle-all": [{"func": self._handle_toggle_all}],
128
+ "toggle-all-true": [{"func": self._handle_toggle_all, "args": [True]}],
129
+ "toggle-all-false": [{"func": self._handle_toggle_all, "args": [False]}],
130
+ }
131
+
132
+ @property
133
+ def content_control(self) -> InquirerPyUIListControl:
134
+ """Get the content controller object.
135
+
136
+ Needs to be an instance of :class:`~InquirerPy.base.control.InquirerPyUIListControl`.
137
+
138
+ Each :class:`.BaseComplexPrompt` requires a `content_control` to display custom
139
+ contents for the prompt.
140
+
141
+ Raises:
142
+ NotImplementedError: When `self._content_control` is not found.
143
+ """
144
+ if not self._content_control:
145
+ raise NotImplementedError
146
+ return self._content_control
147
+
148
+ @content_control.setter
149
+ def content_control(self, value: InquirerPyUIListControl) -> None:
150
+ self._content_control = value
151
+
152
+ @property
153
+ def result_name(self) -> Any:
154
+ """Get the result value that should be printed to the terminal.
155
+
156
+ In multiselect scenario, return result as a list.
157
+ """
158
+ if self._multiselect:
159
+ return [choice["name"] for choice in self.selected_choices]
160
+ else:
161
+ try:
162
+ return self.content_control.selection["name"]
163
+ except IndexError:
164
+ return ""
165
+
166
+ @property
167
+ def result_value(self) -> Any:
168
+ """Get the result value that should return to the user.
169
+
170
+ In multiselect scenario, return result as a list.
171
+ """
172
+ if self._multiselect:
173
+ return [choice["value"] for choice in self.selected_choices]
174
+ else:
175
+ try:
176
+ return self.content_control.selection["value"]
177
+ except IndexError:
178
+ return ""
179
+
180
+ @property
181
+ def selected_choices(self) -> List[Any]:
182
+ """List[Any]: Get all user selected choices."""
183
+
184
+ def filter_choice(choice):
185
+ return not isinstance(choice, Separator) and choice["enabled"]
186
+
187
+ return list(filter(filter_choice, self.content_control.choices))
188
+
189
+ def _handle_down(self, _) -> bool:
190
+ """Handle event when user attempts to move down.
191
+
192
+ Returns:
193
+ Boolean indicating if the action hits the cap.
194
+ """
195
+ if self._cycle:
196
+ self.content_control.selected_choice_index = (
197
+ self.content_control.selected_choice_index + 1
198
+ ) % self.content_control.choice_count
199
+ return False
200
+ else:
201
+ self.content_control.selected_choice_index += 1
202
+ if (
203
+ self.content_control.selected_choice_index
204
+ >= self.content_control.choice_count
205
+ ):
206
+ self.content_control.selected_choice_index = (
207
+ self.content_control.choice_count - 1
208
+ )
209
+ return True
210
+ return False
211
+
212
+ def _handle_up(self, _) -> bool:
213
+ """Handle event when user attempts to move up.
214
+
215
+ Returns:
216
+ Boolean indicating if the action hits the cap.
217
+ """
218
+ if self._cycle:
219
+ self.content_control.selected_choice_index = (
220
+ self.content_control.selected_choice_index - 1
221
+ ) % self.content_control.choice_count
222
+ return False
223
+ else:
224
+ self.content_control.selected_choice_index -= 1
225
+ if self.content_control.selected_choice_index < 0:
226
+ self.content_control.selected_choice_index = 0
227
+ return True
228
+ return False
229
+
230
+ @abstractmethod
231
+ def _handle_toggle_choice(self, event) -> None:
232
+ """Handle event when user attempting to toggle the state of the chocie."""
233
+ pass
234
+
235
+ @abstractmethod
236
+ def _handle_toggle_all(self, event, value: bool) -> None:
237
+ """Handle event when user attempting to alter the state of all choices."""
238
+ pass
.env/lib/python3.12/site-packages/InquirerPy/base/simple.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Contains the base class :class:`.BaseSimplePrompt`."""
2
+ import os
3
+ import re
4
+ from abc import ABC, abstractmethod
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Callable,
9
+ Dict,
10
+ List,
11
+ Optional,
12
+ Tuple,
13
+ Union,
14
+ cast,
15
+ )
16
+
17
+ from prompt_toolkit.enums import EditingMode
18
+ from prompt_toolkit.filters.base import Condition, FilterOrBool
19
+ from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyHandlerCallable
20
+ from prompt_toolkit.keys import Keys
21
+ from prompt_toolkit.styles.style import Style
22
+ from prompt_toolkit.validation import Validator
23
+
24
+ from InquirerPy.enum import INQUIRERPY_KEYBOARD_INTERRUPT
25
+ from InquirerPy.exceptions import RequiredKeyNotFound
26
+ from InquirerPy.utils import (
27
+ InquirerPyMessage,
28
+ InquirerPySessionResult,
29
+ InquirerPyStyle,
30
+ InquirerPyValidate,
31
+ get_style,
32
+ )
33
+
34
+ if TYPE_CHECKING:
35
+ from prompt_toolkit.key_binding.key_processor import KeyPressEvent
36
+
37
+
38
+ class BaseSimplePrompt(ABC):
39
+ """The base class to create a simple terminal input prompt.
40
+
41
+ Note:
42
+ No actual :class:`~prompt_toolkit.application.Application` is created by this class.
43
+ This class only creates some common interface and attributes that can be easily used
44
+ by `prompt_toolkit`.
45
+
46
+ To have a functional prompt, you'll at least have to implement the :meth:`.BaseSimplePrompt._run`
47
+ and :meth:`.BaseSimplePrompt._get_prompt_message`.
48
+
49
+ See Also:
50
+ :class:`~InquirerPy.prompts.input.InputPrompt`
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ message: InquirerPyMessage,
56
+ style: Optional[InquirerPyStyle] = None,
57
+ vi_mode: bool = False,
58
+ qmark: str = "?",
59
+ amark: str = "?",
60
+ instruction: str = "",
61
+ validate: Optional[InquirerPyValidate] = None,
62
+ invalid_message: str = "Invalid input",
63
+ transformer: Optional[Callable[[Any], Any]] = None,
64
+ filter: Optional[Callable[[Any], Any]] = None,
65
+ default: Any = "",
66
+ wrap_lines: bool = True,
67
+ raise_keyboard_interrupt: bool = True,
68
+ mandatory: bool = True,
69
+ mandatory_message: str = "Mandatory prompt",
70
+ session_result: Optional[InquirerPySessionResult] = None,
71
+ ) -> None:
72
+ self._mandatory = mandatory
73
+ self._mandatory_message = mandatory_message
74
+ self._result = session_result or {}
75
+ self._message = (
76
+ message
77
+ if not isinstance(message, Callable)
78
+ else cast(Callable, message)(self._result)
79
+ )
80
+ self._instruction = instruction
81
+ self._default = (
82
+ default if not isinstance(default, Callable) else default(self._result)
83
+ )
84
+ self._style = Style.from_dict(style.dict if style else get_style().dict)
85
+ self._qmark = qmark
86
+ self._amark = amark
87
+ self._status = {"answered": False, "result": None, "skipped": False}
88
+ self._kb = KeyBindings()
89
+ self._lexer = "class:input"
90
+ self._transformer = transformer
91
+ self._filter = filter
92
+ self._wrap_lines = wrap_lines
93
+ self._editing_mode = (
94
+ EditingMode.VI
95
+ if vi_mode or bool(os.getenv("INQUIRERPY_VI_MODE", False))
96
+ else EditingMode.EMACS
97
+ )
98
+ if isinstance(validate, Validator):
99
+ self._validator = validate
100
+ else:
101
+ self._validator = Validator.from_callable(
102
+ validate if validate else lambda _: True,
103
+ invalid_message,
104
+ move_cursor_to_end=True,
105
+ )
106
+ self._raise_kbi = not os.getenv(
107
+ "INQUIRERPY_NO_RAISE_KBI", not raise_keyboard_interrupt
108
+ )
109
+ self._is_rasing_kbi = Condition(lambda: self._raise_kbi)
110
+
111
+ self._kb_maps = {
112
+ "answer": [{"key": Keys.Enter}],
113
+ "interrupt": [
114
+ {"key": "c-c", "filter": self._is_rasing_kbi},
115
+ {"key": "c-d", "filter": ~self._is_rasing_kbi},
116
+ ],
117
+ "skip": [{"key": "c-z"}, {"key": "c-c", "filter": ~self._is_rasing_kbi}],
118
+ }
119
+ self._kb_func_lookup = {
120
+ "answer": [{"func": self._handle_enter}],
121
+ "interrupt": [{"func": self._handle_interrupt}],
122
+ "skip": [{"func": self._handle_skip}],
123
+ }
124
+
125
+ def _keybinding_factory(self):
126
+ """Register all keybindings in `self._kb_maps`.
127
+
128
+ It's required to call this function at the end of prompt constructor if
129
+ it inherits from :class:`~InquirerPy.base.simple.BaseSimplePrompt` or
130
+ :class:`~InquirerPy.base.complex.BaseComplexPrompt`.
131
+ """
132
+
133
+ def _factory(keys, filter, action):
134
+ if action not in self.kb_func_lookup:
135
+ raise RequiredKeyNotFound(f"keybinding action {action} not found")
136
+ if not isinstance(keys, list):
137
+ keys = [keys]
138
+
139
+ @self.register_kb(*keys, filter=filter)
140
+ def _(event):
141
+ for method in self.kb_func_lookup[action]:
142
+ method["func"](event, *method.get("args", []))
143
+
144
+ for key, item in self.kb_maps.items():
145
+ if not isinstance(item, list):
146
+ item = [item]
147
+ for kb in item:
148
+ _factory(kb["key"], kb.get("filter", Condition(lambda: True)), key)
149
+
150
+ @abstractmethod
151
+ def _set_error(self, message: str) -> None:
152
+ """Set the error message for the prompt.
153
+
154
+ Args:
155
+ message: Error message to set.
156
+ """
157
+ pass
158
+
159
+ def _handle_skip(self, event: Optional["KeyPressEvent"]) -> None:
160
+ """Handle the event when attempting to skip a prompt.
161
+
162
+ Skip the prompt if the `_mandatory` field is False, otherwise
163
+ show an error message that the prompt cannot be skipped.
164
+ """
165
+ if not self._mandatory:
166
+ self.status["answered"] = True
167
+ self.status["skipped"] = True
168
+ self.status["result"] = None
169
+ if event:
170
+ event.app.exit(result=None)
171
+ else:
172
+ self._set_error(message=self._mandatory_message)
173
+
174
+ def _handle_interrupt(self, event: Optional["KeyPressEvent"]) -> None:
175
+ """Handle the event when a KeyboardInterrupt signal is sent."""
176
+ self.status["answered"] = True
177
+ self.status["result"] = INQUIRERPY_KEYBOARD_INTERRUPT
178
+ self.status["skipped"] = True
179
+ if event:
180
+ event.app.exit(result=INQUIRERPY_KEYBOARD_INTERRUPT)
181
+
182
+ @abstractmethod
183
+ def _handle_enter(self, event: Optional["KeyPressEvent"]) -> None:
184
+ """Handle the event when user attempt to answer the question."""
185
+ pass
186
+
187
+ @property
188
+ def status(self) -> Dict[str, Any]:
189
+ """Dict[str, Any]: Get current prompt status.
190
+
191
+ The status contains 3 keys: "answered" and "result".
192
+ answered: If the current prompt is answered.
193
+ result: The result of the user answer.
194
+ skipped: If the prompt is skipped.
195
+ """
196
+ return self._status
197
+
198
+ @status.setter
199
+ def status(self, value) -> None:
200
+ self._status = value
201
+
202
+ def register_kb(
203
+ self, *keys: Union[Keys, str], filter: FilterOrBool = True, **kwargs
204
+ ) -> Callable[[KeyHandlerCallable], KeyHandlerCallable]:
205
+ """Keybinding registration decorator.
206
+
207
+ This decorator wraps around the :meth:`prompt_toolkit.key_binding.KeyBindings.add` with
208
+ added feature to process `alt` realted keybindings.
209
+
210
+ By default, `prompt_toolkit` doesn't process `alt` related keybindings,
211
+ it requires `alt-ANY` to `escape` + `ANY`.
212
+
213
+ Args:
214
+ keys: The keys to bind that can trigger the function.
215
+ filter: :class:`~prompt_toolkit.filter.Condition` to indicate if this keybinding should be active.
216
+
217
+ Returns:
218
+ A decorator that should be applied to the function thats intended to be active when the keys
219
+ are pressed.
220
+
221
+ Examples:
222
+ >>> @self.register_kb("alt-j")
223
+ ... def test(event):
224
+ ... pass
225
+ """
226
+ alt_pattern = re.compile(r"^alt-(.*)")
227
+
228
+ def decorator(func: KeyHandlerCallable) -> KeyHandlerCallable:
229
+ formatted_keys = []
230
+ for key in keys:
231
+ match = alt_pattern.match(key)
232
+ if match:
233
+ formatted_keys.append("escape")
234
+ formatted_keys.append(match.group(1))
235
+ else:
236
+ formatted_keys.append(key)
237
+
238
+ @self._kb.add(*formatted_keys, filter=filter, **kwargs)
239
+ def executable(event) -> None:
240
+ func(event)
241
+
242
+ return executable
243
+
244
+ return decorator
245
+
246
+ @abstractmethod
247
+ def _get_prompt_message(
248
+ self, pre_answer: Tuple[str, str], post_answer: Tuple[str, str]
249
+ ) -> List[Tuple[str, str]]:
250
+ """Get the question message in formatted text form to display in the prompt.
251
+
252
+ This function is mainly used to render the question message dynamically based
253
+ on the current status (answered or not answered) of the prompt.
254
+
255
+ Note:
256
+ The function requires implementation when inheriting :class:`.BaseSimplePrompt`.
257
+ You should call `super()._get_prompt_message(pre_answer, post_answer)` in
258
+ the implemented `_get_prompt_message`.
259
+
260
+ Args:
261
+ pre_answer: The message to display before the question is answered.
262
+ post_answer: The information to display after the question is answered.
263
+
264
+ Returns:
265
+ Formatted text in list of tuple format.
266
+ """
267
+ display_message = []
268
+ if self.status["skipped"]:
269
+ display_message.append(("class:skipped", self._qmark))
270
+ display_message.append(
271
+ ("class:skipped", "%s%s " % (" " if self._qmark else "", self._message))
272
+ )
273
+ elif self.status["answered"]:
274
+ display_message.append(("class:answermark", self._amark))
275
+ display_message.append(
276
+ (
277
+ "class:answered_question",
278
+ "%s%s" % (" " if self._amark else "", self._message),
279
+ )
280
+ )
281
+ display_message.append(
282
+ post_answer
283
+ if not self._transformer
284
+ else (
285
+ "class:answer",
286
+ " %s" % self._transformer(self.status["result"]),
287
+ )
288
+ )
289
+ else:
290
+ display_message.append(("class:questionmark", self._qmark))
291
+ display_message.append(
292
+ (
293
+ "class:question",
294
+ "%s%s" % (" " if self._qmark else "", self._message),
295
+ )
296
+ )
297
+ display_message.append(pre_answer)
298
+ return display_message
299
+
300
+ @abstractmethod
301
+ def _run(self) -> Any:
302
+ """Abstractmethod to enforce a run function is implemented.
303
+
304
+ All prompt instance requires a `_run` call to initialise and run an instance of
305
+ `PromptSession` or `Application`.
306
+ """
307
+ pass
308
+
309
+ @abstractmethod
310
+ async def _run_async(self) -> Any:
311
+ """Abstractmethod to enforce a run function is implemented.
312
+
313
+ All prompt instance requires a `_run_async` call to initialise and run an instance of
314
+ `PromptSession` or `Application`.
315
+ """
316
+ pass
317
+
318
+ def execute(self, raise_keyboard_interrupt: Optional[bool] = None) -> Any:
319
+ """Run the prompt and get the result.
320
+
321
+ Args:
322
+ raise_keyboard_interrupt: **Deprecated**. Set this parameter on the prompt initialisation instead.
323
+
324
+ Returns:
325
+ Value of the user answer. Types varies depending on the prompt.
326
+
327
+ Raises:
328
+ KeyboardInterrupt: When `ctrl-c` is pressed and `raise_keyboard_interrupt` is True.
329
+ """
330
+ result = self._run()
331
+ if raise_keyboard_interrupt is not None:
332
+ self._raise_kbi = not os.getenv(
333
+ "INQUIRERPY_NO_RAISE_KBI", not raise_keyboard_interrupt
334
+ )
335
+ if result == INQUIRERPY_KEYBOARD_INTERRUPT:
336
+ raise KeyboardInterrupt
337
+ if not self._filter:
338
+ return result
339
+ return self._filter(result)
340
+
341
+ async def execute_async(self) -> None:
342
+ """Run the prompt asynchronously and get the result.
343
+
344
+ Returns:
345
+ Value of the user answer. Types varies depending on the prompt.
346
+
347
+ Raises:
348
+ KeyboardInterrupt: When `ctrl-c` is pressed and `raise_keyboard_interrupt` is True.
349
+ """
350
+ result = await self._run_async()
351
+ if result == INQUIRERPY_KEYBOARD_INTERRUPT:
352
+ raise KeyboardInterrupt
353
+ if not self._filter:
354
+ return result
355
+ return self._filter(result)
356
+
357
+ @property
358
+ def instruction(self) -> str:
359
+ """str: Instruction to display next to question."""
360
+ return self._instruction
361
+
362
+ @property
363
+ def kb_maps(self) -> Dict[str, Any]:
364
+ """Dict[str, Any]: Keybinding mappings."""
365
+ return self._kb_maps
366
+
367
+ @kb_maps.setter
368
+ def kb_maps(self, value: Dict[str, Any]) -> None:
369
+ self._kb_maps = {**self._kb_maps, **value}
370
+
371
+ @property
372
+ def kb_func_lookup(self) -> Dict[str, Any]:
373
+ """Dict[str, Any]: Keybinding function lookup mappings.."""
374
+ return self._kb_func_lookup
375
+
376
+ @kb_func_lookup.setter
377
+ def kb_func_lookup(self, value: Dict[str, Any]) -> None:
378
+ self._kb_func_lookup = {**self._kb_func_lookup, **value}
.env/lib/python3.12/site-packages/InquirerPy/containers/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .spinner import SpinnerWindow
.env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (235 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/instruction.cpython-312.pyc ADDED
Binary file (1.94 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/message.cpython-312.pyc ADDED
Binary file (1.99 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/spinner.cpython-312.pyc ADDED
Binary file (5.47 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/validation.cpython-312.pyc ADDED
Binary file (2.68 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/containers/instruction.py ADDED
@@ -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)]
.env/lib/python3.12/site-packages/InquirerPy/containers/message.py ADDED
@@ -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
+ )
.env/lib/python3.12/site-packages/InquirerPy/containers/spinner.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains spinner related resources.
2
+
3
+ Note:
4
+ The spinner is not a standalone spinner to run in the terminal
5
+ but rather a `prompt_toolkit` :class:`~prompt_toolkit.layout.Window` that displays a spinner.
6
+
7
+ Use library such as `yaspin <https://github.com/pavdmyt/yaspin>`_ if you need a plain spinner.
8
+ """
9
+ import asyncio
10
+ from typing import TYPE_CHECKING, Callable, List, NamedTuple, Optional, Tuple, Union
11
+
12
+ from prompt_toolkit.filters.utils import to_filter
13
+ from prompt_toolkit.layout.containers import ConditionalContainer, Window
14
+ from prompt_toolkit.layout.controls import FormattedTextControl
15
+
16
+ if TYPE_CHECKING:
17
+ from prompt_toolkit.filters.base import Filter
18
+
19
+ __all__ = ["SPINNERS", "SpinnerWindow"]
20
+
21
+
22
+ class SPINNERS(NamedTuple):
23
+ """Presets of spinner patterns.
24
+
25
+ See Also:
26
+ https://github.com/pavdmyt/yaspin/blob/master/yaspin/data/spinners.json
27
+
28
+ This only contains some basic ones thats ready to use. For more patterns, checkout the
29
+ URL above.
30
+
31
+ Examples:
32
+ >>> from InquirerPy import inquirer
33
+ >>> from InquirerPy.spinner import SPINNERS
34
+ >>> inquirer.select(message="", choices=lambda _: [1, 2, 3], spinner_pattern=SPINNERS.dots)
35
+ """
36
+
37
+ dots = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
38
+ dots2 = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
39
+ line = ["-", "\\", "|", "/"]
40
+ line2 = ["⠂", "-", "–", "—", "–", "-"]
41
+ pipe = ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"]
42
+ star = ["✶", "✸", "✹", "✺", "✹", "✷"]
43
+ star2 = ["+", "x", "*"]
44
+ flip = ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"]
45
+ hamburger = ["☱", "☲", "☴"]
46
+ grow_vertical = ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"]
47
+ grow_horizontal = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"]
48
+ box_bounce = ["▖", "▘", "▝", "▗"]
49
+ triangle = ["◢", "◣", "◤", "◥"]
50
+ arc = ["◜", "◠", "◝", "◞", "◡", "◟"]
51
+ circle = ["◡", "⊙", "◠"]
52
+
53
+
54
+ class SpinnerWindow(ConditionalContainer):
55
+ """Conditional `prompt_toolkit` :class:`~prompt_toolkit.layout.Window` that displays a spinner.
56
+
57
+ Args:
58
+ loading: A :class:`~prompt_toolkit.filters.Condition` to indicate if the spinner should be visible.
59
+ redraw: A redraw function (i.e. :meth:`~prompt_toolkit.application.Application.invalidate`) to refresh the UI.
60
+ pattern: List of pattern to display as the spinner.
61
+ delay: Spinner refresh frequency.
62
+ text: Loading text to display.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ loading: "Filter",
68
+ redraw: Callable[[], None],
69
+ pattern: Optional[Union[List[str], SPINNERS]] = None,
70
+ delay: float = 0.1,
71
+ text: str = "",
72
+ ) -> None:
73
+ self._loading = to_filter(loading)
74
+ self._spinning = False
75
+ self._redraw = redraw
76
+ self._pattern = pattern or SPINNERS.line
77
+ self._char = self._pattern[0]
78
+ self._delay = delay
79
+ self._text = text or "Loading ..."
80
+
81
+ super().__init__(
82
+ content=Window(content=FormattedTextControl(text=self._get_text)),
83
+ filter=self._loading,
84
+ )
85
+
86
+ def _get_text(self) -> List[Tuple[str, str]]:
87
+ """Dynamically get the text for the :class:`~prompt_toolkit.layout.Window`.
88
+
89
+ Returns:
90
+ Formatted text.
91
+ """
92
+ return [
93
+ ("class:spinner_pattern", self._char),
94
+ ("", " "),
95
+ ("class:spinner_text", self._text),
96
+ ]
97
+
98
+ async def start(self) -> None:
99
+ """Start the spinner."""
100
+ if self._spinning:
101
+ return
102
+ self._spinning = True
103
+ while self._loading():
104
+ for char in self._pattern:
105
+ await asyncio.sleep(self._delay)
106
+ self._char = char
107
+ self._redraw()
108
+ self._spinning = False
.env/lib/python3.12/site-packages/InquirerPy/containers/validation.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains :class:`.ValidationWindow` which can be used to display error."""
2
+
3
+ from typing import Optional
4
+
5
+ from prompt_toolkit.filters.base import FilterOrBool
6
+ from prompt_toolkit.formatted_text.base import AnyFormattedText
7
+ from prompt_toolkit.layout.containers import ConditionalContainer, Float, Window
8
+ from prompt_toolkit.layout.controls import FormattedTextControl
9
+
10
+
11
+ class ValidationWindow(ConditionalContainer):
12
+ """Conditional `prompt_toolkit` :class:`~prompt_toolkit.layout.Window` that displays error.
13
+
14
+ Args:
15
+ invalid_message: Error message to display when error occured.
16
+ filter: Condition to display the error window.
17
+ """
18
+
19
+ def __init__(
20
+ self, invalid_message: AnyFormattedText, filter: FilterOrBool, **kwargs
21
+ ) -> None:
22
+ super().__init__(
23
+ Window(
24
+ FormattedTextControl(invalid_message), dont_extend_height=True, **kwargs
25
+ ),
26
+ filter=filter,
27
+ )
28
+
29
+
30
+ class ValidationFloat(Float):
31
+ """:class:`~prompt_toolkit.layout.Float` wrapper around :class:`.ValidationWindow`.
32
+
33
+ Args:
34
+ invalid_message: Error message to display when error occured.
35
+ filter: Condition to display the error window.
36
+ left: Distance to left.
37
+ right: Distance to right.
38
+ bottom: Distance to bottom.
39
+ top: Distance to top.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ invalid_message: AnyFormattedText,
45
+ filter: FilterOrBool,
46
+ left: Optional[int] = None,
47
+ right: Optional[int] = None,
48
+ bottom: Optional[int] = None,
49
+ top: Optional[int] = None,
50
+ **kwargs
51
+ ) -> None:
52
+ super().__init__(
53
+ content=ValidationWindow(
54
+ invalid_message=invalid_message, filter=filter, **kwargs
55
+ ),
56
+ left=left,
57
+ right=right,
58
+ bottom=bottom,
59
+ top=top,
60
+ )
.env/lib/python3.12/site-packages/InquirerPy/enum.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """Module contains common constants."""
2
+ INQUIRERPY_KEYBOARD_INTERRUPT: str = "INQUIRERPY_KEYBOARD_INTERRUPT"
3
+
4
+ INQUIRERPY_POINTER_SEQUENCE: str = "\u276f"
5
+ INQUIRERPY_FILL_CIRCLE_SEQUENCE: str = "\u25c9"
6
+ INQUIRERPY_EMPTY_CIRCLE_SEQUENCE: str = "\u25cb"
7
+ INQUIRERPY_QMARK_SEQUENCE: str = "\u003f"
.env/lib/python3.12/site-packages/InquirerPy/exceptions.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains exceptions that will be raised by `InquirerPy`."""
2
+
3
+
4
+ class InvalidArgument(Exception):
5
+ """Provided argument is invalid.
6
+
7
+ Args:
8
+ message: Exception message.
9
+ """
10
+
11
+ def __init__(self, message: str = "invalid argument"):
12
+ self._message = message
13
+ super().__init__(self._message)
14
+
15
+
16
+ class RequiredKeyNotFound(Exception):
17
+ """Missing required keys in dictionary.
18
+
19
+ Args:
20
+ message: Exception message.
21
+ """
22
+
23
+ def __init__(self, message="required key not found"):
24
+ self.message = message
25
+ super().__init__(self.message)
.env/lib/python3.12/site-packages/InquirerPy/inquirer.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Servers as another entry point for `InquirerPy`.
2
+
3
+ See Also:
4
+ :ref:`index:Alternate Syntax`.
5
+
6
+ `inquirer` directly interact with individual prompt classes. It’s more flexible, easier to customise and also provides IDE type hintings/completions.
7
+ """
8
+ from InquirerPy.prompts import CheckboxPrompt as checkbox
9
+ from InquirerPy.prompts import ConfirmPrompt as confirm
10
+ from InquirerPy.prompts import ExpandPrompt as expand
11
+ from InquirerPy.prompts import FilePathPrompt as filepath
12
+ from InquirerPy.prompts import FuzzyPrompt as fuzzy
13
+ from InquirerPy.prompts import InputPrompt as text
14
+ from InquirerPy.prompts import ListPrompt as select
15
+ from InquirerPy.prompts import NumberPrompt as number
16
+ from InquirerPy.prompts import RawlistPrompt as rawlist
17
+ from InquirerPy.prompts import SecretPrompt as secret
.env/lib/python3.12/site-packages/InquirerPy/prompts/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains import of all prompts classes."""
2
+ from InquirerPy.prompts.checkbox import CheckboxPrompt
3
+ from InquirerPy.prompts.confirm import ConfirmPrompt
4
+ from InquirerPy.prompts.expand import ExpandPrompt
5
+ from InquirerPy.prompts.filepath import FilePathPrompt
6
+ from InquirerPy.prompts.fuzzy import FuzzyPrompt
7
+ from InquirerPy.prompts.input import InputPrompt
8
+ from InquirerPy.prompts.list import ListPrompt
9
+ from InquirerPy.prompts.number import NumberPrompt
10
+ from InquirerPy.prompts.rawlist import RawlistPrompt
11
+ from InquirerPy.prompts.secret import SecretPrompt
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (884 Bytes). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/checkbox.cpython-312.pyc ADDED
Binary file (11.6 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/confirm.cpython-312.pyc ADDED
Binary file (10.5 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/expand.cpython-312.pyc ADDED
Binary file (21.9 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/filepath.cpython-312.pyc ADDED
Binary file (10.2 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/fuzzy.cpython-312.pyc ADDED
Binary file (32.4 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/input.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/list.cpython-312.pyc ADDED
Binary file (17.4 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/number.cpython-312.pyc ADDED
Binary file (30 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/rawlist.cpython-312.pyc ADDED
Binary file (14.1 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/secret.cpython-312.pyc ADDED
Binary file (6.59 kB). View file
 
.env/lib/python3.12/site-packages/InquirerPy/prompts/checkbox.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create a checkbox prompt."""
2
+ from typing import Any, Callable, List, Optional, Tuple, Union
3
+
4
+ from prompt_toolkit.validation import ValidationError
5
+
6
+ from InquirerPy.base import FakeDocument, InquirerPyUIListControl
7
+ from InquirerPy.enum import (
8
+ INQUIRERPY_EMPTY_CIRCLE_SEQUENCE,
9
+ INQUIRERPY_FILL_CIRCLE_SEQUENCE,
10
+ INQUIRERPY_POINTER_SEQUENCE,
11
+ )
12
+ from InquirerPy.prompts.list import ListPrompt
13
+ from InquirerPy.separator import Separator
14
+ from InquirerPy.utils import (
15
+ InquirerPyKeybindings,
16
+ InquirerPyListChoices,
17
+ InquirerPyMessage,
18
+ InquirerPySessionResult,
19
+ InquirerPyStyle,
20
+ InquirerPyValidate,
21
+ )
22
+
23
+ __all__ = ["CheckboxPrompt"]
24
+
25
+
26
+ class InquirerPyCheckboxControl(InquirerPyUIListControl):
27
+ """An :class:`~prompt_toolkit.layout.UIControl` class that displays a list of choices.
28
+
29
+ Reference the parameter definition in :class:`.CheckboxPrompt`.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ choices: InquirerPyListChoices,
35
+ default: Any,
36
+ pointer: str,
37
+ enabled_symbol: str,
38
+ disabled_symbol: str,
39
+ session_result: Optional[InquirerPySessionResult],
40
+ ) -> None:
41
+ """Initialise required attributes and call base class."""
42
+ self._pointer = pointer
43
+ self._enabled_symbol = enabled_symbol
44
+ self._disabled_symbol = disabled_symbol
45
+ super().__init__(
46
+ choices=choices,
47
+ default=default,
48
+ session_result=session_result,
49
+ multiselect=True,
50
+ )
51
+
52
+ def _format_choices(self) -> None:
53
+ pass
54
+
55
+ def _get_hover_text(self, choice) -> List[Tuple[str, str]]:
56
+ display_choices = []
57
+ display_choices.append(("class:pointer", self._pointer))
58
+ if self._pointer:
59
+ display_choices.append(("", " "))
60
+ if not isinstance(choice["value"], Separator):
61
+ display_choices.append(
62
+ (
63
+ "class:checkbox",
64
+ self._enabled_symbol
65
+ if choice["enabled"]
66
+ else self._disabled_symbol,
67
+ )
68
+ )
69
+ if self._enabled_symbol and self._disabled_symbol:
70
+ display_choices.append(("", " "))
71
+ display_choices.append(("[SetCursorPosition]", ""))
72
+ display_choices.append(("class:pointer", choice["name"]))
73
+ return display_choices
74
+
75
+ def _get_normal_text(self, choice) -> List[Tuple[str, str]]:
76
+ display_choices = []
77
+ display_choices.append(("", len(self._pointer) * " "))
78
+ if self._pointer:
79
+ display_choices.append(("", " "))
80
+ if not isinstance(choice["value"], Separator):
81
+ display_choices.append(
82
+ (
83
+ "class:checkbox",
84
+ self._enabled_symbol
85
+ if choice["enabled"]
86
+ else self._disabled_symbol,
87
+ )
88
+ )
89
+ if self._enabled_symbol and self._disabled_symbol:
90
+ display_choices.append(("", " "))
91
+ display_choices.append(("", choice["name"]))
92
+ else:
93
+ display_choices.append(("class:separator", choice["name"]))
94
+ return display_choices
95
+
96
+
97
+ class CheckboxPrompt(ListPrompt):
98
+ """Create a prompt which displays a list of checkboxes to toggle.
99
+
100
+ A wrapper class around :class:`~prompt_toolkit.application.Application`.
101
+
102
+ User can toggle on/off on each checkbox.
103
+
104
+ Works very similar to :class:`~InquirerPy.prompts.list.ListPrompt` with `multiselect` enabled,
105
+ the main difference is visual/UI and also when not toggling anything, the result will be empty.
106
+
107
+ Args:
108
+ message: The question to ask the user.
109
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
110
+ choices: List of choices to display and select.
111
+ Refer to :ref:`pages/dynamic:choices` documentation for more details.
112
+ style: An :class:`InquirerPyStyle` instance.
113
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
114
+ vi_mode: Use vim keybinding for the prompt.
115
+ Refer to :ref:`pages/kb:Keybindings` documentation for more details.
116
+ default: Set the default value of the prompt.
117
+ This will be used to determine which choice is highlighted (current selection),
118
+ The default value should be the value of one of the choices.
119
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
120
+ separator: Separator symbol. Custom symbol that will be used as a separator between the choice index number and the choices.
121
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
122
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
123
+ pointer: Pointer symbol. Customer symbol that will be used to indicate the current choice selection.
124
+ enabled_symbol: Checkbox ticked symbol. Custom symbol which indicate the checkbox is ticked.
125
+ disabled_symbol: Checkbox not ticked symbol. Custom symbol which indicate the checkbox is not ticked.
126
+ instruction: Short instruction to display next to the question.
127
+ long_instruction: Long instructions to display at the bottom of the prompt.
128
+ validate: Add validation to user input.
129
+ The main use case for this prompt would be when `multiselect` is True, you can enforce a min/max selection.
130
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
131
+ invalid_message: Error message to display when user input is invalid.
132
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
133
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
134
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
135
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
136
+ filter: A function which performs additional transformation on the result.
137
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
138
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
139
+ height: Preferred height of the prompt.
140
+ Refer to :ref:`pages/height:Height` documentation for more details.
141
+ max_height: Max height of the prompt.
142
+ Refer to :ref:`pages/height:Height` documentation for more details.
143
+ border: Create border around the choice window.
144
+ keybindings: Customise the builtin keybindings.
145
+ Refer to :ref:`pages/kb:Keybindings` for more details.
146
+ show_cursor: Display cursor at the end of the prompt.
147
+ Set to False to hide the cursor.
148
+ cycle: Return to top item if hit bottom during navigation or vice versa.
149
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
150
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
151
+ will be `None` and the question is skiped.
152
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
153
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
154
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
155
+
156
+ Examples:
157
+ >>> from InquirerPy import inquirer
158
+ >>> result = inquirer.checkbox(message="Select:", choices=[1, 2, 3]).execute()
159
+ >>> print(result)
160
+ [1]
161
+ """
162
+
163
+ def __init__(
164
+ self,
165
+ message: InquirerPyMessage,
166
+ choices: InquirerPyListChoices,
167
+ default: Any = None,
168
+ style: Optional[InquirerPyStyle] = None,
169
+ vi_mode: bool = False,
170
+ qmark: str = "?",
171
+ amark: str = "?",
172
+ pointer: str = INQUIRERPY_POINTER_SEQUENCE,
173
+ enabled_symbol: str = INQUIRERPY_FILL_CIRCLE_SEQUENCE,
174
+ disabled_symbol: str = INQUIRERPY_EMPTY_CIRCLE_SEQUENCE,
175
+ border: bool = False,
176
+ instruction: str = "",
177
+ long_instruction: str = "",
178
+ transformer: Optional[Callable[[Any], Any]] = None,
179
+ filter: Optional[Callable[[Any], Any]] = None,
180
+ height: Optional[Union[int, str]] = None,
181
+ max_height: Optional[Union[int, str]] = None,
182
+ validate: Optional[InquirerPyValidate] = None,
183
+ invalid_message: str = "Invalid input",
184
+ keybindings: Optional[InquirerPyKeybindings] = None,
185
+ show_cursor: bool = True,
186
+ cycle: bool = True,
187
+ wrap_lines: bool = True,
188
+ raise_keyboard_interrupt: bool = True,
189
+ mandatory: bool = True,
190
+ mandatory_message: str = "Mandatory prompt",
191
+ session_result: Optional[InquirerPySessionResult] = None,
192
+ ) -> None:
193
+ self.content_control = InquirerPyCheckboxControl(
194
+ choices=choices,
195
+ default=default,
196
+ pointer=pointer,
197
+ enabled_symbol=enabled_symbol,
198
+ disabled_symbol=disabled_symbol,
199
+ session_result=session_result,
200
+ )
201
+ super().__init__(
202
+ message=message,
203
+ choices=choices,
204
+ style=style,
205
+ border=border,
206
+ vi_mode=vi_mode,
207
+ qmark=qmark,
208
+ amark=amark,
209
+ instruction=instruction,
210
+ long_instruction=long_instruction,
211
+ transformer=transformer,
212
+ filter=filter,
213
+ height=height,
214
+ max_height=max_height,
215
+ validate=validate,
216
+ invalid_message=invalid_message,
217
+ multiselect=True,
218
+ keybindings=keybindings,
219
+ show_cursor=show_cursor,
220
+ cycle=cycle,
221
+ wrap_lines=wrap_lines,
222
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
223
+ mandatory=mandatory,
224
+ mandatory_message=mandatory_message,
225
+ session_result=session_result,
226
+ )
227
+
228
+ def _handle_enter(self, event) -> None:
229
+ """Override this method to force empty array result.
230
+
231
+ When user does not select anything, exit with empty list.
232
+
233
+ Args:
234
+ event: Keypress event.
235
+ """
236
+ try:
237
+ fake_document = FakeDocument(self.result_value)
238
+ self._validator.validate(fake_document) # type: ignore
239
+ except ValidationError:
240
+ self._invalid = True
241
+ else:
242
+ self.status["answered"] = True
243
+ self.status["result"] = self.result_name
244
+ event.app.exit(result=self.result_value)
.env/lib/python3.12/site-packages/InquirerPy/prompts/confirm.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create a confirm prompt."""
2
+ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple
3
+
4
+ from prompt_toolkit.buffer import ValidationState
5
+ from prompt_toolkit.keys import Keys
6
+ from prompt_toolkit.shortcuts import PromptSession
7
+ from prompt_toolkit.validation import ValidationError
8
+
9
+ from InquirerPy.base import BaseSimplePrompt
10
+ from InquirerPy.exceptions import InvalidArgument
11
+ from InquirerPy.utils import (
12
+ InquirerPyDefault,
13
+ InquirerPyKeybindings,
14
+ InquirerPyMessage,
15
+ InquirerPySessionResult,
16
+ InquirerPyStyle,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from prompt_toolkit.input.base import Input
21
+ from prompt_toolkit.key_binding.key_processor import KeyPressEvent
22
+ from prompt_toolkit.output.base import Output
23
+
24
+ __all__ = ["ConfirmPrompt"]
25
+
26
+
27
+ class ConfirmPrompt(BaseSimplePrompt):
28
+ """Create a prompt that provides 2 options (confirm/deny) and controlled via single keypress.
29
+
30
+ A wrapper class around :class:`~prompt_toolkit.shortcuts.PromptSession`.
31
+
32
+ Args:
33
+ message: The question to ask the user.
34
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
35
+ style: An :class:`InquirerPyStyle` instance.
36
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
37
+ vi_mode: Used for compatibility .
38
+ default: Set the default value of the prompt, should be either `True` or `False`.
39
+ This affects the value returned when user directly hit `enter` key.
40
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
41
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
42
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
43
+ instruction: Short instruction to display next to the question.
44
+ long_instruction: Long instructions to display at the bottom of the prompt.
45
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
46
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
47
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
48
+ filter: A function which performs additional transformation on the result.
49
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
50
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
51
+ keybindings: Customise the builtin keybindings.
52
+ Refer to :ref:`pages/kb:Keybindings` for more details.
53
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
54
+ confirm_letter: Letter used to confirm the prompt. A keybinding will be created for this letter.
55
+ Default is `y` and pressing `y` will answer the prompt with value `True`.
56
+ reject_letter: Letter used to reject the prompt. A keybinding will be created for this letter.
57
+ Default is `n` and pressing `n` will answer the prompt with value `False`.
58
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
59
+ will be `None` and the question is skiped.
60
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
61
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
62
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
63
+ input: Used internally and will be removed in future updates.
64
+ output: Used internally and will be removed in future updates.
65
+
66
+ Examples:
67
+ >>> from InquirerPy import inquirer
68
+ >>> result = inquirer.confirm(message="Confirm?").execute()
69
+ >>> print(result)
70
+ True
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ message: InquirerPyMessage,
76
+ style: Optional[InquirerPyStyle] = None,
77
+ default: InquirerPyDefault = False,
78
+ vi_mode: bool = False,
79
+ qmark: str = "?",
80
+ amark: str = "?",
81
+ instruction: str = "",
82
+ long_instruction: str = "",
83
+ transformer: Optional[Callable[[bool], Any]] = None,
84
+ filter: Optional[Callable[[bool], Any]] = None,
85
+ keybindings: Optional[InquirerPyKeybindings] = None,
86
+ wrap_lines: bool = True,
87
+ confirm_letter: str = "y",
88
+ reject_letter: str = "n",
89
+ raise_keyboard_interrupt: bool = True,
90
+ mandatory: bool = True,
91
+ mandatory_message: str = "Mandatory prompt",
92
+ session_result: Optional[InquirerPySessionResult] = None,
93
+ input: Optional["Input"] = None,
94
+ output: Optional["Output"] = None,
95
+ ) -> None:
96
+ vi_mode = False
97
+ super().__init__(
98
+ message=message,
99
+ style=style,
100
+ vi_mode=vi_mode,
101
+ qmark=qmark,
102
+ amark=amark,
103
+ instruction=instruction,
104
+ transformer=transformer,
105
+ filter=filter,
106
+ default=default,
107
+ wrap_lines=wrap_lines,
108
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
109
+ mandatory=mandatory,
110
+ mandatory_message=mandatory_message,
111
+ session_result=session_result,
112
+ )
113
+ if not isinstance(self._default, bool):
114
+ raise InvalidArgument(
115
+ f"{type(self).__name__} argument default should be type of bool"
116
+ )
117
+ self._confirm_letter = confirm_letter
118
+ self._reject_letter = reject_letter
119
+
120
+ if not keybindings:
121
+ keybindings = {}
122
+ self.kb_maps = {
123
+ "confirm": [
124
+ {"key": self._confirm_letter},
125
+ {"key": self._confirm_letter.upper()},
126
+ ],
127
+ "reject": [
128
+ {"key": self._reject_letter},
129
+ {"key": self._reject_letter.upper()},
130
+ ],
131
+ "any": [{"key": Keys.Any}],
132
+ **keybindings,
133
+ }
134
+ self.kb_func_lookup = {
135
+ "confirm": [{"func": self._handle_confirm}],
136
+ "reject": [{"func": self._handle_reject}],
137
+ "any": [{"func": lambda _: None}],
138
+ }
139
+ self._keybinding_factory()
140
+
141
+ self._session = PromptSession(
142
+ message=self._get_prompt_message,
143
+ key_bindings=self._kb,
144
+ style=self._style,
145
+ wrap_lines=self._wrap_lines,
146
+ bottom_toolbar=[("class:long_instruction", long_instruction)]
147
+ if long_instruction
148
+ else None,
149
+ input=input,
150
+ output=output,
151
+ )
152
+
153
+ def _set_error(self, message: str) -> None:
154
+ self._session.default_buffer.validation_state = ValidationState.INVALID
155
+ self._session.default_buffer.validation_error = ValidationError(message=message)
156
+
157
+ def _handle_reject(self, event) -> None:
158
+ self._session.default_buffer.text = ""
159
+ self.status["answered"] = True
160
+ self.status["result"] = False
161
+ event.app.exit(result=False)
162
+
163
+ def _handle_confirm(self, event) -> None:
164
+ self._session.default_buffer.text = ""
165
+ self.status["answered"] = True
166
+ self.status["result"] = True
167
+ event.app.exit(result=True)
168
+
169
+ def _handle_enter(self, event: "KeyPressEvent") -> None:
170
+ self.status["answered"] = True
171
+ self.status["result"] = self._default
172
+ event.app.exit(result=self._default)
173
+
174
+ def _get_prompt_message(self) -> List[Tuple[str, str]]:
175
+ """Get message to display infront of the input buffer.
176
+
177
+ Returns:
178
+ Formatted text in list of tuple format.
179
+ """
180
+ if not self.instruction:
181
+ pre_answer = (
182
+ "class:instruction",
183
+ " (%s/%s) " % (self._confirm_letter.upper(), self._reject_letter)
184
+ if self._default
185
+ else " (%s/%s) " % (self._confirm_letter, self._reject_letter.upper()),
186
+ )
187
+ else:
188
+ pre_answer = ("class:instruction", " %s " % self.instruction)
189
+ post_answer = ("class:answer", " Yes" if self.status["result"] else " No")
190
+ return super()._get_prompt_message(pre_answer, post_answer)
191
+
192
+ def _run(self) -> bool:
193
+ return self._session.prompt()
194
+
195
+ async def _run_async(self) -> Any:
196
+ return await self._session.prompt_async()
.env/lib/python3.12/site-packages/InquirerPy/prompts/expand.py ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create an expand prompt."""
2
+ from dataclasses import dataclass
3
+ from typing import Any, Callable, List, Optional, Tuple, Union
4
+
5
+ from InquirerPy.base import BaseListPrompt, InquirerPyUIListControl
6
+ from InquirerPy.base.control import Choice
7
+ from InquirerPy.enum import INQUIRERPY_POINTER_SEQUENCE
8
+ from InquirerPy.exceptions import InvalidArgument, RequiredKeyNotFound
9
+ from InquirerPy.prompts.list import ListPrompt
10
+ from InquirerPy.separator import Separator
11
+ from InquirerPy.utils import (
12
+ InquirerPyDefault,
13
+ InquirerPyKeybindings,
14
+ InquirerPyListChoices,
15
+ InquirerPyMessage,
16
+ InquirerPySessionResult,
17
+ InquirerPyStyle,
18
+ InquirerPyValidate,
19
+ )
20
+
21
+ __all__ = ["ExpandPrompt", "ExpandHelp", "ExpandChoice"]
22
+
23
+
24
+ @dataclass
25
+ class ExpandHelp:
26
+ """Help choice for the :class:`.ExpandPrompt`.
27
+
28
+ Args:
29
+ key: The key to bind to toggle the expansion of the prompt.
30
+ message: The help message.
31
+ """
32
+
33
+ key: str = "h"
34
+ message: str = "Help, list all choices"
35
+
36
+
37
+ @dataclass
38
+ class ExpandChoice(Choice):
39
+ """Choice class for :class:`.ExpandPrompt`.
40
+
41
+ See Also:
42
+ :class:`~InquirerPy.base.control.Choice`
43
+
44
+ Args:
45
+ value: The value of the choice when user selects this choice.
46
+ name: The value that should be presented to the user prior/after selection of the choice.
47
+ This value is optional, if not provided, it will fallback to the string representation of `value`.
48
+ enabled: Indicates if the choice should be pre-selected.
49
+ This only has effects when the prompt has `multiselect` enabled.
50
+ key: Char to bind to the choice. Pressing this value will jump to the choice,
51
+ If this value is missing, the first char of the `str(value)` will be used as the key.
52
+ """
53
+
54
+ key: Optional[str] = None
55
+
56
+ def __post_init__(self):
57
+ """Assign stringify value to name and also create key using the first char of the value if not present."""
58
+ super().__post_init__()
59
+ if self.key is None:
60
+ self.key = str(self.value)[0].lower()
61
+
62
+
63
+ class InquirerPyExpandControl(InquirerPyUIListControl):
64
+ """An :class:`~prompt_toolkit.layout.UIControl` class that displays a list of choices.
65
+
66
+ Reference the parameter definition in :class:`.ExpandPrompt`.
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ choices: InquirerPyListChoices,
72
+ default: Any,
73
+ pointer: str,
74
+ separator: str,
75
+ expand_help: ExpandHelp,
76
+ expand_pointer: str,
77
+ marker: str,
78
+ session_result: Optional[InquirerPySessionResult],
79
+ multiselect: bool,
80
+ marker_pl: str,
81
+ ) -> None:
82
+ self._pointer = pointer
83
+ self._separator = separator
84
+ self._expanded = False
85
+ self._expand_pointer = expand_pointer
86
+ self._marker = marker
87
+ self._marker_pl = marker_pl
88
+ self._expand_help = expand_help
89
+ super().__init__(
90
+ choices=choices,
91
+ default=default,
92
+ session_result=session_result,
93
+ multiselect=multiselect,
94
+ )
95
+
96
+ def _format_choices(self) -> None:
97
+ self._key_maps = {}
98
+ try:
99
+ count = 0
100
+ separator_count = 0
101
+ for raw_choice, choice in zip(self._raw_choices, self.choices): # type: ignore
102
+ if (
103
+ not isinstance(raw_choice, dict)
104
+ and not isinstance(raw_choice, Separator)
105
+ and not isinstance(raw_choice, ExpandChoice)
106
+ ):
107
+ raise InvalidArgument(
108
+ "expand prompt argument choices requires each choice to be type of dictionary or Separator or ExpandChoice"
109
+ )
110
+ if isinstance(raw_choice, Separator):
111
+ separator_count += 1
112
+ else:
113
+ choice["key"] = (
114
+ raw_choice.key
115
+ if isinstance(raw_choice, ExpandChoice)
116
+ else raw_choice["key"]
117
+ )
118
+ self._key_maps[choice["key"]] = count
119
+ count += 1
120
+ except KeyError:
121
+ raise RequiredKeyNotFound(
122
+ "expand prompt choice requires a key 'key' to exists"
123
+ )
124
+
125
+ self.choices.append(
126
+ {
127
+ "key": self._expand_help.key,
128
+ "value": self._expand_help,
129
+ "name": self._expand_help.message,
130
+ "enabled": False,
131
+ }
132
+ )
133
+ self._key_maps[self._expand_help.key] = len(self.choices) - 1
134
+
135
+ first_valid_choice_index = 0
136
+ while isinstance(self.choices[first_valid_choice_index]["value"], Separator):
137
+ first_valid_choice_index += 1
138
+ if self.selected_choice_index == first_valid_choice_index:
139
+ for index, choice in enumerate(self.choices):
140
+ if isinstance(choice["value"], Separator):
141
+ continue
142
+ if choice["key"] == self._default:
143
+ self.selected_choice_index = index
144
+ break
145
+
146
+ def _get_formatted_choices(self) -> List[Tuple[str, str]]:
147
+ """Override this parent class method as expand require visual switch of content.
148
+
149
+ Two types of mode:
150
+ * non expand mode
151
+ * expand mode
152
+ """
153
+ if self._expanded:
154
+ return super()._get_formatted_choices()
155
+ else:
156
+ display_choices = []
157
+ display_choices.append(("class:pointer", self._expand_pointer))
158
+ display_choices.append(
159
+ ("", self.choices[self.selected_choice_index]["name"])
160
+ )
161
+ return display_choices
162
+
163
+ def _get_hover_text(self, choice) -> List[Tuple[str, str]]:
164
+ display_choices = []
165
+ display_choices.append(("class:pointer", self._pointer))
166
+ display_choices.append(
167
+ (
168
+ "class:marker",
169
+ self._marker if choice["enabled"] else self._marker_pl,
170
+ )
171
+ )
172
+ if not isinstance(choice["value"], Separator):
173
+ display_choices.append(
174
+ ("class:pointer", "%s%s" % (choice["key"], self._separator))
175
+ )
176
+ display_choices.append(("[SetCursorPosition]", ""))
177
+ display_choices.append(("class:pointer", choice["name"]))
178
+ return display_choices
179
+
180
+ def _get_normal_text(self, choice) -> List[Tuple[str, str]]:
181
+ display_choices = []
182
+ display_choices.append(("", len(self._pointer) * " "))
183
+ display_choices.append(
184
+ (
185
+ "class:marker",
186
+ self._marker if choice["enabled"] else self._marker_pl,
187
+ )
188
+ )
189
+ if not isinstance(choice["value"], Separator):
190
+ display_choices.append(("", "%s%s" % (choice["key"], self._separator)))
191
+ display_choices.append(("", choice["name"]))
192
+ else:
193
+ display_choices.append(("class:separator", choice["name"]))
194
+ return display_choices
195
+
196
+
197
+ class ExpandPrompt(ListPrompt):
198
+ """Create a compact prompt with the ability to expand.
199
+
200
+ A wrapper class around :class:`~prompt_toolkit.application.Application`.
201
+
202
+ Contains a list of chocies binded to a shortcut letter.
203
+ The prompt can be expanded using `h` key.
204
+
205
+ Args:
206
+ message: The question to ask the user.
207
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
208
+ choices: List of choices to display and select.
209
+ Refer to :ref:`pages/prompts/expand:Choices` documentation for more details.
210
+ style: An :class:`InquirerPyStyle` instance.
211
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
212
+ vi_mode: Use vim keybinding for the prompt.
213
+ Refer to :ref:`pages/kb:Keybindings` documentation for more details.
214
+ default: Set the default value of the prompt.
215
+ This will be used to determine which choice is highlighted (current selection),
216
+ The default value should the value of one of the choices.
217
+ For :class:`.ExpandPrompt` specifically, default value can also be a `choice["key"]` which is the shortcut key for the choice.
218
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
219
+ separator: Separator symbol. Custom symbol that will be used as a separator between the choice index number and the choices.
220
+ help_msg: This parameter is DEPRECATED. Use expand_help instead.
221
+ expand_help: The help configuration for the prompt. Must be an instance of :class:`.ExpandHelp`.
222
+ If this value is None, the default help key will be binded to `h` and the default help message would be
223
+ "Help, List all choices."
224
+ expand_pointer: Pointer symbol before prompt expansion. Custom symbol that will be displayed to indicate the prompt is not expanded.
225
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
226
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
227
+ pointer: Pointer symbol. Customer symbol that will be used to indicate the current choice selection.
228
+ instruction: Short instruction to display next to the question.
229
+ long_instruction: Long instructions to display at the bottom of the prompt.
230
+ validate: Add validation to user input.
231
+ The main use case for this prompt would be when `multiselect` is True, you can enforce a min/max selection.
232
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
233
+ invalid_message: Error message to display when user input is invalid.
234
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
235
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
236
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
237
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
238
+ filter: A function which performs additional transformation on the result.
239
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
240
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
241
+ height: Preferred height of the prompt.
242
+ Refer to :ref:`pages/height:Height` documentation for more details.
243
+ max_height: Max height of the prompt.
244
+ Refer to :ref:`pages/height:Height` documentation for more details.
245
+ multiselect: Enable multi-selection on choices.
246
+ You can use `validate` parameter to control min/max selections.
247
+ Setting to True will also change the result from a single value to a list of values.
248
+ marker: Marker Symbol. Custom symbol to indicate if a choice is selected.
249
+ This will take effects when `multiselect` is True.
250
+ marker_pl: Marker place holder when the choice is not selected.
251
+ This is empty space by default.
252
+ border: Create border around the choice window.
253
+ keybindings: Customise the builtin keybindings.
254
+ Refer to :ref:`pages/kb:Keybindings` for more details.
255
+ show_cursor: Display cursor at the end of the prompt.
256
+ Set to False to hide the cursor.
257
+ cycle: Return to top item if hit bottom during navigation or vice versa.
258
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
259
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
260
+ will be `None` and the question is skiped.
261
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
262
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
263
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
264
+
265
+ Examples:
266
+ >>> from InquirerPy import inquirer
267
+ >>> result = inquirer.expand(message="Select one:", choices[{"name": "1", "value": "1", "key": "a"}]).execute()
268
+ >>> print(result)
269
+ "1"
270
+ """
271
+
272
+ def __init__(
273
+ self,
274
+ message: InquirerPyMessage,
275
+ choices: InquirerPyListChoices,
276
+ default: InquirerPyDefault = "",
277
+ style: Optional[InquirerPyStyle] = None,
278
+ vi_mode: bool = False,
279
+ qmark: str = "?",
280
+ amark: str = "?",
281
+ pointer: str = " ",
282
+ separator: str = ") ",
283
+ help_msg: str = "Help, list all choices",
284
+ expand_help: Optional[ExpandHelp] = None,
285
+ expand_pointer: str = "%s " % INQUIRERPY_POINTER_SEQUENCE,
286
+ instruction: str = "",
287
+ long_instruction: str = "",
288
+ transformer: Optional[Callable[[Any], Any]] = None,
289
+ filter: Optional[Callable[[Any], Any]] = None,
290
+ height: Optional[Union[int, str]] = None,
291
+ max_height: Optional[Union[int, str]] = None,
292
+ multiselect: bool = False,
293
+ marker: str = INQUIRERPY_POINTER_SEQUENCE,
294
+ marker_pl: str = " ",
295
+ border: bool = False,
296
+ validate: Optional[InquirerPyValidate] = None,
297
+ invalid_message: str = "Invalid input",
298
+ keybindings: Optional[InquirerPyKeybindings] = None,
299
+ show_cursor: bool = True,
300
+ cycle: bool = True,
301
+ wrap_lines: bool = True,
302
+ raise_keyboard_interrupt: bool = True,
303
+ mandatory: bool = True,
304
+ mandatory_message: str = "Mandatory prompt",
305
+ session_result: Optional[InquirerPySessionResult] = None,
306
+ ) -> None:
307
+ if expand_help is None:
308
+ expand_help = ExpandHelp(message=help_msg)
309
+ self._expand_help = expand_help
310
+ self.content_control: InquirerPyExpandControl = InquirerPyExpandControl(
311
+ choices=choices,
312
+ default=default,
313
+ pointer=pointer,
314
+ separator=separator,
315
+ expand_help=expand_help,
316
+ expand_pointer=expand_pointer,
317
+ marker=marker,
318
+ marker_pl=marker_pl,
319
+ session_result=session_result,
320
+ multiselect=multiselect,
321
+ )
322
+ super().__init__(
323
+ message=message,
324
+ choices=choices,
325
+ style=style,
326
+ border=border,
327
+ vi_mode=vi_mode,
328
+ qmark=qmark,
329
+ amark=amark,
330
+ instruction=instruction,
331
+ long_instruction=long_instruction,
332
+ transformer=transformer,
333
+ filter=filter,
334
+ height=height,
335
+ max_height=max_height,
336
+ validate=validate,
337
+ invalid_message=invalid_message,
338
+ multiselect=multiselect,
339
+ keybindings=keybindings,
340
+ show_cursor=show_cursor,
341
+ cycle=cycle,
342
+ wrap_lines=wrap_lines,
343
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
344
+ mandatory=mandatory,
345
+ mandatory_message=mandatory_message,
346
+ session_result=session_result,
347
+ )
348
+
349
+ def _on_rendered(self, _) -> None:
350
+ """Override this method to apply custom keybindings.
351
+
352
+ Needs to creat these kb in the callback due to `after_render`
353
+ retrieve the choices asynchronously.
354
+ """
355
+
356
+ def keybinding_factory(key):
357
+ @self.register_kb(key.lower())
358
+ def keybinding(_) -> None:
359
+ if key == self._expand_help.key:
360
+ self.content_control._expanded = not self.content_control._expanded
361
+ else:
362
+ self.content_control.selected_choice_index = (
363
+ self.content_control._key_maps[key]
364
+ )
365
+
366
+ return keybinding
367
+
368
+ for choice in self.content_control.choices:
369
+ if not isinstance(choice["value"], Separator):
370
+ keybinding_factory(choice["key"])
371
+
372
+ def _handle_up(self, event) -> None:
373
+ """Handle the event when user attempt to move up.
374
+
375
+ Overriding this method to skip the help choice.
376
+ """
377
+ if not self.content_control._expanded:
378
+ return
379
+ while True:
380
+ cap = BaseListPrompt._handle_up(self, event)
381
+ if not isinstance(
382
+ self.content_control.selection["value"], Separator
383
+ ) and not isinstance(self.content_control.selection["value"], ExpandHelp):
384
+ break
385
+ else:
386
+ if cap and not self._cycle:
387
+ self._handle_down(event)
388
+ break
389
+
390
+ def _handle_down(self, event) -> None:
391
+ """Handle the event when user attempt to move down.
392
+
393
+ Overriding this method to skip the help choice.
394
+ """
395
+ if not self.content_control._expanded:
396
+ return
397
+ while True:
398
+ cap = BaseListPrompt._handle_down(self, event)
399
+ if not isinstance(
400
+ self.content_control.selection["value"], Separator
401
+ ) and not isinstance(self.content_control.selection["value"], ExpandHelp):
402
+ break
403
+ elif (
404
+ isinstance(self.content_control.selection["value"], ExpandHelp)
405
+ and not self._cycle
406
+ ):
407
+ self._handle_up(event)
408
+ break
409
+ else:
410
+ if cap and not self._cycle:
411
+ self._handle_up(event)
412
+ break
413
+
414
+ @property
415
+ def instruction(self) -> str:
416
+ """Construct the instruction behind the question.
417
+
418
+ If _instruction exists, use that.
419
+
420
+ :return: The instruction text.
421
+ """
422
+ return (
423
+ "(%s)" % "".join(self.content_control._key_maps.keys())
424
+ if not self._instruction
425
+ else self._instruction
426
+ )
427
+
428
+ def _get_prompt_message(self) -> List[Tuple[str, str]]:
429
+ """Return the formatted text to display in the prompt.
430
+
431
+ Overriding this method to allow multiple formatted class to be displayed.
432
+ """
433
+ display_message = super()._get_prompt_message()
434
+ if not self.status["answered"]:
435
+ display_message.append(
436
+ ("class:input", self.content_control.selection["key"])
437
+ )
438
+ return display_message
439
+
440
+ def _handle_toggle_all(self, _, value: Optional[bool] = None) -> None:
441
+ """Override this method to ignore `ExpandHelp`.
442
+
443
+ :param value: Specify a value to toggle.
444
+ """
445
+ if not self.content_control._expanded:
446
+ return
447
+ for choice in self.content_control.choices:
448
+ if isinstance(choice["value"], Separator) or isinstance(
449
+ choice["value"], ExpandHelp
450
+ ):
451
+ continue
452
+ choice["enabled"] = value if value else not choice["enabled"]
453
+
454
+ def _handle_toggle_choice(self, event) -> None:
455
+ """Override this method to ignore keypress when not expanded."""
456
+ if not self.content_control._expanded:
457
+ return
458
+ super()._handle_toggle_choice(event)
.env/lib/python3.12/site-packages/InquirerPy/prompts/filepath.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create filepath prompt and filepath completer class."""
2
+ import os
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Optional
5
+
6
+ from prompt_toolkit.completion import Completer, Completion
7
+ from prompt_toolkit.completion.base import ThreadedCompleter
8
+
9
+ from InquirerPy.prompts.input import InputPrompt
10
+ from InquirerPy.utils import (
11
+ InquirerPyDefault,
12
+ InquirerPyKeybindings,
13
+ InquirerPyMessage,
14
+ InquirerPySessionResult,
15
+ InquirerPyStyle,
16
+ InquirerPyValidate,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from prompt_toolkit.input.base import Input
21
+ from prompt_toolkit.output.base import Output
22
+
23
+ __all__ = ["FilePathPrompt", "FilePathCompleter"]
24
+
25
+
26
+ class FilePathCompleter(Completer):
27
+ """An auto completion class which generates system filepath.
28
+
29
+ See Also:
30
+ :class:`~prompt_toolkit.completion.Completer`
31
+
32
+ Args:
33
+ only_directories: Only complete directories.
34
+ only_files: Only complete files.
35
+ """
36
+
37
+ def __init__(self, only_directories: bool = False, only_files: bool = False):
38
+ self._only_directories = only_directories
39
+ self._only_files = only_files
40
+ self._delimiter = "/" if os.name == "posix" else "\\"
41
+
42
+ def get_completions(
43
+ self, document, complete_event
44
+ ) -> Generator[Completion, None, None]:
45
+ """Get a list of valid system paths."""
46
+ if document.text == "~":
47
+ return
48
+
49
+ validation = lambda file, doc_text: str(file).startswith(doc_text)
50
+
51
+ if document.cursor_position == 0:
52
+ dirname = Path.cwd()
53
+ validation = lambda file, doc_text: True
54
+ elif document.text.startswith("~"):
55
+ dirname = Path(os.path.dirname(f"{Path.home()}{document.text[1:]}"))
56
+ validation = lambda file, doc_text: str(file).startswith(
57
+ f"{Path.home()}{doc_text[1:]}"
58
+ )
59
+ elif document.text.startswith(f".{self._delimiter}"):
60
+ dirname = Path(os.path.dirname(document.text))
61
+ validation = lambda file, doc_text: str(file).startswith(doc_text[2:])
62
+ else:
63
+ dirname = Path(os.path.dirname(document.text))
64
+
65
+ for item in self._get_completion(document, dirname, validation):
66
+ yield item
67
+
68
+ def _get_completion(
69
+ self, document, path, validation
70
+ ) -> Generator[Completion, None, None]:
71
+ if not path.is_dir():
72
+ return
73
+ for file in path.iterdir():
74
+ if self._only_directories and not file.is_dir():
75
+ continue
76
+ if self._only_files and not file.is_file():
77
+ continue
78
+ if validation(file, document.text):
79
+ file_name = file.name
80
+ display_name = file_name
81
+ if file.is_dir():
82
+ display_name = f"{file_name}{self._delimiter}"
83
+ yield Completion(
84
+ file.name,
85
+ start_position=-1 * len(os.path.basename(document.text)),
86
+ display=display_name,
87
+ )
88
+
89
+
90
+ class FilePathPrompt(InputPrompt):
91
+ """Create a prompt that provides auto completion for system filepaths.
92
+
93
+ A wrapper class around :class:`~prompt_toolkit.shortcuts.PromptSession`.
94
+
95
+ Args:
96
+ message: The question to ask the user.
97
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
98
+ style: An :class:`InquirerPyStyle` instance.
99
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
100
+ vi_mode: Use vim keybinding for the prompt.
101
+ Refer to :ref:`pages/kb:Keybindings` documentation for more details.
102
+ default: Set the default text value of the prompt.
103
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
104
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
105
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
106
+ instruction: Short instruction to display next to the question.
107
+ long_instruction: Long instructions to display at the bottom of the prompt.
108
+ multicolumn_complete: Change the auto-completion UI to a multi column display.
109
+ validate: Add validation to user input.
110
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
111
+ invalid_message: Error message to display when user input is invalid.
112
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
113
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
114
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
115
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
116
+ filter: A function which performs additional transformation on the result.
117
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
118
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
119
+ keybindings: Customise the builtin keybindings.
120
+ Refer to :ref:`pages/kb:Keybindings` for more details.
121
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
122
+ only_directories: Only complete directories.
123
+ only_files: Only complete files.
124
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
125
+ will be `None` and the question is skiped.
126
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
127
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
128
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
129
+ input: Used internally and will be removed in future updates.
130
+ output: Used internally and will be removed in future updates.
131
+
132
+ Examples:
133
+ >>> from InquirerPy import inquirer
134
+ >>> result = inquirer.filepath(message="Enter a path:").execute()
135
+ >>> print(result)
136
+ /home/ubuntu/README.md
137
+ """
138
+
139
+ def __init__(
140
+ self,
141
+ message: InquirerPyMessage,
142
+ style: Optional[InquirerPyStyle] = None,
143
+ vi_mode: bool = False,
144
+ default: InquirerPyDefault = "",
145
+ qmark: str = "?",
146
+ amark: str = "?",
147
+ instruction: str = "",
148
+ long_instruction: str = "",
149
+ multicolumn_complete: bool = False,
150
+ validate: Optional[InquirerPyValidate] = None,
151
+ invalid_message: str = "Invalid input",
152
+ only_directories: bool = False,
153
+ only_files: bool = False,
154
+ transformer: Optional[Callable[[str], Any]] = None,
155
+ filter: Optional[Callable[[str], Any]] = None,
156
+ keybindings: Optional[InquirerPyKeybindings] = None,
157
+ wrap_lines: bool = True,
158
+ raise_keyboard_interrupt: bool = True,
159
+ mandatory: bool = True,
160
+ mandatory_message: str = "Mandatory prompt",
161
+ session_result: Optional[InquirerPySessionResult] = None,
162
+ input: Optional["Input"] = None,
163
+ output: Optional["Output"] = None,
164
+ ) -> None:
165
+ super().__init__(
166
+ message=message,
167
+ style=style,
168
+ vi_mode=vi_mode,
169
+ default=default,
170
+ qmark=qmark,
171
+ amark=amark,
172
+ instruction=instruction,
173
+ long_instruction=long_instruction,
174
+ completer=ThreadedCompleter(
175
+ FilePathCompleter(
176
+ only_directories=only_directories, only_files=only_files
177
+ )
178
+ ),
179
+ multicolumn_complete=multicolumn_complete,
180
+ validate=validate,
181
+ invalid_message=invalid_message,
182
+ transformer=transformer,
183
+ filter=filter,
184
+ keybindings=keybindings,
185
+ wrap_lines=wrap_lines,
186
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
187
+ mandatory=mandatory,
188
+ mandatory_message=mandatory_message,
189
+ session_result=session_result,
190
+ input=input,
191
+ output=output,
192
+ )
.env/lib/python3.12/site-packages/InquirerPy/prompts/fuzzy.py ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create a fuzzy prompt."""
2
+ import asyncio
3
+ import math
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Any,
7
+ Callable,
8
+ Dict,
9
+ List,
10
+ Optional,
11
+ Tuple,
12
+ Union,
13
+ cast,
14
+ )
15
+
16
+ from pfzy import fuzzy_match
17
+ from pfzy.score import fzy_scorer, substr_scorer
18
+ from pfzy.types import HAYSTACKS
19
+ from prompt_toolkit.application.application import Application
20
+ from prompt_toolkit.buffer import Buffer
21
+ from prompt_toolkit.filters.cli import IsDone
22
+ from prompt_toolkit.layout.containers import (
23
+ ConditionalContainer,
24
+ FloatContainer,
25
+ HSplit,
26
+ Window,
27
+ )
28
+ from prompt_toolkit.layout.controls import BufferControl, DummyControl
29
+ from prompt_toolkit.layout.dimension import Dimension, LayoutDimension
30
+ from prompt_toolkit.layout.layout import Layout
31
+ from prompt_toolkit.layout.processors import AfterInput, BeforeInput
32
+ from prompt_toolkit.lexers.base import SimpleLexer
33
+ from prompt_toolkit.validation import ValidationError
34
+ from prompt_toolkit.widgets.base import Frame
35
+
36
+ from InquirerPy.base import FakeDocument, InquirerPyUIListControl
37
+ from InquirerPy.base.list import BaseListPrompt
38
+ from InquirerPy.containers.instruction import InstructionWindow
39
+ from InquirerPy.containers.message import MessageWindow
40
+ from InquirerPy.containers.validation import ValidationFloat
41
+ from InquirerPy.enum import INQUIRERPY_POINTER_SEQUENCE
42
+ from InquirerPy.exceptions import InvalidArgument
43
+ from InquirerPy.separator import Separator
44
+ from InquirerPy.utils import (
45
+ InquirerPyDefault,
46
+ InquirerPyKeybindings,
47
+ InquirerPyListChoices,
48
+ InquirerPyMessage,
49
+ InquirerPySessionResult,
50
+ InquirerPyStyle,
51
+ InquirerPyValidate,
52
+ calculate_height,
53
+ )
54
+
55
+ if TYPE_CHECKING:
56
+ from prompt_toolkit.key_binding.key_processor import KeyPressEvent
57
+
58
+ __all__ = ["FuzzyPrompt"]
59
+
60
+
61
+ class InquirerPyFuzzyControl(InquirerPyUIListControl):
62
+ """An :class:`~prompt_toolkit.layout.UIControl` class that displays a list of choices.
63
+
64
+ This only displays the chocies. The actual input buffer will be handled by a separate
65
+ :class:`~prompt_toolkit.layout.BufferControl`.
66
+
67
+ Reference the parameter definition in :class:`.FuzzyPrompt`.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ choices: InquirerPyListChoices,
73
+ pointer: str,
74
+ marker: str,
75
+ current_text: Callable[[], str],
76
+ max_lines: int,
77
+ session_result: Optional[InquirerPySessionResult],
78
+ multiselect: bool,
79
+ marker_pl: str,
80
+ match_exact: bool,
81
+ ) -> None:
82
+ self._pointer = pointer
83
+ self._marker = marker
84
+ self._marker_pl = marker_pl
85
+ self._current_text = current_text
86
+ self._max_lines = max_lines if max_lines > 0 else 1
87
+ self._scorer = fzy_scorer if not match_exact else substr_scorer
88
+ super().__init__(
89
+ choices=choices,
90
+ default=None,
91
+ session_result=session_result,
92
+ multiselect=multiselect,
93
+ )
94
+
95
+ def _format_choices(self) -> None:
96
+ for index, choice in enumerate(self.choices):
97
+ if isinstance(choice["value"], Separator):
98
+ raise InvalidArgument(
99
+ "fuzzy prompt argument choices should not contain Separator"
100
+ )
101
+ choice["index"] = index
102
+ choice["indices"] = []
103
+ self._filtered_choices = self.choices
104
+ self._first_line = 0
105
+ self._last_line = min(self._max_lines, self.choice_count)
106
+ self._height = self._last_line - self._first_line
107
+
108
+ def _get_hover_text(self, choice) -> List[Tuple[str, str]]:
109
+ """Get the current highlighted line of text.
110
+
111
+ If in the middle of filtering, loop through the char and color
112
+ indices matched char into style class `class:fuzzy_match`.
113
+
114
+ Returns:
115
+ FormattedText in list of tuple format.
116
+ """
117
+ display_choices = []
118
+ display_choices.append(("class:pointer", self._pointer))
119
+ display_choices.append(
120
+ (
121
+ "class:marker",
122
+ self._marker
123
+ if self.choices[choice["index"]]["enabled"]
124
+ else self._marker_pl,
125
+ )
126
+ )
127
+ display_choices.append(("[SetCursorPosition]", ""))
128
+ if not choice["indices"]:
129
+ display_choices.append(("class:pointer", choice["name"]))
130
+ else:
131
+ indices = set(choice["indices"])
132
+ for index, char in enumerate(choice["name"]):
133
+ if index in indices:
134
+ display_choices.append(("class:fuzzy_match", char))
135
+ else:
136
+ display_choices.append(("class:pointer", char))
137
+ return display_choices
138
+
139
+ def _get_normal_text(self, choice) -> List[Tuple[str, str]]:
140
+ """Get the line of text in `FormattedText`.
141
+
142
+ If in the middle of filtering, loop through the char and color
143
+ indices matched char into `class:fuzzy_match`.
144
+
145
+ Calculate spaces of pointer to make the choice equally align.
146
+
147
+ Returns:
148
+ FormattedText in list of tuple format.
149
+ """
150
+ display_choices = []
151
+ display_choices.append(("class:pointer", len(self._pointer) * " "))
152
+ display_choices.append(
153
+ (
154
+ "class:marker",
155
+ self._marker
156
+ if self.choices[choice["index"]]["enabled"]
157
+ else self._marker_pl,
158
+ )
159
+ )
160
+ if not choice["indices"]:
161
+ display_choices.append(("", choice["name"]))
162
+ else:
163
+ indices = set(choice["indices"])
164
+ for index, char in enumerate(choice["name"]):
165
+ if index in indices:
166
+ display_choices.append(("class:fuzzy_match", char))
167
+ else:
168
+ display_choices.append(("", char))
169
+ return display_choices
170
+
171
+ def _get_formatted_choices(self) -> List[Tuple[str, str]]:
172
+ """Get all available choices in formatted text format.
173
+
174
+ Overriding this method because `self.choice` will be the
175
+ full choice list. Using `self.filtered_choice` to get
176
+ a list of choice based on current_text.
177
+
178
+ Returns:
179
+ FormattedText in list of tuple format.
180
+ """
181
+ display_choices = []
182
+ if self.choice_count == 0:
183
+ self._selected_choice_index = 0
184
+ return display_choices
185
+
186
+ if self._selected_choice_index < 0:
187
+ self._selected_choice_index = 0
188
+ elif self._selected_choice_index >= self.choice_count:
189
+ self._selected_choice_index = self.choice_count - 1
190
+
191
+ if (self._last_line - self._first_line) < min(self.choice_count, self._height):
192
+ self._last_line = min(self.choice_count, self._height)
193
+ self._first_line = self._last_line - min(self.choice_count, self._height)
194
+
195
+ if self._selected_choice_index <= self._first_line:
196
+ self._first_line = self._selected_choice_index
197
+ self._last_line = self._first_line + min(self._height, self.choice_count)
198
+ elif self._selected_choice_index >= self._last_line:
199
+ self._last_line = self._selected_choice_index + 1
200
+ self._first_line = self._last_line - min(self._height, self.choice_count)
201
+
202
+ if self._last_line > self.choice_count:
203
+ self._last_line = self.choice_count
204
+ self._first_line = self._last_line - min(self._height, self.choice_count)
205
+ if self._first_line < 0:
206
+ self._first_line = 0
207
+ self._last_line = self._first_line + min(self._height, self.choice_count)
208
+
209
+ for index in range(self._first_line, self._last_line):
210
+ if index == self.selected_choice_index:
211
+ display_choices += self._get_hover_text(self._filtered_choices[index])
212
+ else:
213
+ display_choices += self._get_normal_text(self._filtered_choices[index])
214
+ display_choices.append(("", "\n"))
215
+ if display_choices:
216
+ display_choices.pop()
217
+ return display_choices
218
+
219
+ async def _filter_choices(self, wait_time: float) -> List[Dict[str, Any]]:
220
+ """Call to filter choices using fzy fuzzy match.
221
+
222
+ Args:
223
+ wait_time: Additional time to wait before filtering the choice.
224
+
225
+ Returns:
226
+ Filtered choices.
227
+ """
228
+ if not self._current_text():
229
+ for choice in self.choices:
230
+ choice["indices"] = []
231
+ choices = self.choices
232
+ else:
233
+ await asyncio.sleep(wait_time)
234
+ choices = await fuzzy_match(
235
+ self._current_text(),
236
+ cast(HAYSTACKS, self.choices),
237
+ key="name",
238
+ scorer=self._scorer,
239
+ )
240
+ return choices
241
+
242
+ @property
243
+ def selection(self) -> Dict[str, Any]:
244
+ """Override this value since `self.choice` does not indicate the choice displayed.
245
+
246
+ `self.filtered_choice` is the up to date choice displayed.
247
+
248
+ Returns:
249
+ A dictionary of name and value for the current pointed choice.
250
+ """
251
+ return self._filtered_choices[self.selected_choice_index]
252
+
253
+ @property
254
+ def choice_count(self) -> int:
255
+ """int: Filtered choice count."""
256
+ return len(self._filtered_choices)
257
+
258
+
259
+ class FuzzyPrompt(BaseListPrompt):
260
+ """Create a prompt that lists choices while also allowing fuzzy search like fzf.
261
+
262
+ A wrapper class around :class:`~prompt_toolkit.application.Application`.
263
+
264
+ Fuzzy search using :func:`pfzy.match.fuzzy_match` function.
265
+
266
+ Override the default keybindings for up/down as j/k cannot be bind even if `editing_mode` is vim
267
+ due to the input buffer.
268
+
269
+ Args:
270
+ message: The question to ask the user.
271
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
272
+ choices: List of choices to display and select.
273
+ Refer to :ref:`pages/dynamic:choices` documentation for more details.
274
+ style: An :class:`InquirerPyStyle` instance.
275
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
276
+ vi_mode: Use vim keybinding for the prompt.
277
+ Refer to :ref:`pages/kb:Keybindings` documentation for more details.
278
+ default: Set the default value in the search buffer.
279
+ Different than other list type prompts, the `default` parameter tries to replicate what fzf does and
280
+ add the value in `default` to search buffer so it starts searching immediatelly.
281
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
282
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
283
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
284
+ pointer: Pointer symbol. Customer symbol that will be used to indicate the current choice selection.
285
+ instruction: Short instruction to display next to the question.
286
+ long_instruction: Long instructions to display at the bottom of the prompt.
287
+ validate: Add validation to user input.
288
+ The main use case for this prompt would be when `multiselect` is True, you can enforce a min/max selection.
289
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
290
+ invalid_message: Error message to display when user input is invalid.
291
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
292
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
293
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
294
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
295
+ filter: A function which performs additional transformation on the result.
296
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
297
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
298
+ height: Preferred height of the prompt.
299
+ Refer to :ref:`pages/height:Height` documentation for more details.
300
+ max_height: Max height of the prompt.
301
+ Refer to :ref:`pages/height:Height` documentation for more details.
302
+ multiselect: Enable multi-selection on choices.
303
+ You can use `validate` parameter to control min/max selections.
304
+ Setting to True will also change the result from a single value to a list of values.
305
+ prompt: Input prompt symbol. Custom symbol to display infront of the input buffer to indicate for input.
306
+ border: Create border around the choice window.
307
+ info: Display choice information similar to fzf --info=inline next to the prompt.
308
+ match_exact: Use exact sub-string match instead of using fzy fuzzy match algorithm.
309
+ exact_symbol: Custom symbol to display in the info section when `info=True`.
310
+ marker: Marker Symbol. Custom symbol to indicate if a choice is selected.
311
+ This will take effects when `multiselect` is True.
312
+ marker_pl: Marker place holder when the choice is not selected.
313
+ This is empty space by default.
314
+ keybindings: Customise the builtin keybindings.
315
+ Refer to :ref:`pages/kb:Keybindings` for more details.
316
+ cycle: Return to top item if hit bottom during navigation or vice versa.
317
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
318
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
319
+ will be `None` and the question is skiped.
320
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
321
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
322
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
323
+
324
+ Examples:
325
+ >>> from InquirerPy import inquirer
326
+ >>> result = inquirer.fuzzy(message="Select one:", choices=[1, 2, 3]).execute()
327
+ >>> print(result)
328
+ 1
329
+ """
330
+
331
+ def __init__(
332
+ self,
333
+ message: InquirerPyMessage,
334
+ choices: InquirerPyListChoices,
335
+ default: InquirerPyDefault = "",
336
+ pointer: str = INQUIRERPY_POINTER_SEQUENCE,
337
+ style: Optional[InquirerPyStyle] = None,
338
+ vi_mode: bool = False,
339
+ qmark: str = "?",
340
+ amark: str = "?",
341
+ transformer: Optional[Callable[[Any], Any]] = None,
342
+ filter: Optional[Callable[[Any], Any]] = None,
343
+ instruction: str = "",
344
+ long_instruction: str = "",
345
+ multiselect: bool = False,
346
+ prompt: str = INQUIRERPY_POINTER_SEQUENCE,
347
+ marker: str = INQUIRERPY_POINTER_SEQUENCE,
348
+ marker_pl: str = " ",
349
+ border: bool = False,
350
+ info: bool = True,
351
+ match_exact: bool = False,
352
+ exact_symbol: str = " E",
353
+ height: Optional[Union[str, int]] = None,
354
+ max_height: Optional[Union[str, int]] = None,
355
+ validate: Optional[InquirerPyValidate] = None,
356
+ invalid_message: str = "Invalid input",
357
+ keybindings: Optional[InquirerPyKeybindings] = None,
358
+ cycle: bool = True,
359
+ wrap_lines: bool = True,
360
+ raise_keyboard_interrupt: bool = True,
361
+ mandatory: bool = True,
362
+ mandatory_message: str = "Mandatory prompt",
363
+ session_result: Optional[InquirerPySessionResult] = None,
364
+ ) -> None:
365
+ if not keybindings:
366
+ keybindings = {}
367
+ self._prompt = prompt
368
+ self._info = info
369
+ self._task = None
370
+ self._rendered = False
371
+ self._exact_symbol = exact_symbol
372
+
373
+ keybindings = {
374
+ "up": [{"key": "up"}, {"key": "c-p"}],
375
+ "down": [{"key": "down"}, {"key": "c-n"}],
376
+ "toggle": [],
377
+ "toggle-exact": [],
378
+ **keybindings,
379
+ }
380
+ super().__init__(
381
+ message=message,
382
+ style=style,
383
+ border=border,
384
+ vi_mode=vi_mode,
385
+ qmark=qmark,
386
+ amark=amark,
387
+ transformer=transformer,
388
+ filter=filter,
389
+ validate=validate,
390
+ invalid_message=invalid_message,
391
+ multiselect=multiselect,
392
+ instruction=instruction,
393
+ long_instruction=long_instruction,
394
+ keybindings=keybindings,
395
+ cycle=cycle,
396
+ wrap_lines=wrap_lines,
397
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
398
+ mandatory=mandatory,
399
+ mandatory_message=mandatory_message,
400
+ session_result=session_result,
401
+ )
402
+ self.kb_func_lookup = {"toggle-exact": [{"func": self._toggle_exact}]}
403
+ self._default = (
404
+ default
405
+ if not isinstance(default, Callable)
406
+ else cast(Callable, default)(self._result)
407
+ )
408
+ self._height_offset += 1 # search input
409
+ self._dimmension_height, self._dimmension_max_height = calculate_height(
410
+ height, max_height, height_offset=self.height_offset
411
+ )
412
+
413
+ self._content_control: InquirerPyFuzzyControl = InquirerPyFuzzyControl(
414
+ choices=choices,
415
+ pointer=pointer,
416
+ marker=marker,
417
+ current_text=self._get_current_text,
418
+ max_lines=self._dimmension_max_height,
419
+ session_result=session_result,
420
+ multiselect=multiselect,
421
+ marker_pl=marker_pl,
422
+ match_exact=match_exact,
423
+ )
424
+
425
+ self._buffer = Buffer(on_text_changed=self._on_text_changed)
426
+ input_window = Window(
427
+ height=LayoutDimension.exact(1),
428
+ content=BufferControl(
429
+ self._buffer,
430
+ [
431
+ AfterInput(self._generate_after_input),
432
+ BeforeInput(self._generate_before_input),
433
+ ],
434
+ lexer=SimpleLexer("class:input"),
435
+ ),
436
+ )
437
+
438
+ choice_height_dimmension = lambda: Dimension(
439
+ max=self._dimmension_max_height,
440
+ preferred=self._dimmension_height,
441
+ min=self.content_control._height if self.content_control._height > 0 else 1,
442
+ )
443
+ self.choice_window = Window(
444
+ content=self.content_control,
445
+ height=choice_height_dimmension,
446
+ dont_extend_height=True,
447
+ )
448
+
449
+ main_content_window = HSplit([input_window, self.choice_window])
450
+ if self._border:
451
+ main_content_window = Frame(main_content_window)
452
+ self._layout = Layout(
453
+ FloatContainer(
454
+ content=HSplit(
455
+ [
456
+ MessageWindow(
457
+ message=self._get_prompt_message,
458
+ filter=True,
459
+ wrap_lines=self._wrap_lines,
460
+ show_cursor=True,
461
+ ),
462
+ ConditionalContainer(
463
+ main_content_window,
464
+ filter=~IsDone(),
465
+ ),
466
+ ConditionalContainer(
467
+ Window(content=DummyControl()),
468
+ filter=~IsDone() & self._is_displaying_long_instruction,
469
+ ),
470
+ InstructionWindow(
471
+ message=self._long_instruction,
472
+ filter=~IsDone() & self._is_displaying_long_instruction,
473
+ wrap_lines=self._wrap_lines,
474
+ ),
475
+ ],
476
+ ),
477
+ floats=[
478
+ ValidationFloat(
479
+ invalid_message=self._get_error_message,
480
+ filter=self._is_invalid & ~IsDone(),
481
+ wrap_lines=self._wrap_lines,
482
+ left=0,
483
+ bottom=self._validation_window_bottom_offset,
484
+ ),
485
+ ],
486
+ )
487
+ )
488
+ self._layout.focus(input_window)
489
+
490
+ self._application = Application(
491
+ layout=self._layout,
492
+ style=self._style,
493
+ key_bindings=self._kb,
494
+ editing_mode=self._editing_mode,
495
+ after_render=self._after_render,
496
+ )
497
+
498
+ def _toggle_exact(self, _, value: Optional[bool] = None) -> None:
499
+ """Toggle matching algorithm.
500
+
501
+ Switch between fzy fuzzy match or sub-string exact match.
502
+
503
+ Args:
504
+ value: Specify the value to toggle.
505
+ """
506
+ if value is not None:
507
+ self.content_control._scorer = fzy_scorer if not value else substr_scorer
508
+ else:
509
+ self.content_control._scorer = (
510
+ fzy_scorer
511
+ if self.content_control._scorer == substr_scorer
512
+ else substr_scorer
513
+ )
514
+
515
+ def _on_rendered(self, _) -> None:
516
+ """Render callable choices and set the buffer default text.
517
+
518
+ Setting buffer default text has to be after application is rendered and choice are loaded,
519
+ because `self._filter_choices` will use the event loop from `Application`.
520
+ """
521
+ if self._default:
522
+ default_text = str(self._default)
523
+ self._buffer.text = default_text
524
+ self._buffer.cursor_position = len(default_text)
525
+
526
+ def _handle_toggle_all(self, _, value: Optional[bool] = None) -> None:
527
+ """Toggle all choice `enabled` status.
528
+
529
+ Args:
530
+ value: Specify the value to toggle.
531
+ """
532
+ if not self._multiselect:
533
+ return
534
+ for choice in self.content_control._filtered_choices:
535
+ raw_choice = self.content_control.choices[choice["index"]]
536
+ if isinstance(raw_choice["value"], Separator):
537
+ continue
538
+ raw_choice["enabled"] = value if value else not raw_choice["enabled"]
539
+
540
+ def _generate_after_input(self) -> List[Tuple[str, str]]:
541
+ """Virtual text displayed after the user input."""
542
+ display_message = []
543
+ if self._info:
544
+ display_message.append(("", " "))
545
+ display_message.append(
546
+ (
547
+ "class:fuzzy_info",
548
+ f"{self.content_control.choice_count}/{len(self.content_control.choices)}",
549
+ )
550
+ )
551
+ if self._multiselect:
552
+ display_message.append(
553
+ ("class:fuzzy_info", f" ({len(self.selected_choices)})")
554
+ )
555
+ if self.content_control._scorer == substr_scorer:
556
+ display_message.append(("class:fuzzy_info", self._exact_symbol))
557
+ return display_message
558
+
559
+ def _generate_before_input(self) -> List[Tuple[str, str]]:
560
+ """Display prompt symbol as virtual text before user input."""
561
+ display_message = []
562
+ display_message.append(("class:fuzzy_prompt", "%s " % self._prompt))
563
+ return display_message
564
+
565
+ def _filter_callback(self, task):
566
+ """Redraw `self._application` when the filter task is finished."""
567
+ if task.cancelled():
568
+ return
569
+ self.content_control._filtered_choices = task.result()
570
+ self._application.invalidate()
571
+
572
+ def _calculate_wait_time(self) -> float:
573
+ """Calculate wait time to smoother the application on big data set.
574
+
575
+ Using digit of the choices lengeth to get wait time.
576
+ For digit greater than 6, using formula 2^(digit - 5) * 0.3 to increase the wait_time.
577
+
578
+ Returns:
579
+ Desired wait time before running the filter.
580
+ """
581
+ wait_table = {
582
+ 2: 0.05,
583
+ 3: 0.1,
584
+ 4: 0.2,
585
+ 5: 0.3,
586
+ }
587
+ digit = 1
588
+ if len(self.content_control.choices) > 0:
589
+ digit = int(math.log10(len(self.content_control.choices))) + 1
590
+
591
+ if digit < 2:
592
+ return 0.0
593
+ if digit in wait_table:
594
+ return wait_table[digit]
595
+ return wait_table[5] * (2 ** (digit - 5))
596
+
597
+ def _on_text_changed(self, _) -> None:
598
+ """Handle buffer text change event.
599
+
600
+ 1. Check if there is current task running.
601
+ 2. Cancel if already has task, increase wait_time
602
+ 3. Create a filtered_choice task in asyncio event loop
603
+ 4. Add callback
604
+
605
+ 1. Run a new filter on all choices.
606
+ 2. Re-calculate current selected_choice_index
607
+ if it exceeds the total filtered_choice.
608
+ 3. Avoid selected_choice_index less than zero,
609
+ this fix the issue of cursor lose when:
610
+ choice -> empty choice -> choice
611
+
612
+ Don't need to create or check asyncio event loop, `prompt_toolkit`
613
+ application already has a event loop running.
614
+ """
615
+ if self._invalid:
616
+ self._invalid = False
617
+ wait_time = self._calculate_wait_time()
618
+ if self._task and not self._task.done():
619
+ self._task.cancel()
620
+ self._task = asyncio.create_task(
621
+ self.content_control._filter_choices(wait_time)
622
+ )
623
+ self._task.add_done_callback(self._filter_callback)
624
+
625
+ def _handle_toggle_choice(self, _) -> None:
626
+ """Handle tab event, alter the `selected` state of the choice."""
627
+ if not self._multiselect:
628
+ return
629
+ current_selected_index = self.content_control.selection["index"]
630
+ self.content_control.choices[current_selected_index][
631
+ "enabled"
632
+ ] = not self.content_control.choices[current_selected_index]["enabled"]
633
+
634
+ def _handle_enter(self, event: "KeyPressEvent") -> None:
635
+ """Handle enter event.
636
+
637
+ Validate the result first.
638
+
639
+ In multiselect scenario, if no TAB is entered, then capture the current
640
+ highlighted choice and return the value in a list.
641
+ Otherwise, return all TAB choices as a list.
642
+
643
+ In normal scenario, reutrn the current highlighted choice.
644
+
645
+ If current UI contains no choice due to filter, return None.
646
+ """
647
+ try:
648
+ fake_document = FakeDocument(self.result_value)
649
+ self._validator.validate(fake_document) # type: ignore
650
+ if self._multiselect:
651
+ self.status["answered"] = True
652
+ if not self.selected_choices:
653
+ self.status["result"] = [self.content_control.selection["name"]]
654
+ event.app.exit(result=[self.content_control.selection["value"]])
655
+ else:
656
+ self.status["result"] = self.result_name
657
+ event.app.exit(result=self.result_value)
658
+ else:
659
+ self.status["answered"] = True
660
+ self.status["result"] = self.content_control.selection["name"]
661
+ event.app.exit(result=self.content_control.selection["value"])
662
+ except ValidationError as e:
663
+ self._set_error(str(e))
664
+ except IndexError:
665
+ self.status["answered"] = True
666
+ self.status["result"] = None if not self._multiselect else []
667
+ event.app.exit(result=None if not self._multiselect else [])
668
+
669
+ @property
670
+ def content_control(self) -> InquirerPyFuzzyControl:
671
+ """InquirerPyFuzzyControl: Override for type-hinting."""
672
+ return cast(InquirerPyFuzzyControl, super().content_control)
673
+
674
+ @content_control.setter
675
+ def content_control(self, value: InquirerPyFuzzyControl) -> None:
676
+ self._content_control = value
677
+
678
+ def _get_current_text(self) -> str:
679
+ """Get current input buffer text."""
680
+ return self._buffer.text
.env/lib/python3.12/site-packages/InquirerPy/prompts/input.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module contains the class to create an input prompt."""
2
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
3
+
4
+ from prompt_toolkit.buffer import ValidationState
5
+ from prompt_toolkit.completion import NestedCompleter
6
+ from prompt_toolkit.completion.base import Completer
7
+ from prompt_toolkit.filters.base import Condition
8
+ from prompt_toolkit.keys import Keys
9
+ from prompt_toolkit.lexers import SimpleLexer
10
+ from prompt_toolkit.shortcuts.prompt import CompleteStyle, PromptSession
11
+ from prompt_toolkit.validation import ValidationError
12
+
13
+ from InquirerPy.base import BaseSimplePrompt
14
+ from InquirerPy.enum import INQUIRERPY_POINTER_SEQUENCE
15
+ from InquirerPy.exceptions import InvalidArgument
16
+ from InquirerPy.utils import (
17
+ InquirerPyDefault,
18
+ InquirerPyKeybindings,
19
+ InquirerPyMessage,
20
+ InquirerPySessionResult,
21
+ InquirerPyStyle,
22
+ InquirerPyValidate,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from prompt_toolkit.input.base import Input
27
+ from prompt_toolkit.key_binding.key_processor import KeyPressEvent
28
+ from prompt_toolkit.output.base import Output
29
+
30
+ __all__ = ["InputPrompt"]
31
+
32
+
33
+ class InputPrompt(BaseSimplePrompt):
34
+ """Create a text prompt that accepts user input.
35
+
36
+ A wrapper class around :class:`~prompt_toolkit.shortcuts.PromptSession`.
37
+
38
+ Args:
39
+ message: The question to ask the user.
40
+ Refer to :ref:`pages/dynamic:message` documentation for more details.
41
+ style: An :class:`InquirerPyStyle` instance.
42
+ Refer to :ref:`Style <pages/style:Alternate Syntax>` documentation for more details.
43
+ vi_mode: Use vim keybinding for the prompt.
44
+ Refer to :ref:`pages/kb:Keybindings` documentation for more details.
45
+ default: Set the default text value of the prompt.
46
+ Refer to :ref:`pages/dynamic:default` documentation for more details.
47
+ qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered.
48
+ amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered.
49
+ instruction: Short instruction to display next to the question.
50
+ long_instruction: Long instructions to display at the bottom of the prompt.
51
+ completer: Add auto completion to the prompt.
52
+ Refer to :ref:`pages/prompts/input:Auto Completion` documentation for more details.
53
+ multicolumn_complete: Change the auto-completion UI to a multi column display.
54
+ multiline: Enable multiline edit. While multiline edit is active, pressing `enter` won't complete the answer.
55
+ and will create a new line. Use `esc` followd by `enter` to complete the question.
56
+ validate: Add validation to user input.
57
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
58
+ invalid_message: Error message to display when user input is invalid.
59
+ Refer to :ref:`pages/validator:Validator` documentation for more details.
60
+ transformer: A function which performs additional transformation on the value that gets printed to the terminal.
61
+ Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
62
+ Refer to :ref:`pages/dynamic:transformer` documentation for more details.
63
+ filter: A function which performs additional transformation on the result.
64
+ This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`.
65
+ Refer to :ref:`pages/dynamic:filter` documentation for more details.
66
+ keybindings: Customise the builtin keybindings.
67
+ Refer to :ref:`pages/kb:Keybindings` for more details.
68
+ wrap_lines: Soft wrap question lines when question exceeds the terminal width.
69
+ raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result
70
+ will be `None` and the question is skiped.
71
+ is_password: Used internally for :class:`~InquirerPy.prompts.secret.SecretPrompt`.
72
+ mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped.
73
+ mandatory_message: Error message to show when user attempts to skip mandatory prompt.
74
+ session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`.
75
+ input: Used internally and will be removed in future updates.
76
+ output: Used internally and will be removed in future updates.
77
+
78
+ Examples:
79
+ >>> from InquirerPy import inquirer
80
+ >>> result = inquirer.text(message="Enter your name:").execute()
81
+ >>> print(f"Name: {result}")
82
+ Name: Michael
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ message: InquirerPyMessage,
88
+ style: Optional[InquirerPyStyle] = None,
89
+ vi_mode: bool = False,
90
+ default: InquirerPyDefault = "",
91
+ qmark: str = "?",
92
+ amark: str = "?",
93
+ instruction: str = "",
94
+ long_instruction: str = "",
95
+ completer: Optional[Union[Dict[str, Optional[str]], "Completer"]] = None,
96
+ multicolumn_complete: bool = False,
97
+ multiline: bool = False,
98
+ validate: Optional[InquirerPyValidate] = None,
99
+ invalid_message: str = "Invalid input",
100
+ transformer: Optional[Callable[[str], Any]] = None,
101
+ filter: Optional[Callable[[str], Any]] = None,
102
+ keybindings: Optional[InquirerPyKeybindings] = None,
103
+ wrap_lines: bool = True,
104
+ raise_keyboard_interrupt: bool = True,
105
+ is_password: bool = False,
106
+ mandatory: bool = True,
107
+ mandatory_message: str = "Mandatory prompt",
108
+ session_result: Optional[InquirerPySessionResult] = None,
109
+ input: Optional["Input"] = None,
110
+ output: Optional["Output"] = None,
111
+ ) -> None:
112
+ super().__init__(
113
+ message,
114
+ style,
115
+ vi_mode=vi_mode,
116
+ qmark=qmark,
117
+ amark=amark,
118
+ instruction=instruction,
119
+ validate=validate,
120
+ invalid_message=invalid_message,
121
+ transformer=transformer,
122
+ filter=filter,
123
+ session_result=session_result,
124
+ default=default,
125
+ wrap_lines=wrap_lines,
126
+ mandatory=mandatory,
127
+ mandatory_message=mandatory_message,
128
+ raise_keyboard_interrupt=raise_keyboard_interrupt,
129
+ )
130
+ if not isinstance(self._default, str):
131
+ raise InvalidArgument(
132
+ f"{type(self).__name__} argument 'default' should be type of str"
133
+ )
134
+ self._completer = None
135
+ if isinstance(completer, dict):
136
+ self._completer = NestedCompleter.from_nested_dict(completer)
137
+ elif isinstance(completer, Completer):
138
+ self._completer = completer
139
+ self._multiline = multiline
140
+ self._complete_style = (
141
+ CompleteStyle.COLUMN
142
+ if not multicolumn_complete
143
+ else CompleteStyle.MULTI_COLUMN
144
+ )
145
+
146
+ @Condition
147
+ def is_multiline():
148
+ return self._multiline
149
+
150
+ if not keybindings:
151
+ keybindings = {}
152
+ self.kb_maps = {
153
+ "answer": [
154
+ {"key": Keys.Enter, "filter": ~is_multiline},
155
+ {"key": [Keys.Escape, Keys.Enter], "filter": is_multiline},
156
+ ],
157
+ "completion": [{"key": "c-space"}],
158
+ **keybindings,
159
+ }
160
+ self.kb_func_lookup = {"completion": [{"func": self._handle_completion}]}
161
+ self._keybinding_factory()
162
+
163
+ self._session = PromptSession(
164
+ message=self._get_prompt_message,
165
+ key_bindings=self._kb,
166
+ style=self._style,
167
+ completer=self._completer,
168
+ validator=self._validator,
169
+ validate_while_typing=False,
170
+ input=input,
171
+ output=output,
172
+ editing_mode=self._editing_mode,
173
+ lexer=SimpleLexer(self._lexer),
174
+ is_password=is_password,
175
+ multiline=self._multiline,
176
+ complete_style=self._complete_style,
177
+ wrap_lines=wrap_lines,
178
+ bottom_toolbar=[("class:long_instruction", long_instruction)]
179
+ if long_instruction
180
+ else None,
181
+ )
182
+
183
+ def _set_error(self, message: str) -> None:
184
+ self._session.default_buffer.validation_state = ValidationState.INVALID
185
+ self._session.default_buffer.validation_error = ValidationError(message=message)
186
+
187
+ def _handle_enter(self, event: "KeyPressEvent") -> None:
188
+ try:
189
+ self._session.validator.validate(self._session.default_buffer) # type: ignore
190
+ except ValidationError:
191
+ self._session.default_buffer.validate_and_handle()
192
+ else:
193
+ self.status["answered"] = True
194
+ self.status["result"] = self._session.default_buffer.text
195
+ self._session.default_buffer.text = ""
196
+ event.app.exit(result=self.status["result"])
197
+
198
+ def _handle_completion(self, event) -> None:
199
+ if self._completer is None:
200
+ return
201
+ buff = event.app.current_buffer
202
+ if buff.complete_state:
203
+ buff.complete_next()
204
+ else:
205
+ buff.start_completion(select_first=False)
206
+
207
+ def _get_prompt_message(
208
+ self,
209
+ pre_answer: Optional[Tuple[str, str]] = None,
210
+ post_answer: Optional[Tuple[str, str]] = None,
211
+ ) -> List[Tuple[str, str]]:
212
+ """Get message to display infront of the input buffer.
213
+
214
+ Args:
215
+ pre_answer: The formatted text to display before answering the question.
216
+ post_answer: The formatted text to display after answering the question.
217
+
218
+ Returns:
219
+ Formatted text in list of tuple format.
220
+ """
221
+ if not pre_answer:
222
+ if self._multiline and not self._instruction:
223
+ pre_answer = ("class:instruction", " ESC + Enter to finish input")
224
+ else:
225
+ pre_answer = (
226
+ "class:instruction",
227
+ " %s " % self.instruction if self.instruction else " ",
228
+ )
229
+ if not post_answer:
230
+ if self._multiline and self.status["result"]:
231
+ lines = self.status["result"].split("\n")
232
+ if len(lines) > 1:
233
+ number_of_chars = len("".join(lines[1:]))
234
+ lines[0] += "...[%s char%s]" % (
235
+ number_of_chars,
236
+ "s" if number_of_chars > 1 else "",
237
+ )
238
+ post_answer = ("class:answer", " %s" % lines[0])
239
+ else:
240
+ post_answer = ("class:answer", " %s" % self.status["result"])
241
+
242
+ formatted_message = super()._get_prompt_message(pre_answer, post_answer)
243
+ if not self.status["answered"] and self._multiline:
244
+ formatted_message.append(
245
+ ("class:questionmark", "\n%s " % INQUIRERPY_POINTER_SEQUENCE)
246
+ )
247
+ return formatted_message
248
+
249
+ def _run(self) -> str:
250
+ return self._session.prompt(default=self._default)
251
+
252
+ async def _run_async(self) -> Any:
253
+ return await self._session.prompt_async(default=self._default)