xulbux 1.6.5__py3-none-any.whl → 1.6.7__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.
Potentially problematic release.
This version of xulbux might be problematic. Click here for more details.
- xulbux/__init__.py +1 -1
- xulbux/_cli_.py +9 -9
- xulbux/_consts_.py +98 -62
- xulbux/xx_code.py +37 -42
- xulbux/xx_color.py +27 -55
- xulbux/xx_console.py +205 -110
- xulbux/xx_data.py +176 -128
- xulbux/xx_env_path.py +3 -7
- xulbux/xx_file.py +10 -4
- xulbux/xx_format_codes.py +26 -37
- xulbux/xx_json.py +2 -5
- xulbux/xx_path.py +33 -23
- xulbux/xx_regex.py +18 -20
- xulbux/xx_string.py +23 -76
- xulbux/xx_system.py +16 -19
- {xulbux-1.6.5.dist-info → xulbux-1.6.7.dist-info}/METADATA +92 -13
- xulbux-1.6.7.dist-info/RECORD +21 -0
- {xulbux-1.6.5.dist-info → xulbux-1.6.7.dist-info}/WHEEL +1 -1
- xulbux-1.6.5.dist-info/RECORD +0 -21
- {xulbux-1.6.5.dist-info → xulbux-1.6.7.dist-info}/LICENSE +0 -0
- {xulbux-1.6.5.dist-info → xulbux-1.6.7.dist-info}/entry_points.txt +0 -0
- {xulbux-1.6.5.dist-info → xulbux-1.6.7.dist-info}/top_level.txt +0 -0
xulbux/xx_console.py
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Functions for logging and other small actions within the console
|
|
3
|
-
|
|
4
|
-
- `Console.user()`
|
|
5
|
-
- `Console.is_admin()`
|
|
6
|
-
- `Console.pause_exit()`
|
|
7
|
-
- `Console.cls()`
|
|
8
|
-
- `Console.log()`
|
|
9
|
-
- `Console.debug()`
|
|
10
|
-
- `Console.info()`
|
|
11
|
-
- `Console.done()`
|
|
12
|
-
- `Console.warn()`
|
|
13
|
-
- `Console.fail()`
|
|
14
|
-
- `Console.exit()`
|
|
15
|
-
- `Console.confirm()`
|
|
16
|
-
- `Console.restricted_input()`
|
|
17
|
-
- `Console.pwd_input()`\n
|
|
18
|
-
------------------------------------------------------------------------------------------------------
|
|
2
|
+
Functions for logging and other small actions within the console.\n
|
|
3
|
+
----------------------------------------------------------------------------------------------------------
|
|
19
4
|
You can also use special formatting codes directly inside the log message to change their appearance.
|
|
20
|
-
For more detailed information about formatting codes, see the the `xx_format_codes`
|
|
5
|
+
For more detailed information about formatting codes, see the the `xx_format_codes` module documentation.
|
|
21
6
|
"""
|
|
22
7
|
|
|
23
|
-
from ._consts_ import
|
|
24
|
-
from .xx_format_codes import FormatCodes
|
|
8
|
+
from ._consts_ import COLOR, CHARS
|
|
9
|
+
from .xx_format_codes import FormatCodes, _COMPILED
|
|
25
10
|
from .xx_string import String
|
|
26
|
-
from .xx_color import
|
|
11
|
+
from .xx_color import Color, rgba, hexa
|
|
27
12
|
|
|
28
|
-
from
|
|
13
|
+
from prompt_toolkit.key_binding.key_bindings import KeyBindings
|
|
14
|
+
from typing import Optional
|
|
15
|
+
import prompt_toolkit as _prompt_toolkit
|
|
29
16
|
import pyperclip as _pyperclip
|
|
30
17
|
import keyboard as _keyboard
|
|
31
18
|
import getpass as _getpass
|
|
@@ -35,10 +22,81 @@ import sys as _sys
|
|
|
35
22
|
import os as _os
|
|
36
23
|
|
|
37
24
|
|
|
25
|
+
# YAPF: disable
|
|
26
|
+
class _ConsoleWidth:
|
|
27
|
+
def __get__(self, obj, owner=None):
|
|
28
|
+
return _os.get_terminal_size().columns
|
|
29
|
+
|
|
30
|
+
class _ConsoleHeight:
|
|
31
|
+
def __get__(self, obj, owner=None):
|
|
32
|
+
return _os.get_terminal_size().lines
|
|
33
|
+
|
|
34
|
+
class _ConsoleSize:
|
|
35
|
+
def __get__(self, obj, owner=None):
|
|
36
|
+
size = _os.get_terminal_size()
|
|
37
|
+
return (size.columns, size.lines)
|
|
38
|
+
|
|
39
|
+
class _ConsoleUser:
|
|
40
|
+
def __get__(self, obj, owner=None):
|
|
41
|
+
return _os.getenv("USER") or _os.getenv("USERNAME") or _getpass.getuser()
|
|
42
|
+
|
|
43
|
+
class ArgResult:
|
|
44
|
+
"""Exists: if the argument was found or not\n
|
|
45
|
+
Value: the value from behind the found argument"""
|
|
46
|
+
def __init__(self, exists: bool, value: any):
|
|
47
|
+
self.exists = exists
|
|
48
|
+
self.value = value
|
|
49
|
+
|
|
50
|
+
class Args:
|
|
51
|
+
"""Stores arguments under their aliases with their results."""
|
|
52
|
+
def __init__(self, **kwargs):
|
|
53
|
+
for key, value in kwargs.items():
|
|
54
|
+
if not key.isidentifier():
|
|
55
|
+
raise TypeError(f"Argument alias '{key}' is invalid. It must be a valid Python variable name.")
|
|
56
|
+
setattr(self, key, ArgResult(**value))
|
|
57
|
+
# YAPF: enable
|
|
58
|
+
|
|
59
|
+
|
|
38
60
|
class Console:
|
|
39
61
|
|
|
62
|
+
w: int = _ConsoleWidth()
|
|
63
|
+
"""The width of the console in characters."""
|
|
64
|
+
h: int = _ConsoleHeight()
|
|
65
|
+
"""The height of the console in lines."""
|
|
66
|
+
wh: tuple[int, int] = _ConsoleSize()
|
|
67
|
+
"""A tuple with the width and height of
|
|
68
|
+
the console in characters and lines."""
|
|
69
|
+
usr: str = _ConsoleUser()
|
|
70
|
+
"""The name of the current user."""
|
|
71
|
+
|
|
40
72
|
@staticmethod
|
|
41
|
-
def get_args(find_args: dict
|
|
73
|
+
def get_args(find_args: dict[str, list[str] | tuple[str, ...]]) -> Args:
|
|
74
|
+
"""Will search for the specified arguments in the command line
|
|
75
|
+
arguments and return the results as a special `Args` object.\n
|
|
76
|
+
----------------------------------------------------------------
|
|
77
|
+
The `find_args` dictionary should have the following structure:
|
|
78
|
+
```python
|
|
79
|
+
find_args={
|
|
80
|
+
"arg1_alias": ["-a1", "--arg1", "--argument-1"],
|
|
81
|
+
"arg2_alias": ("-a2", "--arg2", "--argument-2"),
|
|
82
|
+
...
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
And if the script is called via the command line:\n
|
|
86
|
+
`python script.py -a1 "argument value" --arg2`\n
|
|
87
|
+
...it would return the following `Args` object:
|
|
88
|
+
```python
|
|
89
|
+
Args(
|
|
90
|
+
arg1_alias=ArgResult(exists=True, value="argument value"),
|
|
91
|
+
arg2_alias=ArgResult(exists=True, value=None),
|
|
92
|
+
...
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
...which can be accessed like this:\n
|
|
96
|
+
- `Args.<arg_alias>.exists` is `True` if any of the specified
|
|
97
|
+
args were found and `False` if not
|
|
98
|
+
- `Args.<arg_alias>.value` the value from behind the found arg,
|
|
99
|
+
`None` if no value was found"""
|
|
42
100
|
args = _sys.argv[1:]
|
|
43
101
|
results = {}
|
|
44
102
|
for arg_key, arg_group in find_args.items():
|
|
@@ -52,19 +110,7 @@ class Console:
|
|
|
52
110
|
value = String.to_type(args[arg_index + 1])
|
|
53
111
|
break
|
|
54
112
|
results[arg_key] = {"exists": exists, "value": value}
|
|
55
|
-
return results
|
|
56
|
-
|
|
57
|
-
def w() -> int:
|
|
58
|
-
return getattr(_shutil.get_terminal_size(), "columns", 80)
|
|
59
|
-
|
|
60
|
-
def h() -> int:
|
|
61
|
-
return getattr(_shutil.get_terminal_size(), "lines", 24)
|
|
62
|
-
|
|
63
|
-
def wh() -> tuple[int, int]:
|
|
64
|
-
return Console.w(), Console.h()
|
|
65
|
-
|
|
66
|
-
def user() -> str:
|
|
67
|
-
return _os.getenv("USER") or _os.getenv("USERNAME") or _getpass.getuser()
|
|
113
|
+
return Args(**results)
|
|
68
114
|
|
|
69
115
|
@staticmethod
|
|
70
116
|
def pause_exit(
|
|
@@ -84,6 +130,7 @@ class Console:
|
|
|
84
130
|
if exit:
|
|
85
131
|
_sys.exit(exit_code)
|
|
86
132
|
|
|
133
|
+
@staticmethod
|
|
87
134
|
def cls() -> None:
|
|
88
135
|
"""Will clear the console in addition to completely resetting the ANSI formats."""
|
|
89
136
|
if _shutil.which("cls"):
|
|
@@ -94,33 +141,46 @@ class Console:
|
|
|
94
141
|
|
|
95
142
|
@staticmethod
|
|
96
143
|
def log(
|
|
97
|
-
title: str,
|
|
144
|
+
title: Optional[str] = None,
|
|
98
145
|
prompt: object = "",
|
|
146
|
+
format_linebreaks: bool = True,
|
|
99
147
|
start: str = "",
|
|
100
148
|
end: str = "\n",
|
|
101
149
|
title_bg_color: hexa | rgba = None,
|
|
102
150
|
default_color: hexa | rgba = None,
|
|
151
|
+
_console_tabsize: int = 8,
|
|
103
152
|
) -> None:
|
|
104
153
|
"""Will print a formatted log message:
|
|
105
154
|
- `title` -⠀the title of the log message (e.g. `DEBUG`, `WARN`, `FAIL`, etc.)
|
|
106
155
|
- `prompt` -⠀the log message
|
|
156
|
+
- `format_linebreaks` -⠀whether to format (indent after) the line breaks or not
|
|
107
157
|
- `start` -⠀something to print before the log is printed
|
|
108
158
|
- `end` -⠀something to print after the log is printed (e.g. `\\n`)
|
|
109
159
|
- `title_bg_color` -⠀the background color of the `title`
|
|
110
|
-
- `default_color` -⠀the default text color of the `prompt
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
160
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
161
|
+
- `_console_tabsize` -⠀the tab size of the console (default is 8)\n
|
|
162
|
+
-----------------------------------------------------------------------------------
|
|
163
|
+
The log message can be formatted with special formatting codes. For more detailed
|
|
164
|
+
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
165
|
+
title = "" if title is None else title.strip().upper()
|
|
166
|
+
title_len, tab_len = len(title) + 4, _console_tabsize - ((len(title) + 4) % _console_tabsize)
|
|
114
167
|
title_color = "_color" if not title_bg_color else Color.text_color_for_on_bg(title_bg_color)
|
|
115
|
-
if
|
|
168
|
+
if format_linebreaks:
|
|
169
|
+
prompt_lst = (String.split_count(l, Console.w - (title_len + tab_len)) for l in str(prompt).splitlines())
|
|
170
|
+
prompt_lst = (item for lst in prompt_lst for item in (lst if isinstance(lst, list) else [lst]))
|
|
171
|
+
prompt = f"\n{' ' * title_len}\t".join(prompt_lst)
|
|
172
|
+
else:
|
|
173
|
+
prompt = str(prompt)
|
|
174
|
+
if title == "":
|
|
116
175
|
FormatCodes.print(
|
|
117
|
-
f'{start}
|
|
176
|
+
f'{start} {f"[{default_color}]" if default_color else ""}{str(prompt)}[_]',
|
|
118
177
|
default_color=default_color,
|
|
119
178
|
end=end,
|
|
120
179
|
)
|
|
121
180
|
else:
|
|
122
181
|
FormatCodes.print(
|
|
123
|
-
f'{start} {f"[{
|
|
182
|
+
f'{start} [bold][{title_color}]{f"[BG:{title_bg_color}]" if title_bg_color else ""} {title} [_]'
|
|
183
|
+
+ f'\t{f"[{default_color}]" if default_color else ""}{prompt}[_]',
|
|
124
184
|
default_color=default_color,
|
|
125
185
|
end=end,
|
|
126
186
|
)
|
|
@@ -129,94 +189,101 @@ class Console:
|
|
|
129
189
|
def debug(
|
|
130
190
|
prompt: object = "Point in program reached.",
|
|
131
191
|
active: bool = True,
|
|
192
|
+
format_linebreaks: bool = True,
|
|
132
193
|
start: str = "",
|
|
133
194
|
end: str = "\n",
|
|
134
|
-
title_bg_color: hexa | rgba =
|
|
135
|
-
default_color: hexa | rgba =
|
|
195
|
+
title_bg_color: hexa | rgba = COLOR.yellow,
|
|
196
|
+
default_color: hexa | rgba = COLOR.text,
|
|
136
197
|
pause: bool = False,
|
|
137
198
|
exit: bool = False,
|
|
138
199
|
) -> None:
|
|
139
200
|
"""A preset for `log()`: `DEBUG` log message with the options to pause
|
|
140
|
-
at the message and exit the program after the message was printed.
|
|
201
|
+
at the message and exit the program after the message was printed.
|
|
202
|
+
If `active` is false, no debug message will be printed."""
|
|
141
203
|
if active:
|
|
142
|
-
Console.log("DEBUG", prompt, start, end, title_bg_color, default_color)
|
|
204
|
+
Console.log("DEBUG", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
143
205
|
Console.pause_exit(pause, exit)
|
|
144
206
|
|
|
145
207
|
@staticmethod
|
|
146
208
|
def info(
|
|
147
209
|
prompt: object = "Program running.",
|
|
210
|
+
format_linebreaks: bool = True,
|
|
148
211
|
start: str = "",
|
|
149
212
|
end: str = "\n",
|
|
150
|
-
title_bg_color: hexa | rgba =
|
|
151
|
-
default_color: hexa | rgba =
|
|
213
|
+
title_bg_color: hexa | rgba = COLOR.blue,
|
|
214
|
+
default_color: hexa | rgba = COLOR.text,
|
|
152
215
|
pause: bool = False,
|
|
153
216
|
exit: bool = False,
|
|
154
217
|
) -> None:
|
|
155
218
|
"""A preset for `log()`: `INFO` log message with the options to pause
|
|
156
219
|
at the message and exit the program after the message was printed."""
|
|
157
|
-
Console.log("INFO", prompt, start, end, title_bg_color, default_color)
|
|
220
|
+
Console.log("INFO", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
158
221
|
Console.pause_exit(pause, exit)
|
|
159
222
|
|
|
160
223
|
@staticmethod
|
|
161
224
|
def done(
|
|
162
225
|
prompt: object = "Program finished.",
|
|
226
|
+
format_linebreaks: bool = True,
|
|
163
227
|
start: str = "",
|
|
164
228
|
end: str = "\n",
|
|
165
|
-
title_bg_color: hexa | rgba =
|
|
166
|
-
default_color: hexa | rgba =
|
|
229
|
+
title_bg_color: hexa | rgba = COLOR.teal,
|
|
230
|
+
default_color: hexa | rgba = COLOR.text,
|
|
167
231
|
pause: bool = False,
|
|
168
232
|
exit: bool = False,
|
|
169
233
|
) -> None:
|
|
170
234
|
"""A preset for `log()`: `DONE` log message with the options to pause
|
|
171
235
|
at the message and exit the program after the message was printed."""
|
|
172
|
-
Console.log("DONE", prompt, start, end, title_bg_color, default_color)
|
|
236
|
+
Console.log("DONE", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
173
237
|
Console.pause_exit(pause, exit)
|
|
174
238
|
|
|
175
239
|
@staticmethod
|
|
176
240
|
def warn(
|
|
177
241
|
prompt: object = "Important message.",
|
|
242
|
+
format_linebreaks: bool = True,
|
|
178
243
|
start: str = "",
|
|
179
244
|
end: str = "\n",
|
|
180
|
-
title_bg_color: hexa | rgba =
|
|
181
|
-
default_color: hexa | rgba =
|
|
245
|
+
title_bg_color: hexa | rgba = COLOR.orange,
|
|
246
|
+
default_color: hexa | rgba = COLOR.text,
|
|
182
247
|
pause: bool = False,
|
|
183
248
|
exit: bool = False,
|
|
184
249
|
) -> None:
|
|
185
250
|
"""A preset for `log()`: `WARN` log message with the options to pause
|
|
186
251
|
at the message and exit the program after the message was printed."""
|
|
187
|
-
Console.log("WARN", prompt, start, end, title_bg_color, default_color)
|
|
252
|
+
Console.log("WARN", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
188
253
|
Console.pause_exit(pause, exit)
|
|
189
254
|
|
|
190
255
|
@staticmethod
|
|
191
256
|
def fail(
|
|
192
257
|
prompt: object = "Program error.",
|
|
258
|
+
format_linebreaks: bool = True,
|
|
193
259
|
start: str = "",
|
|
194
260
|
end: str = "\n",
|
|
195
|
-
title_bg_color: hexa | rgba =
|
|
196
|
-
default_color: hexa | rgba =
|
|
261
|
+
title_bg_color: hexa | rgba = COLOR.red,
|
|
262
|
+
default_color: hexa | rgba = COLOR.text,
|
|
197
263
|
pause: bool = False,
|
|
198
264
|
exit: bool = True,
|
|
199
265
|
reset_ansi=True,
|
|
200
266
|
) -> None:
|
|
201
267
|
"""A preset for `log()`: `FAIL` log message with the options to pause
|
|
202
268
|
at the message and exit the program after the message was printed."""
|
|
203
|
-
Console.log("FAIL", prompt, start, end, title_bg_color, default_color)
|
|
269
|
+
Console.log("FAIL", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
204
270
|
Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
|
|
205
271
|
|
|
206
272
|
@staticmethod
|
|
207
273
|
def exit(
|
|
208
274
|
prompt: object = "Program ended.",
|
|
275
|
+
format_linebreaks: bool = True,
|
|
209
276
|
start: str = "",
|
|
210
277
|
end: str = "\n",
|
|
211
|
-
title_bg_color: hexa | rgba =
|
|
212
|
-
default_color: hexa | rgba =
|
|
278
|
+
title_bg_color: hexa | rgba = COLOR.magenta,
|
|
279
|
+
default_color: hexa | rgba = COLOR.text,
|
|
213
280
|
pause: bool = False,
|
|
214
281
|
exit: bool = True,
|
|
215
282
|
reset_ansi=True,
|
|
216
283
|
) -> None:
|
|
217
284
|
"""A preset for `log()`: `EXIT` log message with the options to pause
|
|
218
285
|
at the message and exit the program after the message was printed."""
|
|
219
|
-
Console.log("EXIT", prompt, start, end, title_bg_color, default_color)
|
|
286
|
+
Console.log("EXIT", prompt, format_linebreaks, start, end, title_bg_color, default_color)
|
|
220
287
|
Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
|
|
221
288
|
|
|
222
289
|
@staticmethod
|
|
@@ -226,28 +293,33 @@ class Console:
|
|
|
226
293
|
end: str = "\n",
|
|
227
294
|
box_bg_color: str | hexa | rgba = "green",
|
|
228
295
|
default_color: hexa | rgba = "#000",
|
|
229
|
-
|
|
296
|
+
w_padding: int = 2,
|
|
297
|
+
w_full: bool = False,
|
|
230
298
|
) -> None:
|
|
231
299
|
"""Will print a box, containing a formatted log message:
|
|
232
300
|
- `*values` -⠀the box content (each value is on a new line)
|
|
233
301
|
- `start` -⠀something to print before the log box is printed
|
|
234
302
|
- `end` -⠀something to print after the log box is printed (e.g. `\\n`)
|
|
235
|
-
- `box_bg_color` -⠀the
|
|
236
|
-
- `default_color` -⠀the default text color of the `*values
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
303
|
+
- `box_bg_color` -⠀the background color of the box
|
|
304
|
+
- `default_color` -⠀the default text color of the `*values`
|
|
305
|
+
- `w_padding` -⠀the horizontal padding (in chars) to the box content
|
|
306
|
+
- `w_full` -⠀whether to make the box be the full console width or not\n
|
|
307
|
+
-----------------------------------------------------------------------------------
|
|
308
|
+
The box content can be formatted with special formatting codes. For more detailed
|
|
309
|
+
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
310
|
+
lines = [line.strip() for val in values for line in val.splitlines()]
|
|
241
311
|
unfmt_lines = [FormatCodes.remove_formatting(line) for line in lines]
|
|
242
312
|
max_line_len = max(len(line) for line in unfmt_lines)
|
|
313
|
+
pad_w_full = (Console.w - (max_line_len + (2 * w_padding))) if w_full else 0
|
|
243
314
|
lines = [
|
|
244
|
-
f"[bg:{box_bg_color}]{' ' *
|
|
245
|
-
for line, unfmt in zip(lines, unfmt_lines)
|
|
315
|
+
f"[bg:{box_bg_color}]{' ' * w_padding}{line}" + " " *
|
|
316
|
+
((w_padding + max_line_len - len(unfmt)) + pad_w_full) + "[_bg]" for line, unfmt in zip(lines, unfmt_lines)
|
|
246
317
|
]
|
|
318
|
+
pady = " " * (Console.w if w_full else max_line_len + (2 * w_padding))
|
|
247
319
|
FormatCodes.print(
|
|
248
|
-
f"{start}[bg:{box_bg_color}]{
|
|
249
|
-
+ "\n".join(lines)
|
|
250
|
-
+ f"\n[bg:{box_bg_color}]{
|
|
320
|
+
f"{start}[bg:{box_bg_color}]{pady}[_bg]\n"
|
|
321
|
+
+ _COMPILED["formatting"].sub(lambda m: f"{m.group(0)}[bg:{box_bg_color}]", "\n".join(lines))
|
|
322
|
+
+ f"\n[bg:{box_bg_color}]{pady}[_bg]",
|
|
251
323
|
default_color=default_color,
|
|
252
324
|
sep="\n",
|
|
253
325
|
end=end,
|
|
@@ -258,13 +330,13 @@ class Console:
|
|
|
258
330
|
prompt: object = "Do you want to continue?",
|
|
259
331
|
start="",
|
|
260
332
|
end="\n",
|
|
261
|
-
default_color: hexa | rgba =
|
|
333
|
+
default_color: hexa | rgba = COLOR.cyan,
|
|
262
334
|
default_is_yes: bool = True,
|
|
263
335
|
) -> bool:
|
|
264
336
|
"""Ask a yes/no question.\n
|
|
265
|
-
|
|
266
|
-
The
|
|
267
|
-
information about formatting codes, see the `xx_format_codes`
|
|
337
|
+
---------------------------------------------------------------------------------------
|
|
338
|
+
The prompt can be formatted with special formatting codes. For more detailed
|
|
339
|
+
information about formatting codes, see the `xx_format_codes` module documentation."""
|
|
268
340
|
confirmed = input(
|
|
269
341
|
FormatCodes.to_ansi(
|
|
270
342
|
f'{start} {str(prompt)} [_|dim](({"Y" if default_is_yes else "y"}/{"n" if default_is_yes else "N"}): )',
|
|
@@ -275,26 +347,61 @@ class Console:
|
|
|
275
347
|
Console.log("", end, end="")
|
|
276
348
|
return confirmed
|
|
277
349
|
|
|
350
|
+
@staticmethod
|
|
351
|
+
def multiline_input(
|
|
352
|
+
prompt: object = "",
|
|
353
|
+
start="",
|
|
354
|
+
end="\n",
|
|
355
|
+
default_color: hexa | rgba = COLOR.cyan,
|
|
356
|
+
show_keybindings=True,
|
|
357
|
+
input_prefix=" ⤷ ",
|
|
358
|
+
reset_ansi=True,
|
|
359
|
+
) -> str:
|
|
360
|
+
"""An input where users can input (and paste) text over multiple lines.\n
|
|
361
|
+
-----------------------------------------------------------------------------------
|
|
362
|
+
- `prompt` -⠀the input prompt
|
|
363
|
+
- `start` -⠀something to print before the input
|
|
364
|
+
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
365
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
366
|
+
- `show_keybindings` -⠀whether to show the special keybindings or not
|
|
367
|
+
- `input_prefix` -⠀the prefix of the input line
|
|
368
|
+
- `reset_ansi` -⠀whether to reset the ANSI codes after the input or not
|
|
369
|
+
-----------------------------------------------------------------------------------
|
|
370
|
+
The input prompt can be formatted with special formatting codes. For more detailed
|
|
371
|
+
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
372
|
+
kb = KeyBindings()
|
|
373
|
+
|
|
374
|
+
@kb.add("c-d", eager=True) # CTRL+D
|
|
375
|
+
def _(event):
|
|
376
|
+
event.app.exit(result=event.app.current_buffer.document.text)
|
|
377
|
+
|
|
378
|
+
FormatCodes.print(start + prompt, default_color=default_color)
|
|
379
|
+
if show_keybindings:
|
|
380
|
+
FormatCodes.print("[dim][[b](CTRL+D)[dim] : end of input][_dim]")
|
|
381
|
+
input_string = _prompt_toolkit.prompt(input_prefix, multiline=True, wrap_lines=True, key_bindings=kb)
|
|
382
|
+
FormatCodes.print("[_]" if reset_ansi else "", end=end[1:] if end.startswith("\n") else end)
|
|
383
|
+
return input_string
|
|
384
|
+
|
|
278
385
|
@staticmethod
|
|
279
386
|
def restricted_input(
|
|
280
387
|
prompt: object = "",
|
|
281
388
|
start="",
|
|
282
389
|
end="\n",
|
|
283
|
-
default_color: hexa | rgba =
|
|
390
|
+
default_color: hexa | rgba = COLOR.cyan,
|
|
284
391
|
allowed_chars: str = CHARS.all,
|
|
285
392
|
min_len: int = None,
|
|
286
393
|
max_len: int = None,
|
|
287
394
|
mask_char: str = None,
|
|
288
395
|
reset_ansi: bool = True,
|
|
289
|
-
) -> str
|
|
396
|
+
) -> Optional[str]:
|
|
290
397
|
"""Acts like a standard Python `input()` with the advantage, that you can specify:
|
|
291
398
|
- what text characters the user is allowed to type and
|
|
292
399
|
- the minimum and/or maximum length of the users input
|
|
293
400
|
- optional mask character (hide user input, e.g. for passwords)
|
|
294
401
|
- reset the ANSI formatting codes after the user continues\n
|
|
295
|
-
|
|
402
|
+
---------------------------------------------------------------------------------------
|
|
296
403
|
The input can be formatted with special formatting codes. For more detailed
|
|
297
|
-
information about formatting codes, see the `xx_format_codes`
|
|
404
|
+
information about formatting codes, see the `xx_format_codes` module documentation."""
|
|
298
405
|
FormatCodes.print(start + prompt, default_color=default_color, end="")
|
|
299
406
|
result = ""
|
|
300
407
|
select_all = False
|
|
@@ -303,22 +410,17 @@ class Console:
|
|
|
303
410
|
|
|
304
411
|
def update_display(console_width: int) -> None:
|
|
305
412
|
nonlocal select_all, last_line_count, last_console_width
|
|
306
|
-
lines = String.split_count(
|
|
307
|
-
str(prompt) + (mask_char * len(result) if mask_char else result),
|
|
308
|
-
console_width,
|
|
309
|
-
)
|
|
413
|
+
lines = String.split_count(str(prompt) + (mask_char * len(result) if mask_char else result), console_width)
|
|
310
414
|
line_count = len(lines)
|
|
311
415
|
if (line_count > 1 or line_count < last_line_count) and not last_line_count == 1:
|
|
312
416
|
if last_console_width > console_width:
|
|
313
417
|
line_count *= 2
|
|
314
|
-
for _ in range(
|
|
315
|
-
|
|
316
|
-
if line_count < last_line_count and not line_count > last_line_count
|
|
317
|
-
else (line_count - 2 if line_count > last_line_count else line_count - 1)
|
|
318
|
-
):
|
|
418
|
+
for _ in range(line_count if line_count < last_line_count and not line_count > last_line_count else (
|
|
419
|
+
line_count - 2 if line_count > last_line_count else line_count - 1)):
|
|
319
420
|
_sys.stdout.write("\033[2K\r\033[A")
|
|
320
421
|
prompt_len = len(str(prompt)) if prompt else 0
|
|
321
|
-
prompt_str
|
|
422
|
+
prompt_str = lines[0][:prompt_len]
|
|
423
|
+
input_str = (
|
|
322
424
|
lines[0][prompt_len:] if len(lines) == 1 else "\n".join([lines[0][prompt_len:]] + lines[1:])
|
|
323
425
|
) # SEPARATE THE PROMPT AND THE INPUT
|
|
324
426
|
_sys.stdout.write(
|
|
@@ -338,7 +440,7 @@ class Console:
|
|
|
338
440
|
result, select_all = "", False
|
|
339
441
|
elif result and event.name == "backspace":
|
|
340
442
|
result = result[:-1]
|
|
341
|
-
update_display(Console.w
|
|
443
|
+
update_display(Console.w)
|
|
342
444
|
|
|
343
445
|
def handle_paste():
|
|
344
446
|
nonlocal result, select_all
|
|
@@ -347,25 +449,18 @@ class Console:
|
|
|
347
449
|
filtered_text = "".join(char for char in _pyperclip.paste() if allowed_chars == CHARS.all or char in allowed_chars)
|
|
348
450
|
if max_len is None or len(result) + len(filtered_text) <= max_len:
|
|
349
451
|
result += filtered_text
|
|
350
|
-
update_display(Console.w
|
|
452
|
+
update_display(Console.w)
|
|
351
453
|
|
|
352
454
|
def handle_select_all():
|
|
353
455
|
nonlocal select_all
|
|
354
456
|
select_all = True
|
|
355
|
-
update_display(Console.w
|
|
356
|
-
|
|
357
|
-
def handle_copy():
|
|
358
|
-
nonlocal select_all
|
|
359
|
-
with suppress(KeyboardInterrupt):
|
|
360
|
-
select_all = False
|
|
361
|
-
update_display(Console.w())
|
|
362
|
-
_pyperclip.copy(result)
|
|
457
|
+
update_display(Console.w)
|
|
363
458
|
|
|
364
459
|
def handle_character_input():
|
|
365
460
|
nonlocal result
|
|
366
461
|
if (allowed_chars == CHARS.all or event.name in allowed_chars) and (max_len is None or len(result) < max_len):
|
|
367
462
|
result += event.name
|
|
368
|
-
update_display(Console.w
|
|
463
|
+
update_display(Console.w)
|
|
369
464
|
|
|
370
465
|
while True:
|
|
371
466
|
event = _keyboard.read_event()
|
|
@@ -378,8 +473,8 @@ class Console:
|
|
|
378
473
|
handle_paste()
|
|
379
474
|
elif event.name == "a" and _keyboard.is_pressed("ctrl"):
|
|
380
475
|
handle_select_all()
|
|
381
|
-
elif event.name == "c" and _keyboard.is_pressed("ctrl")
|
|
382
|
-
|
|
476
|
+
elif event.name == "c" and _keyboard.is_pressed("ctrl"):
|
|
477
|
+
raise KeyboardInterrupt
|
|
383
478
|
elif event.name == "esc":
|
|
384
479
|
return None
|
|
385
480
|
elif event.name == "space":
|
|
@@ -388,19 +483,19 @@ class Console:
|
|
|
388
483
|
handle_character_input()
|
|
389
484
|
else:
|
|
390
485
|
select_all = False
|
|
391
|
-
update_display(Console.w
|
|
486
|
+
update_display(Console.w)
|
|
392
487
|
|
|
393
488
|
@staticmethod
|
|
394
489
|
def pwd_input(
|
|
395
490
|
prompt: object = "Password: ",
|
|
396
491
|
start="",
|
|
397
492
|
end="\n",
|
|
398
|
-
default_color: hexa | rgba =
|
|
493
|
+
default_color: hexa | rgba = COLOR.cyan,
|
|
399
494
|
allowed_chars: str = CHARS.standard_ascii,
|
|
400
495
|
min_len: int = None,
|
|
401
496
|
max_len: int = None,
|
|
402
|
-
|
|
497
|
+
reset_ansi: bool = True,
|
|
403
498
|
) -> str:
|
|
404
499
|
"""Password input (preset for `Console.restricted_input()`)
|
|
405
500
|
that always masks the entered characters with asterisks."""
|
|
406
|
-
return Console.restricted_input(prompt, start, end, default_color, allowed_chars, min_len, max_len, "*",
|
|
501
|
+
return Console.restricted_input(prompt, start, end, default_color, allowed_chars, min_len, max_len, "*", reset_ansi)
|