xulbux 1.6.6__py3-none-any.whl → 1.6.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/__init__.py +1 -1
- xulbux/_consts_.py +31 -8
- xulbux/xx_code.py +2 -3
- xulbux/xx_console.py +175 -51
- xulbux/xx_data.py +13 -16
- xulbux/xx_env_path.py +5 -17
- xulbux/xx_file.py +12 -14
- xulbux/xx_format_codes.py +43 -17
- xulbux/xx_path.py +30 -20
- xulbux/xx_regex.py +1 -5
- xulbux/xx_system.py +11 -6
- {xulbux-1.6.6.dist-info → xulbux-1.6.8.dist-info}/METADATA +96 -19
- xulbux-1.6.8.dist-info/RECORD +21 -0
- {xulbux-1.6.6.dist-info → xulbux-1.6.8.dist-info}/WHEEL +1 -1
- xulbux-1.6.6.dist-info/RECORD +0 -21
- {xulbux-1.6.6.dist-info → xulbux-1.6.8.dist-info}/LICENSE +0 -0
- {xulbux-1.6.6.dist-info → xulbux-1.6.8.dist-info}/entry_points.txt +0 -0
- {xulbux-1.6.6.dist-info → xulbux-1.6.8.dist-info}/top_level.txt +0 -0
xulbux/__init__.py
CHANGED
xulbux/_consts_.py
CHANGED
|
@@ -3,13 +3,16 @@ from typing import TypeAlias
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
FormattableString: TypeAlias = str
|
|
6
|
+
"""A `str` object that is made to be formatted with the `.format()` method."""
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@dataclass
|
|
9
10
|
class COLOR:
|
|
10
|
-
"""Color presets used in the
|
|
11
|
+
"""Color presets used in the `xulbux` library."""
|
|
11
12
|
|
|
12
13
|
text = "#A5D6FF"
|
|
14
|
+
"""The default text color used in the `xulbux` library."""
|
|
15
|
+
|
|
13
16
|
white = "#F1F2FF"
|
|
14
17
|
lightgray = "#B6B7C0"
|
|
15
18
|
gray = "#7B7C8D"
|
|
@@ -21,6 +24,7 @@ class COLOR:
|
|
|
21
24
|
tangerine = "#FF9962"
|
|
22
25
|
gold = "#FFAF60"
|
|
23
26
|
yellow = "#FFD260"
|
|
27
|
+
lime = "#C9F16E"
|
|
24
28
|
green = "#7EE787"
|
|
25
29
|
teal = "#50EAAF"
|
|
26
30
|
cyan = "#3EDEE6"
|
|
@@ -40,50 +44,67 @@ class _AllTextCharacters:
|
|
|
40
44
|
|
|
41
45
|
@dataclass
|
|
42
46
|
class CHARS:
|
|
43
|
-
"""
|
|
47
|
+
"""Text character sets."""
|
|
44
48
|
|
|
45
|
-
# CODE TO SIGNAL, ALL CHARACTERS ARE ALLOWED
|
|
46
49
|
all = _AllTextCharacters
|
|
50
|
+
"""Code to signal that all characters are allowed."""
|
|
47
51
|
|
|
48
|
-
# DIGIT SETS
|
|
49
52
|
digits: str = "0123456789"
|
|
53
|
+
"""Digits: `0`-`9`"""
|
|
50
54
|
float_digits: str = digits + "."
|
|
55
|
+
"""Digits: `0`-`9` with decimal point `.`"""
|
|
51
56
|
hex_digits: str = digits + "#abcdefABCDEF"
|
|
57
|
+
"""Digits: `0`-`9` Letters: `a`-`f` `A`-`F` and a hashtag `#`"""
|
|
52
58
|
|
|
53
|
-
# LETTER CATEGORIES
|
|
54
59
|
lowercase: str = "abcdefghijklmnopqrstuvwxyz"
|
|
60
|
+
"""Lowercase letters `a`-`z`"""
|
|
55
61
|
lowercase_extended: str = lowercase + "äëïöüÿàèìòùáéíóúýâêîôûãñõåæç"
|
|
62
|
+
"""Lowercase letters `a`-`z` with all lowercase diacritic letters."""
|
|
56
63
|
uppercase: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
64
|
+
"""Uppercase letters `A`-`Z`"""
|
|
57
65
|
uppercase_extended: str = uppercase + "ÄËÏÖÜÀÈÌÒÙÁÉÍÓÚÝÂÊÎÔÛÃÑÕÅÆÇß"
|
|
66
|
+
"""Uppercase letters `A`-`Z` with all uppercase diacritic letters."""
|
|
58
67
|
|
|
59
|
-
# COMBINED LETTER SETS
|
|
60
68
|
letters: str = lowercase + uppercase
|
|
69
|
+
"""Lowercase and uppercase letters `a`-`z` and `A`-`Z`"""
|
|
61
70
|
letters_extended: str = lowercase_extended + uppercase_extended
|
|
71
|
+
"""Lowercase and uppercase letters `a`-`z` `A`-`Z` and all diacritic letters."""
|
|
62
72
|
|
|
63
|
-
# ASCII sets
|
|
64
73
|
special_ascii: str = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
|
74
|
+
"""All ASCII special characters."""
|
|
65
75
|
special_ascii_extended: str = special_ascii + "ø£Ø×ƒªº¿®¬½¼¡«»░▒▓│┤©╣║╗╝¢¥┐└┴┬├─┼╚╔╩╦╠═╬¤ðÐı┘┌█▄¦▀µþÞ¯´≡±‗¾¶§÷¸°¨·¹³²■ "
|
|
76
|
+
"""All ASCII special characters with the extended ASCII special characters."""
|
|
66
77
|
standard_ascii: str = special_ascii + digits + letters
|
|
78
|
+
"""All standard ASCII characters."""
|
|
67
79
|
full_ascii: str = special_ascii_extended + digits + letters_extended
|
|
80
|
+
"""All characters in the ASCII table."""
|
|
68
81
|
|
|
69
82
|
|
|
70
83
|
class ANSI:
|
|
71
84
|
"""Constants and class-methods for use of ANSI escape codes."""
|
|
72
85
|
|
|
73
86
|
escaped_char: str = "\\x1b"
|
|
87
|
+
"""The printable ANSI escape character."""
|
|
74
88
|
CHAR = char = "\x1b"
|
|
89
|
+
"""The ANSI escape character."""
|
|
75
90
|
START = start = "["
|
|
91
|
+
"""The start of an ANSI escape sequence."""
|
|
76
92
|
SEP = sep = ";"
|
|
93
|
+
"""The separator between ANSI escape sequence parts."""
|
|
77
94
|
END = end = "m"
|
|
95
|
+
"""The end of an ANSI escape sequence."""
|
|
78
96
|
default_color_modifiers: dict[str, str] = {"lighten": "+l", "darken": "-d"}
|
|
97
|
+
"""Characters to modify the lightness of the default color with."""
|
|
79
98
|
|
|
80
99
|
@classmethod
|
|
81
|
-
def seq(cls, parts: int = 1) ->
|
|
100
|
+
def seq(cls, parts: int = 1) -> FormattableString:
|
|
82
101
|
"""Generate an ANSI sequence with `parts` amount of placeholders."""
|
|
83
102
|
return cls.CHAR + cls.START + cls.SEP.join(["{}" for _ in range(parts)]) + cls.END
|
|
84
103
|
|
|
85
104
|
seq_color: FormattableString = CHAR + START + "38" + SEP + "2" + SEP + "{}" + SEP + "{}" + SEP + "{}" + END
|
|
105
|
+
"""The ANSI escape sequence for setting the text RGB color."""
|
|
86
106
|
seq_bg_color: FormattableString = CHAR + START + "48" + SEP + "2" + SEP + "{}" + SEP + "{}" + SEP + "{}" + END
|
|
107
|
+
"""The ANSI escape sequence for setting the background RGB color."""
|
|
87
108
|
|
|
88
109
|
color_map: list[str] = [
|
|
89
110
|
########### DEFAULT CONSOLE COLOR NAMES ############
|
|
@@ -96,6 +117,7 @@ class ANSI:
|
|
|
96
117
|
"cyan",
|
|
97
118
|
"white",
|
|
98
119
|
]
|
|
120
|
+
"""The console default color names."""
|
|
99
121
|
|
|
100
122
|
codes_map: dict[str | tuple[str, ...], int] = {
|
|
101
123
|
################# SPECIFIC RESETS ##################
|
|
@@ -156,3 +178,4 @@ class ANSI:
|
|
|
156
178
|
"bg:br:cyan": 106,
|
|
157
179
|
"bg:br:white": 107,
|
|
158
180
|
}
|
|
181
|
+
"""All custom format keys and their corresponding ANSI format number codes."""
|
xulbux/xx_code.py
CHANGED
|
@@ -21,14 +21,13 @@ class Code:
|
|
|
21
21
|
non_zero_indents = [i for i in indents if i > 0]
|
|
22
22
|
return min(non_zero_indents) if non_zero_indents else 0
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
def change_tab_size(code: str, new_tab_size: int, remove_empty_lines: bool = False) -> str:
|
|
24
|
+
def change_tab_size(self, code: str, new_tab_size: int, remove_empty_lines: bool = False) -> str:
|
|
26
25
|
"""Replaces all tabs with `new_tab_size` spaces.\n
|
|
27
26
|
----------------------------------------------------------------------------------
|
|
28
27
|
If `remove_empty_lines` is `True`, empty lines will be removed in the process."""
|
|
29
28
|
code_lines = String.get_lines(code, remove_empty_lines=True)
|
|
30
29
|
lines = code_lines if remove_empty_lines else String.get_lines(code)
|
|
31
|
-
tab_spaces =
|
|
30
|
+
tab_spaces = self.get_tab_spaces(code)
|
|
32
31
|
if (tab_spaces == new_tab_size) or tab_spaces == 0:
|
|
33
32
|
if remove_empty_lines:
|
|
34
33
|
return "\n".join(code_lines)
|
xulbux/xx_console.py
CHANGED
|
@@ -39,32 +39,122 @@ class _ConsoleSize:
|
|
|
39
39
|
class _ConsoleUser:
|
|
40
40
|
def __get__(self, obj, owner=None):
|
|
41
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
|
+
def __bool__(self):
|
|
50
|
+
return self.exists
|
|
51
|
+
|
|
52
|
+
class Args:
|
|
53
|
+
"""Stores found command arguments under their aliases with their results."""
|
|
54
|
+
def __init__(self, **kwargs):
|
|
55
|
+
for key, value in kwargs.items():
|
|
56
|
+
if not key.isidentifier():
|
|
57
|
+
raise TypeError(f"Argument alias '{key}' is invalid. It must be a valid Python variable name.")
|
|
58
|
+
setattr(self, key, ArgResult(**value))
|
|
59
|
+
def __len__(self):
|
|
60
|
+
return len(vars(self))
|
|
61
|
+
def __contains__(self, key):
|
|
62
|
+
return hasattr(self, key)
|
|
63
|
+
def __getitem__(self, key):
|
|
64
|
+
if isinstance(key, int):
|
|
65
|
+
return list(self.__iter__())[key]
|
|
66
|
+
return getattr(self, key)
|
|
67
|
+
def __iter__(self):
|
|
68
|
+
for key, value in vars(self).items():
|
|
69
|
+
yield (key, {"exists": value.exists, "value": value.value})
|
|
70
|
+
def dict(self) -> dict[str, dict[str, any]]:
|
|
71
|
+
"""Returns the arguments as a dictionary."""
|
|
72
|
+
return {k: {"exists": v.exists, "value": v.value} for k, v in vars(self).items()}
|
|
73
|
+
def keys(self):
|
|
74
|
+
"""Returns the argument aliases as `dict_keys([...])`."""
|
|
75
|
+
return vars(self).keys()
|
|
76
|
+
def values(self):
|
|
77
|
+
"""Returns the argument results as `dict_values([...])`."""
|
|
78
|
+
return vars(self).values()
|
|
79
|
+
def items(self):
|
|
80
|
+
"""Returns the argument aliases and results as `dict_items([...])`."""
|
|
81
|
+
return vars(self).items()
|
|
42
82
|
# YAPF: enable
|
|
43
83
|
|
|
44
84
|
|
|
45
85
|
class Console:
|
|
46
86
|
|
|
47
87
|
w: int = _ConsoleWidth()
|
|
88
|
+
"""The width of the console in characters."""
|
|
48
89
|
h: int = _ConsoleHeight()
|
|
90
|
+
"""The height of the console in lines."""
|
|
49
91
|
wh: tuple[int, int] = _ConsoleSize()
|
|
92
|
+
"""A tuple with the width and height of
|
|
93
|
+
the console in characters and lines."""
|
|
50
94
|
usr: str = _ConsoleUser()
|
|
95
|
+
"""The name of the current user."""
|
|
51
96
|
|
|
52
97
|
@staticmethod
|
|
53
|
-
def get_args(find_args: dict
|
|
98
|
+
def get_args(find_args: dict[str, list[str] | tuple[str, ...]], allow_spaces: bool = False) -> Args:
|
|
99
|
+
"""Will search for the specified arguments in the command line
|
|
100
|
+
arguments and return the results as a special `Args` object.\n
|
|
101
|
+
----------------------------------------------------------------
|
|
102
|
+
The `find_args` dictionary should have the following structure:
|
|
103
|
+
```python
|
|
104
|
+
find_args={
|
|
105
|
+
"arg1_alias": ["-a1", "--arg1", "--argument-1"],
|
|
106
|
+
"arg2_alias": ("-a2", "--arg2", "--argument-2"),
|
|
107
|
+
...
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
And if the script is called via the command line:\n
|
|
111
|
+
`python script.py -a1 "argument value" --arg2`\n
|
|
112
|
+
...it would return the following `Args` object:
|
|
113
|
+
```python
|
|
114
|
+
Args(
|
|
115
|
+
arg1_alias=ArgResult(exists=True, value="argument value"),
|
|
116
|
+
arg2_alias=ArgResult(exists=True, value=None),
|
|
117
|
+
...
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
...which can be accessed like this:\n
|
|
121
|
+
- `Args.<arg_alias>.exists` is `True` if any of the specified
|
|
122
|
+
args were found and `False` if not
|
|
123
|
+
- `Args.<arg_alias>.value` the value from behind the found arg,
|
|
124
|
+
`None` if no value was found\n
|
|
125
|
+
----------------------------------------------------------------
|
|
126
|
+
Normally if `allow_spaces` is false, it will take a space as
|
|
127
|
+
the end of an args value. If it is true, it will take spaces as
|
|
128
|
+
part of the value until the next arg is found.
|
|
129
|
+
(Multiple spaces will become one space in the value.)"""
|
|
54
130
|
args = _sys.argv[1:]
|
|
55
|
-
|
|
131
|
+
args_len = len(args)
|
|
132
|
+
arg_lookup = {}
|
|
56
133
|
for arg_key, arg_group in find_args.items():
|
|
57
|
-
value = None
|
|
58
|
-
exists = False
|
|
59
134
|
for arg in arg_group:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
135
|
+
arg_lookup[arg] = arg_key
|
|
136
|
+
results = {key: {"exists": False, "value": None} for key in find_args}
|
|
137
|
+
i = 0
|
|
138
|
+
while i < args_len:
|
|
139
|
+
arg = args[i]
|
|
140
|
+
arg_key = arg_lookup.get(arg)
|
|
141
|
+
if arg_key:
|
|
142
|
+
results[arg_key]["exists"] = True
|
|
143
|
+
if i + 1 < args_len and not args[i + 1].startswith("-"):
|
|
144
|
+
if not allow_spaces:
|
|
145
|
+
results[arg_key]["value"] = String.to_type(args[i + 1])
|
|
146
|
+
i += 1
|
|
147
|
+
else:
|
|
148
|
+
value_parts = []
|
|
149
|
+
j = i + 1
|
|
150
|
+
while j < args_len and not args[j].startswith("-"):
|
|
151
|
+
value_parts.append(args[j])
|
|
152
|
+
j += 1
|
|
153
|
+
if value_parts:
|
|
154
|
+
results[arg_key]["value"] = String.to_type(" ".join(value_parts))
|
|
155
|
+
i = j - 1
|
|
156
|
+
i += 1
|
|
157
|
+
return Args(**results)
|
|
68
158
|
|
|
69
159
|
@staticmethod
|
|
70
160
|
def pause_exit(
|
|
@@ -111,7 +201,8 @@ class Console:
|
|
|
111
201
|
- `start` -⠀something to print before the log is printed
|
|
112
202
|
- `end` -⠀something to print after the log is printed (e.g. `\\n`)
|
|
113
203
|
- `title_bg_color` -⠀the background color of the `title`
|
|
114
|
-
- `default_color` -⠀the default text color of the `prompt
|
|
204
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
205
|
+
- `_console_tabsize` -⠀the tab size of the console (default is 8)\n
|
|
115
206
|
-----------------------------------------------------------------------------------
|
|
116
207
|
The log message can be formatted with special formatting codes. For more detailed
|
|
117
208
|
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
@@ -119,9 +210,10 @@ class Console:
|
|
|
119
210
|
title_len, tab_len = len(title) + 4, _console_tabsize - ((len(title) + 4) % _console_tabsize)
|
|
120
211
|
title_color = "_color" if not title_bg_color else Color.text_color_for_on_bg(title_bg_color)
|
|
121
212
|
if format_linebreaks:
|
|
122
|
-
|
|
213
|
+
clean_prompt, removals = FormatCodes.remove_formatting(str(prompt), get_removals=True)
|
|
214
|
+
prompt_lst = (String.split_count(l, Console.w - (title_len + tab_len)) for l in str(clean_prompt).splitlines())
|
|
123
215
|
prompt_lst = (item for lst in prompt_lst for item in (lst if isinstance(lst, list) else [lst]))
|
|
124
|
-
prompt = f"\n{' ' * title_len}\t".join(prompt_lst)
|
|
216
|
+
prompt = f"\n{' ' * title_len}\t".join(Console.__add_back_removed_parts(list(prompt_lst), removals))
|
|
125
217
|
else:
|
|
126
218
|
prompt = str(prompt)
|
|
127
219
|
if title == "":
|
|
@@ -138,6 +230,38 @@ class Console:
|
|
|
138
230
|
end=end,
|
|
139
231
|
)
|
|
140
232
|
|
|
233
|
+
@staticmethod
|
|
234
|
+
def __add_back_removed_parts(split_string: list[str], removals: tuple[tuple[int, str], ...]) -> list[str]:
|
|
235
|
+
"""Adds back the removed parts into the split string parts at their original positions."""
|
|
236
|
+
lengths, cumulative_pos = [len(s) for s in split_string], [0]
|
|
237
|
+
for length in lengths:
|
|
238
|
+
cumulative_pos.append(cumulative_pos[-1] + length)
|
|
239
|
+
result, offset_adjusts = split_string.copy(), [0] * len(split_string)
|
|
240
|
+
last_idx, total_length = len(split_string) - 1, cumulative_pos[-1]
|
|
241
|
+
|
|
242
|
+
def find_string_part(pos: int) -> int:
|
|
243
|
+
left, right = 0, len(cumulative_pos) - 1
|
|
244
|
+
while left < right:
|
|
245
|
+
mid = (left + right) // 2
|
|
246
|
+
if cumulative_pos[mid] <= pos < cumulative_pos[mid + 1]:
|
|
247
|
+
return mid
|
|
248
|
+
elif pos < cumulative_pos[mid]:
|
|
249
|
+
right = mid
|
|
250
|
+
else:
|
|
251
|
+
left = mid + 1
|
|
252
|
+
return left
|
|
253
|
+
|
|
254
|
+
for pos, removal in removals:
|
|
255
|
+
if pos >= total_length:
|
|
256
|
+
result[last_idx] = result[last_idx] + removal
|
|
257
|
+
continue
|
|
258
|
+
i = find_string_part(pos)
|
|
259
|
+
adjusted_pos = (pos - cumulative_pos[i]) + offset_adjusts[i]
|
|
260
|
+
parts = [result[i][:adjusted_pos], removal, result[i][adjusted_pos:]]
|
|
261
|
+
result[i] = ''.join(parts)
|
|
262
|
+
offset_adjusts[i] += len(removal)
|
|
263
|
+
return result
|
|
264
|
+
|
|
141
265
|
@staticmethod
|
|
142
266
|
def debug(
|
|
143
267
|
prompt: object = "Point in program reached.",
|
|
@@ -253,7 +377,7 @@ class Console:
|
|
|
253
377
|
- `*values` -⠀the box content (each value is on a new line)
|
|
254
378
|
- `start` -⠀something to print before the log box is printed
|
|
255
379
|
- `end` -⠀something to print after the log box is printed (e.g. `\\n`)
|
|
256
|
-
- `box_bg_color` -⠀the
|
|
380
|
+
- `box_bg_color` -⠀the background color of the box
|
|
257
381
|
- `default_color` -⠀the default text color of the `*values`
|
|
258
382
|
- `w_padding` -⠀the horizontal padding (in chars) to the box content
|
|
259
383
|
- `w_full` -⠀whether to make the box be the full console width or not\n
|
|
@@ -300,6 +424,41 @@ class Console:
|
|
|
300
424
|
Console.log("", end, end="")
|
|
301
425
|
return confirmed
|
|
302
426
|
|
|
427
|
+
@staticmethod
|
|
428
|
+
def multiline_input(
|
|
429
|
+
prompt: object = "",
|
|
430
|
+
start="",
|
|
431
|
+
end="\n",
|
|
432
|
+
default_color: hexa | rgba = COLOR.cyan,
|
|
433
|
+
show_keybindings=True,
|
|
434
|
+
input_prefix=" ⤷ ",
|
|
435
|
+
reset_ansi=True,
|
|
436
|
+
) -> str:
|
|
437
|
+
"""An input where users can input (and paste) text over multiple lines.\n
|
|
438
|
+
-----------------------------------------------------------------------------------
|
|
439
|
+
- `prompt` -⠀the input prompt
|
|
440
|
+
- `start` -⠀something to print before the input
|
|
441
|
+
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
442
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
443
|
+
- `show_keybindings` -⠀whether to show the special keybindings or not
|
|
444
|
+
- `input_prefix` -⠀the prefix of the input line
|
|
445
|
+
- `reset_ansi` -⠀whether to reset the ANSI codes after the input or not
|
|
446
|
+
-----------------------------------------------------------------------------------
|
|
447
|
+
The input prompt can be formatted with special formatting codes. For more detailed
|
|
448
|
+
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
449
|
+
kb = KeyBindings()
|
|
450
|
+
|
|
451
|
+
@kb.add("c-d", eager=True) # CTRL+D
|
|
452
|
+
def _(event):
|
|
453
|
+
event.app.exit(result=event.app.current_buffer.document.text)
|
|
454
|
+
|
|
455
|
+
FormatCodes.print(start + prompt, default_color=default_color)
|
|
456
|
+
if show_keybindings:
|
|
457
|
+
FormatCodes.print("[dim][[b](CTRL+D)[dim] : end of input][_dim]")
|
|
458
|
+
input_string = _prompt_toolkit.prompt(input_prefix, multiline=True, wrap_lines=True, key_bindings=kb)
|
|
459
|
+
FormatCodes.print("[_]" if reset_ansi else "", end=end[1:] if end.startswith("\n") else end)
|
|
460
|
+
return input_string
|
|
461
|
+
|
|
303
462
|
@staticmethod
|
|
304
463
|
def restricted_input(
|
|
305
464
|
prompt: object = "",
|
|
@@ -417,38 +576,3 @@ class Console:
|
|
|
417
576
|
"""Password input (preset for `Console.restricted_input()`)
|
|
418
577
|
that always masks the entered characters with asterisks."""
|
|
419
578
|
return Console.restricted_input(prompt, start, end, default_color, allowed_chars, min_len, max_len, "*", reset_ansi)
|
|
420
|
-
|
|
421
|
-
@staticmethod
|
|
422
|
-
def multiline_input(
|
|
423
|
-
prompt: object = "",
|
|
424
|
-
start="",
|
|
425
|
-
end="\n",
|
|
426
|
-
default_color: hexa | rgba = COLOR.cyan,
|
|
427
|
-
show_keybindings=True,
|
|
428
|
-
input_prefix=" ⤷ ",
|
|
429
|
-
reset_ansi=True,
|
|
430
|
-
) -> str:
|
|
431
|
-
"""An input where users can input (and paste) text over multiple lines.\n
|
|
432
|
-
-----------------------------------------------------------------------------------
|
|
433
|
-
- `prompt` -⠀the input prompt
|
|
434
|
-
- `start` -⠀something to print before the input
|
|
435
|
-
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
436
|
-
- `default_color` -⠀the default text color of the `prompt`
|
|
437
|
-
- `show_keybindings` -⠀whether to show the special keybindings or not
|
|
438
|
-
- `input_prefix` -⠀the prefix of the input line
|
|
439
|
-
- `reset_ansi` -⠀whether to reset the ANSI codes after the input or not
|
|
440
|
-
-----------------------------------------------------------------------------------
|
|
441
|
-
The input prompt can be formatted with special formatting codes. For more detailed
|
|
442
|
-
information about formatting codes, see `xx_format_codes` module documentation."""
|
|
443
|
-
kb = KeyBindings()
|
|
444
|
-
|
|
445
|
-
@kb.add("c-d", eager=True) # CTRL+D
|
|
446
|
-
def _(event):
|
|
447
|
-
event.app.exit(result=event.app.current_buffer.document.text)
|
|
448
|
-
|
|
449
|
-
FormatCodes.print(start + prompt, default_color=default_color)
|
|
450
|
-
if show_keybindings:
|
|
451
|
-
FormatCodes.print("[dim][[b](CTRL+D)[dim] : end of input][_dim]")
|
|
452
|
-
input_string = _prompt_toolkit.prompt(input_prefix, multiline=True, wrap_lines=True, key_bindings=kb)
|
|
453
|
-
FormatCodes.print("[_]" if reset_ansi else "", end=end[1:] if end.startswith("\n") else end)
|
|
454
|
-
return input_string
|
xulbux/xx_data.py
CHANGED
|
@@ -63,8 +63,7 @@ class Data:
|
|
|
63
63
|
v if not isinstance(v,
|
|
64
64
|
(list, tuple, set, frozenset, dict)) else Data.remove_empty_items(v, spaces_are_empty)
|
|
65
65
|
)
|
|
66
|
-
for k, v in data.items()
|
|
67
|
-
if not String.is_empty(v, spaces_are_empty)
|
|
66
|
+
for k, v in data.items() if not String.is_empty(v, spaces_are_empty)
|
|
68
67
|
}
|
|
69
68
|
if isinstance(data, (list, tuple, set, frozenset)):
|
|
70
69
|
return type(data)(
|
|
@@ -166,8 +165,7 @@ class Data:
|
|
|
166
165
|
if isinstance(item, dict):
|
|
167
166
|
return {
|
|
168
167
|
k: v
|
|
169
|
-
for k, v in ((process_item(key), process_item(value)) for key, value in item.items())
|
|
170
|
-
if k is not None
|
|
168
|
+
for k, v in ((process_item(key), process_item(value)) for key, value in item.items()) if k is not None
|
|
171
169
|
}
|
|
172
170
|
if isinstance(item, (list, tuple, set, frozenset)):
|
|
173
171
|
processed = (v for v in map(process_item, item) if v is not None)
|
|
@@ -379,8 +377,7 @@ class Data:
|
|
|
379
377
|
|
|
380
378
|
if isinstance(update_values, str):
|
|
381
379
|
update_values = [update_values]
|
|
382
|
-
valid_entries = [(parts[0].strip(), parts[1])
|
|
383
|
-
for update_value in update_values
|
|
380
|
+
valid_entries = [(parts[0].strip(), parts[1]) for update_value in update_values
|
|
384
381
|
if len(parts := update_value.split(str(sep).strip())) == 2]
|
|
385
382
|
if not valid_entries:
|
|
386
383
|
raise ValueError(f"No valid update_values found: {update_values}")
|
|
@@ -493,14 +490,14 @@ class Data:
|
|
|
493
490
|
if not d or compactness == 2:
|
|
494
491
|
return (
|
|
495
492
|
punct["{"]
|
|
496
|
-
+ sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
|
|
497
|
-
|
|
493
|
+
+ sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
|
|
494
|
+
for k, v in d.items()) + punct["}"]
|
|
498
495
|
)
|
|
499
496
|
if not should_expand(d.values()):
|
|
500
497
|
return (
|
|
501
498
|
punct["{"]
|
|
502
|
-
+ sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
|
|
503
|
-
|
|
499
|
+
+ sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
|
|
500
|
+
for k, v in d.items()) + punct["}"]
|
|
504
501
|
)
|
|
505
502
|
items = []
|
|
506
503
|
for k, val in d.items():
|
|
@@ -530,7 +527,7 @@ class Data:
|
|
|
530
527
|
else:
|
|
531
528
|
return f"{punct['(']}\n{formatted_items}\n{' ' * current_indent}{punct[')']}"
|
|
532
529
|
|
|
533
|
-
return format_dict(data, 0) if isinstance(data, dict) else format_sequence(data, 0)
|
|
530
|
+
return _re.sub(r"\s+(?=\n)", "", format_dict(data, 0) if isinstance(data, dict) else format_sequence(data, 0))
|
|
534
531
|
|
|
535
532
|
@staticmethod
|
|
536
533
|
def print(
|
|
@@ -559,11 +556,11 @@ class Data:
|
|
|
559
556
|
part. The formatting can be changed by simply adding the key with the new
|
|
560
557
|
value inside the `syntax_highlighting` dictionary.\n
|
|
561
558
|
The keys with their default values are:
|
|
562
|
-
- `str: COLOR.
|
|
563
|
-
- `number: COLOR.
|
|
564
|
-
- `literal: COLOR.
|
|
565
|
-
- `type: "i|" + COLOR.
|
|
566
|
-
- `punctuation: COLOR.
|
|
559
|
+
- `str: COLOR.blue`
|
|
560
|
+
- `number: COLOR.magenta`
|
|
561
|
+
- `literal: COLOR.cyan`
|
|
562
|
+
- `type: "i|" + COLOR.lightblue`
|
|
563
|
+
- `punctuation: COLOR.darkgray`\n
|
|
567
564
|
For no syntax highlighting, set `syntax_highlighting` to `False` or `None`.\n
|
|
568
565
|
------------------------------------------------------------------------------
|
|
569
566
|
For more detailed information about formatting codes, see `xx_format_codes`
|
xulbux/xx_env_path.py
CHANGED
|
@@ -22,47 +22,35 @@ class EnvPath:
|
|
|
22
22
|
if cwd:
|
|
23
23
|
path = _os.getcwd()
|
|
24
24
|
elif base_dir:
|
|
25
|
-
path = Path.
|
|
25
|
+
path = Path.script_dir
|
|
26
26
|
elif path is None:
|
|
27
27
|
raise ValueError("A path must be provided or either 'cwd' or 'base_dir' must be True.")
|
|
28
28
|
paths = EnvPath.paths(as_list=True)
|
|
29
29
|
return _os.path.normpath(path) in [_os.path.normpath(p) for p in paths]
|
|
30
30
|
|
|
31
31
|
@staticmethod
|
|
32
|
-
def add_path(
|
|
33
|
-
path: str = None,
|
|
34
|
-
cwd: bool = False,
|
|
35
|
-
base_dir: bool = False,
|
|
36
|
-
) -> None:
|
|
32
|
+
def add_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> None:
|
|
37
33
|
"""Add a path to the PATH environment variable."""
|
|
38
34
|
path = EnvPath.__get(path, cwd, base_dir)
|
|
39
35
|
if not EnvPath.has_path(path):
|
|
40
36
|
EnvPath.__persistent(path, add=True)
|
|
41
37
|
|
|
42
38
|
@staticmethod
|
|
43
|
-
def remove_path(
|
|
44
|
-
path: str = None,
|
|
45
|
-
cwd: bool = False,
|
|
46
|
-
base_dir: bool = False,
|
|
47
|
-
) -> None:
|
|
39
|
+
def remove_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> None:
|
|
48
40
|
"""Remove a path from the PATH environment variable."""
|
|
49
41
|
path = EnvPath.__get(path, cwd, base_dir)
|
|
50
42
|
if EnvPath.has_path(path):
|
|
51
43
|
EnvPath.__persistent(path, remove=True)
|
|
52
44
|
|
|
53
45
|
@staticmethod
|
|
54
|
-
def __get(
|
|
55
|
-
path: str = None,
|
|
56
|
-
cwd: bool = False,
|
|
57
|
-
base_dir: bool = False,
|
|
58
|
-
) -> list:
|
|
46
|
+
def __get(path: str = None, cwd: bool = False, base_dir: bool = False) -> list:
|
|
59
47
|
"""Get and/or normalize the paths.\n
|
|
60
48
|
------------------------------------------------------------------------------------
|
|
61
49
|
Raise an error if no path is provided and neither `cwd` or `base_dir` is `True`."""
|
|
62
50
|
if cwd:
|
|
63
51
|
path = _os.getcwd()
|
|
64
52
|
elif base_dir:
|
|
65
|
-
path = Path.
|
|
53
|
+
path = Path.script_dir
|
|
66
54
|
elif path is None:
|
|
67
55
|
raise ValueError("A path must be provided or either 'cwd' or 'base_dir' must be True.")
|
|
68
56
|
return _os.path.normpath(path)
|
xulbux/xx_file.py
CHANGED
|
@@ -4,14 +4,14 @@ from .xx_path import Path
|
|
|
4
4
|
import os as _os
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
class SameContentFileExistsError(FileExistsError):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
7
11
|
class File:
|
|
8
12
|
|
|
9
13
|
@staticmethod
|
|
10
|
-
def rename_extension(
|
|
11
|
-
file: str,
|
|
12
|
-
new_extension: str,
|
|
13
|
-
camel_case_filename: bool = False,
|
|
14
|
-
) -> str:
|
|
14
|
+
def rename_extension(file: str, new_extension: str, camel_case_filename: bool = False) -> str:
|
|
15
15
|
"""Rename the extension of a file.\n
|
|
16
16
|
--------------------------------------------------------------------------
|
|
17
17
|
If the `camel_case_filename` parameter is true, the filename will be made
|
|
@@ -23,20 +23,18 @@ class File:
|
|
|
23
23
|
return _os.path.join(directory, f"{filename}{new_extension}")
|
|
24
24
|
|
|
25
25
|
@staticmethod
|
|
26
|
-
def create(
|
|
27
|
-
file: str,
|
|
28
|
-
content: str = "",
|
|
29
|
-
force: bool = False,
|
|
30
|
-
) -> str:
|
|
26
|
+
def create(file: str, content: str = "", force: bool = False) -> str:
|
|
31
27
|
"""Create a file with ot without content.\n
|
|
32
|
-
|
|
33
|
-
The function will throw a `FileExistsError` if
|
|
28
|
+
----------------------------------------------------------------------
|
|
29
|
+
The function will throw a `FileExistsError` if a file with the same
|
|
30
|
+
name already exists and a `SameContentFileExistsError` if a file with
|
|
31
|
+
the same name and content already exists.
|
|
34
32
|
To always overwrite the file, set the `force` parameter to `True`."""
|
|
35
33
|
if _os.path.exists(file) and not force:
|
|
36
34
|
with open(file, "r", encoding="utf-8") as existing_file:
|
|
37
35
|
existing_content = existing_file.read()
|
|
38
36
|
if existing_content == content:
|
|
39
|
-
raise
|
|
37
|
+
raise SameContentFileExistsError("Already created this file. (nothing changed)")
|
|
40
38
|
raise FileExistsError("File already exists.")
|
|
41
39
|
with open(file, "w", encoding="utf-8") as f:
|
|
42
40
|
f.write(content)
|
|
@@ -62,4 +60,4 @@ class File:
|
|
|
62
60
|
try:
|
|
63
61
|
return Path.extend(file, search_in, raise_error=True, correct_path=correct_paths)
|
|
64
62
|
except FileNotFoundError:
|
|
65
|
-
return _os.path.join(Path.
|
|
63
|
+
return _os.path.join(Path.script_dir, file) if prefer_base_dir else _os.path.join(_os.getcwd(), file)
|
xulbux/xx_format_codes.py
CHANGED
|
@@ -182,6 +182,8 @@ _COMPILED: dict[str, Pattern] = { # PRECOMPILE REGULAR EXPRESSIONS
|
|
|
182
182
|
+ Regex.brackets("(", ")", is_group=True, strip_spaces=False, ignore_in_strings=False)
|
|
183
183
|
+ r")?"
|
|
184
184
|
),
|
|
185
|
+
"escape_char": _re.compile(r"(\s*)(\/|\\)"),
|
|
186
|
+
"escape_char_cond": _re.compile(r"(\s*\[\s*)(\/|\\)(?!\2+)"),
|
|
185
187
|
"bg?_default": _re.compile(r"(?i)((?:" + _PREFIX_RX["BG"] + r")?)\s*default"),
|
|
186
188
|
"bg_default": _re.compile(r"(?i)" + _PREFIX_RX["BG"] + r"\s*default"),
|
|
187
189
|
"modifier": _re.compile(
|
|
@@ -265,9 +267,11 @@ class FormatCodes:
|
|
|
265
267
|
return color in ANSI.color_map or Color.is_valid_rgba(color) or Color.is_valid_hexa(color)
|
|
266
268
|
|
|
267
269
|
def replace_keys(match: _re.Match) -> str:
|
|
268
|
-
formats = match.group(1)
|
|
269
|
-
|
|
270
|
+
_formats = formats = match.group(1)
|
|
271
|
+
auto_reset_escaped = match.group(2)
|
|
270
272
|
auto_reset_txt = match.group(3)
|
|
273
|
+
if formats_escaped := bool(_COMPILED["escape_char_cond"].match(match.group(0))):
|
|
274
|
+
_formats = formats = _COMPILED["escape_char"].sub(r"\1", formats) # REMOVE / OR \\
|
|
271
275
|
if auto_reset_txt and auto_reset_txt.count("[") > 0 and auto_reset_txt.count("]") > 0:
|
|
272
276
|
auto_reset_txt = FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)
|
|
273
277
|
if not formats:
|
|
@@ -279,7 +283,7 @@ class FormatCodes:
|
|
|
279
283
|
r if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)) != k else f"[{k}]"
|
|
280
284
|
for k in format_keys
|
|
281
285
|
]
|
|
282
|
-
if auto_reset_txt and not
|
|
286
|
+
if auto_reset_txt and not auto_reset_escaped:
|
|
283
287
|
reset_keys = []
|
|
284
288
|
for k in format_keys:
|
|
285
289
|
k_lower = k.lower()
|
|
@@ -308,17 +312,20 @@ class FormatCodes:
|
|
|
308
312
|
else:
|
|
309
313
|
ansi_resets = []
|
|
310
314
|
if not (len(ansi_formats) == 1 and ansi_formats[0].count(f"{ANSI.char}{ANSI.start}") >= 1) and not all(
|
|
311
|
-
f.startswith(f"{ANSI.char}{ANSI.start}") for f in ansi_formats):
|
|
315
|
+
f.startswith(f"{ANSI.char}{ANSI.start}") for f in ansi_formats): # FORMATTING WAS INVALID
|
|
312
316
|
return match.group(0)
|
|
313
|
-
|
|
314
|
-
"
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
elif formats_escaped: # FORMATTING WAS VALID BUT ESCAPED
|
|
318
|
+
return f"[{_formats}]({auto_reset_txt})" if auto_reset_txt else f"[{_formats}]"
|
|
319
|
+
else:
|
|
320
|
+
return (
|
|
321
|
+
"".join(ansi_formats) + (
|
|
322
|
+
f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})"
|
|
323
|
+
if auto_reset_escaped and auto_reset_txt else auto_reset_txt if auto_reset_txt else ""
|
|
324
|
+
) + ("" if auto_reset_escaped else "".join(ansi_resets))
|
|
325
|
+
)
|
|
319
326
|
|
|
320
327
|
string = "\n".join(_COMPILED["formatting"].sub(replace_keys, line) for line in string.split("\n"))
|
|
321
|
-
return (FormatCodes.__get_default_ansi(default_color) if _default_start else "") + string if use_default else string
|
|
328
|
+
return ((FormatCodes.__get_default_ansi(default_color) if _default_start else "") + string) if use_default else string
|
|
322
329
|
|
|
323
330
|
@staticmethod
|
|
324
331
|
def escape_ansi(ansi_string: str) -> str:
|
|
@@ -326,14 +333,33 @@ class FormatCodes:
|
|
|
326
333
|
return ansi_string.replace(ANSI.char, ANSI.escaped_char)
|
|
327
334
|
|
|
328
335
|
@staticmethod
|
|
329
|
-
def remove_ansi(ansi_string: str) -> str:
|
|
330
|
-
"""Removes all ANSI codes from the string
|
|
331
|
-
|
|
336
|
+
def remove_ansi(ansi_string: str, get_removals: bool = False) -> str | tuple[str, tuple[tuple[int, str], ...]]:
|
|
337
|
+
"""Removes all ANSI codes from the string.\n
|
|
338
|
+
--------------------------------------------------------------------------------------------------
|
|
339
|
+
If `get_removals` is true, additionally to the cleaned string, a list of tuples will be returned.
|
|
340
|
+
Each tuple contains the position of the removed ansi code and the removed ansi code."""
|
|
341
|
+
if get_removals:
|
|
342
|
+
removals = []
|
|
343
|
+
|
|
344
|
+
def replacement(match: _re.Match) -> str:
|
|
345
|
+
start_pos = match.start() - sum(len(removed) for _, removed in removals)
|
|
346
|
+
if removals and removals[-1][0] == start_pos:
|
|
347
|
+
start_pos = removals[-1][0]
|
|
348
|
+
removals.append((start_pos, match.group()))
|
|
349
|
+
return ""
|
|
350
|
+
|
|
351
|
+
clean_string = _COMPILED["ansi_seq"].sub(replacement, ansi_string)
|
|
352
|
+
return clean_string, tuple(removals)
|
|
353
|
+
else:
|
|
354
|
+
return _COMPILED["ansi_seq"].sub("", ansi_string)
|
|
332
355
|
|
|
333
356
|
@staticmethod
|
|
334
|
-
def remove_formatting(string: str) -> str:
|
|
335
|
-
"""Removes all formatting codes from the string
|
|
336
|
-
|
|
357
|
+
def remove_formatting(string: str, get_removals: bool = False) -> str | tuple[str, tuple[tuple[int, str], ...]]:
|
|
358
|
+
"""Removes all formatting codes from the string.\n
|
|
359
|
+
----------------------------------------------------------------------------------------------------
|
|
360
|
+
If `get_removals` is true, additionally to the cleaned string, a list of tuples will be returned.
|
|
361
|
+
Each tuple contains the position of the removed formatting code and the removed formatting code."""
|
|
362
|
+
return FormatCodes.remove_ansi(FormatCodes.to_ansi(string), get_removals=get_removals)
|
|
337
363
|
|
|
338
364
|
@staticmethod
|
|
339
365
|
def __config_console() -> None:
|
xulbux/xx_path.py
CHANGED
|
@@ -6,27 +6,37 @@ import sys as _sys
|
|
|
6
6
|
import os as _os
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# YAPF: disable
|
|
10
|
+
class ProcessNotFoundError(Exception):
|
|
11
|
+
pass
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
class _Cwd:
|
|
14
|
+
def __get__(self, obj, owner=None):
|
|
15
|
+
return _os.getcwd()
|
|
16
|
+
|
|
17
|
+
class _ScriptDir:
|
|
18
|
+
def __get__(self, obj, owner=None):
|
|
19
|
+
if getattr(_sys, "frozen", False):
|
|
20
|
+
base_path = _os.path.dirname(_sys.executable)
|
|
21
|
+
else:
|
|
22
|
+
main_module = _sys.modules["__main__"]
|
|
23
|
+
if hasattr(main_module, "__file__"):
|
|
24
|
+
base_path = _os.path.dirname(_os.path.abspath(main_module.__file__))
|
|
25
|
+
elif (hasattr(main_module, "__spec__") and main_module.__spec__
|
|
26
|
+
and getattr(main_module.__spec__, "origin", None)):
|
|
27
|
+
base_path = _os.path.dirname(_os.path.abspath(main_module.__spec__.origin))
|
|
19
28
|
else:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
raise RuntimeError("Can only get base directory if accessed from a file.")
|
|
30
|
+
return base_path
|
|
31
|
+
# YAPF: enable
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Path:
|
|
35
|
+
|
|
36
|
+
cwd: str = _Cwd()
|
|
37
|
+
"""The path to the current working directory."""
|
|
38
|
+
script_dir: str = _ScriptDir()
|
|
39
|
+
"""The path to the directory of the current script."""
|
|
30
40
|
|
|
31
41
|
@staticmethod
|
|
32
42
|
def extend(path: str, search_in: str | list[str] = None, raise_error: bool = False, correct_path: bool = False) -> str:
|
|
@@ -68,7 +78,7 @@ class Path:
|
|
|
68
78
|
search_dirs = (drive + _os.sep) if drive else [_os.sep]
|
|
69
79
|
else:
|
|
70
80
|
rel_path = path.lstrip(_os.sep)
|
|
71
|
-
base_dir = Path.
|
|
81
|
+
base_dir = Path.script_dir
|
|
72
82
|
search_dirs = (
|
|
73
83
|
_os.getcwd(),
|
|
74
84
|
base_dir,
|
xulbux/xx_regex.py
CHANGED
|
@@ -55,11 +55,7 @@ class Regex:
|
|
|
55
55
|
return rf'(?<!["\'])(?:{pattern})(?!["\'])'
|
|
56
56
|
|
|
57
57
|
@staticmethod
|
|
58
|
-
def all_except(
|
|
59
|
-
disallowed_pattern: str,
|
|
60
|
-
ignore_pattern: str = "",
|
|
61
|
-
is_group: bool = False,
|
|
62
|
-
) -> str:
|
|
58
|
+
def all_except(disallowed_pattern: str, ignore_pattern: str = "", is_group: bool = False) -> str:
|
|
63
59
|
"""Matches everything except `disallowed_pattern`, unless the `disallowed_pattern`
|
|
64
60
|
is found inside a string (`'...'` or `"..."`).\n
|
|
65
61
|
------------------------------------------------------------------------------------
|
xulbux/xx_system.py
CHANGED
|
@@ -7,15 +7,12 @@ import sys as _sys
|
|
|
7
7
|
import os as _os
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
# YAPF: disable
|
|
10
11
|
class ProcessNotFoundError(Exception):
|
|
11
12
|
pass
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def is_elevated() -> bool:
|
|
18
|
-
"""Returns `True` if the current user is an admin and `False` otherwise."""
|
|
14
|
+
class _IsElevated:
|
|
15
|
+
def __get__(self, obj, owner=None):
|
|
19
16
|
try:
|
|
20
17
|
if _os.name == "nt":
|
|
21
18
|
return _ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
@@ -24,6 +21,14 @@ class System:
|
|
|
24
21
|
except Exception:
|
|
25
22
|
pass
|
|
26
23
|
return False
|
|
24
|
+
# YAPF: enable
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class System:
|
|
28
|
+
|
|
29
|
+
is_elevated: bool = _IsElevated()
|
|
30
|
+
"""Is `True` if the current process has
|
|
31
|
+
elevated privileges and `False` otherwise."""
|
|
27
32
|
|
|
28
33
|
@staticmethod
|
|
29
34
|
def restart(prompt: object = None, wait: int = 0, continue_program: bool = False, force: bool = False) -> None:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: xulbux
|
|
3
|
-
Version: 1.6.
|
|
4
|
-
Summary: A library which includes
|
|
3
|
+
Version: 1.6.8
|
|
4
|
+
Summary: A Python library which includes lots of helpful classes, types and functions aiming to make common programming tasks simpler.
|
|
5
5
|
Author-email: XulbuX <xulbux.real@gmail.com>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
@@ -31,7 +31,7 @@ Project-URL: Documentation, https://github.com/XulbuX/PythonLibraryXulbuX/wiki
|
|
|
31
31
|
Project-URL: Homepage, https://github.com/XulbuX/PythonLibraryXulbuX
|
|
32
32
|
Project-URL: License, https://github.com/XulbuX/PythonLibraryXulbuX/blob/main/LICENSE
|
|
33
33
|
Project-URL: Source Code, https://github.com/XulbuX/PythonLibraryXulbuX/tree/main/src
|
|
34
|
-
Keywords:
|
|
34
|
+
Keywords: args,arguments,attributes,classes,cmd,client,code,codes,color,commands,console,consts,constants,convert,conversion,data,debug,easier,env,environment,error,file,format,formatting,functions,helper,hex,hexa,hsl,hsla,info,input,json,library,log,logging,methods,nice,operations,path,presets,pretty,printing,properties,python,re,regex,rgb,rgba,string,structures,system,tools,types,utility,warn,warning,xulbux
|
|
35
35
|
Classifier: Intended Audience :: Developers
|
|
36
36
|
Classifier: Programming Language :: Python :: 3
|
|
37
37
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -57,41 +57,53 @@ Requires-Dist: flake8-pyproject>=1.2.3; extra == "dev"
|
|
|
57
57
|
|
|
58
58
|
# **$\color{#8085FF}\Huge\textsf{XulbuX}$**
|
|
59
59
|
|
|
60
|
-
**$\color{#8085FF}\textsf{XulbuX}$** is
|
|
60
|
+
**$\color{#8085FF}\textsf{XulbuX}$** is library that contains many useful classes, types, and functions,
|
|
61
|
+
ranging from console logging and working with colors to file management and system operations.
|
|
62
|
+
The library is designed to simplify common programming tasks and improve code readability through its collection of tools.
|
|
61
63
|
|
|
62
|
-
For precise information about the library, see the library's [
|
|
63
|
-
For the libraries latest changes, see the [change log](https://github.com/XulbuX/PythonLibraryXulbuX/blob/main/CHANGELOG.md).
|
|
64
|
+
For precise information about the library, see the library's [wiki page](https://github.com/XulbuX/PythonLibraryXulbuX/wiki).<br>
|
|
65
|
+
For the libraries latest changes and updates, see the [change log](https://github.com/XulbuX/PythonLibraryXulbuX/blob/main/CHANGELOG.md).
|
|
64
66
|
|
|
67
|
+
<br>
|
|
65
68
|
|
|
66
69
|
## Installation
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
Run the following commands in a console with administrator privileges, so the actions take effect for all users.
|
|
72
|
+
|
|
73
|
+
Install the library and all its dependencies with the command:
|
|
74
|
+
```console
|
|
70
75
|
pip install xulbux
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
```
|
|
78
|
+
Upgrade the library and all its dependencies to their latest available version with the command:
|
|
79
|
+
```console
|
|
75
80
|
pip install --upgrade xulbux
|
|
76
81
|
```
|
|
77
82
|
|
|
83
|
+
<br>
|
|
78
84
|
|
|
79
85
|
## Usage
|
|
80
86
|
|
|
81
|
-
Import the full library under the alias `xx`, so
|
|
87
|
+
Import the full library under the alias `xx`, so its constants, classes, methods and types are accessible with `xx.CONSTANT.value`, `xx.Class.method()`, `xx.type()`:
|
|
82
88
|
```python
|
|
83
89
|
import xulbux as xx
|
|
84
90
|
```
|
|
85
|
-
So you don't have to
|
|
91
|
+
So you don't have to import the full library under an alias, you can also import only certain parts of the library's contents:
|
|
86
92
|
```python
|
|
93
|
+
# CONSTANTS
|
|
94
|
+
from xulbux import COLOR, CHARS, ANSI
|
|
95
|
+
# Classes
|
|
96
|
+
from xulbux import Code, Color, Console, ...
|
|
97
|
+
# types
|
|
87
98
|
from xulbux import rgba, hsla, hexa
|
|
88
99
|
```
|
|
89
100
|
|
|
101
|
+
<br>
|
|
90
102
|
|
|
91
|
-
|
|
103
|
+
## Modules
|
|
92
104
|
|
|
93
|
-
| | |
|
|
94
|
-
|
|
|
105
|
+
| Module | Short Description |
|
|
106
|
+
| :----------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------- |
|
|
95
107
|
| <h3>[`xx_code`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_code)</h3> | advanced code-string operations (*changing the indent, finding function calls, ...*) |
|
|
96
108
|
| <h3>[`xx_color`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_color)</h3> | everything around colors (*converting, blending, searching colors in strings, ...*) |
|
|
97
109
|
| <h3>[`xx_console`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_console)</h3> | advanced actions related to the console (*pretty logging, advanced inputs, ...*) |
|
|
@@ -99,13 +111,78 @@ from xulbux import rgba, hsla, hexa
|
|
|
99
111
|
| <h3>[`xx_env_path`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_env_path)</h3> | getting and editing the PATH variable (*get paths, check for paths, add paths, ...*) |
|
|
100
112
|
| <h3>[`xx_file`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_file)</h3> | advanced working with files (*create files, rename file-extensions, ...*) |
|
|
101
113
|
| <h3>[`xx_format_codes`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_format_codes)</h3> | easy pretty printing with custom format codes (*print, inputs, custom format codes to ANSI, ...*) |
|
|
102
|
-
| <h3>`xx_json`</h3>
|
|
103
|
-
| <h3>`xx_path`</h3>
|
|
104
|
-
| <h3>`xx_regex`</h3>
|
|
114
|
+
| <h3>`xx_json`</h3> | advanced working with json files (*read, create, update, ...*) |
|
|
115
|
+
| <h3>`xx_path`</h3> | advanced path operations (*get paths, smart-extend relative paths, delete paths, ...*) |
|
|
116
|
+
| <h3>`xx_regex`</h3> | generated regex pattern-templates (*match bracket- and quote pairs, match colors, ...*) |
|
|
105
117
|
| <h3>[`xx_string`](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_string)</h3> | helpful actions when working with strings. (*normalize, escape, decompose, ...*) |
|
|
106
|
-
| <h3>`xx_system`</h3>
|
|
118
|
+
| <h3>`xx_system`</h3> | advanced system actions (*restart with message, check installed Python libs, ...*) |
|
|
119
|
+
|
|
120
|
+
<br>
|
|
107
121
|
|
|
122
|
+
## Example Usage
|
|
108
123
|
|
|
124
|
+
This is what it could look like using this library for a simple but very nice looking color converter:
|
|
125
|
+
```python
|
|
126
|
+
from xulbux import COLOR # CONSTANTS
|
|
127
|
+
from xulbux import FormatCodes, Console # Classes
|
|
128
|
+
from xulbux import hexa # types
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main() -> None:
|
|
132
|
+
|
|
133
|
+
# LET THE USER ENTER A HEXA COLOR IN ANY HEXA FORMAT
|
|
134
|
+
input_clr = FormatCodes.input(
|
|
135
|
+
"\n[b](Enter a HEXA color in any format) [dim](>) "
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# ANNOUNCE INDEXING THE INPUT COLOR
|
|
139
|
+
Console.log(
|
|
140
|
+
"INDEX",
|
|
141
|
+
"Indexing the input HEXA color...",
|
|
142
|
+
start="\n",
|
|
143
|
+
title_bg_color=COLOR.blue,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# TRY TO CONVERT THE INPUT COLOR INTO A hexa() COLOR
|
|
148
|
+
hexa_color = hexa(input_clr)
|
|
149
|
+
|
|
150
|
+
except ValueError:
|
|
151
|
+
# ANNOUNCE THE ERROR AND EXIT THE PROGRAM
|
|
152
|
+
Console.fail(
|
|
153
|
+
"The input HEXA color is invalid.",
|
|
154
|
+
end="\n\n",
|
|
155
|
+
exit=True,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# ANNOUNCE STARTING THE CONVERSION
|
|
159
|
+
Console.log(
|
|
160
|
+
"CONVERT",
|
|
161
|
+
"Converting the HEXA color into different types...",
|
|
162
|
+
title_bg_color=COLOR.tangerine,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# CONVERT THE HEXA COLOR INTO THE TWO OTHER COLOR TYPES
|
|
166
|
+
rgba_color = hexa_color.to_rgba()
|
|
167
|
+
hsla_color = hexa_color.to_hsla()
|
|
168
|
+
|
|
169
|
+
# ANNOUNCE THE SUCCESSFUL CONVERSION
|
|
170
|
+
Console.done(
|
|
171
|
+
"Successfully converted color into different types.",
|
|
172
|
+
end="\n\n",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# PRETTY PRINT THE COLOR IN DIFFERENT TYPES
|
|
176
|
+
FormatCodes.print(f"[b](HEXA:) [i|white]({hexa_color})")
|
|
177
|
+
FormatCodes.print(f"[b](RGBA:) [i|white]({rgba_color})")
|
|
178
|
+
FormatCodes.print(f"[b](HSLA:) [i|white]({hsla_color})\n")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
main()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
<br>
|
|
109
186
|
<br>
|
|
110
187
|
|
|
111
188
|
--------------------------------------------------------------
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
xulbux/__init__.py,sha256=UY5nyn_XmeHAhiPtZmNMlc9hQRlNWbQ0uirICuihWzg,1654
|
|
2
|
+
xulbux/_cli_.py,sha256=I1TieHnX60mlRvMaTQnon-VRuf_70dkP7sOU1aHthQY,3470
|
|
3
|
+
xulbux/_consts_.py,sha256=b85O5sePS18z7CJgrVw0V7v88PIG9qnQ7G2bJL71odk,6287
|
|
4
|
+
xulbux/xx_code.py,sha256=GfzpbN-41L_qfzEdkl4PWq9tbCSAlnE2xX3lPtHo1Iw,5275
|
|
5
|
+
xulbux/xx_color.py,sha256=nwcd5_4JIRfZ99JqbCXMl4RWpic_-M361AA5zEa9Nuw,44846
|
|
6
|
+
xulbux/xx_console.py,sha256=2ZHNxwpSoILjHk45QwzJJbtCn3WOVQ9TmkyqPpdU_3w,25860
|
|
7
|
+
xulbux/xx_data.py,sha256=5MIEKDgbRLGkZi9Yd35XhzrWZY09oXyVLGs0BgTTHFA,30219
|
|
8
|
+
xulbux/xx_env_path.py,sha256=WoBYywFsncX-GMvSdvrGmuajXeeuRY2l_-3GuJJXChU,4200
|
|
9
|
+
xulbux/xx_file.py,sha256=Rij2NjxyBlwfFIN_Sc-vDJzzsn3jzgIigFQ_p6Zg80o,3246
|
|
10
|
+
xulbux/xx_format_codes.py,sha256=5Q5RAVfL-EmhqjJMQ4wMm0MQ3r0okjNKrpdsXeodupI,22313
|
|
11
|
+
xulbux/xx_json.py,sha256=dw2AiqMErdjW0ot4pICDBdTL6j03IrYJWJz-Lw21d4Q,5149
|
|
12
|
+
xulbux/xx_path.py,sha256=trDke1N9ewbkQmAIqjeB9gfbTuAlzqFY2mtPtlK2Ks0,4639
|
|
13
|
+
xulbux/xx_regex.py,sha256=rmy6stkVP-vm8j7QoIn0Z4ic_fMT9p_hs0QE6PkMcr0,7917
|
|
14
|
+
xulbux/xx_string.py,sha256=nJBXAVNknhTE9N_4yOyCVwSSIwOyHCRlZe_D7LOgrOY,5450
|
|
15
|
+
xulbux/xx_system.py,sha256=4WuItIeVF5cU3u3-cu3XqhtxBcap9YDJiQKTZuWsUyM,6494
|
|
16
|
+
xulbux-1.6.8.dist-info/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
|
|
17
|
+
xulbux-1.6.8.dist-info/METADATA,sha256=D0zdyVryi5SUH7ylNmFbi5qa1dgbPt7zdn08LkgqFOQ,9673
|
|
18
|
+
xulbux-1.6.8.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
19
|
+
xulbux-1.6.8.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
|
|
20
|
+
xulbux-1.6.8.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
|
|
21
|
+
xulbux-1.6.8.dist-info/RECORD,,
|
xulbux-1.6.6.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
xulbux/__init__.py,sha256=kGUOl7SiyAMqKsEZfZODjJILWaFVVRnzAkT5WrwmHMQ,1654
|
|
2
|
-
xulbux/_cli_.py,sha256=I1TieHnX60mlRvMaTQnon-VRuf_70dkP7sOU1aHthQY,3470
|
|
3
|
-
xulbux/_consts_.py,sha256=aQyWSJ5_Spqot1HHWKbYj1j-6YSakIqG89Olv7rp7Ms,4886
|
|
4
|
-
xulbux/xx_code.py,sha256=dY2HRXIDXHN3KTzzUkQVBacFDExNVwH8flREshwi4vk,5288
|
|
5
|
-
xulbux/xx_color.py,sha256=nwcd5_4JIRfZ99JqbCXMl4RWpic_-M361AA5zEa9Nuw,44846
|
|
6
|
-
xulbux/xx_console.py,sha256=pwB1gLuwTj9V2FhcK-kNTfK7eK3Mb-fbsD59Cua-XTM,20078
|
|
7
|
-
xulbux/xx_data.py,sha256=fkJKArU7TTmXTk90vG_s4BHIor6NRqUP_gqypyv3v9U,30254
|
|
8
|
-
xulbux/xx_env_path.py,sha256=e5r47g-dLIo_J9RM9teqLM1rTzNsI0w9U0xjjcK6nro,4321
|
|
9
|
-
xulbux/xx_file.py,sha256=MGGrPDyvgVQJrdcRSGE_jiB_aQz6zo5I0m6_5pKYIo8,3123
|
|
10
|
-
xulbux/xx_format_codes.py,sha256=abR9TWtnBLF0L08oyRjDYXx9VCtun9kjGVPmkv44emU,20359
|
|
11
|
-
xulbux/xx_json.py,sha256=dw2AiqMErdjW0ot4pICDBdTL6j03IrYJWJz-Lw21d4Q,5149
|
|
12
|
-
xulbux/xx_path.py,sha256=KjSurQ9SHqcdhyTo1vzZn2qGXaQr3T1eDr-E_PVdyXo,4537
|
|
13
|
-
xulbux/xx_regex.py,sha256=gvnDel8xVmf11kvhc0iIjSj1dFW3OLnXNDlaJsUxXU4,7952
|
|
14
|
-
xulbux/xx_string.py,sha256=nJBXAVNknhTE9N_4yOyCVwSSIwOyHCRlZe_D7LOgrOY,5450
|
|
15
|
-
xulbux/xx_system.py,sha256=izDmmrh3o66drAp-LkqsA5AruAdHprq5p_CuseT1OJI,6399
|
|
16
|
-
xulbux-1.6.6.dist-info/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
|
|
17
|
-
xulbux-1.6.6.dist-info/METADATA,sha256=s2U6Uel8qNsGjnL8Czi8nxzXcOy_sByZCSd_dLAg-w4,6979
|
|
18
|
-
xulbux-1.6.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
19
|
-
xulbux-1.6.6.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
|
|
20
|
-
xulbux-1.6.6.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
|
|
21
|
-
xulbux-1.6.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|