xulbux 1.5.5__py3-none-any.whl → 1.5.8__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/xx_console.py ADDED
@@ -0,0 +1,381 @@
1
+ """
2
+ Functions for logging and other small actions within the console:
3
+ - `Console.get_args()`
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
+ ----------------------------------------------------------------------------------------------------------
19
+ You can also use special formatting codes directly inside the log message to change their appearance.<br>
20
+ For more detailed information about formatting codes, see the the `xx_format_codes` description.
21
+ """
22
+
23
+ from ._consts_ import DEFAULT, CHARS
24
+ from .xx_format_codes import FormatCodes
25
+ from .xx_string import String
26
+ from .xx_color import *
27
+
28
+ from contextlib import suppress
29
+ import pyperclip as _pyperclip
30
+ import keyboard as _keyboard
31
+ import getpass as _getpass
32
+ import ctypes as _ctypes
33
+ import shutil as _shutil
34
+ import mouse as _mouse
35
+ import sys as _sys
36
+ import os as _os
37
+
38
+
39
+ class Console:
40
+
41
+ @staticmethod
42
+ def get_args(find_args: dict) -> dict[str, dict[str, any]]:
43
+ args = _sys.argv[1:]
44
+ results = {}
45
+ for arg_key, arg_group in find_args.items():
46
+ value = None
47
+ exists = False
48
+ for arg in arg_group:
49
+ if arg in args:
50
+ exists = True
51
+ arg_index = args.index(arg)
52
+ if arg_index + 1 < len(args) and not args[arg_index + 1].startswith("-"):
53
+ value = String.to_type(args[arg_index + 1])
54
+ break
55
+ results[arg_key] = {"exists": exists, "value": value}
56
+ return results
57
+
58
+ def w() -> int:
59
+ return getattr(_shutil.get_terminal_size(), "columns", 80)
60
+
61
+ def h() -> int:
62
+ return getattr(_shutil.get_terminal_size(), "lines", 24)
63
+
64
+ def wh() -> tuple[int, int]:
65
+ return Console.w(), Console.h()
66
+
67
+ def user() -> str:
68
+ return _os.getenv("USER") or _os.getenv("USERNAME") or _getpass.getuser()
69
+
70
+ def is_admin() -> bool:
71
+ try:
72
+ if _os.name == "nt":
73
+ return _ctypes.windll.shell32.IsUserAnAdmin() != 0
74
+ elif _os.name == "posix":
75
+ return _os.geteuid() == 0
76
+ else:
77
+ return False
78
+ except Exception:
79
+ return False
80
+
81
+ @staticmethod
82
+ def pause_exit(
83
+ pause: bool = False,
84
+ exit: bool = False,
85
+ prompt: object = "",
86
+ exit_code: int = 0,
87
+ reset_ansi: bool = False,
88
+ ) -> None:
89
+ """Will print the `last_prompt` and then pause the program if `pause` is set<br>
90
+ to `True` and after the pause, exit the program if `exit` is set to `True`.
91
+ """
92
+ print(prompt, end="", flush=True)
93
+ if reset_ansi:
94
+ FormatCodes.print("[_]", end="")
95
+ if pause:
96
+ _keyboard.read_event()
97
+ if exit:
98
+ _sys.exit(exit_code)
99
+
100
+ def cls() -> None:
101
+ """Will clear the console in addition to completely resetting the ANSI formats."""
102
+ if _shutil.which("cls"):
103
+ _os.system("cls")
104
+ elif _shutil.which("clear"):
105
+ _os.system("clear")
106
+ print("\033[0m", end="", flush=True)
107
+
108
+ @staticmethod
109
+ def log(
110
+ title: str,
111
+ prompt: object = "",
112
+ start: str = "",
113
+ end: str = "\n",
114
+ title_bg_color: hexa | rgba = None,
115
+ default_color: hexa | rgba = None,
116
+ ) -> None:
117
+ """Will print a formatted log message:<br>
118
+ `title` -⠀the title of the log message (e.g. `DEBUG`, `WARN`, `FAIL`, etc.)<br>
119
+ `prompt` -⠀the log message<br>
120
+ `start` -⠀something to print before the log is printed<br>
121
+ `end` -⠀something to print after the log is printed (e.g. `\\n\\n`)<br>
122
+ `title_bg_color` -⠀the background color of the `title`<br>
123
+ `default_color` -⠀the default text color of the `prompt`\n
124
+ --------------------------------------------------------------------------------
125
+ The log message supports special formatting codes. For more detailed<br>
126
+ information about formatting codes, see `xx_format_codes` class description.
127
+ """
128
+ title_color = "_color" if not title_bg_color else Color.text_color_for_on_bg(title_bg_color)
129
+ if title:
130
+ FormatCodes.print(
131
+ f'{start} [bold][{title_color}]{f"[BG:{title_bg_color}]" if title_bg_color else ""} {title.upper()}: [_]\t{f"[{default_color}]" if default_color else ""}{str(prompt)}[_]',
132
+ default_color=default_color,
133
+ end=end,
134
+ )
135
+ else:
136
+ FormatCodes.print(
137
+ f'{start} {f"[{default_color}]" if default_color else ""}{str(prompt)}[_]',
138
+ default_color=default_color,
139
+ end=end,
140
+ )
141
+
142
+ @staticmethod
143
+ def debug(
144
+ prompt: object = "Point in program reached.",
145
+ active: bool = True,
146
+ start: str = "\n",
147
+ end: str = "\n\n",
148
+ title_bg_color: hexa | rgba = DEFAULT.color["yellow"],
149
+ default_color: hexa | rgba = DEFAULT.text_color,
150
+ pause: bool = False,
151
+ exit: bool = False,
152
+ ) -> None:
153
+ """A preset for `log()`: `DEBUG` log message with the options to pause<br>
154
+ at the message and exit the program after the message was printed."""
155
+ if active:
156
+ Console.log("DEBUG", prompt, start, end, title_bg_color, default_color)
157
+ Console.pause_exit(pause, exit)
158
+
159
+ @staticmethod
160
+ def info(
161
+ prompt: object = "Program running.",
162
+ start: str = "\n",
163
+ end: str = "\n\n",
164
+ title_bg_color: hexa | rgba = DEFAULT.color["blue"],
165
+ default_color: hexa | rgba = DEFAULT.text_color,
166
+ pause: bool = False,
167
+ exit: bool = False,
168
+ ) -> None:
169
+ """A preset for `log()`: `INFO` log message with the options to pause<br>
170
+ at the message and exit the program after the message was printed."""
171
+ Console.log("INFO", prompt, start, end, title_bg_color, default_color)
172
+ Console.pause_exit(pause, exit)
173
+
174
+ @staticmethod
175
+ def done(
176
+ prompt: object = "Program finished.",
177
+ start: str = "\n",
178
+ end: str = "\n\n",
179
+ title_bg_color: hexa | rgba = DEFAULT.color["teal"],
180
+ default_color: hexa | rgba = DEFAULT.text_color,
181
+ pause: bool = False,
182
+ exit: bool = False,
183
+ ) -> None:
184
+ """A preset for `log()`: `DONE` log message with the options to pause<br>
185
+ at the message and exit the program after the message was printed."""
186
+ Console.log("DONE", prompt, start, end, title_bg_color, default_color)
187
+ Console.pause_exit(pause, exit)
188
+
189
+ @staticmethod
190
+ def warn(
191
+ prompt: object = "Important message.",
192
+ start: str = "\n",
193
+ end: str = "\n\n",
194
+ title_bg_color: hexa | rgba = DEFAULT.color["orange"],
195
+ default_color: hexa | rgba = DEFAULT.text_color,
196
+ pause: bool = False,
197
+ exit: bool = False,
198
+ ) -> None:
199
+ """A preset for `log()`: `WARN` log message with the options to pause<br>
200
+ at the message and exit the program after the message was printed."""
201
+ Console.log("WARN", prompt, start, end, title_bg_color, default_color)
202
+ Console.pause_exit(pause, exit)
203
+
204
+ @staticmethod
205
+ def fail(
206
+ prompt: object = "Program error.",
207
+ start: str = "\n",
208
+ end: str = "\n\n",
209
+ title_bg_color: hexa | rgba = DEFAULT.color["red"],
210
+ default_color: hexa | rgba = DEFAULT.text_color,
211
+ pause: bool = False,
212
+ exit: bool = True,
213
+ reset_ansi=True,
214
+ ) -> None:
215
+ """A preset for `log()`: `FAIL` log message with the options to pause<br>
216
+ at the message and exit the program after the message was printed."""
217
+ Console.log("FAIL", prompt, start, end, title_bg_color, default_color)
218
+ Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
219
+
220
+ @staticmethod
221
+ def exit(
222
+ prompt: object = "Program ended.",
223
+ start: str = "\n",
224
+ end: str = "\n\n",
225
+ title_bg_color: hexa | rgba = DEFAULT.color["magenta"],
226
+ default_color: hexa | rgba = DEFAULT.text_color,
227
+ pause: bool = False,
228
+ exit: bool = True,
229
+ reset_ansi=True,
230
+ ) -> None:
231
+ """A preset for `log()`: `EXIT` log message with the options to pause<br>
232
+ at the message and exit the program after the message was printed."""
233
+ Console.log("EXIT", prompt, start, end, title_bg_color, default_color)
234
+ Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
235
+
236
+ @staticmethod
237
+ def confirm(
238
+ prompt: object = "Do you want to continue?",
239
+ start="\n",
240
+ end="\n",
241
+ default_color: hexa | rgba = DEFAULT.color["cyan"],
242
+ default_is_yes: bool = True,
243
+ ) -> bool:
244
+ """Ask a yes/no question.\n
245
+ -----------------------------------------------------------------------------------
246
+ The question can be formatted with special formatting codes. For more detailed<br>
247
+ information about formatting codes, see the `xx_format_codes` description.
248
+ """
249
+ confirmed = input(
250
+ FormatCodes.to_ansi(
251
+ f'{start} {str(prompt)} [_|dim](({"Y" if default_is_yes else "y"}/{"n" if default_is_yes else "N"}): )',
252
+ default_color,
253
+ )
254
+ ).strip().lower() in (("", "y", "yes") if default_is_yes else ("y", "yes"))
255
+ if end:
256
+ Console.log("", end, end="")
257
+ return confirmed
258
+
259
+ @staticmethod
260
+ def restricted_input(
261
+ prompt: object = "",
262
+ allowed_chars: str = CHARS.all,
263
+ min_len: int = None,
264
+ max_len: int = None,
265
+ mask_char: str = None,
266
+ reset_ansi: bool = True,
267
+ ) -> str | None:
268
+ """Acts like a standard Python `input()` with the advantage, that you can specify:
269
+ - what text characters the user is allowed to type and
270
+ - the minimum and/or maximum length of the users input
271
+ - optional mask character (hide user input, e.g. for passwords)
272
+ - reset the ANSI formatting codes after the user continues\n
273
+ -----------------------------------------------------------------------------------
274
+ The input can be formatted with special formatting codes. For more detailed<br>
275
+ information about formatting codes, see the `xx_format_codes` description.
276
+ """
277
+ FormatCodes.print(prompt, end="", flush=True)
278
+ result = ""
279
+ select_all = False
280
+ last_line_count = 1
281
+ last_console_width = 0
282
+
283
+ def update_display(console_width: int) -> None:
284
+ nonlocal select_all, last_line_count, last_console_width
285
+ lines = String.split_count(
286
+ str(prompt) + (mask_char * len(result) if mask_char else result),
287
+ console_width,
288
+ )
289
+ line_count = len(lines)
290
+ if (line_count > 1 or line_count < last_line_count) and not last_line_count == 1:
291
+ if last_console_width > console_width:
292
+ line_count *= 2
293
+ for _ in range(
294
+ line_count
295
+ if line_count < last_line_count and not line_count > last_line_count
296
+ else (line_count - 2 if line_count > last_line_count else line_count - 1)
297
+ ):
298
+ _sys.stdout.write("\033[2K\r\033[A")
299
+ prompt_len = len(str(prompt)) if prompt else 0
300
+ prompt_str, input_str = lines[0][:prompt_len], (
301
+ lines[0][prompt_len:] if len(lines) == 1 else "\n".join([lines[0][prompt_len:]] + lines[1:])
302
+ ) # SEPARATE THE PROMPT AND THE INPUT
303
+ _sys.stdout.write(
304
+ "\033[2K\r" + FormatCodes.to_ansi(prompt_str) + ("\033[7m" if select_all else "") + input_str + "\033[27m"
305
+ )
306
+ last_line_count, last_console_width = line_count, console_width
307
+
308
+ def handle_enter():
309
+ if min_len is not None and len(result) < min_len:
310
+ return False
311
+ FormatCodes.print("[_]" if reset_ansi else "", flush=True)
312
+ return True
313
+
314
+ def handle_backspace_delete():
315
+ nonlocal result, select_all
316
+ if select_all:
317
+ result, select_all = "", False
318
+ elif result and event.name == "backspace":
319
+ result = result[:-1]
320
+ update_display(Console.w())
321
+
322
+ def handle_paste():
323
+ nonlocal result, select_all
324
+ if select_all:
325
+ result, select_all = "", False
326
+ filtered_text = "".join(char for char in _pyperclip.paste() if allowed_chars == CHARS.all or char in allowed_chars)
327
+ if max_len is None or len(result) + len(filtered_text) <= max_len:
328
+ result += filtered_text
329
+ update_display(Console.w())
330
+
331
+ def handle_select_all():
332
+ nonlocal select_all
333
+ select_all = True
334
+ update_display(Console.w())
335
+
336
+ def handle_copy():
337
+ nonlocal select_all
338
+ with suppress(KeyboardInterrupt):
339
+ select_all = False
340
+ update_display(Console.w())
341
+ _pyperclip.copy(result)
342
+
343
+ def handle_character_input():
344
+ nonlocal result
345
+ if (allowed_chars == CHARS.all or event.name in allowed_chars) and (max_len is None or len(result) < max_len):
346
+ result += event.name
347
+ update_display(Console.w())
348
+
349
+ while True:
350
+ event = _keyboard.read_event()
351
+ if event.event_type == "down":
352
+ if event.name == "enter" and handle_enter():
353
+ return result.rstrip("\n")
354
+ elif event.name in ("backspace", "delete", "entf"):
355
+ handle_backspace_delete()
356
+ elif (event.name == "v" and _keyboard.is_pressed("ctrl")) or _mouse.is_pressed("right"):
357
+ handle_paste()
358
+ elif event.name == "a" and _keyboard.is_pressed("ctrl"):
359
+ handle_select_all()
360
+ elif event.name == "c" and _keyboard.is_pressed("ctrl") and select_all:
361
+ handle_copy()
362
+ elif event.name == "esc":
363
+ return None
364
+ elif event.name == "space":
365
+ handle_character_input()
366
+ elif len(event.name) == 1:
367
+ handle_character_input()
368
+ else:
369
+ select_all = False
370
+ update_display(Console.w())
371
+
372
+ @staticmethod
373
+ def pwd_input(
374
+ prompt: object = "Password: ",
375
+ allowed_chars: str = CHARS.standard_ascii,
376
+ min_len: int = None,
377
+ max_len: int = None,
378
+ _reset_ansi: bool = True,
379
+ ) -> str:
380
+ """Password input that masks the entered characters with asterisks."""
381
+ return Console.restricted_input(prompt, allowed_chars, min_len, max_len, "*", _reset_ansi)