Skip to content

Commit 3decd1c

Browse files
committed
make type corrections, add docstrings to dunder methods, rename constant, add new helper methods & add new class properties
1 parent 4b34217 commit 3decd1c

16 files changed

Lines changed: 323 additions & 96 deletions

CHANGELOG.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@
1818
<span id="v1-9-3" />
1919

2020
## ... `v1.9.3` Big Update 🚀
21+
2122
* Added a new method `Color.str_to_hsla()` to parse HSLA colors from strings.
22-
* Changed the default syntax colors for `Data.to_str()` and therefore also `Data.print()` to console default colors.
23+
* Changed the default syntax highlighting for `Data.to_str()` and therefore also `Data.print()` to use console default colors.
24+
* Added the missing but needed dunder methods to the `Args` and `ArgResult` classes and the `rgba`, `hsla` and `hexa` color objects for better usability and type checking.
25+
* Added three new methods to `Args`:
26+
- `get()` returns the argument result for a given alias, or a default value if not found
27+
- `existing()` yields only the existing arguments as tuples of `(alias, ArgResult)`
28+
- `missing()` yields only the missing arguments as tuples of `(alias, ArgResult)`
2329
* Added a new attribute `is_positional` to `ArgResult`, which indicates whether the argument is a positional argument or not.
30+
* The `ArgResult` class now also has a `dict()` method, which returns the argument result as a dictionary.
31+
* Added new properties `is_tty` and `supports_color` to the `Console` class, `home` to the `Path` class and `is_win` to the `System` class.
2432
* Added the option to add format specifiers to the `{current}`, `{total}` and `{percentage}` placeholders in the `bar_format` and `limited_bar_format` of `ProgressBar`.
2533
* Finally fixed the `C901 'Console.get_args' is too complex (39)` linting error by refactoring the method into its own helper class.
34+
* Changed the string- and repr-representations of the `rgba` and `hsla` color objects and newly implemented it for the `Args` and `ArgResult` classes.
2635
* Made internal, global constants, which's values never change, into `Final` constants for better type checking.
27-
* The names of all internal classes and methods are all noi longer prefixed with a double underscore (`__`), but a single underscore (`_`) instead.
36+
* The names of all internal classes and methods are all no longer prefixed with a double underscore (`__`), but a single underscore (`_`) instead.
2837
* Changed all methods defined as `@staticmethod` to `@classmethod` where applicable, to improve inheritance capabilities.
2938
* Adjusted the whole library's type hints to be way more strict and accurate, using `mypy` as static type checker.
3039
* Change the class-property definitions to be defined via `metaclass` and using `@property` decorators, to make them compatible with `mypyc`.
@@ -35,13 +44,15 @@
3544

3645
**BREAKING CHANGES:**
3746
* Renamed `Data.to_str()` to `Data.render()`, since that describes its functionality better (*especially with the syntax highlighting option*).
47+
* Renamed the constant `ANSI.ESCAPED_CHAR` to `ANSI.CHAR_ESCAPED` for better consistency with the other constant names.
3848
* Removed the general `Pattern` and `Match` type aliases from the `base.types` module (*they are pointless since you should always use a specific type and not "type1 OR typeB"*).
3949
* Removed the `_` prefix from the param `_syntax_highlighting` in `Data.render()`, since it's no longer just for internal use.
4050

4151

4252
<span id="v1-9-2" />
4353

4454
## 16.12.2025 `v1.9.2`
55+
4556
* Added a new class `LazyRegex` to the `regex` module, which is used to define regex patterns that are only compiled when they are used for the first time.
4657
* Removed unnecessary character escaping in the precompiled regex patterns in the `console` module.
4758
* Removed all the runtime type-checks that can also be checked using static type-checking tools, since you're supposed to use type checkers in modern python anyway, and to improve performance.
@@ -64,6 +75,7 @@
6475
<span id="v1-9-1" />
6576

6677
## 26.11.2025 `v1.9.1`
78+
6779
* Unified the module and class docstring styles throughout the whole library.
6880
* Moved the Protocol `ProgressUpdater` from the `console` module to the `types` module.
6981
* Added throttling to the `ProgressBar` update methods to impact the actual process' performance as little as possible.
@@ -79,6 +91,7 @@
7991
<span id="v1-9-0" />
8092

