xulbux 1.7.0__py3-none-any.whl → 1.7.2__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
@@ -1,4 +1,4 @@
1
- __version__ = "1.7.0"
1
+ __version__ = "1.7.2"
2
2
  __author__ = "XulbuX"
3
3
  __email__ = "xulbux.real@gmail.com"
4
4
  __license__ = "MIT"
xulbux/_consts_.py CHANGED
@@ -94,8 +94,6 @@ class ANSI:
94
94
  """The separator between ANSI escape sequence parts."""
95
95
  END = end = "m"
96
96
  """The end of an ANSI escape sequence."""
97
- default_color_modifiers: dict[str, str] = {"lighten": "+l", "darken": "-d"}
98
- """Characters to modify the lightness of the default color with."""
99
97
 
100
98
  @classmethod
101
99
  def seq(cls, parts: int = 1) -> FormattableString:
@@ -107,7 +105,7 @@ class ANSI:
107
105
  seq_bg_color: FormattableString = CHAR + START + "48" + SEP + "2" + SEP + "{}" + SEP + "{}" + SEP + "{}" + END
108
106
  """The ANSI escape sequence for setting the background RGB color."""
109
107
 
110
- color_map: list[str] = [
108
+ color_map: tuple[str, ...] = (
111
109
  ########### DEFAULT CONSOLE COLOR NAMES ############
112
110
  "black",
113
111
  "red",
@@ -117,7 +115,7 @@ class ANSI:
117
115
  "magenta",
118
116
  "cyan",
119
117
  "white",
120
- ]
118
+ )
121
119
  """The console default color names."""
122
120
 
