Spaces:
Sleeping
Sleeping
Commit
·
2d876d1
1
Parent(s):
392d02b
fix import issue
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .DS_Store +0 -0
- app.py +1 -1
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__init__.py +2 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/__init__.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/enum.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/exceptions.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/inquirer.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/resolver.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/separator.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/utils.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/validator.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__init__.py +15 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/__init__.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/complex.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/control.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/list.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/simple.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/complex.py +294 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/control.py +227 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/list.py +238 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/base/simple.py +378 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__init__.py +1 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/__init__.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/instruction.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/message.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/spinner.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/validation.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/instruction.py +38 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/message.py +42 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/spinner.py +108 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/validation.py +60 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/enum.py +7 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/exceptions.py +25 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/inquirer.py +17 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__init__.py +11 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/__init__.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/checkbox.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/confirm.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/expand.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/filepath.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/fuzzy.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/input.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/list.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/number.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/rawlist.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/secret.cpython-312.pyc +0 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/checkbox.py +244 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/confirm.py +196 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/expand.py +458 -0
- first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/filepath.py +192 -0
.DS_Store
CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
|
|
app.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
|
2 |
-
import
|
3 |
import os
|
4 |
import torch
|
5 |
|
|
|
1 |
|
2 |
+
import gradio as gr
|
3 |
import os
|
4 |
import torch
|
5 |
|
first-space-venv/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
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (320 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/enum.cpython-312.pyc
ADDED
Binary file (592 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/exceptions.cpython-312.pyc
ADDED
Binary file (1.45 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/inquirer.cpython-312.pyc
ADDED
Binary file (933 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/resolver.cpython-312.pyc
ADDED
Binary file (8.4 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/separator.cpython-312.pyc
ADDED
Binary file (1.28 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/utils.cpython-312.pyc
ADDED
Binary file (12.9 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/__pycache__/validator.cpython-312.pyc
ADDED
Binary file (7.49 kB). View file
|
|
first-space-venv/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
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (675 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/complex.cpython-312.pyc
ADDED
Binary file (13.9 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/control.cpython-312.pyc
ADDED
Binary file (10.6 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/list.cpython-312.pyc
ADDED
Binary file (9.46 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/base/__pycache__/simple.cpython-312.pyc
ADDED
Binary file (17.9 kB). View file
|
|
first-space-venv/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
|
first-space-venv/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
|
first-space-venv/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
|
first-space-venv/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}
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .spinner import SpinnerWindow
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (252 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/instruction.cpython-312.pyc
ADDED
Binary file (1.96 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/message.cpython-312.pyc
ADDED
Binary file (2 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/spinner.cpython-312.pyc
ADDED
Binary file (5.49 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/containers/__pycache__/validation.cpython-312.pyc
ADDED
Binary file (2.7 kB). View file
|
|
first-space-venv/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)]
|
first-space-venv/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 |
+
)
|
first-space-venv/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
|
first-space-venv/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 |
+
)
|
first-space-venv/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"
|
first-space-venv/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)
|
first-space-venv/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
|
first-space-venv/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
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (901 Bytes). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/checkbox.cpython-312.pyc
ADDED
Binary file (11.6 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/confirm.cpython-312.pyc
ADDED
Binary file (10.5 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/expand.cpython-312.pyc
ADDED
Binary file (21.9 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/filepath.cpython-312.pyc
ADDED
Binary file (10.2 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/fuzzy.cpython-312.pyc
ADDED
Binary file (32.4 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/input.cpython-312.pyc
ADDED
Binary file (13.3 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/list.cpython-312.pyc
ADDED
Binary file (17.5 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/number.cpython-312.pyc
ADDED
Binary file (30 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/rawlist.cpython-312.pyc
ADDED
Binary file (14.1 kB). View file
|
|
first-space-venv/lib/python3.12/site-packages/InquirerPy/prompts/__pycache__/secret.cpython-312.pyc
ADDED
Binary file (6.61 kB). View file
|
|
first-space-venv/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)
|
first-space-venv/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()
|
first-space-venv/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)
|
first-space-venv/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 |
+
)
|