8193
## 21.11.2025 `v1.9.0` Big Update 🚀
94+
8295
* Standardized the docstrings for all public methods in the whole library to use the same style and structure.
8396
* Replaced left over single quotes with double quotes for consistency.
8497
* Fixed a bug inside `Data.remove_empty_items()`, where types other than strings where passed to `String.is_empty()`, which caused an exception.
@@ -218,7 +231,7 @@
218231
## 17.06.2025 `v1.7.2`
219232

220233
* The `Console.w`, `Console.h` and `Console.wh` class properties now return a default size if there is no console, instead of throwing an error.
221-
* It wasn't actually possible to use default console-colors (*e.g.* `"red"`, `"green"`, ...) for the color params in `Console.log()` so that option was completely removed again.
234+
* It wasn't actually possible to use default console-colors (*e.g.* `"red"`, `"green"`, ) for the color params in `Console.log()` so that option was completely removed again.
222235
* Upgraded the speed of `FormatCodes.to_ansi()` by adding the internal ability to skip the `default_color` validation.
223236
* Fixed type hints for the whole library.
224237
* Fixed a small bug in `Console.pause_exit()`, where the key, pressed to unpause wasn't suppressed, so it was written into the next console input after unpausing.
@@ -232,7 +245,7 @@
232245
* Added a new method `Console.log_box_bordered()`, which does the same as `Console.log_box_filled()`, but with a border instead of a background color.
233246
* The module `xx_format_codes` now treats the `[*]` to-default-color-reset as a normal full-reset, when no `default_color` is set, instead of just counting it as an invalid format code.
234247
* Fixed bug where entering a color as HEX integer in the color params of the methods `Console.log()`, `Console.log_box_filled()` and `Console.log_box_bordered()` would not work, because it was not properly converted to a format code.
235-
* You can now use default console colors (*e.g.* `"red"`, `"green"`, ...) for the color params in `Console.log()`.
248+
* You can now use default console colors (*e.g.* `"red"`, `"green"`, ) for the color params in `Console.log()`.
236249
* The methods `Console.log_box_filled()` and `Console.log_box_bordered()` no longer right-strip spaces, so you can make multiple log boxes the same width, by adding spaces to the end of the text.
237250

238251
**BREAKING CHANGES:**
@@ -798,7 +811,7 @@ from XulbuX import rgb, hsl, hexa
798811
<thead>
799812
<tr>
800813
<th>Features</th>
801-
<th>class, type, function, ...</th>
814+
<th>class, type, function, </th>
802815
</tr>
803816
</thead>
804817
<tbody>

src/xulbux/base/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class CHARS:
7676
class ANSI:
7777
"""Constants and utilities for ANSI escape code sequences."""
7878

79-
ESCAPED_CHAR: Final = "\\x1b"
79+
CHAR_ESCAPED: Final = r"\x1b"
8080
"""Printable ANSI escape character."""
8181
CHAR: Final = "\x1b"
8282
"""ANSI escape character."""

