InquirerPrompt 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- InquirerPy/__init__.py +4 -0
- InquirerPy/base/__init__.py +24 -0
- InquirerPy/base/complex.py +295 -0
- InquirerPy/base/control.py +230 -0
- InquirerPy/base/list.py +239 -0
- InquirerPy/base/simple.py +379 -0
- InquirerPy/containers/__init__.py +3 -0
- InquirerPy/containers/instruction.py +38 -0
- InquirerPy/containers/message.py +42 -0
- InquirerPy/containers/spinner.py +109 -0
- InquirerPy/containers/validation.py +60 -0
- InquirerPy/enum.py +8 -0
- InquirerPy/exceptions.py +25 -0
- InquirerPy/inquirer.py +31 -0
- InquirerPy/prompts/__init__.py +25 -0
- InquirerPy/prompts/checkbox.py +249 -0
- InquirerPy/prompts/confirm.py +202 -0
- InquirerPy/prompts/expand.py +459 -0
- InquirerPy/prompts/filepath.py +193 -0
- InquirerPy/prompts/fuzzy.py +685 -0
- InquirerPy/prompts/input.py +256 -0
- InquirerPy/prompts/list.py +371 -0
- InquirerPy/prompts/number.py +621 -0
- InquirerPy/prompts/rawlist.py +286 -0
- InquirerPy/prompts/secret.py +137 -0
- InquirerPy/py.typed +1 -0
- InquirerPy/resolver.py +220 -0
- InquirerPy/separator.py +23 -0
- InquirerPy/utils.py +291 -0
- InquirerPy/validator.py +166 -0
- inquirerprompt-0.3.0.dist-info/METADATA +217 -0
- inquirerprompt-0.3.0.dist-info/RECORD +34 -0
- inquirerprompt-0.3.0.dist-info/WHEEL +4 -0
- inquirerprompt-0.3.0.dist-info/licenses/LICENSE +21 -0
InquirerPy/utils.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Module contains shared utility functions and typing aliases."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
List,
|
|
12
|
+
NamedTuple,
|
|
13
|
+
Optional,
|
|
14
|
+
Tuple,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from prompt_toolkit import print_formatted_text
|
|
19
|
+
from prompt_toolkit.application import run_in_terminal
|
|
20
|
+
from prompt_toolkit.application.current import get_app
|
|
21
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
22
|
+
from prompt_toolkit.styles import Style
|
|
23
|
+
from prompt_toolkit.validation import Validator
|
|
24
|
+
|
|
25
|
+
from InquirerPy.exceptions import InvalidArgument
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from prompt_toolkit.filters.base import FilterOrBool
|
|
29
|
+
|
|
30
|
+
from InquirerPy.base.control import Choice
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"get_style",
|
|
34
|
+
"calculate_height",
|
|
35
|
+
"InquirerPyStyle",
|
|
36
|
+
"patched_print",
|
|
37
|
+
"color_print",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InquirerPyStyle(NamedTuple):
|
|
42
|
+
"""`InquirerPy` Style class.
|
|
43
|
+
|
|
44
|
+
Used as a helper class to enforce the method `get_style` to be used
|
|
45
|
+
while also avoiding :class:`dict` to be passed into prompts.
|
|
46
|
+
|
|
47
|
+
Note:
|
|
48
|
+
The class is an instance of :class:`typing.NamedTuple`.
|
|
49
|
+
|
|
50
|
+
Warning:
|
|
51
|
+
You should not directly be using this class besides for type hinting
|
|
52
|
+
purposes. Obtain an instance of this class using :func:`.get_style`.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
dict: Dict[str, str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
InquirerPySessionResult = Dict[Union[str, int], Optional[Union[str, bool, List[Any]]]]
|
|
59
|
+
InquirerPyChoice = Union[List[Any], List["Choice"], List[Dict[str, Any]]]
|
|
60
|
+
InquirerPyListChoices = Union[
|
|
61
|
+
Callable[["InquirerPySessionResult"], InquirerPyChoice],
|
|
62
|
+
InquirerPyChoice,
|
|
63
|
+
]
|
|
64
|
+
InquirerPyValidate = Union[Callable[[Any], bool], "Validator"]
|
|
65
|
+
InquirerPyQuestions = Union[List[Dict[str, Any]], Dict[str, Any]]
|
|
66
|
+
InquirerPyMessage = Union[str, Callable[["InquirerPySessionResult"], str]]
|
|
67
|
+
InquirerPyDefault = Union[Any, Callable[["InquirerPySessionResult"], Any]]
|
|
68
|
+
InquirerPyKeybindings = Dict[
|
|
69
|
+
str, List[Dict[str, Union[str, "FilterOrBool", List[str]]]]
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_style(
|
|
74
|
+
style: Optional[Dict[str, str]] = None, style_override: bool = True
|
|
75
|
+
) -> InquirerPyStyle:
|
|
76
|
+
"""Obtain an :class:`.InquirerPyStyle` instance which can be consumed by the `style` parameter in prompts.
|
|
77
|
+
|
|
78
|
+
Tip:
|
|
79
|
+
This function supports ENV variables.
|
|
80
|
+
|
|
81
|
+
For all the color ENV variable names, refer to the :ref:`ENV <pages/env:Style>` documentation.
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
If no style is provided, then a default theme based on `one dark <https://github.com/joshdick/onedark.vim#color-reference>`_
|
|
85
|
+
color palette is applied.
|
|
86
|
+
|
|
87
|
+
Note:
|
|
88
|
+
Priority: style parameter -> ENV variable -> default style
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
style: The dictionary of style classes and their colors, If nothing is passed, the style will be resolved to the :ref:`pages/style:Default Style`.
|
|
92
|
+
style_override: A boolean to determine if the supplied `style` parameter should be merged with the :ref:`pages/style:Default Style` or override them.
|
|
93
|
+
By default, the supplied style will overwrite the :ref:`pages/style:Default Style`.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
An instance of :class:`.InquirerPyStyle`.
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
>>> from InquirerPy import get_style
|
|
100
|
+
>>> from InquirerPy import inquirer
|
|
101
|
+
>>> style = get_style({"questionmark": "#ffffff", "answer": "#000000"}, style_override=False)
|
|
102
|
+
>>> result = inquirer.confirm(message="Confirm?", style=style).execute()
|
|
103
|
+
"""
|
|
104
|
+
if not style_override or style is None:
|
|
105
|
+
if not style:
|
|
106
|
+
style = {}
|
|
107
|
+
result = {
|
|
108
|
+
"questionmark": os.getenv("INQUIRERPY_STYLE_QUESTIONMARK", "#e5c07b"),
|
|
109
|
+
"answermark": os.getenv("INQUIRERPY_STYLE_ANSWERMARK", "#e5c07b"),
|
|
110
|
+
"answer": os.getenv("INQUIRERPY_STYLE_ANSWER", "#61afef"),
|
|
111
|
+
"input": os.getenv("INQUIRERPY_STYLE_INPUT", "#98c379"),
|
|
112
|
+
"question": os.getenv("INQUIRERPY_STYLE_QUESTION", ""),
|
|
113
|
+
"answered_question": os.getenv("INQUIRERPY_STYLE_ANSWERED_QUESTION", ""),
|
|
114
|
+
"instruction": os.getenv("INQUIRERPY_STYLE_INSTRUCTION", "#abb2bf"),
|
|
115
|
+
"long_instruction": os.getenv(
|
|
116
|
+
"INQUIRERPY_STYLE_LONG_INSTRUCTION", "#abb2bf"
|
|
117
|
+
),
|
|
118
|
+
"pointer": os.getenv("INQUIRERPY_STYLE_POINTER", "#61afef"),
|
|
119
|
+
"checkbox": os.getenv("INQUIRERPY_STYLE_CHECKBOX", "#98c379"),
|
|
120
|
+
"separator": os.getenv("INQUIRERPY_STYLE_SEPARATOR", ""),
|
|
121
|
+
"skipped": os.getenv("INQUIRERPY_STYLE_SKIPPED", "#5c6370"),
|
|
122
|
+
"validator": os.getenv("INQUIRERPY_STYLE_VALIDATOR", ""),
|
|
123
|
+
"marker": os.getenv("INQUIRERPY_STYLE_MARKER", "#e5c07b"),
|
|
124
|
+
"fuzzy_prompt": os.getenv("INQUIRERPY_STYLE_FUZZY_PROMPT", "#c678dd"),
|
|
125
|
+
"fuzzy_info": os.getenv("INQUIRERPY_STYLE_FUZZY_INFO", "#abb2bf"),
|
|
126
|
+
"fuzzy_border": os.getenv("INQUIRERPY_STYLE_FUZZY_BORDER", "#4b5263"),
|
|
127
|
+
"fuzzy_match": os.getenv("INQUIRERPY_STYLE_FUZZY_MATCH", "#c678dd"),
|
|
128
|
+
"spinner_pattern": os.getenv("INQUIRERPY_STYLE_SPINNER_PATTERN", "#e5c07b"),
|
|
129
|
+
"spinner_text": os.getenv("INQUIRERPY_STYLE_SPINNER_TEXT", ""),
|
|
130
|
+
**style,
|
|
131
|
+
}
|
|
132
|
+
else:
|
|
133
|
+
result = {
|
|
134
|
+
"questionmark": os.getenv("INQUIRERPY_STYLE_QUESTIONMARK", ""),
|
|
135
|
+
"answermark": os.getenv("INQUIRERPY_STYLE_ANSWERMARK", ""),
|
|
136
|
+
"answer": os.getenv("INQUIRERPY_STYLE_ANSWER", ""),
|
|
137
|
+
"input": os.getenv("INQUIRERPY_STYLE_INPUT", ""),
|
|
138
|
+
"question": os.getenv("INQUIRERPY_STYLE_QUESTION", ""),
|
|
139
|
+
"answered_question": os.getenv("INQUIRERPY_STYLE_ANSWERED_QUESTION", ""),
|
|
140
|
+
"instruction": os.getenv("INQUIRERPY_STYLE_INSTRUCTION", ""),
|
|
141
|
+
"long_instruction": os.getenv("INQUIRERPY_STYLE_LONG_INSTRUCTION", ""),
|
|
142
|
+
"pointer": os.getenv("INQUIRERPY_STYLE_POINTER", ""),
|
|
143
|
+
"checkbox": os.getenv("INQUIRERPY_STYLE_CHECKBOX", ""),
|
|
144
|
+
"separator": os.getenv("INQUIRERPY_STYLE_SEPARATOR", ""),
|
|
145
|
+
"skipped": os.getenv("INQUIRERPY_STYLE_SKIPPED", ""),
|
|
146
|
+
"validator": os.getenv("INQUIRERPY_STYLE_VALIDATOR", ""),
|
|
147
|
+
"marker": os.getenv("INQUIRERPY_STYLE_MARKER", ""),
|
|
148
|
+
"fuzzy_prompt": os.getenv("INQUIRERPY_STYLE_FUZZY_PROMPT", ""),
|
|
149
|
+
"fuzzy_info": os.getenv("INQUIRERPY_STYLE_FUZZY_INFO", ""),
|
|
150
|
+
"fuzzy_border": os.getenv("INQUIRERPY_STYLE_FUZZY_BORDER", ""),
|
|
151
|
+
"fuzzy_match": os.getenv("INQUIRERPY_STYLE_FUZZY_MATCH", ""),
|
|
152
|
+
"spinner_pattern": os.getenv("INQUIRERPY_STYLE_SPINNER_PATTERN", ""),
|
|
153
|
+
"spinner_text": os.getenv("INQUIRERPY_STYLE_SPINNER_TEXT", ""),
|
|
154
|
+
**style,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if result.get("fuzzy_border"):
|
|
158
|
+
result["frame.border"] = result.pop("fuzzy_border")
|
|
159
|
+
if result.get("validator"):
|
|
160
|
+
result["validation-toolbar"] = result.pop("validator")
|
|
161
|
+
result["bottom-toolbar"] = "noreverse"
|
|
162
|
+
return InquirerPyStyle(result)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def calculate_height(
|
|
166
|
+
height: Optional[Union[int, str]],
|
|
167
|
+
max_height: Optional[Union[int, str]],
|
|
168
|
+
height_offset: int = 2,
|
|
169
|
+
) -> Tuple[Optional[int], int]:
|
|
170
|
+
"""Calculate the `height` and `max_height` for the main question contents.
|
|
171
|
+
|
|
172
|
+
Tip:
|
|
173
|
+
The parameter `height`/`max_height` can be specified by either a :class:`string` or :class:`int`.
|
|
174
|
+
|
|
175
|
+
When `height`/`max_height` is :class:`str`:
|
|
176
|
+
It will set the height to a percentage based on the value provided.
|
|
177
|
+
You can optionally add the '%' sign which will be ignored while processing.
|
|
178
|
+
|
|
179
|
+
Example: "60%" or "60" (60% of the current terminal visible lines)
|
|
180
|
+
|
|
181
|
+
When `height`/`max_height` is :class:`int`:
|
|
182
|
+
It will set the height to exact number of lines based on the value provided.
|
|
183
|
+
|
|
184
|
+
Example: 20 (20 lines in terminal)
|
|
185
|
+
|
|
186
|
+
Note:
|
|
187
|
+
If `max_height` is not provided or is None, the default `max_height` will be configured to `70%` for
|
|
188
|
+
best visual presentation in the terminal.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
height: The desired height in either percentage as string or exact value as int.
|
|
192
|
+
max_height: Maximum acceptable height in either percentage as string or exact value as int.
|
|
193
|
+
height_offset: Height offset to apply to the height.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A :class:`tuple` with the first value being the desired height and the second value being
|
|
197
|
+
the maximum height.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
InvalidArgument: The provided `height`/`max_height` is not able to to be converted to int.
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
>>> calculate_height(height="60%", max_height="100%")
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
_, term_lines = shutil.get_terminal_size()
|
|
207
|
+
term_lines = term_lines
|
|
208
|
+
if not height:
|
|
209
|
+
dimmension_height = None
|
|
210
|
+
else:
|
|
211
|
+
if isinstance(height, str):
|
|
212
|
+
height = height.replace("%", "")
|
|
213
|
+
height = int(height)
|
|
214
|
+
dimmension_height = (
|
|
215
|
+
math.floor(term_lines * (height / 100)) - height_offset
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
dimmension_height = height
|
|
219
|
+
|
|
220
|
+
if not max_height:
|
|
221
|
+
max_height = "70%" if not height else "100%"
|
|
222
|
+
if isinstance(max_height, str):
|
|
223
|
+
max_height = max_height.replace("%", "")
|
|
224
|
+
max_height = int(max_height)
|
|
225
|
+
dimmension_max_height = (
|
|
226
|
+
math.floor(term_lines * (max_height / 100)) - height_offset
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
dimmension_max_height = max_height
|
|
230
|
+
|
|
231
|
+
if dimmension_height and dimmension_height > dimmension_max_height:
|
|
232
|
+
dimmension_height = dimmension_max_height
|
|
233
|
+
if dimmension_height and dimmension_height <= 0:
|
|
234
|
+
dimmension_height = 1
|
|
235
|
+
if dimmension_max_height <= 0:
|
|
236
|
+
dimmension_max_height = 1
|
|
237
|
+
return dimmension_height, dimmension_max_height
|
|
238
|
+
|
|
239
|
+
except ValueError:
|
|
240
|
+
raise InvalidArgument(
|
|
241
|
+
"prompt argument height/max_height needs to be type of an int or str"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def patched_print(*values) -> None:
|
|
246
|
+
"""Patched :func:`print` that can print values without interrupting the prompt.
|
|
247
|
+
|
|
248
|
+
See Also:
|
|
249
|
+
:func:`print`
|
|
250
|
+
:func:`~prompt_toolkit.application.run_in_terminal`
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
*values: Refer to :func:`print`.
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
>>> patched_print("Hello World")
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
def _print():
|
|
260
|
+
print(*values)
|
|
261
|
+
|
|
262
|
+
run_in_terminal(_print)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def color_print(
|
|
266
|
+
formatted_text: List[Tuple[str, str]], style: Optional[Dict[str, str]] = None
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Print colored text leveraging :func:`~prompt_toolkit.shortcuts.print_formatted_text`.
|
|
269
|
+
|
|
270
|
+
This function automatically handles printing the text without interrupting the
|
|
271
|
+
current prompt.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
formatted_text: A list of formatted_text.
|
|
275
|
+
style: Style to apply to `formatted_text` in :class:`dictionary` form.
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
>>> color_print(formatted_text=[("class:aa", "hello "), ("class:bb", "world")], style={"aa": "red", "bb": "blue"})
|
|
279
|
+
>>> color_print([("red", "yes"), ("", " "), ("blue", "no")])
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
def _print():
|
|
283
|
+
print_formatted_text(
|
|
284
|
+
FormattedText(formatted_text),
|
|
285
|
+
style=Style.from_dict(style) if style else None,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if get_app().is_running:
|
|
289
|
+
run_in_terminal(_print)
|
|
290
|
+
else:
|
|
291
|
+
_print()
|
InquirerPy/validator.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Module contains pre-built validators."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit.validation import ValidationError, Validator
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"PathValidator",
|
|
11
|
+
"EmptyInputValidator",
|
|
12
|
+
"PasswordValidator",
|
|
13
|
+
"NumberValidator",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NumberValidator(Validator):
|
|
18
|
+
""":class:`~prompt_toolkit.validation.Validator` to validate if input is a number.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
message: Error message to display in the validatation toolbar when validation failed.
|
|
22
|
+
float_allowed: Allow input to contain floating number (with decimal).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self, message: str = "Input should be a number", float_allowed: bool = False
|
|
27
|
+
) -> None:
|
|
28
|
+
self._message = message
|
|
29
|
+
self._float_allowed = float_allowed
|
|
30
|
+
|
|
31
|
+
def validate(self, document) -> None:
|
|
32
|
+
"""Check if user input is a valid number.
|
|
33
|
+
|
|
34
|
+
This method is used internally by `prompt_toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/>`_.
|
|
35
|
+
|
|
36
|
+
See Also:
|
|
37
|
+
https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html?highlight=validator#input-validation
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
if self._float_allowed:
|
|
41
|
+
float(document.text)
|
|
42
|
+
else:
|
|
43
|
+
int(document.text)
|
|
44
|
+
except ValueError:
|
|
45
|
+
raise ValidationError(
|
|
46
|
+
message=self._message, cursor_position=document.cursor_position
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PathValidator(Validator):
|
|
51
|
+
""":class:`~prompt_toolkit.validation.Validator` to validate if input is a valid filepath on the system.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
message: Error message to display in the validatation toolbar when validation failed.
|
|
55
|
+
is_file: Explicitly check if the input is a valid file on the system.
|
|
56
|
+
is_dir: Explicitly check if the input is a valid directory/folder on the system.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
message: str = "Input is not a valid path",
|
|
62
|
+
is_file: bool = False,
|
|
63
|
+
is_dir: bool = False,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._message = message
|
|
66
|
+
self._is_file = is_file
|
|
67
|
+
self._is_dir = is_dir
|
|
68
|
+
|
|
69
|
+
def validate(self, document) -> None:
|
|
70
|
+
"""Check if user input is a filepath that exists on the system based on conditions.
|
|
71
|
+
|
|
72
|
+
This method is used internally by `prompt_toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/>`_.
|
|
73
|
+
|
|
74
|
+
See Also:
|
|
75
|
+
https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html?highlight=validator#input-validation
|
|
76
|
+
"""
|
|
77
|
+
path = Path(document.text).expanduser()
|
|
78
|
+
if self._is_file and not path.is_file():
|
|
79
|
+
raise ValidationError(
|
|
80
|
+
message=self._message,
|
|
81
|
+
cursor_position=document.cursor_position,
|
|
82
|
+
)
|
|
83
|
+
elif self._is_dir and not path.is_dir():
|
|
84
|
+
raise ValidationError(
|
|
85
|
+
message=self._message,
|
|
86
|
+
cursor_position=document.cursor_position,
|
|
87
|
+
)
|
|
88
|
+
elif not path.exists():
|
|
89
|
+
raise ValidationError(
|
|
90
|
+
message=self._message,
|
|
91
|
+
cursor_position=document.cursor_position,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class EmptyInputValidator(Validator):
|
|
96
|
+
""":class:`~prompt_toolkit.validation.Validator` to validate if the input is empty.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
message: Error message to display in the validatation toolbar when validation failed.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, message: str = "Input cannot be empty") -> None:
|
|
103
|
+
self._message = message
|
|
104
|
+
|
|
105
|
+
def validate(self, document) -> None:
|
|
106
|
+
"""Check if user input is empty.
|
|
107
|
+
|
|
108
|
+
This method is used internally by `prompt_toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/>`_.
|
|
109
|
+
|
|
110
|
+
See Also:
|
|
111
|
+
https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html?highlight=validator#input-validation
|
|
112
|
+
"""
|
|
113
|
+
if not len(document.text) > 0:
|
|
114
|
+
raise ValidationError(
|
|
115
|
+
message=self._message,
|
|
116
|
+
cursor_position=document.cursor_position,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PasswordValidator(Validator):
|
|
121
|
+
""":class:`~prompt_toolkit.validation.Validator` to validate password compliance.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
message: Error message to display in the validatation toolbar when validation failed.
|
|
125
|
+
length: The minimum length of the password.
|
|
126
|
+
cap: Password should include at least one capital letter.
|
|
127
|
+
special: Password should include at least one special char "@$!%*#?&".
|
|
128
|
+
number: Password should include at least one number.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
message: str = "Input is not compliant with the password constraints",
|
|
134
|
+
length: Optional[int] = None,
|
|
135
|
+
cap: bool = False,
|
|
136
|
+
special: bool = False,
|
|
137
|
+
number: bool = False,
|
|
138
|
+
) -> None:
|
|
139
|
+
password_pattern = r"^"
|
|
140
|
+
if cap:
|
|
141
|
+
password_pattern += r"(?=.*[A-Z])"
|
|
142
|
+
if special:
|
|
143
|
+
password_pattern += r"(?=.*[@$!%*#?&])"
|
|
144
|
+
if number:
|
|
145
|
+
password_pattern += r"(?=.*[0-9])"
|
|
146
|
+
password_pattern += r"."
|
|
147
|
+
if length:
|
|
148
|
+
password_pattern += r"{%s,}" % length
|
|
149
|
+
else:
|
|
150
|
+
password_pattern += r"*"
|
|
151
|
+
password_pattern += r"$"
|
|
152
|
+
self._re = re.compile(password_pattern)
|
|
153
|
+
self._message = message
|
|
154
|
+
|
|
155
|
+
def validate(self, document) -> None:
|
|
156
|
+
"""Check if user input is compliant with the specified password constraints.
|
|
157
|
+
|
|
158
|
+
This method is used internally by `prompt_toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/>`_.
|
|
159
|
+
|
|
160
|
+
See Also:
|
|
161
|
+
https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html?highlight=validator#input-validation
|
|
162
|
+
"""
|
|
163
|
+
if not self._re.match(document.text):
|
|
164
|
+
raise ValidationError(
|
|
165
|
+
message=self._message, cursor_position=document.cursor_position
|
|
166
|
+
)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: InquirerPrompt
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python port of Inquirer.js — a collection of common interactive command-line user interfaces (community-maintained fork of InquirerPy)
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: cli,prompt-toolkit,commandline,inquirer,development,interactive,prompt
|
|
8
|
+
Author: Kevin Zhuang
|
|
9
|
+
Author-email: kevin7441@gmail.com
|
|
10
|
+
Maintainer: Tobias Hochgürtel
|
|
11
|
+
Maintainer-email: tobiashochguertel@users.noreply.github.com
|
|
12
|
+
Requires-Python: >=3.9,<4.0
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: Microsoft
|
|
18
|
+
Classifier: Operating System :: Unix
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Topic :: Software Development
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
29
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: Sphinx (>=4.1.2,<5.0.0) ; extra == "docs"
|
|
32
|
+
Requires-Dist: furo (>=2021.8.17b43,<2022.0.0) ; extra == "docs"
|
|
33
|
+
Requires-Dist: myst-parser (>=0.15.1,<0.16.0) ; extra == "docs"
|
|
34
|
+
Requires-Dist: pfzy (>=0.3.1,<0.4.0)
|
|
35
|
+
Requires-Dist: prompt-toolkit (>=3.0.1,<4.0.0)
|
|
36
|
+
Requires-Dist: sphinx-autobuild (>=2021.3.14,<2022.0.0) ; extra == "docs"
|
|
37
|
+
Requires-Dist: sphinx-copybutton (>=0.4.0,<0.5.0) ; extra == "docs"
|
|
38
|
+
Project-URL: Documentation, https://InquirerPrompt.readthedocs.io
|
|
39
|
+
Project-URL: Repository, https://github.com/tobiashochguertel/InquirerPy
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# InquirerPy (published as **InquirerPrompt**)
|
|
43
|
+
|
|
44
|
+
> **🍴 Community-Maintained Fork**
|
|
45
|
+
>
|
|
46
|
+
> This is a community fork of [kazhala/InquirerPy](https://github.com/kazhala/InquirerPy), maintained at
|
|
47
|
+
> [`tobiashochguertel/InquirerPy`](https://github.com/tobiashochguertel/InquirerPy).
|
|
48
|
+
>
|
|
49
|
+
> The original project is a fantastic library that we rely on and deeply appreciate. Unfortunately the upstream
|
|
50
|
+
> has been quiet for some time, and the community has accumulated valuable bug fixes, compatibility updates,
|
|
51
|
+
> and feature contributions that have not been reviewed or merged. Rather than letting those improvements go to
|
|
52
|
+
> waste, we decided to maintain this fork so that InquirerPy stays healthy and up-to-date for everyone.
|
|
53
|
+
>
|
|
54
|
+
> **Our goal is to contribute all improvements back upstream** — if the original author becomes active again
|
|
55
|
+
> we will happily defer to them. Until then, we are keeping the lights on here. 🙏
|
|
56
|
+
>
|
|
57
|
+
> | Item | Detail |
|
|
58
|
+
> |------|--------|
|
|
59
|
+
> | Upstream | [kazhala/InquirerPy](https://github.com/kazhala/InquirerPy) @ `0.3.4` (last commit Nov 2022) |
|
|
60
|
+
> | This fork | [tobiashochguertel/InquirerPy](https://github.com/tobiashochguertel/InquirerPy) |
|
|
61
|
+
> | Install | `pip install InquirerPrompt` (import still: `import InquirerPy`) |
|
|
62
|
+
> | Docs | [InquirerPrompt.readthedocs.io](https://InquirerPrompt.readthedocs.io) |
|
|
63
|
+
>
|
|
64
|
+
> If you are the original author and would like to resume ownership or collaborate, please open an issue — we
|
|
65
|
+
> would love to work together!
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
[](https://github.com/tobiashochguertel/InquirerPy/actions?query=workflow%3ACI)
|
|
70
|
+
[](https://codecov.io/gh/tobiashochguertel/InquirerPy)
|
|
71
|
+
[](https://pypi.org/project/InquirerPrompt/)
|
|
72
|
+
[](https://pypi.org/project/InquirerPrompt/)
|
|
73
|
+
[](https://github.com/kazhala/InquirerPy)
|
|
74
|
+
|
|
75
|
+
Documentation: [InquirerPrompt.readthedocs.io](https://InquirerPrompt.readthedocs.io)
|
|
76
|
+
|
|
77
|
+
<!-- start intro -->
|
|
78
|
+
|
|
79
|
+
## Introduction
|
|
80
|
+
|
|
81
|
+
`InquirerPy` is a Python port of the famous [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/) (A collection of common interactive command line user interfaces).
|
|
82
|
+
This project is a re-implementation of the [PyInquirer](https://github.com/CITGuru/PyInquirer) project, with bug fixes of known issues, new prompts, backward compatible APIs
|
|
83
|
+
as well as more customisation options.
|
|
84
|
+
|
|
85
|
+
<!-- end intro -->
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
## Motivation
|
|
90
|
+
|
|
91
|
+
[PyInquirer](https://github.com/CITGuru/PyInquirer) is a great Python port of [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/), however, the project is slowly reaching
|
|
92
|
+
to an unmaintained state with various issues left behind and no intention to implement more feature requests. I was heavily relying on this library for other projects but
|
|
93
|
+
could not proceed due to the limitations.
|
|
94
|
+
|
|
95
|
+
Some noticeable ones that bother me the most:
|
|
96
|
+
|
|
97
|
+
- hard limit on `prompt_toolkit` version 1.0.3
|
|
98
|
+
- various color issues
|
|
99
|
+
- various cursor issues
|
|
100
|
+
- No options for VI/Emacs navigation key bindings
|
|
101
|
+
- Pagination option doesn't work
|
|
102
|
+
|
|
103
|
+
This project uses python3.7+ type hinting with focus on resolving above issues while providing greater customisation options.
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
### OS
|
|
108
|
+
|
|
109
|
+
Leveraging [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit), `InquirerPy` works cross platform for all OS. Although Unix platform may have a better experience than Windows.
|
|
110
|
+
|
|
111
|
+
### Python
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
python >= 3.7
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Getting Started
|
|
118
|
+
|
|
119
|
+
Checkout full documentation **[here](https://InquirerPrompt.readthedocs.io/)**.
|
|
120
|
+
|
|
121
|
+
### Install
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
pip3 install InquirerPy
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Quick Start
|
|
128
|
+
|
|
129
|
+
#### Classic Syntax (PyInquirer)
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from InquirerPy import prompt
|
|
133
|
+
|
|
134
|
+
questions = [
|
|
135
|
+
{"type": "input", "message": "What's your name:", "name": "name"},
|
|
136
|
+
{"type": "confirm", "message": "Confirm?", "name": "confirm"},
|
|
137
|
+
]
|
|
138
|
+
result = prompt(questions)
|
|
139
|
+
name = result["name"]
|
|
140
|
+
confirm = result["confirm"]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Alternate Syntax
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from InquirerPy import inquirer
|
|
147
|
+
|
|
148
|
+
name = inquirer.text(message="What's your name:").execute()
|
|
149
|
+
confirm = inquirer.confirm(message="Confirm?").execute()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
<!-- start migration -->
|
|
153
|
+
|
|
154
|
+
## Migrating from PyInquirer
|
|
155
|
+
|
|
156
|
+
Most APIs from [PyInquirer](https://github.com/CITGuru/PyInquirer) should be compatible with `InquirerPy`. If you have discovered more incompatible APIs, please
|
|
157
|
+
create an issue or directly update README via a pull request.
|
|
158
|
+
|
|
159
|
+
### EditorPrompt
|
|
160
|
+
|
|
161
|
+
`InquirerPy` does not support [editor](https://github.com/CITGuru/PyInquirer#editor---type-editor) prompt as of now.
|
|
162
|
+
|
|
163
|
+
### CheckboxPrompt
|
|
164
|
+
|
|
165
|
+
The following table contains the mapping of incompatible parameters.
|
|
166
|
+
|
|
167
|
+
| PyInquirer | InquirerPy |
|
|
168
|
+
| --------------- | --------------- |
|
|
169
|
+
| pointer_sign | pointer |
|
|
170
|
+
| selected_sign | enabled_symbol |
|
|
171
|
+
| unselected_sign | disabled_symbol |
|
|
172
|
+
|
|
173
|
+
### Style
|
|
174
|
+
|
|
175
|
+
Every style keys from [PyInquirer](https://github.com/CITGuru/PyInquirer) is present in `InquirerPy` except the ones in the following table.
|
|
176
|
+
|
|
177
|
+
| PyInquirer | InquirerPy |
|
|
178
|
+
| ---------- | ---------- |
|
|
179
|
+
| selected | pointer |
|
|
180
|
+
|
|
181
|
+
Although `InquirerPy` support all the keys from [PyInquirer](https://github.com/CITGuru/PyInquirer), the styling works slightly different.
|
|
182
|
+
Please refer to the [Style](https://InquirerPrompt.readthedocs.io/en/latest/pages/style.html) documentation for detailed information.
|
|
183
|
+
|
|
184
|
+
<!-- end migration -->
|
|
185
|
+
|
|
186
|
+
## Similar projects
|
|
187
|
+
|
|
188
|
+
### questionary
|
|
189
|
+
|
|
190
|
+
[questionary](https://github.com/tmbo/questionary) is a fantastic fork which supports `prompt_toolkit` 3.0.0+ with performance improvement and more customisation options.
|
|
191
|
+
It's already a well established and stable library.
|
|
192
|
+
|
|
193
|
+
Comparing with [questionary](https://github.com/tmbo/questionary), `InquirerPy` offers even more customisation options in styles, UI as well as key bindings. `InquirerPy` also provides a new
|
|
194
|
+
and powerful [fuzzy](https://InquirerPrompt.readthedocs.io/en/latest/pages/prompts/fuzzy.html) prompt.
|
|
195
|
+
|
|
196
|
+
### python-inquirer
|
|
197
|
+
|
|
198
|
+
[python-inquirer](https://github.com/magmax/python-inquirer) is another great Python port of [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/). Instead of using `prompt_toolkit`, it
|
|
199
|
+
leverages the library `blessed` to implement the UI.
|
|
200
|
+
|
|
201
|
+
Before implementing `InquirerPy`, this library came up as an alternative. It's a more stable library comparing to the original [PyInquirer](https://github.com/CITGuru/PyInquirer), however
|
|
202
|
+
it has a rather limited customisation options and an older UI which did not solve the issues I was facing described in the [Motivation](#Motivation) section.
|
|
203
|
+
|
|
204
|
+
Comparing with [python-inquirer](https://github.com/magmax/python-inquirer), `InquirerPy` offers a slightly better UI,
|
|
205
|
+
more customisation options in key bindings and styles, providing pagination as well as more prompts.
|
|
206
|
+
|
|
207
|
+
## Credit
|
|
208
|
+
|
|
209
|
+
This project is based on the great work done by the following projects & their authors.
|
|
210
|
+
|
|
211
|
+
- [PyInquirer](https://github.com/CITGuru/PyInquirer)
|
|
212
|
+
- [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit)
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
This project is licensed under [MIT](https://github.com/kazhala/InquirerPy/blob/master/LICENSE).
|
|
217
|
+
|