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 CHANGED
@@ -19,7 +19,7 @@
19
19
  • REGEX PATTERN TEMPLATES xx.Regex
20
20
  """
21
21
 
22
- __version__ = "1.6.6"
22
+ __version__ = "1.6.8"
23
23
  __author__ = "XulbuX"
24
24
  __email__ = "xulbux.real@gmail.com"
25
25
  __license__ = "MIT"
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 XulbuX library."""
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
- """Strings with only certain text characters."""
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) -> str:
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
- @staticmethod
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 = Code.get_tab_spaces(code)
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) -> dict[str, dict[str, any]]:
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
- results = {}
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
- if arg in args:
61
- exists = True
62
- arg_index = args.index(arg)
63
- if arg_index + 1 < len(args) and not args[arg_index + 1].startswith("-"):
64
- value = String.to_type(args[arg_index + 1])
65
- break
66
- results[arg_key] = {"exists": exists, "value": value}
67
- return results
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`\n
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
- prompt_lst = (String.split_count(l, Console.w - (title_len + tab_len)) for l in str(prompt).splitlines())
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 box's background color
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)}" for k, v in d.items())
497
- + punct["}"]
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)}" for k, v in d.items())
503
- + punct["}"]
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.["blue"]`
563
- - `number: COLOR.["magenta"]`
564
- - `literal: COLOR.["cyan"]`
565
- - `type: "i|" + COLOR.["lightblue"]`
566
- - `punctuation: COLOR.["darkgray"]`\n
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.get(base_dir=True)
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.get(base_dir=True)
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 the file already exists.
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 FileExistsError("Already created this file. (nothing changed)")
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.get(base_dir=True), file) if prefer_base_dir else _os.path.join(_os.getcwd(), file)
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
- escaped = match.group(2)
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 escaped:
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
- return (
314
- "".join(ansi_formats) + (
315
- f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})"
316
- if escaped and auto_reset_txt else auto_reset_txt if auto_reset_txt else ""
317
- ) + ("" if escaped else "".join(ansi_resets))
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
- return _COMPILED["ansi_seq"].sub("", ansi_string)
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
- return _COMPILED["ansi_seq"].sub("", FormatCodes.to_ansi(string))
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
- class Path:
9
+ # YAPF: disable
10
+ class ProcessNotFoundError(Exception):
11
+ pass
10
12
 
11
- @staticmethod
12
- def get(cwd: bool = False, base_dir: bool = False) -> str | list[str]:
13
- paths = []
14
- if cwd:
15
- paths.append(_os.getcwd())
16
- if base_dir:
17
- if getattr(_sys, "frozen", False):
18
- base_path = _os.path.dirname(_sys.executable)
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
- main_module = _sys.modules["__main__"]
21
- if hasattr(main_module, "__file__"):
22
- base_path = _os.path.dirname(_os.path.abspath(main_module.__file__))
23
- elif (hasattr(main_module, "__spec__") and main_module.__spec__
24
- and getattr(main_module.__spec__, "origin", None)):
25
- base_path = _os.path.dirname(_os.path.abspath(main_module.__spec__.origin))
26
- else:
27
- raise RuntimeError("Can only get base directory if ran from a file.")
28
- paths.append(base_path)
29
- return paths[0] if len(paths) == 1 else paths
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.get(base_dir=True)
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
- class System:
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.6
4
- Summary: A library which includes a lot of really helpful functions.
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: xulbux,python,library,utility,helper,functions,tools,classes,types,methods,cmd,console,code,color,data,structures,env,environment,file,format,json,path,regex,string,system,operations,presets
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 a library which includes a lot of really helpful classes, types and functions.
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 [Wiki page](https://github.com/XulbuX/PythonLibraryXulbuX/wiki).<br>
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
- To install the library and all its dependencies, open a console and run the command:
69
- ```prolog
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
- To upgrade the library to the latest available version, run the following command in your console:
74
- ```prolog
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 it's classes, types and functions are accessible with `xx.Class.method()`, `xx.type()` and `xx.function()`:
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 write `xx` in front of the library's types, you can import them directly:
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
- # Modules
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> | advanced working with json files (*read, create, update, ...*) |
103
- | <h3>`xx_path`</h3> | advanced path operations (*get paths, smart-extend relative paths, delete paths, ...*) |
104
- | <h3>`xx_regex`</h3> | generated regex pattern-templates (*match bracket- and quote pairs, match colors, ...*) |
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> | advanced system actions (*restart with message, check installed Python libs, ...*) |
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,