src/xulbux/color.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def __init__(self, r: int, g: int, b: int, a: Optional[float] = None, _validate:
6666
self.a = None if a is None else (1.0 if a > 1.0 else float(a))
6767

6868
def __len__(self) -> int:
69+
"""The number of components in the color (3 or 4)."""
6970
return 3 if self.a is None else 4
7071

7172
def __iter__(self) -> Iterator:
@@ -74,17 +75,21 @@ def __iter__(self) -> Iterator:
7475
def __getitem__(self, index: int) -> int | float:
7576
return ((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))[index]
7677

78+
def __eq__(self, other: object) -> bool:
79+
"""Check if two `rgba` objects are the same color."""
80+
if not isinstance(other, rgba):
81+
return False
82+
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
83+
84+
def __ne__(self, other: object) -> bool:
85+
"""Check if two `rgba` objects are different colors."""
86+
return not self.__eq__(other)
87+
7788
def __repr__(self) -> str:
7889
return f"rgba({self.r}, {self.g}, {self.b}{'' if self.a is None else f', {self.a}'})"
7990

8091
def __str__(self) -> str:
81-
return f"({self.r}, {self.g}, {self.b}{'' if self.a is None else f', {self.a}'})"
82-
83-
def __eq__(self, other: "rgba") -> bool: # type: ignore[override]
84-
if not isinstance(other, rgba):
85-
return False
86-
else:
87-
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
92+
return self.__repr__()
8893

8994
def dict(self) -> dict:
9095
"""Returns the color components as a dictionary with keys `"r"`, `"g"`, `"b"` and optionally `"a"`."""
@@ -325,6 +330,7 @@ def __init__(self, h: int, s: int, l: int, a: Optional[float] = None, _validate:
325330
self.a = None if a is None else (1.0 if a > 1.0 else float(a))
326331

327332
def __len__(self) -> int:
333+
"""The number of components in the color (3 or 4)."""
328334
return 3 if self.a is None else 4
329335

330336
def __iter__(self) -> Iterator:
@@ -333,17 +339,21 @@ def __iter__(self) -> Iterator:
333339
def __getitem__(self, index: int) -> int | float:
334340
return ((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))[index]
335341

342+
def __eq__(self, other: object) -> bool:
343+
"""Check if two `hsla` objects are the same color."""
344+
if not isinstance(other, hsla):
345+
return False
346+
return (self.h, self.s, self.l, self.a) == (other.h, other.s, other.l, other.a)
347+
348+
def __ne__(self, other: object) -> bool:
349+
"""Check if two `hsla` objects are different colors."""
350+
return not self.__eq__(other)
351+
336352
def __repr__(self) -> str:
337-
return f'hsla({self.h}°, {self.s}%, {self.l}%{"" if self.a is None else f", {self.a}"})'
353+
return f"hsla({self.h}°, {self.s}%, {self.l}%{'' if self.a is None else f', {self.a}'})"
338354

339355
def __str__(self) -> str:
340-
return f'({self.h}°, {self.s}%, {self.l}%{"" if self.a is None else f", {self.a}"})'
341-
342-
def __eq__(self, other: "hsla") -> bool: # type: ignore[override]
343-
if not isinstance(other, hsla):
344-
return False
345-
else:
346-
return (self.h, self.s, self.l, self.a) == (other.h, other.s, other.l, other.a)
356+
return self.__repr__()
347357

348358
def dict(self) -> dict:
349359
"""Returns the color components as a dictionary with keys `"h"`, `"s"`, `"l"` and optionally `"a"`."""
@@ -617,6 +627,7 @@ def __init__(
617627
raise TypeError(f"The 'color' parameter must be a string or integer, got {type(color)}")
618628

619629
def __len__(self) -> int:
630+
"""The number of components in the color (3 or 4)."""
620631
return 3 if self.a is None else 4
621632

622633
def __iter__(self) -> Iterator:
@@ -627,18 +638,21 @@ def __getitem__(self, index: int) -> str | int:
627638
return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") \
628639
+ (() if self.a is None else (f"{int(self.a * 255):02X}", )))[index]
629640

641+
def __eq__(self, other: object) -> bool:
642+
"""Check if two `hexa` objects are the same color."""
643+
if not isinstance(other, hexa):
644+
return False
645+
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
646+
647+
def __ne__(self, other: object) -> bool:
648+
"""Check if two `hexa` objects are different colors."""
649+
return not self.__eq__(other)
650+
630651
def __repr__(self) -> str:
631-
return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"})'
652+
return f"hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{'' if self.a is None else f'{int(self.a * 255):02X}'})"
632653

633654
def __str__(self) -> str:
634-
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
635-
636-
def __eq__(self, other: "hexa") -> bool: # type: ignore[override]
637-
"""Returns whether the other color is equal to this one."""
638-
if not isinstance(other, hexa):
639-
return False
640-
else:
641-
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
655+
return f"#{self.r:02X}{self.g:02X}{self.b:02X}{'' if self.a is None else f'{int(self.a * 255):02X}'}"
642656

643657
def dict(self) -> dict:
644658
"""Returns the color components as a dictionary with hex string values for keys `"r"`, `"g"`, `"b"` and optionally `"a"`."""

0 commit comments

Comments
 (0)