123
121
  codes_map: dict[str | tuple[str, ...], int] = {
xulbux/xx_code.py CHANGED
@@ -53,7 +53,7 @@ class Code:
53
53
  return list(Data.remove_duplicates(funcs + nested_func_calls))
54
54
 
55
55
  @staticmethod
56
- def is_js(code: str, funcs: list = ["__", "$t", "$lang"]) -> bool:
56
+ def is_js(code: str, funcs: list[str] = ["__", "$t", "$lang"]) -> bool:
57
57
  """Will check if the code is very likely to be JavaScript."""
58
58
  if not code or len(code.strip()) < 3:
59
59
  return False
xulbux/xx_color.py CHANGED
@@ -33,7 +33,7 @@ The `Color` class, which contains all sorts of different color-related methods:
33
33
 
34
34
  from .xx_regex import Regex
35
35
 
36
- from typing import Annotated, TypeAlias, Iterator, Optional, Literal, Union
36
+ from typing import Annotated, TypeAlias, Iterator, Optional, Literal, Union, Any, cast
37
37
  import re as _re
38
38
 
39
39
 
@@ -42,6 +42,10 @@ Int_0_255 = Annotated[int, "An integer value between 0 and 255, inclusive."]
42
42
  Int_0_360 = Annotated[int, "An integer value between 0 and 360, inclusive."]
43
43
  Float_0_1 = Annotated[float, "A float value between 0.0 and 1.0, inclusive."]
44
44
 
45
+ AnyRgba: TypeAlias = Any
46
+ AnyHsla: TypeAlias = Any
47
+ AnyHexa: TypeAlias = Any
48
+
45
49
  Rgba: TypeAlias = Union[
46
50
  tuple[Int_0_255, Int_0_255, Int_0_255],
47
51
  tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
@@ -178,7 +182,7 @@ class rgba:
178
182
  self.a = 1 - self.a
179
183
  return rgba(self.r, self.g, self.b, self.a, _validate=False)
180
184
 
181
- def grayscale(self, method: str = "wcag2") -> "rgba":
185
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "rgba":
182
186
  """Converts the color to grayscale using the luminance formula.\n
183
187
  ------------------------------------------------------------------
184
188
  The `method` is the luminance calculation method to use:
@@ -387,7 +391,7 @@ class hsla:
387
391
  self.a = 1 - self.a
388
392
  return hsla(self.h, self.s, self.l, self.a, _validate=False)
389
393
 
390
- def grayscale(self, method: str = "wcag2") -> "hsla":
394
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hsla":
391
395
  """Converts the color to grayscale using the luminance formula.\n
392
396
  ------------------------------------------------------------------
393
397
  The `method` is the luminance calculation method to use:
@@ -496,7 +500,7 @@ class hexa:
496
500
  self.b: int
497
501
  self.a: Optional[float]
498
502
  if all(x is not None for x in (_r, _g, _b)):
499
- self.r, self.g, self.b, self.a = _r, _g, _b, _a # type: ignore[assignment]
503
+ self.r, self.g, self.b, self.a = cast(int, _r), cast(int, _g), cast(int, _b), _a
500
504
  return
501
505
  if isinstance(color, hexa):
502
506
  raise ValueError("Color is already a hexa() color")
@@ -628,7 +632,7 @@ class hexa:
628
632
  self.a = 1 - self.a
629
633
  return hexa("", self.r, self.g, self.b, self.a)
630
634
 
631
- def grayscale(self, method: str = "wcag2") -> "hexa":
635
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hexa":
632
636
  """Converts the color to grayscale using the luminance formula.\n
633
637
  ------------------------------------------------------------------
634
638
  The `method` is the luminance calculation method to use:
@@ -677,7 +681,7 @@ class hexa:
677
681
  class Color:
678
682
 
679
683
  @staticmethod
680
- def is_valid_rgba(color: Rgba, allow_alpha: bool = True) -> bool:
684
+ def is_valid_rgba(color: AnyRgba, allow_alpha: bool = True) -> bool:
681
685
  try:
682
686
  if isinstance(color, rgba):
683
687
  return True
@@ -685,7 +689,7 @@ class Color:
685
689
  if allow_alpha and Color.has_alpha(color):
686
690
  return (
687
691
  0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
688
- and (0 <= color[3] <= 1 or color[3] is None) # type: ignore[index]
692
+ and (0 <= color[3] <= 1 or color[3] is None)
689
693
  )
690
694
  elif len(color) == 3:
691
695
  return 0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
@@ -708,7 +712,7 @@ class Color:
708
712
  return False
709
713
 
710
714
  @staticmethod
711
- def is_valid_hsla(color: Hsla, allow_alpha: bool = True) -> bool:
715
+ def is_valid_hsla(color: AnyHsla, allow_alpha: bool = True) -> bool:
712
716
  try:
713
717
  if isinstance(color, hsla):
714
718
  return True
@@ -716,7 +720,7 @@ class Color:
716
720
  if allow_alpha and Color.has_alpha(color):
717
721
  return (
718
722
  0 <= color[0] <= 360 and 0 <= color[1] <= 100 and 0 <= color[2] <= 100
719
- and (0 <= color[3] <= 1 or color[3] is None) # type: ignore[index]
723
+ and (0 <= color[3] <= 1 or color[3] is None)
720
724
  )
721
725
  elif len(color) == 3:
722
726
  return 0 <= color[0] <= 360 and 0 <= color[1] <= 100 and 0 <= color[2] <= 100
@@ -734,12 +738,13 @@ class Color:
734
738
  return False
735
739
  elif isinstance(color, str):
736
740
  return bool(_re.fullmatch(Regex.hsla_str(allow_alpha=allow_alpha), color))
741
+ return False
737
742
  except Exception:
738
743
  return False
739
744
 
740
745
  @staticmethod
741
746
  def is_valid_hexa(
742
- color: Hexa,
747
+ color: AnyHexa,
743
748
  allow_alpha: bool = True,
744
749
  get_prefix: bool = False,
745
750
  ) -> bool | tuple[bool, Optional[Literal['#', '0x']]]:
@@ -754,14 +759,15 @@ class Color:
754
759
  (color[2:], "0x") if color.startswith("0x") else (color, None))
755
760
  return ((bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)),
756
761
  prefix) if get_prefix else bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)))
762
+ return False
757
763
  except Exception:
758
764
  return (False, None) if get_prefix else False
759
765
 
760
766
  @staticmethod
761
- def is_valid(color: Rgba | Hsla | Hexa, allow_alpha: bool = True) -> bool:
767
+ def is_valid(color: AnyRgba | AnyHsla | AnyHexa, allow_alpha: bool = True) -> bool:
762
768
  return bool(
763
- Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha) # type: ignore[assignment]
764
- or Color.is_valid_hexa(color, allow_alpha) # type: ignore[assignment]
769
+ Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha)
770
+ or Color.is_valid_hexa(color, allow_alpha)
765
771
  )
766
772
 
767
773
  @staticmethod
@@ -772,7 +778,7 @@ class Color:
772
778
  Returns `True` if the color has an alpha channel and `False` otherwise."""
773
779
  if isinstance(color, (rgba, hsla, hexa)):
774
780
  return color.has_alpha()
775
- if Color.is_valid_hexa(color): # type: ignore[assignment]
781
+ if Color.is_valid_hexa(color):
776
782
  if isinstance(color, str):
777
783
  if color.startswith("#"):
778
784
  color = color[1:]
@@ -791,11 +797,11 @@ class Color:
791
797
  """Will try to convert any color type to a color of type RGBA."""
792
798
  if isinstance(color, (hsla, hexa)):
793
799
  return color.to_rgba()
794
- elif Color.is_valid_hsla(color): # type: ignore[assignment]
800
+ elif Color.is_valid_hsla(color):
795
801
  return hsla(*color, _validate=False).to_rgba() # type: ignore[not-iterable]
796
- elif Color.is_valid_hexa(color): # type: ignore[assignment]
797
- return hexa(color).to_rgba() # type: ignore[assignment]
798
- elif Color.is_valid_rgba(color): # type: ignore[assignment]
802
+ elif Color.is_valid_hexa(color):
803
+ return hexa(cast(str | int, color)).to_rgba()
804
+ elif Color.is_valid_rgba(color):
799
805
  return color if isinstance(color, rgba) else (rgba(*color, _validate=False)) # type: ignore[not-iterable]
800
806
  raise ValueError(f"Invalid color format '{color}'")
801
807
 
@@ -804,11 +810,11 @@ class Color:
804
810
  """Will try to convert any color type to a color of type HSLA."""
805
811
  if isinstance(color, (rgba, hexa)):
806
812
  return color.to_hsla()
807
- elif Color.is_valid_rgba(color): # type: ignore[assignment]
813
+ elif Color.is_valid_rgba(color):
808
814
  return rgba(*color, _validate=False).to_hsla() # type: ignore[not-iterable]
809
- elif Color.is_valid_hexa(color): # type: ignore[assignment]
810
- return hexa(color).to_hsla() # type: ignore[assignment]
811
- elif Color.is_valid_hsla(color): # type: ignore[assignment]
815
+ elif Color.is_valid_hexa(color):
816
+ return hexa(cast(str | int, color)).to_hsla()
817
+ elif Color.is_valid_hsla(color):
812
818
  return color if isinstance(color, hsla) else (hsla(*color, _validate=False)) # type: ignore[not-iterable]
813
819
  raise ValueError(f"Invalid color format '{color}'")
814
820
 
@@ -817,12 +823,12 @@ class Color:
817
823
  """Will try to convert any color type to a color of type HEXA."""
818
824
  if isinstance(color, (rgba, hsla)):
819
825
  return color.to_hexa()
820
- elif Color.is_valid_rgba(color): # type: ignore[assignment]
826
+ elif Color.is_valid_rgba(color):
821
827
  return rgba(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
822
- elif Color.is_valid_hsla(color): # type: ignore[assignment]
828
+ elif Color.is_valid_hsla(color):
823
829
  return hsla(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
824
- elif Color.is_valid_hexa(color): # type: ignore[assignment]
825
- return color if isinstance(color, hexa) else hexa(color) # type: ignore[assignment]
830
+ elif Color.is_valid_hexa(color):
831
+ return color if isinstance(color, hexa) else hexa(cast(str | int, color))
826
832
  raise ValueError(f"Invalid color format '{color}'")
827
833
 
828
834
  @staticmethod
@@ -919,7 +925,13 @@ class Color:
919
925
  raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
920
926
 
921
927
  @staticmethod
922
- def luminance(r: int, g: int, b: int, output_type: Optional[type] = None, method: str = "wcag2") -> int | float:
928
+ def luminance(
929
+ r: int,
930
+ g: int,
931
+ b: int,
932
+ output_type: Optional[type] = None,
933
+ method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2",
934
+ ) -> int | float:
923
935
  """Calculates the relative luminance of a color according to various standards.\n
924
936
  ----------------------------------------------------------------------------------
925
937
  The `output_type` controls the range of the returned luminance value:
@@ -963,7 +975,7 @@ class Color:
963
975
 
964
976
  @staticmethod
965
977
  def text_color_for_on_bg(text_bg_color: Rgba | Hexa) -> rgba | hexa | int:
966
- was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int) # type: ignore[assignment]
978
+ was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int)
967
979
  text_bg_color = Color.to_rgba(text_bg_color)
968
980
  brightness = 0.2126 * text_bg_color[0] + 0.7152 * text_bg_color[1] + 0.0722 * text_bg_color[2]
969
981
  return (((0xFFFFFF if was_int else hexa("", 255, 255, 255)) if was_hexa else rgba(255, 255, 255, _validate=False))
@@ -978,22 +990,22 @@ class Color:
978
990
  - lightness_change (float): float between -1.0 (darken by `100%`) and 1.0 (lighten by `100%`)\n
979
991
  -----------------------------------------------------------------------------------------------------
980
992
  returns (rgba|hexa): the adjusted color in the format of the input color"""
981
- was_hexa = Color.is_valid_hexa(color) # type: ignore[assignment]
982
- _color: hsla = Color.to_hsla(color) # type: ignore[assignment]
993
+ was_hexa = Color.is_valid_hexa(color)
994
+ _color: hsla = Color.to_hsla(color)
983
995
  h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
984
996
  l = int(max(0, min(100, l + lightness_change * 100)))
985
997
  return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
986
998
 
987
999
  @staticmethod
988
- def adjust_saturation(color: Rgba | Hsla | Hexa, saturation_change: float) -> rgba | hexa:
1000
+ def adjust_saturation(color: Rgba | Hexa, saturation_change: float) -> rgba | hexa:
989
1001
  """In- or decrease the saturation of the input color.\n
990
1002
  -----------------------------------------------------------------------------------------------------------
991
1003
  - color (rgba|hexa): HEX or RGBA color
992
1004
  - saturation_change (float): float between -1.0 (saturate by `100%`) and 1.0 (desaturate by `100%`)\n
993
1005
  -----------------------------------------------------------------------------------------------------------
994
1006
  returns (rgba|hexa): the adjusted color in the format of the input color"""
995
- was_hexa = Color.is_valid_hexa(color) # type: ignore[assignment]
996
- _color: hsla = Color.to_hsla(color) # type: ignore[assignment]
1007
+ was_hexa = Color.is_valid_hexa(color)
1008
+ _color: hsla = Color.to_hsla(color)
997
1009
  h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
998
1010
  s = int(max(0, min(100, s + saturation_change * 100)))
999
1011
  return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
xulbux/xx_console.py CHANGED
@@ -11,7 +11,7 @@ from .xx_string import String
11
11
  from .xx_color import Color, Rgba, Hexa
12
12
 
13
13
  from prompt_toolkit.key_binding.key_bindings import KeyBindings
14
- from typing import Optional, Any
14
+ from typing import Optional, Literal, Mapping, Any, cast
15
15
  import prompt_toolkit as _prompt_toolkit
16
16
  import pyperclip as _pyperclip
17
17
  import keyboard as _keyboard
@@ -25,20 +25,29 @@ import os as _os
25
25
  class _ConsoleWidth:
26
26
 
27
27
  def __get__(self, obj, owner=None):
28
- return _os.get_terminal_size().columns
28
+ try:
29
+ return _os.get_terminal_size().columns
30
+ except OSError:
31
+ return 80
29
32
 
30
33
 
31
34
  class _ConsoleHeight:
32
35
 
33
36
  def __get__(self, obj, owner=None):
34
- return _os.get_terminal_size().lines
37
+ try:
38
+ return _os.get_terminal_size().lines
39
+ except OSError:
40
+ return 24
35
41
 
36
42
 
37
43
  class _ConsoleSize:
38
44
 
39
45
  def __get__(self, obj, owner=None):
40
- size = _os.get_terminal_size()
41
- return (size.columns, size.lines)
46
+ try:
47
+ size = _os.get_terminal_size()
48
+ return (size.columns, size.lines)
49
+ except OSError:
50
+ return (80, 24)
42
51
 
43
52
 
44
53
  class _ConsoleUser:
@@ -48,25 +57,31 @@ class _ConsoleUser:
48
57
 
49
58
 
50
59
  class ArgResult:
51
- """Exists: if the argument was found or not\n
52
- Value: the value from behind the found argument"""
60
+ """Represents the result of a parsed command-line argument and contains the following attributes:
61
+ - `exists` -⠀if the argument was found or not
62
+ - `value` -⠀the value given with the found argument\n
63
+ --------------------------------------------------------------------------------------------------------
64
+ When the `ArgResult` instance is accessed as a boolean it will correspond to the `exists` attribute."""
53
65
 
54
66
  def __init__(self, exists: bool, value: Any):
55
- self.exists = exists
56
- self.value = value
67
+ self.exists: bool = exists
68
+ self.value: Any = value
57
69
 
58
70
  def __bool__(self):
59
71
  return self.exists
60
72
 
61
73
 
62
74
  class Args:
63
- """Stores found command arguments under their aliases with their results."""
75
+ """Container for parsed command-line arguments, allowing attribute-style access.
76
+ For example, if an argument `foo` was parsed, it can be accessed via `args.foo`.
77
+ Each such attribute (e.g. `args.foo`) is an instance of `ArgResult`."""
64
78
 
65
- def __init__(self, **kwargs):
66
- for key, value in kwargs.items():
67
- if not key.isidentifier():
68
- raise TypeError(f"Argument alias '{key}' is invalid. It must be a valid Python variable name.")
69
- setattr(self, key, ArgResult(**value))
79
+ def __init__(self, **kwargs: dict[str, Any]):
80
+ for alias_name, data_dict in kwargs.items():
81
+ if not alias_name.isidentifier():
82
+ raise TypeError(f"Argument alias '{alias_name}' is invalid. It must be a valid Python variable name.")
83
+ arg_result_instance = ArgResult(exists=data_dict["exists"], value=data_dict["value"])
84
+ setattr(self, alias_name, arg_result_instance)
70
85
 
71
86
  def __len__(self):
72
87
  return len(vars(self))
@@ -74,6 +89,9 @@ class Args:
74
89
  def __contains__(self, key):
75
90
  return hasattr(self, key)
76
91
 
92
+ def __getattr__(self, name: str) -> ArgResult:
93
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
94
+
77
95
  def __getitem__(self, key):
78
96
  if isinstance(key, int):
79
97
  return list(self.__iter__())[key]
@@ -108,14 +126,13 @@ class Console:
108
126
  h: int = _ConsoleHeight() # type: ignore[assignment]
109
127
  """The height of the console in lines."""
110
128
  wh: tuple[int, int] = _ConsoleSize() # type: ignore[assignment]
111
- """A tuple with the width and height of
112
- the console in characters and lines."""
129
+ """A tuple with the width and height of the console in characters and lines."""
113
130
  usr: str = _ConsoleUser() # type: ignore[assignment]
114
131
  """The name of the current user."""
115
132
 
116
133
  @staticmethod
117
134
  def get_args(
118
- find_args: dict[str, list[str] | tuple[str, ...] | dict[str, list[str] | tuple[str, ...] | Any]],
135
+ find_args: Mapping[str, list[str] | tuple[str, ...] | dict[str, list[str] | tuple[str, ...] | Any]],
119
136
  allow_spaces: bool = False
120
137
  ) -> Args:
121
138
  """Will search for the specified arguments in the command line
@@ -223,13 +240,13 @@ class Console:
223
240
  exit_code: int = 0,
224
241
  reset_ansi: bool = False,
225
242
  ) -> None:
226
- """Will print the `last_prompt` and then pause the program if `pause` is set
243
+ """Will print the `prompt` and then pause the program if `pause` is set
227
244
  to `True` and after the pause, exit the program if `exit` is set to `True`."""
228
245
  print(prompt, end="", flush=True)
229
246
  if reset_ansi:
230
247
  FormatCodes.print("[_]", end="")
231
248
  if pause:
232
- _keyboard.read_event()
249
+ _keyboard.read_key(suppress=True)
233
250
  if exit:
234
251
  _sys.exit(exit_code)
235
252
 
@@ -267,7 +284,11 @@ class Console:
267
284
  information about formatting codes, see `xx_format_codes` module documentation."""
268
285
  title = "" if title is None else title.strip().upper()
269
286
  title_len, tab_len = len(title) + 4, _console_tabsize - ((len(title) + 4) % _console_tabsize)
270
- title_color = "_color" if not title_bg_color else Color.text_color_for_on_bg(title_bg_color)
287
+ if title_bg_color is not None and Color.is_valid(title_bg_color):
288
+ title_bg_color = Color.to_hexa(title_bg_color)
289
+ title_color = Color.text_color_for_on_bg(title_bg_color)
290
+ else:
291
+ title_color = "_color" if title_bg_color is None else "#000"
271
292
  if format_linebreaks:
272
293
  clean_prompt, removals = FormatCodes.remove_formatting(str(prompt), get_removals=True, _ignore_linebreaks=True)
273
294
  prompt_lst = (String.split_count(l, Console.w - (title_len + tab_len)) for l in str(clean_prompt).splitlines())
@@ -275,7 +296,7 @@ class Console:
275
296
  item for lst in prompt_lst for item in ([""] if lst == [] else (lst if isinstance(lst, list) else [lst]))
276
297
  )
277
298
  prompt = f"\n{' ' * title_len}\t".join(
278
- Console.__add_back_removed_parts(list(prompt_lst), removals) # type: ignore[assignment]
299
+ Console.__add_back_removed_parts(list(prompt_lst), cast(tuple[tuple[int, str], ...], removals))
279
300
  )
280
301
  else:
281
302
  prompt = str(prompt)
@@ -332,8 +353,8 @@ class Console:
332
353
  format_linebreaks: bool = True,
333
354
  start: str = "",
334
355
  end: str = "\n",
335
- title_bg_color: Rgba | Hexa = COLOR.yellow,
336
- default_color: Rgba | Hexa = COLOR.text,
356
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.yellow,
357
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
337
358
  pause: bool = False,
338
359
  exit: bool = False,
339
360
  ) -> None:
@@ -350,8 +371,8 @@ class Console:
350
371
  format_linebreaks: bool = True,
351
372
  start: str = "",
352
373
  end: str = "\n",
353
- title_bg_color: Rgba | Hexa = COLOR.blue,
354
- default_color: Rgba | Hexa = COLOR.text,
374
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.blue,
375
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
355
376
  pause: bool = False,
356
377
  exit: bool = False,
357
378
  ) -> None:
@@ -366,8 +387,8 @@ class Console:
366
387
  format_linebreaks: bool = True,
367
388
  start: str = "",
368
389
  end: str = "\n",
369
- title_bg_color: Rgba | Hexa = COLOR.teal,
370
- default_color: Rgba | Hexa = COLOR.text,
390
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.teal,
391
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
371
392
  pause: bool = False,
372
393
  exit: bool = False,
373
394
  ) -> None:
@@ -382,8 +403,8 @@ class Console:
382
403
  format_linebreaks: bool = True,
383
404
  start: str = "",
384
405
  end: str = "\n",
385
- title_bg_color: Rgba | Hexa = COLOR.orange,
386
- default_color: Rgba | Hexa = COLOR.text,
406
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.orange,
407
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
387
408
  pause: bool = False,
388
409
  exit: bool = False,
389
410
  ) -> None:
@@ -398,11 +419,11 @@ class Console:
398
419
  format_linebreaks: bool = True,
399
420
  start: str = "",
400
421
  end: str = "\n",
401
- title_bg_color: Rgba | Hexa = COLOR.red,
402
- default_color: Rgba | Hexa = COLOR.text,
422
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.red,
423
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
403
424
  pause: bool = False,
404
425
  exit: bool = True,
405
- reset_ansi=True,
426
+ reset_ansi: bool = True,
406
427
  ) -> None:
407
428
  """A preset for `log()`: `FAIL` log message with the options to pause
408
429
  at the message and exit the program after the message was printed."""
@@ -415,11 +436,11 @@ class Console:
415
436
  format_linebreaks: bool = True,
416
437
  start: str = "",
417
438
  end: str = "\n",
418
- title_bg_color: Rgba | Hexa = COLOR.magenta,
419
- default_color: Rgba | Hexa = COLOR.text,
439
+ title_bg_color: Optional[Rgba | Hexa] = COLOR.magenta,
440
+ default_color: Optional[Rgba | Hexa] = COLOR.text,
420
441
  pause: bool = False,
421
442
  exit: bool = True,
422
- reset_ansi=True,
443
+ reset_ansi: bool = True,
423
444
  ) -> None:
424
445
  """A preset for `log()`: `EXIT` log message with the options to pause
425
446
  at the message and exit the program after the message was printed."""
@@ -427,18 +448,18 @@ class Console:
427
448
  Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
428
449
 
429
450
  @staticmethod
430
- def log_box(
451
+ def log_box_filled(
431
452
  *values: object,
432
453
  start: str = "",
433
454
  end: str = "\n",
434
455
  box_bg_color: str | Rgba | Hexa = "green",
435
- default_color: Rgba | Hexa = "#000",
456
+ default_color: Optional[Rgba | Hexa] = None,
436
457
  w_padding: int = 2,
437
458
  w_full: bool = False,
438
459
  ) -> None:
439
- """Will print a box, containing a formatted log message:
460
+ """Will print a box with a colored background, containing a formatted log message:
440
461
  - `*values` -⠀the box content (each value is on a new line)
441
- - `start` -⠀something to print before the log box is printed
462
+ - `start` -⠀something to print before the log box is printed (e.g. `\\n`)
442
463
  - `end` -⠀something to print after the log box is printed (e.g. `\\n`)
443
464
  - `box_bg_color` -⠀the background color of the box
444
465
  - `default_color` -⠀the default text color of the `*values`
@@ -447,30 +468,108 @@ class Console:
447
468
  -----------------------------------------------------------------------------------
448
469
  The box content can be formatted with special formatting codes. For more detailed
449
470
  information about formatting codes, see `xx_format_codes` module documentation."""
450
- lines = [line.rstrip() for val in values for line in str(val).splitlines()]
451
- unfmt_lines = [FormatCodes.remove_formatting(line) for line in lines]
452
- max_line_len = max(len(line) for line in unfmt_lines)
471
+ lines, unfmt_lines, max_line_len = Console.__prepare_log_box(values, default_color)
453
472
  pad_w_full = (Console.w - (max_line_len + (2 * w_padding))) if w_full else 0
473
+ if box_bg_color is not None and Color.is_valid(box_bg_color):
474
+ box_bg_color = Color.to_hexa(box_bg_color)
454
475
  lines = [
455
476
  f"[bg:{box_bg_color}]{' ' * w_padding}{line}" + " " *
456
- ((w_padding + max_line_len - len(unfmt)) + pad_w_full) + "[_bg]" for line, unfmt in zip(lines, unfmt_lines)
477
+ ((w_padding + max_line_len - len(unfmt)) + pad_w_full) + "[*]" for line, unfmt in zip(lines, unfmt_lines)
457
478
  ]
458
479
  pady = " " * (Console.w if w_full else max_line_len + (2 * w_padding))
459
480
  FormatCodes.print(
460
- f"{start}[bg:{box_bg_color}]{pady}[_bg]\n"
481
+ f"{start}[bg:{box_bg_color}]{pady}[*]\n"
461
482
  + _COMPILED["formatting"].sub(lambda m: f"{m.group(0)}[bg:{box_bg_color}]", "\n".join(lines))
462
- + f"\n[bg:{box_bg_color}]{pady}[_bg]",
483
+ + f"\n[bg:{box_bg_color}]{pady}[_]",
484
+ default_color=default_color or "#000",
485
+ sep="\n",
486
+ end=end,
487
+ )
488
+
489
+ @staticmethod
490
+ def log_box_bordered(
491
+ *values: object,
492
+ start: str = "",
493
+ end: str = "\n",
494
+ border_type: Literal["standard", "rounded", "strong", "double"] = "rounded",
495
+ border_style: str | Rgba | Hexa = f"dim|{COLOR.gray}",
496
+ default_color: Optional[Rgba | Hexa] = None,
497
+ w_padding: int = 1,
498
+ w_full: bool = False,
499
+ _border_chars: Optional[tuple[str, str, str, str, str, str, str, str]] = None,
500
+ ) -> None:
501
+ """Will print a bordered box, containing a formatted log message:
502
+ - `*values` -⠀the box content (each value is on a new line)
503
+ - `start` -⠀something to print before the log box is printed (e.g. `\\n`)
504
+ - `end` -⠀something to print after the log box is printed (e.g. `\\n`)
505
+ - `border_type` -⠀one of the predefined border character sets
506
+ - `border_style` -⠀the style of the border (special formatting codes)
507
+ - `default_color` -⠀the default text color of the `*values`
508
+ - `w_padding` -⠀the horizontal padding (in chars) to the box content
509
+ - `w_full` -⠀whether to make the box be the full console width or not
510
+ - `_border_chars` -⠀define your own border characters set (overwrites `border_type`)\n
511
+ ---------------------------------------------------------------------------------------
512
+ The box content can be formatted with special formatting codes. For more detailed
513
+ information about formatting codes, see `xx_format_codes` module documentation.\n
514
+ ---------------------------------------------------------------------------------------
515
+ The `border_type` can be one of the following:
516
+ - `"standard" = ('┌', '─', '┐', '│', '┘', '─', '└', '│')`
517
+ - `"rounded" = ('╭', '─', '╮', '│', '╯', '─', '╰', '│')`
518
+ - `"strong" = ('┏', '━', '┓', '┃', '┛', '━', '┗', '┃')`
519
+ - `"double" = ('╔', '═', '╗', '║', '╝', '═', '╚', '║')`\n
520
+ The order of the characters is always:
521
+ 1. top-left corner
522
+ 2. top border
523
+ 3. top-right corner
524
+ 4. right border
525
+ 5. bottom-right corner
526
+ 6. bottom border
527
+ 7. bottom-left corner
528
+ 8. left border"""
529
+ borders = {
530
+ "standard": ('┌', '─', '┐', '│', '┘', '─', '└', '│'),
531
+ "rounded": ('╭', '─', '╮', '│', '╯', '─', '╰', '│'),
532
+ "strong": ('┏', '━', '┓', '┃', '┛', '━', '┗', '┃'),
533
+ "double": ('╔', '═', '╗', '║', '╝', '═', '╚', '║'),
534
+ }
535
+ border_chars = borders.get(border_type, borders["standard"]) if _border_chars is None else _border_chars
536
+ lines, unfmt_lines, max_line_len = Console.__prepare_log_box(values, default_color)
537
+ print(unfmt_lines)
538
+ pad_w_full = (Console.w - (max_line_len + (2 * w_padding)) - (len(border_chars[1] * 2))) if w_full else 0
539
+ if border_style is not None and Color.is_valid(border_style):
540
+ border_style = Color.to_hexa(border_style)
541
+ border_l = f"[{border_style}]{border_chars[7]}[*]"
542
+ border_r = f"[{border_style}]{border_chars[3]}[_]"
543
+ lines = [
544
+ f"{border_l}{' ' * w_padding}{line}[_]" + " " * ((w_padding + max_line_len - len(unfmt)) + pad_w_full) + border_r
545
+ for line, unfmt in zip(lines, unfmt_lines)
546
+ ]
547
+ border_t = f"[{border_style}]{border_chars[0]}{border_chars[1] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[2]}[_]"
548
+ border_b = f"[{border_style}]{border_chars[6]}{border_chars[5] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[4]}[_]"
549
+ FormatCodes.print(
550
+ f"{start}{border_t}[_]\n" + "\n".join(lines) + f"\n{border_b}[_]",
463
551
  default_color=default_color,
464
552
  sep="\n",
465
553
  end=end,
466
554
  )
467
555
 
556
+ @staticmethod
557
+ def __prepare_log_box(
558
+ values: tuple[object, ...],
559
+ default_color: Optional[Rgba | Hexa] = None,
560
+ ) -> tuple[list[str], list[tuple[str, tuple[tuple[int, str], ...]]], int]:
561
+ """Prepares the log box content and returns it along with the max line length."""
562
+ lines = [line for val in values for line in str(val).splitlines()]
563
+ unfmt_lines = [FormatCodes.remove_formatting(line, default_color) for line in lines]
564
+ max_line_len = max(len(line) for line in unfmt_lines)
565
+ return lines, cast(list[tuple[str, tuple[tuple[int, str], ...]]], unfmt_lines), max_line_len
566
+
468
567
  @staticmethod
469
568
  def confirm(
470
569
  prompt: object = "Do you want to continue?",
471
570
  start="",
472
571
  end="\n",
473
- default_color: Rgba | Hexa = COLOR.cyan,
572
+ default_color: Optional[Rgba | Hexa] = COLOR.cyan,
474
573
  default_is_yes: bool = True,
475
574
  ) -> bool:
476
575
  """Ask a yes/no question.\n
@@ -480,7 +579,7 @@ class Console:
480
579
  confirmed = input(
481
580
  FormatCodes.to_ansi(
482
581
  f'{start} {str(prompt)} [_|dim](({"Y" if default_is_yes else "y"}/{"n" if default_is_yes else "N"}): )',
483
- default_color,
582
+ default_color=default_color,
484
583
  )
485
584
  ).strip().lower() in (("", "y", "yes") if default_is_yes else ("y", "yes"))
486
585
  if end:
@@ -492,9 +591,9 @@ class Console:
492
591
  prompt: object = "",
493
592
  start="",
494
593
  end="\n",
495
- default_color: Rgba | Hexa = COLOR.cyan,
594
+ default_color: Optional[Rgba | Hexa] = COLOR.cyan,
496
595
  show_keybindings=True,
497
- input_prefix=" ",
596
+ input_prefix=" ",
498
597
  reset_ansi=True,
499
598
  ) -> str:
500
599
  """An input where users can input (and paste) text over multiple lines.\n
@@ -527,7 +626,7 @@ class Console:
527
626
  prompt: object = "",
528
627
  start="",
529
628
  end="\n",
530
- default_color: Rgba | Hexa = COLOR.cyan,
629
+ default_color: Optional[Rgba | Hexa] = COLOR.cyan,
531
630
  allowed_chars: str = CHARS.all, # type: ignore[assignment]
532
631
  min_len: Optional[int] = None,
533
632
  max_len: Optional[int] = None,
@@ -631,7 +730,7 @@ class Console:
631
730
  prompt: object = "Password: ",
632
731
  start="",
633
732
  end="\n",
634
- default_color: Rgba | Hexa = COLOR.cyan,
733
+ default_color: Optional[Rgba | Hexa] = COLOR.cyan,
635
734
  allowed_chars: str = CHARS.standard_ascii,
636
735
  min_len: Optional[int] = None,
637
736
  max_len: Optional[int] = None,
xulbux/xx_format_codes.py CHANGED
@@ -133,9 +133,13 @@ the formatting code:
133
133
  #### Additional Formatting Codes when a `default_color` is set
134
134
 
135
135
  1. `[*]` resets everything, just like `[_]`, but the text color will remain in `default_color`
136
- 2. `[*color]` will reset the text color, just like `[_color]`, but then also make it `default_color`
136
+ (if no `default_color` is set, it resets everything, exactly like `[_]`)
137
+ 2. `[*color]` `[*c]` will reset the text color, just like `[_color]`, but then also make it `default_color`
138
+ (if no `default_color` is set, both are treated as invalid formatting codes)
137
139
  3. `[default]` will just color the text in `default_color`
140
+ (if no `default_color` is set, it's treated as an invalid formatting code)
138
141
  4. `[background:default]` `[BG:default]` will color the background in `default_color`
142
+ (if no `default_color` is set, both are treated as invalid formatting codes)\n
139
143
 
140
144
  Unlike the standard console colors, the default color can be changed by using the following modifiers:
141
145
 
@@ -148,6 +152,7 @@ Unlike the standard console colors, the default color can be changed by using th
148
152
  - `[ddd]` will darken the `default_color` text by `3 × brightness_steps`%
149
153
  - ... etc.
150
154
  Per default, you can also use `+` and `-` to get lighter and darker `default_color` versions.
155
+ All of these lighten/darken formatting codes are treated as invalid if no `default_color` is set.
151
156
  """
152
157
 
153
158
  from ._consts_ import ANSI
@@ -155,7 +160,7 @@ from .xx_string import String
155
160
  from .xx_regex import Regex, Match, Pattern
156
161
  from .xx_color import Color, rgba, Rgba, Hexa
157
162
 
158
- from typing import Optional
163
+ from typing import Optional, cast
159
164
  import ctypes as _ctypes
160
165
  import regex as _rx
161
166
  import sys as _sys
@@ -164,6 +169,11 @@ import re as _re
164
169
 
165
170
  _CONSOLE_ANSI_CONFIGURED: bool = False
166
171
 
172
+ _ANSI_SEQ_1: str = ANSI.seq(1)
173
+ _DEFAULT_COLOR_MODS: dict[str, str] = {
174
+ "lighten": "+l",
175
+ "darken": "-d",
176
+ }
167
177
  _PREFIX: dict[str, set[str]] = {
168
178
  "BG": {"background", "bg"},
169
179
  "BR": {"bright", "br"},
@@ -174,7 +184,7 @@ _PREFIX_RX: dict[str, str] = {
174
184
  }
175
185
  _COMPILED: dict[str, Pattern] = { # PRECOMPILE REGULAR EXPRESSIONS
176
186
  "*": _re.compile(r"\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]"),
177
- "*color": _re.compile(r"\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]"),
187
+ "*color": _re.compile(r"\[\s*([^]_]*?)\s*\*c(?:olor)?\s*([^]_]*?)\]"),
178
188
  "ansi_seq": _re.compile(ANSI.char + r"(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])"),
179
189
  "formatting": _rx.compile(
180
190
  Regex.brackets("[", "]", is_group=True, ignore_in_strings=False)
@@ -189,7 +199,7 @@ _COMPILED: dict[str, Pattern] = { # PRECOMPILE REGULAR EXPRESSIONS
189
199
  "modifier": _re.compile(
190
200
  r"(?i)((?:BG\s*:)?)\s*("
191
201
  + "|".join(
192
- [f"{_re.escape(m)}+" for m in ANSI.default_color_modifiers["lighten"] + ANSI.default_color_modifiers["darken"]]
202
+ [f"{_re.escape(m)}+" for m in _DEFAULT_COLOR_MODS["lighten"] + _DEFAULT_COLOR_MODS["darken"]]
193
203
  )
194
204
  + r")$"
195
205
  ),
@@ -246,6 +256,7 @@ class FormatCodes:
246
256
  default_color: Optional[Rgba | Hexa] = None,
247
257
  brightness_steps: int = 20,
248
258
  _default_start: bool = True,
259
+ _validate_default: bool = True,
249
260
  ) -> str:
250
261
  """Convert the formatting codes inside a string to ANSI formatting.\n
251
262
  -------------------------------------------------------------------------
@@ -253,15 +264,22 @@ class FormatCodes:
253
264
  `xx_format_codes` module documentation."""
254
265
  if not isinstance(string, str):
255
266
  string = str(string)
256
- if default_color and Color.is_valid_rgba(default_color, False): # type: ignore[assignment]
257
- use_default = True
258
- elif default_color and Color.is_valid_hexa(default_color, False): # type: ignore[assignment]
259
- use_default, default_color = True, Color.to_rgba(default_color)
267
+ use_default, default_specified = False, default_color is not None
268
+ if _validate_default and default_specified:
269
+ if Color.is_valid_rgba(default_color, False):
270
+ use_default = True
271
+ elif Color.is_valid_hexa(default_color, False):
272
+ use_default, default_color = True, Color.to_rgba(default_color) # type: ignore[assignment]
260
273
  else:
261
- use_default = False
274
+ use_default = default_specified
275
+ default_color = cast(Optional[rgba], default_color)
262
276
  if use_default:
263
277
  string = _COMPILED["*"].sub(r"[\1_|default\2]", string) # REPLACE `[…|*|…]` WITH `[…|_|default|…]`
264
- string = _COMPILED["*color"].sub(r"[\1default\2]", string) # REPLACE `[…|*color|…]` WITH `[…|default|…]`
278
+ string = _COMPILED["*color"].sub(
279
+ r"[\1default\2]", string
280
+ ) # REPLACE `[…|*color|…]` OR `[…|*c|…]` WITH `[…|default|…]`
281
+ else:
282
+ string = _COMPILED["*"].sub(r"[\1_\2]", string) # REPLACE `[…|*|…]` WITH `[…|_|…]`
265
283
 
266
284
  def is_valid_color(color: str) -> bool:
267
285
  return bool((color in ANSI.color_map) or Color.is_valid_rgba(color) or Color.is_valid_hexa(color))
@@ -273,15 +291,27 @@ class FormatCodes:
273
291
  if formats_escaped := bool(_COMPILED["escape_char_cond"].match(match.group(0))):
274
292
  _formats = formats = _COMPILED["escape_char"].sub(r"\1", formats) # REMOVE / OR \\
275
293
  if auto_reset_txt and auto_reset_txt.count("[") > 0 and auto_reset_txt.count("]") > 0:
276
- auto_reset_txt = FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)
294
+ auto_reset_txt = FormatCodes.to_ansi(
295
+ auto_reset_txt,
296
+ default_color,
297
+ brightness_steps,
298
+ _default_start=False,
299
+ _validate_default=False,
300
+ )
277
301
  if not formats:
278
302
  return match.group(0)
279
303
  if formats.count("[") > 0 and formats.count("]") > 0:
280
- formats = FormatCodes.to_ansi(formats, default_color, brightness_steps, False)
304
+ formats = FormatCodes.to_ansi(
305
+ formats,
306
+ default_color,
307
+ brightness_steps,
308
+ _default_start=False,
309
+ _validate_default=False,
310
+ )
281
311
  format_keys = [k.strip() for k in formats.split("|") if k.strip()]
282
312
  ansi_formats = [
283
- r if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)) != k # type: ignore[assignment]
284
- else f"[{k}]" for k in format_keys
313
+ r if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)) != k else f"[{k}]"
314
+ for k in format_keys
285
315
  ]
286
316
  if auto_reset_txt and not auto_reset_escaped:
287
317
  reset_keys = []
@@ -292,7 +322,7 @@ class FormatCodes:
292
322
  if k_set & _PREFIX["BR"]:
293
323
  for i in range(len(k)):
294
324
  if is_valid_color(k[i:]):
295
- reset_keys.extend(["_bg", "_color"])
325
+ reset_keys.extend(["_bg", "default"] if use_default else ["_bg", "_c"])
296
326
  break
297
327
  else:
298
328
  for i in range(len(k)):
@@ -302,13 +332,12 @@ class FormatCodes:
302
332
  elif is_valid_color(k) or any(
303
333
  k_lower.startswith(pref_colon := f"{prefix}:") and is_valid_color(k[len(pref_colon):])
304
334
  for prefix in _PREFIX["BR"]):
305
- reset_keys.append("_color")
335
+ reset_keys.append("default" if use_default else "_c")
306
336
  else:
307
337
  reset_keys.append(f"_{k}")
308
338
  ansi_resets = [
309
- r for k in reset_keys
310
- if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps) # type: ignore[assignment]
311
- ).startswith(f"{ANSI.char}{ANSI.start}")
339
+ r for k in reset_keys if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)
340
+ ).startswith(f"{ANSI.char}{ANSI.start}")
312
341
  ]
313
342
  else:
314
343
  ansi_resets = []
@@ -320,14 +349,14 @@ class FormatCodes:
320
349
  else:
321
350
  return (
322
351
  "".join(ansi_formats) + (
323
- f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})"
352
+ f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, _default_start=False, _validate_default=False)})"
324
353
  if auto_reset_escaped and auto_reset_txt else auto_reset_txt if auto_reset_txt else ""
325
354
  ) + ("" if auto_reset_escaped else "".join(ansi_resets))
326
355
  )
327
356
 
328
357
  string = "\n".join(_COMPILED["formatting"].sub(replace_keys, line) for line in string.split("\n"))
329
- return (((FormatCodes.__get_default_ansi(default_color) or "") if _default_start else "") # type: ignore[assignment]
330
- + string) if use_default else string
358
+ return (((FormatCodes.__get_default_ansi(default_color) or "") if _default_start else "")
359
+ + string) if default_color is not None else string
331
360
 
332
361
  @staticmethod
333
362
  def escape_ansi(ansi_string: str) -> str:
@@ -366,6 +395,7 @@ class FormatCodes:
366
395
  @staticmethod
367
396
  def remove_formatting(
368
397
  string: str,
398
+ default_color: Optional[Rgba | Hexa] = None,
369
399
  get_removals: bool = False,
370
400
  _ignore_linebreaks: bool = False,
371
401
  ) -> str | tuple[str, tuple[tuple[int, str], ...]]:
@@ -375,7 +405,7 @@ class FormatCodes:
375
405
  Each tuple contains the position of the removed formatting code and the removed formatting code.\n
376
406
  If `_ignore_linebreaks` is true, linebreaks will be ignored for the removal positions."""
377
407
  return FormatCodes.remove_ansi(
378
- FormatCodes.to_ansi(string),
408
+ FormatCodes.to_ansi(string, default_color=default_color),
379
409
  get_removals=get_removals,
380
410
  _ignore_linebreaks=_ignore_linebreaks,
381
411
  )
@@ -395,19 +425,21 @@ class FormatCodes:
395
425
 
396
426
  @staticmethod
397
427
  def __get_default_ansi(
398
- default_color: tuple,
428
+ default_color: rgba,
399
429
  format_key: Optional[str] = None,
400
430
  brightness_steps: Optional[int] = None,
401
- _modifiers: tuple[str, str] = (ANSI.default_color_modifiers["lighten"], ANSI.default_color_modifiers["darken"]),
431
+ _modifiers: tuple[str, str] = (_DEFAULT_COLOR_MODS["lighten"], _DEFAULT_COLOR_MODS["darken"]),
402
432
  ) -> Optional[str]:
403
433
  """Get the `default_color` and lighter/darker versions of it as ANSI code."""
404
- if not brightness_steps or (format_key and _COMPILED["bg?_default"].search(format_key)):
434
+ if not isinstance(default_color, rgba):
435
+ return None
436
+ _default_color: tuple[int, int, int] = tuple(default_color)[:3]
437
+ if brightness_steps is None or (format_key and _COMPILED["bg?_default"].search(format_key)):
405
438
  return (ANSI.seq_bg_color if format_key and _COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
406
- *default_color[:3]
439
+ *_default_color
407
440
  )
408
441
  if format_key is None or not (format_key in _modifiers[0] or format_key in _modifiers[1]):
409
442
  return None
410
- assert format_key is not None
411
443
  match = _COMPILED["modifier"].match(format_key)
412
444
  if not match:
413
445
  return None
@@ -418,7 +450,7 @@ class FormatCodes:
418
450
  if adjust and adjust > 0:
419
451
  modifiers = mod
420
452
  break
421
- new_rgb = default_color
453
+ new_rgb = _default_color
422
454
  if adjust == 0:
423
455
  return None
424
456
  elif modifiers in _modifiers[0]:
@@ -428,19 +460,17 @@ class FormatCodes:
428
460
  return (ANSI.seq_bg_color if is_bg else ANSI.seq_color).format(*new_rgb[:3])
429
461
 
430
462
  @staticmethod
431
- def __get_replacement(format_key: str, default_color: Optional[Rgba] = None, brightness_steps: int = 20) -> str:
463
+ def __get_replacement(format_key: str, default_color: Optional[rgba], brightness_steps: int = 20) -> str:
432
464
  """Gives you the corresponding ANSI code for the given format key.
433
465
  If `default_color` is not `None`, the text color will be `default_color` if all formats
434
466
  are reset or you can get lighter or darker version of `default_color` (also as BG)"""
435
- use_default = default_color and Color.is_valid_rgba(default_color, False)
436
- _default_color = tuple(Color.to_rgba(default_color)) if use_default else () # type: ignore[assignment]
437
467
  _format_key, format_key = format_key, FormatCodes.__normalize_key(format_key) # NORMALIZE KEY AND SAVE ORIGINAL
438
- if use_default:
439
- if new_default_color := FormatCodes.__get_default_ansi(_default_color, format_key, brightness_steps):
440
- return new_default_color
468
+ if default_color and (new_default_color := FormatCodes.__get_default_ansi(default_color, format_key,
469
+ brightness_steps)):
470
+ return new_default_color
441
471
  for map_key in ANSI.codes_map:
442
472
  if (isinstance(map_key, tuple) and format_key in map_key) or format_key == map_key:
443
- return ANSI.seq().format(
473
+ return _ANSI_SEQ_1.format(
444
474
  next((
445
475
  v for k, v in ANSI.codes_map.items() if format_key == k or (isinstance(k, tuple) and format_key in k)
446
476
  ), None)
xulbux/xx_regex.py CHANGED
@@ -11,13 +11,13 @@ class Regex:
11
11
 
12
12
  @staticmethod
13
13
  def quotes() -> str:
14
- """Matches everything inside quotes. (strings)\n
14
+ """Matches pairs of quotes. (strings)\n
15
15
  --------------------------------------------------------------------------------
16
16
  Will create two named groups:
17
17
  - `quote` the quote type (single or double)
18
18
  - `string` everything inside the found quote pair\n
19
- --------------------------------------------------------------------------------
20
- Attention: Requires non standard library `regex` not standard library `re`!"""
19
+ ---------------------------------------------------------------------------------
20
+ Attention: Requires non-standard library `regex`, not standard library `re`!"""
21
21
  return r'(?P<quote>[\'"])(?P<string>(?:\\.|(?!\g<quote>).)*?)\g<quote>'
22
22
 
23
23
  @staticmethod
@@ -28,16 +28,16 @@ class Regex:
28
28
  strip_spaces: bool = True,
29
29
  ignore_in_strings: bool = True,
30
30
  ) -> str:
31
- """Matches everything inside brackets, including other nested brackets.\n
32
- --------------------------------------------------------------------------------
31
+ """Matches everything inside pairs of brackets, including other nested brackets.\n
32
+ -----------------------------------------------------------------------------------
33
33
  If `is_group` is true, you will be able to reference the matched content as a
34
34
  group (e.g. `match.group(…)` or `r'\\…'`).
35
35
  If `strip_spaces` is true, it will ignore spaces around the content inside the
36
36
  brackets.
37
37
  If `ignore_in_strings` is true and a bracket is inside a string (e.g. `'...'`
38
38
  or `"..."`), it will not be counted as the matching closing bracket.\n
39
- --------------------------------------------------------------------------------
40
- Attention: Requires non standard library `regex` not standard library `re`!"""
39
+ -----------------------------------------------------------------------------------
40
+ Attention: Requires non-standard library `regex`, not standard library `re`!"""
41
41
  g, b1, b2, s1, s2 = (
42
42
  "" if is_group else "?:",
43
43
  _rx.escape(bracket1) if len(bracket1) == 1 else bracket1,
@@ -73,8 +73,8 @@ class Regex:
73
73
  1. function name
74
74
  2. the function's arguments\n
75
75
  If no `func_name` is given, it will match any function call.\n
76
- --------------------------------------------------------------------------------
77
- Attention: Requires non standard library `regex` not standard library `re`!"""
76
+ ---------------------------------------------------------------------------------
77
+ Attention: Requires non-standard library `regex`, not standard library `re`!"""
78
78
  return (
79
79
  r"(?<=\b)(" + (r"[\w_]+" if func_name is None else func_name) + r")\s*" + Regex.brackets("(", ")", is_group=True)
80
80
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xulbux
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
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-Expression: MIT
@@ -34,14 +34,14 @@ Requires-Dist: flake8>=6.1.0; extra == "dev"
34
34
  Requires-Dist: flake8-pyproject>=1.2.3; extra == "dev"
35
35
  Dynamic: license-file
36
36
 
37
- # **$\color{#8085FF}\Huge\textsf{XulbuX}$**
37
+ # **$\Huge\textsf{XulbuX}$**
38
38
 
39
- **$\color{#8085FF}\textsf{XulbuX}$** is library that contains many useful classes, types, and functions,
39
+ **XulbuX** is library that contains many useful classes, types, and functions,
40
40
  ranging from console logging and working with colors to file management and system operations.
41
41
  The library is designed to simplify common programming tasks and improve code readability through its collection of tools.
42
42
 
43
- For precise information about the library, see the library's [wiki page](https://github.com/XulbuX/PythonLibraryXulbuX/wiki).<br>
44
- For the libraries latest changes and updates, see the [change log](https://github.com/XulbuX/PythonLibraryXulbuX/blob/main/CHANGELOG.md).
43
+ For precise information about the library, see the library's [**wiki page**](https://github.com/XulbuX/PythonLibraryXulbuX/wiki).<br>
44
+ For the libraries latest changes and updates, see the [**change log**](https://github.com/XulbuX/PythonLibraryXulbuX/blob/main/CHANGELOG.md).
45
45
 
46
46
  <br>
47
47
 
@@ -81,20 +81,20 @@ from xulbux import rgba, hsla, hexa
81
81
 
82
82
  ## Modules
83
83
 
84
- | Module | Short Description |
85
- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- |
86
- | [![xx_code](https://img.shields.io/badge/xx__code-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_code) | advanced code-string operations (*changing the indent, finding function calls, ...*) |
87
- | [![xx_color](https://img.shields.io/badge/xx__color-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_color) | everything around colors (*converting, blending, searching colors in strings, ...*) |
88
- | [![xx_console](https://img.shields.io/badge/xx__console-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_console) | advanced actions related to the console (*pretty logging, advanced inputs, ...*) |
89
- | [![xx_data](https://img.shields.io/badge/xx__data-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_data) | advanced operations with data structures (*compare, generate path ID's, pretty print/format, ...*) |
84
+ | Module | Short Description |
85
+ | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- |
86
+ | [![xx_code](https://img.shields.io/badge/xx__code-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_code) | advanced code-string operations (*changing the indent, finding function calls, ...*) |
87
+ | [![xx_color](https://img.shields.io/badge/xx__color-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_color) | everything around colors (*converting, blending, searching colors in strings, ...*) |
88
+ | [![xx_console](https://img.shields.io/badge/xx__console-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_console) | advanced actions related to the console (*pretty logging, advanced inputs, ...*) |
89
+ | [![xx_data](https://img.shields.io/badge/xx__data-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_data) | advanced operations with data structures (*compare, generate path ID's, pretty print/format, ...*) |
90
90
  | [![xx_env_path](https://img.shields.io/badge/xx__env__path-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_env_path) | getting and editing the PATH variable (*get paths, check for paths, add paths, ...*) |
91
- | [![xx_file](https://img.shields.io/badge/xx__file-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_file) | advanced working with files (*create files, rename file-extensions, ...*) |
91
+ | [![xx_file](https://img.shields.io/badge/xx__file-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_file) | advanced working with files (*create files, rename file-extensions, ...*) |
92
92
  | [![xx_format_codes](https://img.shields.io/badge/xx__format__codes-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_format_codes) | easy pretty printing with custom format codes (*print, inputs, custom format codes to ANSI, ...*) |
93
- | [![xx_json](https://img.shields.io/badge/xx__json-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_json) | advanced working with json files (*read, create, update, ...*) |
94
- | [![xx_path](https://img.shields.io/badge/xx__path-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_path) | advanced path operations (*get paths, smart-extend relative paths, delete paths, ...*) |
95
- | ![xx_regex](https://img.shields.io/badge/xx__regex-6065FF?style=flat) | generated regex pattern-templates (*match bracket- and quote pairs, match colors, ...*) |
96
- | [![xx_string](https://img.shields.io/badge/xx__string-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_string) | helpful actions when working with strings. (*normalize, escape, decompose, ...*) |
97
- | ![xx_system](https://img.shields.io/badge/xx__system-6065FF?style=flat) | advanced system actions (*restart with message, check installed Python libs, ...*) |
93
+ | [![xx_json](https://img.shields.io/badge/xx__json-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_json) | advanced working with json files (*read, create, update, ...*) |
94
+ | [![xx_path](https://img.shields.io/badge/xx__path-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_path) | advanced path operations (*get paths, smart-extend relative paths, delete paths, ...*) |
95
+ | ![xx_regex](https://img.shields.io/badge/xx__regex-6065FF?style=flat) | generated regex pattern-templates (*match bracket- and quote pairs, match colors, ...*) |
96
+ | [![xx_string](https://img.shields.io/badge/xx__string-6065FF?style=flat)](https://github.com/XulbuX/PythonLibraryXulbuX/wiki/xx_string) | helpful actions when working with strings. (*normalize, escape, decompose, ...*) |
97
+ | ![xx_system](https://img.shields.io/badge/xx__system-6065FF?style=flat) | advanced system actions (*restart with message, check installed Python libs, ...*) |
98
98
 
99
99
  <br>
100
100
 
@@ -111,15 +111,15 @@ def main() -> None:
111
111
 
112
112
  # LET THE USER ENTER A HEXA COLOR IN ANY HEXA FORMAT
113
113
  input_clr = FormatCodes.input(
114
- "\n[b](Enter a HEXA color in any format) [dim](>) "
114
+ "\n[b](Enter a HEXA color in any format) [dim](>) "
115
115
  )
116
116
 
117
117
  # ANNOUNCE INDEXING THE INPUT COLOR
118
118
  Console.log(
119
- "INDEX",
120
- "Indexing the input HEXA color...",
121
- start="\n",
122
- title_bg_color=COLOR.blue,
119
+ "INDEX",
120
+ "Indexing the input HEXA color...",
121
+ start="\n",
122
+ title_bg_color=COLOR.blue,
123
123
  )
124
124
 
125
125
  try:
@@ -129,16 +129,16 @@ def main() -> None:
129
129
  except ValueError:
130
130
  # ANNOUNCE THE ERROR AND EXIT THE PROGRAM
131
131
  Console.fail(
132
- "The input HEXA color is invalid.",
133
- end="\n\n",
134
- exit=True,
132
+ "The input HEXA color is invalid.",
133
+ end="\n\n",
134
+ exit=True,
135
135
  )
136
136
 
137
137
  # ANNOUNCE STARTING THE CONVERSION
138
138
  Console.log(
139
- "CONVERT",
140
- "Converting the HEXA color into different types...",
141
- title_bg_color=COLOR.tangerine,
139
+ "CONVERT",
140
+ "Converting the HEXA color into different types...",
141
+ title_bg_color=COLOR.tangerine,
142
142
  )
143
143
 
144
144
  # CONVERT THE HEXA COLOR INTO THE TWO OTHER COLOR TYPES
@@ -147,14 +147,16 @@ def main() -> None:
147
147
 
148
148
  # ANNOUNCE THE SUCCESSFUL CONVERSION
149
149
  Console.done(
150
- "Successfully converted color into different types.",
151
- end="\n\n",
150
+ "Successfully converted color into different types.",
151
+ end="\n\n",
152
152
  )
153
153
 
154
154
  # PRETTY PRINT THE COLOR IN DIFFERENT TYPES
155
- FormatCodes.print(f"[b](HEXA:) [i|white]({hexa_color})")
156
- FormatCodes.print(f"[b](RGBA:) [i|white]({rgba_color})")
157
- FormatCodes.print(f"[b](HSLA:) [i|white]({hsla_color})\n")
155
+ Console.log_box_bordered(
156
+ f"[b](HEXA:) [i|white]({hexa_color})",
157
+ f"[b](RGBA:) [i|white]({rgba_color})",
158
+ f"[b](HSLA:) [i|white]({hsla_color})",
159
+ )
158
160
 
159
161
 
160
162
  if __name__ == "__main__":
@@ -165,4 +167,4 @@ if __name__ == "__main__":
165
167
  <br>
166
168
 
167
169
  --------------------------------------------------------------
168
- [View this library on PyPI](https://pypi.org/project/XulbuX/)
170
+ [View this library on **PyPI**](https://pypi.org/project/XulbuX/)
@@ -0,0 +1,21 @@
1
+ xulbux/__init__.py,sha256=vd8y5L0AwqnxqXm_IAJ5grbfm8ZkcSHJx36n1KUQTlc,815
2
+ xulbux/_cli_.py,sha256=J4vfJHLJEYxCZzA_VJUB46w2WGShfdYFoetsLG5PfKo,3428
3
+ xulbux/_consts_.py,sha256=4CLJE-YH3si2YQ-IHcjsT5PW0D7vGvm8ZH13rUoG5cE,6166
4
+ xulbux/xx_code.py,sha256=b8MJId-BmZOsBH38z-h055op7qIICdsTT3rFJ7SWkiE,6111
5
+ xulbux/xx_color.py,sha256=Thj7fFTc8x-VmYyULI3HQW0uCk9dIihwrGUwuXGn83s,49744
6
+ xulbux/xx_console.py,sha256=c61EXVGQo_OL_MUWb4oXbIr6EKesm1v-FwxBIe47ZTg,34866
7
+ xulbux/xx_data.py,sha256=nEfVwK6-ILaL3K-bLezKpG1G7117CY5ZgC3BGwANrUE,30886
8
+ xulbux/xx_env_path.py,sha256=x56mKK4lSvU5yMCAs8k0RVIqXWUJcpcHYz5HoZ_RklM,4160
9
+ xulbux/xx_file.py,sha256=KerXOvKS93zIoAt36YTYuZboSmxVFVf2WcOrDcdwXfE,2627
10
+ xulbux/xx_format_codes.py,sha256=-tSsgy1tRelmU5x5ZS7ujaiXntyRqk1sm759tyvVIyU,24698
11
+ xulbux/xx_json.py,sha256=V7vdfpvSe9wpktR_c8zG_Meix7x9IRmn66k5nB3HUyo,7457
12
+ xulbux/xx_path.py,sha256=lLAEVZrW0TAwCewlONFVQcQ_8tVn9LTJZVOZpeGvE5s,7673
13
+ xulbux/xx_regex.py,sha256=_BtMHRDNcD9zF4SL87dQuUVZcYGfZx9H5YNSDiEtzm8,8059
14
+ xulbux/xx_string.py,sha256=QaTo0TQ9m_2USNgQNaVw5ivQt-A1E-e5x8OpIB3xIlY,5561
15
+ xulbux/xx_system.py,sha256=Tsx4wgztUg46KloqcGeiFkarDoM3EgJLXw3XNxgHBmU,6460
16
+ xulbux-1.7.2.dist-info/licenses/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
17
+ xulbux-1.7.2.dist-info/METADATA,sha256=vlIkVkj97ZLjiFajkGrSZ5O0GDrzuIunJ1E1iqY-QTA,9209
18
+ xulbux-1.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ xulbux-1.7.2.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
20
+ xulbux-1.7.2.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
21
+ xulbux-1.7.2.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- xulbux/__init__.py,sha256=TbXPioXPENwpR9t4lvqBBhV4cby0JGdIrSzzdMFYrEc,815
2
- xulbux/_cli_.py,sha256=J4vfJHLJEYxCZzA_VJUB46w2WGShfdYFoetsLG5PfKo,3428
3
- xulbux/_consts_.py,sha256=AuYTTmqrP2lawyVGlPLUaP1syxOoPA-ejJGH7WlwFzk,6314
4
- xulbux/xx_code.py,sha256=w9yO-GPMeaE-xDi-L3VtpPpWpu5jOwagfMsG93aXANE,6106
5
- xulbux/xx_color.py,sha256=ZEV9AG3MtgNMh4t4VexmBiaperan1pHO4d_VPCdv188,49923
6
- xulbux/xx_console.py,sha256=WmH6YURaA-Y-LNB-2kZc8SuPxoZim8ZmzsuI01V9vcM,28682
7
- xulbux/xx_data.py,sha256=nEfVwK6-ILaL3K-bLezKpG1G7117CY5ZgC3BGwANrUE,30886
8
- xulbux/xx_env_path.py,sha256=x56mKK4lSvU5yMCAs8k0RVIqXWUJcpcHYz5HoZ_RklM,4160
9
- xulbux/xx_file.py,sha256=KerXOvKS93zIoAt36YTYuZboSmxVFVf2WcOrDcdwXfE,2627
10
- xulbux/xx_format_codes.py,sha256=fOFidFDBYV-rUXOlYTeKIZarfh9Q1jSUCGYyk8pg-ic,23397
11
- xulbux/xx_json.py,sha256=V7vdfpvSe9wpktR_c8zG_Meix7x9IRmn66k5nB3HUyo,7457
12
- xulbux/xx_path.py,sha256=lLAEVZrW0TAwCewlONFVQcQ_8tVn9LTJZVOZpeGvE5s,7673
13
- xulbux/xx_regex.py,sha256=ejqVs4a-eCSTrSQfENoyl0AVztb8BuI2TNl-wzMQwYw,8048
14
- xulbux/xx_string.py,sha256=QaTo0TQ9m_2USNgQNaVw5ivQt-A1E-e5x8OpIB3xIlY,5561
15
- xulbux/xx_system.py,sha256=Tsx4wgztUg46KloqcGeiFkarDoM3EgJLXw3XNxgHBmU,6460
16
- xulbux-1.7.0.dist-info/licenses/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
17
- xulbux-1.7.0.dist-info/METADATA,sha256=symGYQzZhSYyGcwM_dGI3FETAcqgTL-ft90CLtJoNlY,9222
18
- xulbux-1.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- xulbux-1.7.0.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
20
- xulbux-1.7.0.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
21
- xulbux-1.7.0.dist-info/RECORD,,
File without changes