xulbux 1.6.1__py3-none-any.whl → 1.6.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 +1 -1
- xulbux/_consts_.py +16 -16
- xulbux/xx_color.py +102 -92
- xulbux/xx_console.py +0 -12
- xulbux/xx_env_path.py +1 -1
- xulbux/xx_format_codes.py +76 -26
- xulbux/xx_system.py +52 -0
- {xulbux-1.6.1.dist-info → xulbux-1.6.2.dist-info}/METADATA +2 -2
- xulbux-1.6.2.dist-info/RECORD +21 -0
- xulbux-1.6.1.dist-info/RECORD +0 -21
- {xulbux-1.6.1.dist-info → xulbux-1.6.2.dist-info}/LICENSE +0 -0
- {xulbux-1.6.1.dist-info → xulbux-1.6.2.dist-info}/WHEEL +0 -0
- {xulbux-1.6.1.dist-info → xulbux-1.6.2.dist-info}/entry_points.txt +0 -0
- {xulbux-1.6.1.dist-info → xulbux-1.6.2.dist-info}/top_level.txt +0 -0
xulbux/__init__.py
CHANGED
xulbux/_consts_.py
CHANGED
|
@@ -116,14 +116,14 @@ class ANSI:
|
|
|
116
116
|
"cyan": 36,
|
|
117
117
|
"white": 37,
|
|
118
118
|
########## BRIGHT DEFAULT CONSOLE COLORS ###########
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
"br:black": 90,
|
|
120
|
+
"br:red": 91,
|
|
121
|
+
"br:green": 92,
|
|
122
|
+
"br:yellow": 93,
|
|
123
|
+
"br:blue": 94,
|
|
124
|
+
"br:magenta": 95,
|
|
125
|
+
"br:cyan": 96,
|
|
126
|
+
"br:white": 97,
|
|
127
127
|
######## DEFAULT CONSOLE BACKGROUND COLORS #########
|
|
128
128
|
"bg:black": 40,
|
|
129
129
|
"bg:red": 41,
|
|
@@ -134,12 +134,12 @@ class ANSI:
|
|
|
134
134
|
"bg:cyan": 46,
|
|
135
135
|
"bg:white": 47,
|
|
136
136
|
##### BRIGHT DEFAULT CONSOLE BACKGROUND COLORS #####
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
"bg:br:black": 100,
|
|
138
|
+
"bg:br:red": 101,
|
|
139
|
+
"bg:br:green": 102,
|
|
140
|
+
"bg:br:yellow": 103,
|
|
141
|
+
"bg:br:blue": 104,
|
|
142
|
+
"bg:br:magenta": 105,
|
|
143
|
+
"bg:br:cyan": 106,
|
|
144
|
+
"bg:br:white": 107,
|
|
145
145
|
}
|
xulbux/xx_color.py
CHANGED
|
@@ -59,7 +59,10 @@ class rgba:
|
|
|
59
59
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
60
60
|
- `complementary()` to get the complementary color"""
|
|
61
61
|
|
|
62
|
-
def __init__(self, r: int, g: int, b: int, a: float = None):
|
|
62
|
+
def __init__(self, r: int, g: int, b: int, a: float = None, _validate: bool = True):
|
|
63
|
+
if not _validate:
|
|
64
|
+
self.r, self.g, self.b, self.a = r, g, b, a
|
|
65
|
+
return
|
|
63
66
|
if any(isinstance(x, rgba) for x in (r, g, b)):
|
|
64
67
|
raise ValueError("Color is already a rgba() color")
|
|
65
68
|
elif not all(isinstance(x, int) and 0 <= x <= 255 for x in (r, g, b)):
|
|
@@ -70,25 +73,25 @@ class rgba:
|
|
|
70
73
|
elif a is not None and not (isinstance(a, (int, float)) and 0 <= a <= 1):
|
|
71
74
|
raise ValueError(f"Alpha channel must be a float/int in [0.0, 1.0]: got '{a}'")
|
|
72
75
|
self.r, self.g, self.b = r, g, b
|
|
73
|
-
self.a = (1.0 if a > 1.0 else float(a))
|
|
76
|
+
self.a = None if a is None else (1.0 if a > 1.0 else float(a))
|
|
74
77
|
|
|
75
78
|
def __len__(self):
|
|
76
|
-
return
|
|
79
|
+
return 3 if self.a is None else 4
|
|
77
80
|
|
|
78
81
|
def __iter__(self):
|
|
79
|
-
return iter((self.r, self.g, self.b) + ((
|
|
82
|
+
return iter((self.r, self.g, self.b) + (() if self.a is None else (self.a,)))
|
|
80
83
|
|
|
81
84
|
def __dict__(self):
|
|
82
85
|
return self.dict()
|
|
83
86
|
|
|
84
87
|
def __getitem__(self, index):
|
|
85
|
-
return ((self.r, self.g, self.b) + ((
|
|
88
|
+
return ((self.r, self.g, self.b) + (() if self.a is None else (self.a,)))[index]
|
|
86
89
|
|
|
87
90
|
def __repr__(self):
|
|
88
|
-
return f'rgba({self.r}, {self.g}, {self.b}{
|
|
91
|
+
return f'rgba({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
|
|
89
92
|
|
|
90
93
|
def __str__(self):
|
|
91
|
-
return f'({self.r}, {self.g}, {self.b}{
|
|
94
|
+
return f'({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
|
|
92
95
|
|
|
93
96
|
def __eq__(self, other):
|
|
94
97
|
if not isinstance(other, rgba):
|
|
@@ -102,7 +105,7 @@ class rgba:
|
|
|
102
105
|
|
|
103
106
|
def dict(self) -> dict:
|
|
104
107
|
"""Returns the color components as a dictionary with keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
|
|
105
|
-
return dict(r=self.r, g=self.g, b=self.b
|
|
108
|
+
return dict(r=self.r, g=self.g, b=self.b) if self.a is None else dict(r=self.r, g=self.g, b=self.b, a=self.a)
|
|
106
109
|
|
|
107
110
|
def values(self) -> tuple:
|
|
108
111
|
"""Returns the color components as separate values `r, g, b, a`"""
|
|
@@ -110,11 +113,11 @@ class rgba:
|
|
|
110
113
|
|
|
111
114
|
def to_hsla(self) -> "hsla":
|
|
112
115
|
"""Returns the color as a `hsla()` color"""
|
|
113
|
-
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a)
|
|
116
|
+
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False)
|
|
114
117
|
|
|
115
118
|
def to_hexa(self) -> "hexa":
|
|
116
119
|
"""Returns the color as a `hexa()` color"""
|
|
117
|
-
return hexa(
|
|
120
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
118
121
|
|
|
119
122
|
def has_alpha(self) -> bool:
|
|
120
123
|
"""Returns `True` if the color has an alpha channel and `False` otherwise"""
|
|
@@ -123,39 +126,39 @@ class rgba:
|
|
|
123
126
|
def lighten(self, amount: float) -> "rgba":
|
|
124
127
|
"""Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
125
128
|
self.r, self.g, self.b, self.a = self.to_hsla().lighten(amount).to_rgba().values()
|
|
126
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
129
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
127
130
|
|
|
128
131
|
def darken(self, amount: float) -> "rgba":
|
|
129
132
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
130
133
|
self.r, self.g, self.b, self.a = self.to_hsla().darken(amount).to_rgba().values()
|
|
131
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
134
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
132
135
|
|
|
133
136
|
def saturate(self, amount: float) -> "rgba":
|
|
134
137
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
135
138
|
self.r, self.g, self.b, self.a = self.to_hsla().saturate(amount).to_rgba().values()
|
|
136
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
139
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
137
140
|
|
|
138
141
|
def desaturate(self, amount: float) -> "rgba":
|
|
139
142
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
140
143
|
self.r, self.g, self.b, self.a = self.to_hsla().desaturate(amount).to_rgba().values()
|
|
141
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
144
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
142
145
|
|
|
143
146
|
def rotate(self, degrees: int) -> "rgba":
|
|
144
147
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
145
148
|
self.r, self.g, self.b, self.a = self.to_hsla().rotate(degrees).to_rgba().values()
|
|
146
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
149
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
147
150
|
|
|
148
151
|
def invert(self, invert_alpha: bool = False) -> "rgba":
|
|
149
152
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
150
153
|
self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
|
|
151
154
|
if invert_alpha:
|
|
152
155
|
self.a = 1 - self.a
|
|
153
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
156
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
154
157
|
|
|
155
158
|
def grayscale(self) -> "rgba":
|
|
156
159
|
"""Converts the color to grayscale using the luminance formula"""
|
|
157
160
|
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
|
|
158
|
-
return rgba(self.r, self.g, self.b, self.a)
|
|
161
|
+
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
159
162
|
|
|
160
163
|
def blend(self, other: "rgba", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
161
164
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
@@ -166,7 +169,7 @@ class rgba:
|
|
|
166
169
|
raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
|
|
167
170
|
elif not isinstance(other, rgba):
|
|
168
171
|
if Color.is_valid_rgba(other):
|
|
169
|
-
other = rgba(*other)
|
|
172
|
+
other = rgba(*other, _validate=False)
|
|
170
173
|
else:
|
|
171
174
|
raise TypeError("'other' must be a valid RGBA color")
|
|
172
175
|
ratio *= 2
|
|
@@ -175,7 +178,7 @@ class rgba:
|
|
|
175
178
|
self.b = max(0, min(255, int(round((self.b * (2 - ratio)) + (other.b * ratio)))))
|
|
176
179
|
none_alpha = self.a is None and (len(other) <= 3 or other[3] is None)
|
|
177
180
|
if not none_alpha:
|
|
178
|
-
self_a =
|
|
181
|
+
self_a = 1 if self.a is None else self.a
|
|
179
182
|
other_a = (other[3] if other[3] is not None else 1) if len(other) > 3 else 1
|
|
180
183
|
if additive_alpha:
|
|
181
184
|
self.a = max(0, min(1, (self_a * (2 - ratio)) + (other_a * ratio)))
|
|
@@ -189,11 +192,11 @@ class rgba:
|
|
|
189
192
|
)
|
|
190
193
|
else:
|
|
191
194
|
self.a = None
|
|
192
|
-
return rgba(self.r, self.g, self.b, None if none_alpha else self.a)
|
|
195
|
+
return rgba(self.r, self.g, self.b, None if none_alpha else self.a, _validate=False)
|
|
193
196
|
|
|
194
197
|
def is_dark(self) -> bool:
|
|
195
198
|
"""Returns `True` if the color is considered dark (`lightness < 50%`)"""
|
|
196
|
-
return
|
|
199
|
+
return self.to_hsla().is_dark()
|
|
197
200
|
|
|
198
201
|
def is_light(self) -> bool:
|
|
199
202
|
"""Returns `True` if the color is considered light (`lightness >= 50%`)"""
|
|
@@ -211,7 +214,7 @@ class rgba:
|
|
|
211
214
|
"""Returns a new color with the specified alpha value"""
|
|
212
215
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
213
216
|
raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
|
|
214
|
-
return rgba(self.r, self.g, self.b, alpha)
|
|
217
|
+
return rgba(self.r, self.g, self.b, alpha, _validate=False)
|
|
215
218
|
|
|
216
219
|
def complementary(self) -> "rgba":
|
|
217
220
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
@@ -259,7 +262,10 @@ class hsla:
|
|
|
259
262
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
260
263
|
- `complementary()` to get the complementary color"""
|
|
261
264
|
|
|
262
|
-
def __init__(self, h: int, s: int, l: int, a: float = None):
|
|
265
|
+
def __init__(self, h: int, s: int, l: int, a: float = None, _validate: bool = True):
|
|
266
|
+
if not _validate:
|
|
267
|
+
self.h, self.s, self.l, self.a = h, s, l, a
|
|
268
|
+
return
|
|
263
269
|
if any(isinstance(x, hsla) for x in (h, s, l)):
|
|
264
270
|
raise ValueError("Color is already a hsla() color")
|
|
265
271
|
elif not (isinstance(h, int) and (0 <= h <= 360) and all(isinstance(x, int) and (0 <= x <= 100) for x in (s, l))):
|
|
@@ -270,25 +276,25 @@ class hsla:
|
|
|
270
276
|
elif a is not None and (not isinstance(a, (int, float)) or not 0 <= a <= 1):
|
|
271
277
|
raise ValueError(f"Alpha channel must be a float/int in [0.0, 1.0]: got '{a}'")
|
|
272
278
|
self.h, self.s, self.l = h, s, l
|
|
273
|
-
self.a = (1.0 if a > 1.0 else float(a))
|
|
279
|
+
self.a = None if a is None else (1.0 if a > 1.0 else float(a))
|
|
274
280
|
|
|
275
281
|
def __len__(self):
|
|
276
|
-
return
|
|
282
|
+
return 3 if self.a is None else 4
|
|
277
283
|
|
|
278
284
|
def __iter__(self):
|
|
279
|
-
return iter((self.h, self.s, self.l) + ((
|
|
285
|
+
return iter((self.h, self.s, self.l) + (() if self.a is None else (self.a,)))
|
|
280
286
|
|
|
281
287
|
def __dict__(self):
|
|
282
288
|
return self.dict()
|
|
283
289
|
|
|
284
290
|
def __getitem__(self, index):
|
|
285
|
-
return ((self.h, self.s, self.l) + ((
|
|
291
|
+
return ((self.h, self.s, self.l) + (() if self.a is None else (self.a,)))[index]
|
|
286
292
|
|
|
287
293
|
def __repr__(self):
|
|
288
|
-
return f'hsla({self.h}, {self.s}, {self.l}{
|
|
294
|
+
return f'hsla({self.h}, {self.s}, {self.l}{"" if self.a is None else f", {self.a}"})'
|
|
289
295
|
|
|
290
296
|
def __str__(self):
|
|
291
|
-
return f'({self.h}, {self.s}, {self.l}{
|
|
297
|
+
return f'({self.h}, {self.s}, {self.l}{"" if self.a is None else f", {self.a}"})'
|
|
292
298
|
|
|
293
299
|
def __eq__(self, other):
|
|
294
300
|
if not isinstance(other, hsla):
|
|
@@ -302,7 +308,7 @@ class hsla:
|
|
|
302
308
|
|
|
303
309
|
def dict(self) -> dict:
|
|
304
310
|
"""Returns the color components as a dictionary with keys `'h'`, `'s'`, `'l'` and optionally `'a'`"""
|
|
305
|
-
return dict(h=self.h, s=self.s, l=self.l
|
|
311
|
+
return dict(h=self.h, s=self.s, l=self.l) if self.a is None else dict(h=self.h, s=self.s, l=self.l, a=self.a)
|
|
306
312
|
|
|
307
313
|
def values(self) -> tuple:
|
|
308
314
|
"""Returns the color components as separate values `h, s, l, a`"""
|
|
@@ -310,12 +316,12 @@ class hsla:
|
|
|
310
316
|
|
|
311
317
|
def to_rgba(self) -> "rgba":
|
|
312
318
|
"""Returns the color as a `rgba()` color"""
|
|
313
|
-
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a)
|
|
319
|
+
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False)
|
|
314
320
|
|
|
315
321
|
def to_hexa(self) -> "hexa":
|
|
316
322
|
"""Returns the color as a `hexa()` color"""
|
|
317
323
|
r, g, b = self._hsl_to_rgb(self.h, self.s, self.l)
|
|
318
|
-
return hexa(
|
|
324
|
+
return hexa("", r, g, b, self.a)
|
|
319
325
|
|
|
320
326
|
def has_alpha(self) -> bool:
|
|
321
327
|
"""Returns `True` if the color has an alpha channel and `False` otherwise"""
|
|
@@ -326,33 +332,33 @@ class hsla:
|
|
|
326
332
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
327
333
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
328
334
|
self.l = int(min(100, self.l + (100 - self.l) * amount))
|
|
329
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
335
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
330
336
|
|
|
331
337
|
def darken(self, amount: float) -> "hsla":
|
|
332
338
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
333
339
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
334
340
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
335
341
|
self.l = int(max(0, self.l * (1 - amount)))
|
|
336
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
342
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
337
343
|
|
|
338
344
|
def saturate(self, amount: float) -> "hsla":
|
|
339
345
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
340
346
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
341
347
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
342
348
|
self.s = int(min(100, self.s + (100 - self.s) * amount))
|
|
343
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
349
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
344
350
|
|
|
345
351
|
def desaturate(self, amount: float) -> "hsla":
|
|
346
352
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
347
353
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
348
354
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
349
355
|
self.s = int(max(0, self.s * (1 - amount)))
|
|
350
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
356
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
351
357
|
|
|
352
358
|
def rotate(self, degrees: int) -> "hsla":
|
|
353
359
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
354
360
|
self.h = (self.h + degrees) % 360
|
|
355
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
361
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
356
362
|
|
|
357
363
|
def invert(self, invert_alpha: bool = False) -> "hsla":
|
|
358
364
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
@@ -360,13 +366,13 @@ class hsla:
|
|
|
360
366
|
self.l = 100 - self.l
|
|
361
367
|
if invert_alpha:
|
|
362
368
|
self.a = 1 - self.a
|
|
363
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
369
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
364
370
|
|
|
365
371
|
def grayscale(self) -> "hsla":
|
|
366
372
|
"""Converts the color to grayscale using the luminance formula"""
|
|
367
373
|
l = Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l))
|
|
368
|
-
self.h, self.s, self.l, _ = rgba(l, l, l).to_hsla().values()
|
|
369
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
374
|
+
self.h, self.s, self.l, _ = rgba(l, l, l, _validate=False).to_hsla().values()
|
|
375
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
370
376
|
|
|
371
377
|
def blend(self, other: "hsla", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
372
378
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
@@ -374,7 +380,7 @@ class hsla:
|
|
|
374
380
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
375
381
|
- if `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:2 mixture)"""
|
|
376
382
|
self.h, self.s, self.l, self.a = self.to_rgba().blend(Color.to_rgba(other), ratio, additive_alpha).to_hsla().values()
|
|
377
|
-
return hsla(self.h, self.s, self.l, self.a)
|
|
383
|
+
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
378
384
|
|
|
379
385
|
def is_dark(self) -> bool:
|
|
380
386
|
"""Returns `True` if the color is considered dark (`lightness < 50%`)"""
|
|
@@ -396,11 +402,11 @@ class hsla:
|
|
|
396
402
|
"""Returns a new color with the specified alpha value"""
|
|
397
403
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
398
404
|
raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
|
|
399
|
-
return hsla(self.h, self.s, self.l, alpha)
|
|
405
|
+
return hsla(self.h, self.s, self.l, alpha, _validate=False)
|
|
400
406
|
|
|
401
407
|
def complementary(self) -> "hsla":
|
|
402
408
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
403
|
-
return hsla((self.h + 180) % 360, self.s, self.l, self.a)
|
|
409
|
+
return hsla((self.h + 180) % 360, self.s, self.l, self.a, _validate=False)
|
|
404
410
|
|
|
405
411
|
def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
|
|
406
412
|
h, s, l = h / 360, s / 100, l / 100
|
|
@@ -452,7 +458,10 @@ class hexa:
|
|
|
452
458
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
453
459
|
- `complementary()` to get the complementary color"""
|
|
454
460
|
|
|
455
|
-
def __init__(self, color: str | int):
|
|
461
|
+
def __init__(self, color: str | int, _r: int = None, _g: int = None, _b: int = None, _a: float = None):
|
|
462
|
+
if all(x is not None for x in (_r, _g, _b)):
|
|
463
|
+
self.r, self.g, self.b, self.a = _r, _g, _b, _a
|
|
464
|
+
return
|
|
456
465
|
if isinstance(color, hexa):
|
|
457
466
|
raise ValueError("Color is already a hexa() color")
|
|
458
467
|
if isinstance(color, str):
|
|
@@ -491,27 +500,31 @@ class hexa:
|
|
|
491
500
|
else:
|
|
492
501
|
raise ValueError(f"Invalid HEX format '{color}'")
|
|
493
502
|
elif isinstance(color, int):
|
|
494
|
-
self.r, self.g, self.b, self.a = Color.hex_int_to_rgba(color)
|
|
503
|
+
self.r, self.g, self.b, self.a = Color.hex_int_to_rgba(color).values()
|
|
495
504
|
else:
|
|
496
505
|
raise TypeError(f"HEX color must be of type 'str' or 'int': got '{type(color)}'")
|
|
497
506
|
|
|
498
507
|
def __len__(self):
|
|
499
|
-
return
|
|
508
|
+
return 3 if self.a is None else 4
|
|
500
509
|
|
|
501
510
|
def __iter__(self):
|
|
502
|
-
return iter(
|
|
511
|
+
return iter(
|
|
512
|
+
(f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else (f"{int(self.a * 255):02X}",))
|
|
513
|
+
)
|
|
503
514
|
|
|
504
515
|
def __dict__(self):
|
|
505
516
|
return self.dict()
|
|
506
517
|
|
|
507
518
|
def __getitem__(self, index):
|
|
508
|
-
return (
|
|
519
|
+
return (
|
|
520
|
+
(f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else (f"{int(self.a * 255):02X}",))
|
|
521
|
+
)[index]
|
|
509
522
|
|
|
510
523
|
def __repr__(self):
|
|
511
|
-
return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}"
|
|
524
|
+
return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"})'
|
|
512
525
|
|
|
513
526
|
def __str__(self):
|
|
514
|
-
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}"
|
|
527
|
+
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
|
|
515
528
|
|
|
516
529
|
def __eq__(self, other):
|
|
517
530
|
if not isinstance(other, hexa):
|
|
@@ -526,14 +539,14 @@ class hexa:
|
|
|
526
539
|
def dict(self) -> dict:
|
|
527
540
|
"""Returns the color components as a dictionary with hex string values for keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
|
|
528
541
|
return (
|
|
529
|
-
dict(
|
|
542
|
+
dict(r=f"{self.r:02X}", g=f"{self.g:02X}", b=f"{self.b:02X}")
|
|
543
|
+
if self.a is None
|
|
544
|
+
else dict(
|
|
530
545
|
r=f"{self.r:02X}",
|
|
531
546
|
g=f"{self.g:02X}",
|
|
532
547
|
b=f"{self.b:02X}",
|
|
533
548
|
a=f"{int(self.a * 255):02X}",
|
|
534
549
|
)
|
|
535
|
-
if self.a
|
|
536
|
-
else dict(r=f"{self.r:02X}", g=f"{self.g:02X}", b=f"{self.b:02X}")
|
|
537
550
|
)
|
|
538
551
|
|
|
539
552
|
def values(self) -> tuple:
|
|
@@ -546,7 +559,8 @@ class hexa:
|
|
|
546
559
|
self.r,
|
|
547
560
|
self.g,
|
|
548
561
|
self.b,
|
|
549
|
-
(round(self.a, 2) if round_alpha else self.a)
|
|
562
|
+
None if self.a is None else (round(self.a, 2) if round_alpha else self.a),
|
|
563
|
+
_validate=False,
|
|
550
564
|
)
|
|
551
565
|
|
|
552
566
|
def to_hsla(self, round_alpha: bool = True) -> "hsla":
|
|
@@ -560,39 +574,39 @@ class hexa:
|
|
|
560
574
|
def lighten(self, amount: float) -> "hexa":
|
|
561
575
|
"""Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
562
576
|
self.r, self.g, self.b, self.a = self.to_rgba(False).lighten(amount).values()
|
|
563
|
-
return hexa(
|
|
577
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
564
578
|
|
|
565
579
|
def darken(self, amount: float) -> "hexa":
|
|
566
580
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
567
581
|
self.r, self.g, self.b, self.a = self.to_rgba(False).darken(amount).values()
|
|
568
|
-
return hexa(
|
|
582
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
569
583
|
|
|
570
584
|
def saturate(self, amount: float) -> "hexa":
|
|
571
585
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
572
586
|
self.r, self.g, self.b, self.a = self.to_rgba(False).saturate(amount).values()
|
|
573
|
-
return hexa(
|
|
587
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
574
588
|
|
|
575
589
|
def desaturate(self, amount: float) -> "hexa":
|
|
576
590
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
577
591
|
self.r, self.g, self.b, self.a = self.to_rgba(False).desaturate(amount).values()
|
|
578
|
-
return hexa(
|
|
592
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
579
593
|
|
|
580
594
|
def rotate(self, degrees: int) -> "hexa":
|
|
581
595
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
582
596
|
self.r, self.g, self.b, self.a = self.to_rgba(False).rotate(degrees).values()
|
|
583
|
-
return hexa(
|
|
597
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
584
598
|
|
|
585
599
|
def invert(self, invert_alpha: bool = False) -> "hexa":
|
|
586
600
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
587
601
|
self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
|
|
588
602
|
if invert_alpha:
|
|
589
603
|
self.a = 1 - self.a
|
|
590
|
-
return hexa(
|
|
604
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
591
605
|
|
|
592
606
|
def grayscale(self) -> "hexa":
|
|
593
607
|
"""Converts the color to grayscale using the luminance formula"""
|
|
594
608
|
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
|
|
595
|
-
return hexa(
|
|
609
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
596
610
|
|
|
597
611
|
def blend(self, other: "hexa", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
598
612
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
@@ -600,7 +614,7 @@ class hexa:
|
|
|
600
614
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
601
615
|
- if `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:2 mixture)"""
|
|
602
616
|
self.r, self.g, self.b, self.a = self.to_rgba(False).blend(Color.to_rgba(other), ratio, additive_alpha).values()
|
|
603
|
-
return hexa(
|
|
617
|
+
return hexa("", self.r, self.g, self.b, self.a)
|
|
604
618
|
|
|
605
619
|
def is_dark(self) -> bool:
|
|
606
620
|
"""Returns `True` if the color is considered dark (`lightness < 50%`)"""
|
|
@@ -608,7 +622,7 @@ class hexa:
|
|
|
608
622
|
|
|
609
623
|
def is_light(self) -> bool:
|
|
610
624
|
"""Returns `True` if the color is considered light (`lightness >= 50%`)"""
|
|
611
|
-
return self.
|
|
625
|
+
return not self.is_dark()
|
|
612
626
|
|
|
613
627
|
def is_grayscale(self) -> bool:
|
|
614
628
|
"""Returns `True` if the color is grayscale (`saturation == 0`)"""
|
|
@@ -616,13 +630,13 @@ class hexa:
|
|
|
616
630
|
|
|
617
631
|
def is_opaque(self) -> bool:
|
|
618
632
|
"""Returns `True` if the color has no transparency (`alpha == 1.0`)"""
|
|
619
|
-
return self.
|
|
633
|
+
return self.a == 1 or self.a is None
|
|
620
634
|
|
|
621
635
|
def with_alpha(self, alpha: float) -> "hexa":
|
|
622
636
|
"""Returns a new color with the specified alpha value"""
|
|
623
637
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
624
638
|
raise ValueError("'alpha' must be in [0.0, 1.0]")
|
|
625
|
-
return hexa(
|
|
639
|
+
return hexa("", self.r, self.g, self.b, alpha)
|
|
626
640
|
|
|
627
641
|
def complementary(self) -> "hexa":
|
|
628
642
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
@@ -748,15 +762,11 @@ class Color:
|
|
|
748
762
|
if isinstance(color, (hsla, hexa)):
|
|
749
763
|
return color.to_rgba()
|
|
750
764
|
elif Color.is_valid_hsla(color):
|
|
751
|
-
return hsla(*color
|
|
765
|
+
return hsla(*color, _validate=False).to_rgba()
|
|
752
766
|
elif Color.is_valid_hexa(color):
|
|
753
767
|
return hexa(color).to_rgba()
|
|
754
768
|
elif Color.is_valid_rgba(color):
|
|
755
|
-
return (
|
|
756
|
-
color
|
|
757
|
-
if isinstance(color, rgba)
|
|
758
|
-
else (rgba(*color) if Color.has_alpha(color) else rgba(color[0], color[1], color[2]))
|
|
759
|
-
)
|
|
769
|
+
return color if isinstance(color, rgba) else (rgba(*color, _validate=False))
|
|
760
770
|
raise ValueError(f"Invalid color format '{color}'")
|
|
761
771
|
|
|
762
772
|
@staticmethod
|
|
@@ -765,15 +775,11 @@ class Color:
|
|
|
765
775
|
if isinstance(color, (rgba, hexa)):
|
|
766
776
|
return color.to_hsla()
|
|
767
777
|
elif Color.is_valid_rgba(color):
|
|
768
|
-
return rgba(*color
|
|
778
|
+
return rgba(*color, _validate=False).to_hsla()
|
|
769
779
|
elif Color.is_valid_hexa(color):
|
|
770
780
|
return hexa(color).to_hsla()
|
|
771
781
|
elif Color.is_valid_hsla(color):
|
|
772
|
-
return (
|
|
773
|
-
color
|
|
774
|
-
if isinstance(color, hsla)
|
|
775
|
-
else (hsla(*color) if Color.has_alpha(color) else hsla(color[0], color[1], color[2]))
|
|
776
|
-
)
|
|
782
|
+
return color if isinstance(color, hsla) else (hsla(*color, _validate=False))
|
|
777
783
|
raise ValueError(f"Invalid color format '{color}'")
|
|
778
784
|
|
|
779
785
|
@staticmethod
|
|
@@ -782,11 +788,11 @@ class Color:
|
|
|
782
788
|
if isinstance(color, (rgba, hsla)):
|
|
783
789
|
return color.to_hexa()
|
|
784
790
|
elif Color.is_valid_rgba(color):
|
|
785
|
-
return rgba(*color
|
|
791
|
+
return rgba(*color, _validate=False).to_hexa()
|
|
786
792
|
elif Color.is_valid_hsla(color):
|
|
787
|
-
return hsla(*color
|
|
793
|
+
return hsla(*color, _validate=False).to_hexa()
|
|
788
794
|
elif Color.is_valid_hexa(color):
|
|
789
|
-
return color if isinstance(color, hexa) else hexa(
|
|
795
|
+
return color if isinstance(color, hexa) else hexa(color)
|
|
790
796
|
raise ValueError(f"Invalid color format '{color}'")
|
|
791
797
|
|
|
792
798
|
@staticmethod
|
|
@@ -804,6 +810,7 @@ class Color:
|
|
|
804
810
|
int(m[1]),
|
|
805
811
|
int(m[2]),
|
|
806
812
|
((int(m[3]) if "." not in m[3] else float(m[3])) if m[3] else None),
|
|
813
|
+
_validate=False,
|
|
807
814
|
)
|
|
808
815
|
else:
|
|
809
816
|
matches = _re.findall(Regex.rgb_str(allow_alpha=True), string)
|
|
@@ -815,6 +822,7 @@ class Color:
|
|
|
815
822
|
int(m[1]),
|
|
816
823
|
int(m[2]),
|
|
817
824
|
((int(m[3]) if "." not in m[3] else float(m[3])) if m[3] else None),
|
|
825
|
+
_validate=False,
|
|
818
826
|
)
|
|
819
827
|
for m in matches
|
|
820
828
|
]
|
|
@@ -838,19 +846,19 @@ class Color:
|
|
|
838
846
|
r = max(0, min(255, int(r)))
|
|
839
847
|
g = max(0, min(255, int(g)))
|
|
840
848
|
b = max(0, min(255, int(b)))
|
|
841
|
-
if a is
|
|
849
|
+
if a is None:
|
|
850
|
+
hex_int = (r << 16) | (g << 8) | b
|
|
851
|
+
if not preserve_original and (hex_int & 0xF00000) == 0:
|
|
852
|
+
hex_int |= 0x010000
|
|
853
|
+
else:
|
|
842
854
|
a = max(0, min(255, int(a * 255)))
|
|
843
855
|
hex_int = (r << 24) | (g << 16) | (b << 8) | a
|
|
844
856
|
if not preserve_original and r == 0:
|
|
845
857
|
hex_int |= 0x01000000
|
|
846
|
-
else:
|
|
847
|
-
hex_int = (r << 16) | (g << 8) | b
|
|
848
|
-
if not preserve_original and (hex_int & 0xF00000) == 0:
|
|
849
|
-
hex_int |= 0x010000
|
|
850
858
|
return hex_int
|
|
851
859
|
|
|
852
860
|
@staticmethod
|
|
853
|
-
def hex_int_to_rgba(hex_int: int, preserve_original: bool = False) ->
|
|
861
|
+
def hex_int_to_rgba(hex_int: int, preserve_original: bool = False) -> rgba:
|
|
854
862
|
"""Convert a HEX integer to RGBA channels.\n
|
|
855
863
|
-------------------------------------------------------------------------------------------
|
|
856
864
|
If the red channel is `1` after conversion, it will be set to `0`, because when converting
|
|
@@ -862,19 +870,21 @@ class Color:
|
|
|
862
870
|
hex_str = f"{hex_int:x}"
|
|
863
871
|
if len(hex_str) <= 6:
|
|
864
872
|
hex_str = hex_str.zfill(6)
|
|
865
|
-
return (
|
|
873
|
+
return rgba(
|
|
866
874
|
r if (r := int(hex_str[0:2], 16)) != 1 or preserve_original else 0,
|
|
867
875
|
int(hex_str[2:4], 16),
|
|
868
876
|
int(hex_str[4:6], 16),
|
|
869
877
|
None,
|
|
878
|
+
_validate=False,
|
|
870
879
|
)
|
|
871
880
|
elif len(hex_str) <= 8:
|
|
872
881
|
hex_str = hex_str.zfill(8)
|
|
873
|
-
return (
|
|
882
|
+
return rgba(
|
|
874
883
|
r if (r := int(hex_str[0:2], 16)) != 1 or preserve_original else 0,
|
|
875
884
|
int(hex_str[2:4], 16),
|
|
876
885
|
int(hex_str[4:6], 16),
|
|
877
886
|
int(hex_str[6:8], 16) / 255.0,
|
|
887
|
+
_validate=False,
|
|
878
888
|
)
|
|
879
889
|
else:
|
|
880
890
|
raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
|
|
@@ -911,9 +921,9 @@ class Color:
|
|
|
911
921
|
text_bg_color = Color.to_rgba(text_bg_color)
|
|
912
922
|
brightness = 0.2126 * text_bg_color[0] + 0.7152 * text_bg_color[1] + 0.0722 * text_bg_color[2]
|
|
913
923
|
return (
|
|
914
|
-
(hexa("
|
|
924
|
+
(hexa("", 255, 255, 255) if was_hexa else rgba(255, 255, 255, _validate=False))
|
|
915
925
|
if brightness < 128
|
|
916
|
-
else ((0x000 if was_int else hexa("
|
|
926
|
+
else ((0x000 if was_int else hexa("", 0, 0, 0)) if was_hexa else rgba(0, 0, 0, _validate=False))
|
|
917
927
|
)
|
|
918
928
|
|
|
919
929
|
@staticmethod
|
|
@@ -933,7 +943,7 @@ class Color:
|
|
|
933
943
|
color[3] if Color.has_alpha(color) else None,
|
|
934
944
|
)
|
|
935
945
|
l = int(max(0, min(100, l + lightness_change * 100)))
|
|
936
|
-
return
|
|
946
|
+
return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
|
|
937
947
|
|
|
938
948
|
@staticmethod
|
|
939
949
|
def adjust_saturation(color: rgba | hexa, saturation_change: float) -> rgba | hexa:
|
|
@@ -952,4 +962,4 @@ class Color:
|
|
|
952
962
|
color[3] if Color.has_alpha(color) else None,
|
|
953
963
|
)
|
|
954
964
|
s = int(max(0, min(100, s + saturation_change * 100)))
|
|
955
|
-
return
|
|
965
|
+
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
|
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|
|
29
29
|
import pyperclip as _pyperclip
|
|
30
30
|
import keyboard as _keyboard
|
|
31
31
|
import getpass as _getpass
|
|
32
|
-
import ctypes as _ctypes
|
|
33
32
|
import shutil as _shutil
|
|
34
33
|
import mouse as _mouse
|
|
35
34
|
import sys as _sys
|
|
@@ -67,17 +66,6 @@ class Console:
|
|
|
67
66
|
def user() -> str:
|
|
68
67
|
return _os.getenv("USER") or _os.getenv("USERNAME") or _getpass.getuser()
|
|
69
68
|
|
|
70
|
-
def is_admin() -> bool:
|
|
71
|
-
try:
|
|
72
|
-
if _os.name == "nt":
|
|
73
|
-
return _ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
74
|
-
elif _os.name == "posix":
|
|
75
|
-
return _os.geteuid() == 0
|
|
76
|
-
else:
|
|
77
|
-
return False
|
|
78
|
-
except Exception:
|
|
79
|
-
return False
|
|
80
|
-
|
|
81
69
|
@staticmethod
|
|
82
70
|
def pause_exit(
|
|
83
71
|
pause: bool = False,
|
xulbux/xx_env_path.py
CHANGED
xulbux/xx_format_codes.py
CHANGED
|
@@ -11,7 +11,7 @@ Example string with formatting codes:
|
|
|
11
11
|
[bold]This is bold text, [#F08]which is pink now [black|BG:#FF0088] and now it changed`
|
|
12
12
|
to black with a pink background. [_]And this is the boring text, where everything is reset.
|
|
13
13
|
```
|
|
14
|
-
⇾ Instead of writing the formats all separate `[
|
|
14
|
+
⇾ Instead of writing the formats all separate `[x][y][z]` you can join them like this `[x|y|z]`\n
|
|
15
15
|
--------------------------------------------------------------------------------------------------------------------
|
|
16
16
|
You can also automatically reset a certain format, behind text like shown in the following example:
|
|
17
17
|
```string
|
|
@@ -31,7 +31,6 @@ If you want to ignore the `()` brackets you can put a `\\` or `/` between:
|
|
|
31
31
|
All possible formatting codes:
|
|
32
32
|
- HEX colors: `[#F08]` or `[#FF0088]` (with or without leading #)
|
|
33
33
|
- RGB colors: `[rgb(255, 0, 136)]`
|
|
34
|
-
- bright colors: `[bright:#F08]`
|
|
35
34
|
- background colors: `[BG:#F08]`
|
|
36
35
|
- standard cmd colors:
|
|
37
36
|
- `[black]`
|
|
@@ -55,10 +54,21 @@ All possible formatting codes:
|
|
|
55
54
|
- `[hidden]`, `[hide]` or `[h]`
|
|
56
55
|
- `[strikethrough]` or `[s]`
|
|
57
56
|
- `[double-underline]` or `[du]`
|
|
58
|
-
- specific reset:
|
|
59
|
-
-
|
|
57
|
+
- specific reset:
|
|
58
|
+
- `[_bold]` or `[_b]`
|
|
59
|
+
- `[_dim]`
|
|
60
|
+
- `[_italic]` or `[_i]`
|
|
61
|
+
- `[_underline]` or `[_u]`
|
|
62
|
+
- `[_inverse]`, `[_invert]` or `[_in]`
|
|
63
|
+
- `[_hidden]`, `[_hide]` or `[_h]`
|
|
64
|
+
- `[_strikethrough]` or `[_s]`
|
|
65
|
+
- `[_double-underline]` or `[_du]`
|
|
66
|
+
- `[_color]` or `[_c]`
|
|
67
|
+
- `[_background]` or `[_bg]`
|
|
68
|
+
- total reset:
|
|
69
|
+
- `[_]`
|
|
60
70
|
--------------------------------------------------------------------------------------------------------------------
|
|
61
|
-
|
|
71
|
+
Additional formats when a `default_color` is set:
|
|
62
72
|
- `[*]` will reset everything, just like `[_]`, but the text-color will remain in `default_color`
|
|
63
73
|
- `[*color]` will reset the text-color, just like `[_color]`, but then also make it `default_color`
|
|
64
74
|
- `[default]` will just color the text in `default_color`,
|
|
@@ -86,20 +96,32 @@ import regex as _rx
|
|
|
86
96
|
import sys as _sys
|
|
87
97
|
import re as _re
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
PREFIX = {
|
|
100
|
+
"BG": {"background", "bg"},
|
|
101
|
+
"BR": {"bright", "br"},
|
|
102
|
+
}
|
|
103
|
+
PREFIXES = {val for values in PREFIX.values() for val in values}
|
|
104
|
+
PREFIX_RX = {
|
|
105
|
+
"BG": rf"(?:{'|'.join(PREFIX['BG'])})\s*:",
|
|
106
|
+
"BR": rf"(?:{'|'.join(PREFIX['BR'])})\s*:",
|
|
107
|
+
}
|
|
90
108
|
COMPILED = { # PRECOMPILE REGULAR EXPRESSIONS
|
|
91
109
|
"*": _re.compile(r"\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]"),
|
|
92
110
|
"*color": _re.compile(r"\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]"),
|
|
93
111
|
"format": _rx.compile(
|
|
94
112
|
Regex.brackets("[", "]", is_group=True) + r"(?:\s*([/\\]?)\s*" + Regex.brackets("(", ")", is_group=True) + r")?"
|
|
95
113
|
),
|
|
96
|
-
"bg?_default": _re.compile(r"(?i)((?:BG
|
|
97
|
-
"bg_default": _re.compile(r"(?i)BG\s
|
|
114
|
+
"bg?_default": _re.compile(r"(?i)((?:" + PREFIX_RX["BG"] + r")?)\s*default"),
|
|
115
|
+
"bg_default": _re.compile(r"(?i)" + PREFIX_RX["BG"] + r"\s*default"),
|
|
98
116
|
"modifier": _re.compile(
|
|
99
|
-
|
|
117
|
+
r"(?i)((?:BG\s*:)?)\s*("
|
|
118
|
+
+ "|".join([f"{_re.escape(m)}+" for m in ANSI.modifier["lighten"] + ANSI.modifier["darken"]])
|
|
119
|
+
+ r")$"
|
|
100
120
|
),
|
|
101
|
-
"rgb": _re.compile(
|
|
102
|
-
|
|
121
|
+
"rgb": _re.compile(
|
|
122
|
+
r"(?i)^\s*(" + PREFIX_RX["BG"] + r")?\s*(?:rgb|rgba)?\s*\(?\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)?\s*$"
|
|
123
|
+
),
|
|
124
|
+
"hex": _re.compile(r"(?i)^\s*(" + PREFIX_RX["BG"] + r")?\s*(?:#|0x)?([0-9A-F]{6}|[0-9A-F]{3})\s*$"),
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
|
|
@@ -114,6 +136,10 @@ class FormatCodes:
|
|
|
114
136
|
end: str = "\n",
|
|
115
137
|
flush: bool = True,
|
|
116
138
|
) -> None:
|
|
139
|
+
"""Print a string that can be formatted using special formatting codes.\n
|
|
140
|
+
--------------------------------------------------------------------------
|
|
141
|
+
For exact information about how to use special formatting codes, see the
|
|
142
|
+
`xx_format_codes` module documentation."""
|
|
117
143
|
FormatCodes.__config_console()
|
|
118
144
|
_sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)) + end, default_color, brightness_steps))
|
|
119
145
|
if flush:
|
|
@@ -124,16 +150,29 @@ class FormatCodes:
|
|
|
124
150
|
prompt: object = "",
|
|
125
151
|
default_color: hexa | rgba = None,
|
|
126
152
|
brightness_steps: int = 20,
|
|
153
|
+
reset_ansi: bool = False,
|
|
127
154
|
) -> str:
|
|
155
|
+
"""An input, which's prompt can be formatted using special formatting codes.\n
|
|
156
|
+
-------------------------------------------------------------------------------
|
|
157
|
+
If `reset_ansi` is true, all ANSI formatting will be reset, after the user has
|
|
158
|
+
confirmed the input and the program continues.\n
|
|
159
|
+
-------------------------------------------------------------------------------
|
|
160
|
+
For exact information about how to use special formatting codes, see the
|
|
161
|
+
`xx_format_codes` module documentation."""
|
|
128
162
|
FormatCodes.__config_console()
|
|
129
|
-
|
|
163
|
+
user_input = input(FormatCodes.to_ansi(prompt, default_color, brightness_steps))
|
|
164
|
+
if reset_ansi:
|
|
165
|
+
_sys.stdout.write("\x1b[0m")
|
|
166
|
+
return user_input
|
|
130
167
|
|
|
131
168
|
@staticmethod
|
|
132
169
|
def to_ansi(
|
|
133
170
|
string: str, default_color: hexa | rgba = None, brightness_steps: int = 20, _default_start: bool = True
|
|
134
171
|
) -> str:
|
|
135
|
-
|
|
136
|
-
|
|
172
|
+
"""Convert the special formatting codes inside a string to printable ANSI codes.\n
|
|
173
|
+
-----------------------------------------------------------------------------------
|
|
174
|
+
For exact information about how to use special formatting codes, see the
|
|
175
|
+
`xx_format_codes` module documentation."""
|
|
137
176
|
if Color.is_valid_rgba(default_color, False):
|
|
138
177
|
use_default = True
|
|
139
178
|
elif Color.is_valid_hexa(default_color, False):
|
|
@@ -166,10 +205,9 @@ class FormatCodes:
|
|
|
166
205
|
reset_keys = []
|
|
167
206
|
for k in format_keys:
|
|
168
207
|
k_lower = k.lower()
|
|
169
|
-
|
|
170
|
-
k_set
|
|
171
|
-
|
|
172
|
-
if k_set & color_pref:
|
|
208
|
+
k_set = set(k_lower.split(":"))
|
|
209
|
+
if PREFIX["BG"] & k_set and len(k_set) <= 3:
|
|
210
|
+
if k_set & PREFIX["BR"]:
|
|
173
211
|
for i in range(len(k)):
|
|
174
212
|
if is_valid_color(k[i:]):
|
|
175
213
|
reset_keys.extend(["_bg", "_color"])
|
|
@@ -181,7 +219,7 @@ class FormatCodes:
|
|
|
181
219
|
break
|
|
182
220
|
elif is_valid_color(k) or any(
|
|
183
221
|
k_lower.startswith(pref_colon := f"{prefix}:") and is_valid_color(k[len(pref_colon) :])
|
|
184
|
-
for prefix in
|
|
222
|
+
for prefix in PREFIX["BR"]
|
|
185
223
|
):
|
|
186
224
|
reset_keys.append("_color")
|
|
187
225
|
else:
|
|
@@ -209,8 +247,8 @@ class FormatCodes:
|
|
|
209
247
|
+ ("" if escaped else "".join(ansi_resets))
|
|
210
248
|
)
|
|
211
249
|
|
|
212
|
-
|
|
213
|
-
return (FormatCodes.__get_default_ansi(default_color) if _default_start else "") +
|
|
250
|
+
string = "\n".join(COMPILED["format"].sub(replace_keys, line) for line in string.split("\n"))
|
|
251
|
+
return (FormatCodes.__get_default_ansi(default_color) if _default_start else "") + string if use_default else string
|
|
214
252
|
|
|
215
253
|
@staticmethod
|
|
216
254
|
def escape_ansi(ansi_string: str, escaped_char: str = ANSI.char_esc) -> str:
|
|
@@ -220,6 +258,7 @@ class FormatCodes:
|
|
|
220
258
|
@staticmethod
|
|
221
259
|
@lru_cache(maxsize=64)
|
|
222
260
|
def __config_console() -> None:
|
|
261
|
+
"""Configure the console to be able to interpret ANSI formatting."""
|
|
223
262
|
_sys.stdout.flush()
|
|
224
263
|
kernel32 = _ctypes.windll.kernel32
|
|
225
264
|
h = kernel32.GetStdHandle(-11)
|
|
@@ -234,6 +273,7 @@ class FormatCodes:
|
|
|
234
273
|
brightness_steps: int = None,
|
|
235
274
|
_modifiers: tuple[str, str] = (ANSI.modifier["lighten"], ANSI.modifier["darken"]),
|
|
236
275
|
) -> str | None:
|
|
276
|
+
"""Get the `default_color` and lighter/darker versions of it in ANSI format."""
|
|
237
277
|
if not brightness_steps or (format_key and COMPILED["bg?_default"].search(format_key)):
|
|
238
278
|
return (ANSI.seq_bg_color if format_key and COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
|
|
239
279
|
*default_color[:3]
|
|
@@ -264,11 +304,7 @@ class FormatCodes:
|
|
|
264
304
|
If `default_color` is not `None`, the text color will be `default_color` if all formats
|
|
265
305
|
are reset or you can get lighter or darker version of `default_color` (also as BG)"""
|
|
266
306
|
use_default = default_color and Color.is_valid_rgba(default_color, False)
|
|
267
|
-
_format_key, format_key = format_key, ( # NORMALIZE
|
|
268
|
-
"bg:" if "bg" in (parts := format_key.replace(" ", "").lower().split(":")) else ""
|
|
269
|
-
) + ("bright:" if any(x in parts for x in ("bright", "br")) else "") + ":".join(
|
|
270
|
-
p for p in parts if p not in ("bg", "bright", "br")
|
|
271
|
-
)
|
|
307
|
+
_format_key, format_key = format_key, FormatCodes.__normalize_key(format_key) # NORMALIZE KEY AND SAVE ORIGINAL
|
|
272
308
|
if use_default:
|
|
273
309
|
if new_default_color := FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps):
|
|
274
310
|
return new_default_color
|
|
@@ -303,3 +339,17 @@ class FormatCodes:
|
|
|
303
339
|
except Exception:
|
|
304
340
|
pass
|
|
305
341
|
return _format_key
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
@lru_cache(maxsize=64)
|
|
345
|
+
def __normalize_key(format_key: str) -> str:
|
|
346
|
+
"""Normalizes the given format key."""
|
|
347
|
+
k_parts = format_key.replace(" ", "").lower().split(":")
|
|
348
|
+
prefix_str = "".join(
|
|
349
|
+
f"{prefix_key.lower()}:"
|
|
350
|
+
for prefix_key, prefix_values in PREFIX.items()
|
|
351
|
+
if any(k_part in prefix_values for k_part in k_parts)
|
|
352
|
+
)
|
|
353
|
+
return prefix_str + ":".join(
|
|
354
|
+
part for part in k_parts if part not in {val for values in PREFIX.values() for val in values}
|
|
355
|
+
)
|
xulbux/xx_system.py
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import subprocess as _subprocess
|
|
2
2
|
import platform as _platform
|
|
3
|
+
import ctypes as _ctypes
|
|
3
4
|
import time as _time
|
|
4
5
|
import sys as _sys
|
|
5
6
|
import os as _os
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class ProcessNotFoundError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
8
13
|
class System:
|
|
9
14
|
|
|
15
|
+
@staticmethod
|
|
16
|
+
def is_elevated() -> bool:
|
|
17
|
+
"""Returns `True` if the current user is an admin and `False` otherwise."""
|
|
18
|
+
try:
|
|
19
|
+
if _os.name == "nt":
|
|
20
|
+
return _ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
21
|
+
elif _os.name == "posix":
|
|
22
|
+
return _os.geteuid() == 0
|
|
23
|
+
except:
|
|
24
|
+
pass
|
|
25
|
+
return False
|
|
26
|
+
|
|
10
27
|
@staticmethod
|
|
11
28
|
def restart(
|
|
12
29
|
prompt: object = None,
|
|
@@ -83,3 +100,38 @@ class System:
|
|
|
83
100
|
return None
|
|
84
101
|
except _subprocess.CalledProcessError:
|
|
85
102
|
return missing
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def elevate(win_title: str | None = None, args: list | None = None) -> bool:
|
|
106
|
+
"""Attempts to start a new process with elevated privileges.\n
|
|
107
|
+
---------------------------------------------------------------------------------
|
|
108
|
+
The param `win_title` is window the title of the elevated process.
|
|
109
|
+
The param `args` is the arguments to be passed to the elevated process.\n
|
|
110
|
+
After the elevated process started, the original process will exit.
|
|
111
|
+
This means, that this method has to be run at the beginning of the program or
|
|
112
|
+
will have to continue in a new window after elevation.\n
|
|
113
|
+
---------------------------------------------------------------------------------
|
|
114
|
+
Returns `True` if the current process already has elevated privileges and raises
|
|
115
|
+
a `PermissionError` if the user denied the elevation or the elevation failed."""
|
|
116
|
+
if System.is_elevated():
|
|
117
|
+
return True
|
|
118
|
+
if _os.name == "nt": # WINDOWS
|
|
119
|
+
if win_title:
|
|
120
|
+
args_str = f'-c "import ctypes; ctypes.windll.kernel32.SetConsoleTitleW(\\"{win_title}\\"); exec(open(\\"{_sys.argv[0]}\\").read())" {" ".join(args)}"'
|
|
121
|
+
else:
|
|
122
|
+
args_str = f'-c "exec(open(\\"{_sys.argv[0]}\\").read())" {" ".join(args)}'
|
|
123
|
+
result = _ctypes.windll.shell32.ShellExecuteW(None, "runas", _sys.executable, args_str, None, 1)
|
|
124
|
+
if result <= 32:
|
|
125
|
+
raise PermissionError("Failed to launch elevated process.")
|
|
126
|
+
else:
|
|
127
|
+
_sys.exit(0)
|
|
128
|
+
else: # POSIX
|
|
129
|
+
cmd = ["pkexec"]
|
|
130
|
+
if win_title:
|
|
131
|
+
cmd.extend(["--description", win_title])
|
|
132
|
+
cmd.extend([_sys.executable] + _sys.argv[1:] + ([] if args is None else args))
|
|
133
|
+
proc = _subprocess.Popen(cmd)
|
|
134
|
+
proc.wait()
|
|
135
|
+
if proc.returncode != 0:
|
|
136
|
+
raise PermissionError("Process elevation was denied.")
|
|
137
|
+
_sys.exit(0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: xulbux
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: A library which includes a lot of really helpful functions.
|
|
5
5
|
Author-email: XulbuX <xulbux.real@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -95,7 +95,7 @@ from xulbux import rgba, hsla, hexa
|
|
|
95
95
|
| <h3>[`xx_console`](https://github.com/XulbuX-dev/PythonLibraryXulbuX/wiki/xx_console)</h3> | advanced actions related to the console (*pretty logging, advanced inputs, ...*) |
|
|
96
96
|
| <h3>[`xx_data`](https://github.com/XulbuX-dev/PythonLibraryXulbuX/wiki/xx_data)</h3> | advanced operations with data structures (*compare, generate path ID's, pretty print/format, ...*) |
|
|
97
97
|
| <h3>[`xx_env_path`](https://github.com/XulbuX-dev/PythonLibraryXulbuX/wiki/xx_env_path)</h3> | getting and editing the PATH variable (*get paths, check for paths, add paths, ...*) |
|
|
98
|
-
| <h3
|
|
98
|
+
| <h3>[`xx_file`](https://github.com/XulbuX-dev/PythonLibraryXulbuX/wiki/xx_file)</h3> | advanced working with files (*create files, rename file-extensions, ...*) |
|
|
99
99
|
| <h3>`xx_format_codes`</h3> | easy pretty printing with custom format codes (*print, inputs, custom format codes to ANSI, ...*) |
|
|
100
100
|
| <h3>`xx_json`</h3> | advanced working with json files (*read, create, update, ...*) |
|
|
101
101
|
| <h3>`xx_path`</h3> | advanced path operations (*get paths, smart-extend relative paths, delete paths, ...*) |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
xulbux/__init__.py,sha256=fxqdXSxUa_iDtV2MWdq4aBXV3B0pQWgz2xHpLnm84zw,1658
|
|
2
|
+
xulbux/_cli_.py,sha256=U25ZrtpQgpKXtvOSTBBbh7-AJ_WTeZ95A66DQARAqzo,3558
|
|
3
|
+
xulbux/_consts_.py,sha256=qAkg6ZwTKhQFcpHpfXjF4QEao8hpDEECRGqrv8oxkCo,4572
|
|
4
|
+
xulbux/xx_code.py,sha256=yBP5WxCxNxjBiS6nVAmUBJpD0hX6fgnh5RWq-NmrnaY,5222
|
|
5
|
+
xulbux/xx_color.py,sha256=cDlgrekH88ZEBj8leIIlJbYzsf1RdS8RW3oGvuUfvC0,45129
|
|
6
|
+
xulbux/xx_console.py,sha256=rd31686X9eXB8__lcLaV7unWzIdXGM2yMOt9zNCsGbs,15079
|
|
7
|
+
xulbux/xx_data.py,sha256=OEKLbI1XeNTrittdz3s3mvQk8YrBoSovj9O1H-b7ArY,25844
|
|
8
|
+
xulbux/xx_env_path.py,sha256=iv3Jw0TsNDbbL_NySnazvIP9Iv9swymhiJIr2B3EG8k,4388
|
|
9
|
+
xulbux/xx_file.py,sha256=-58YnqKvrs5idIF91UzEki7o7qnskFvnQYkBaRrp7Vw,3122
|
|
10
|
+
xulbux/xx_format_codes.py,sha256=hTQBowcFX0gyAtjsYvgrEZK2EQ83b0W-IyqIUTkJAig,16986
|
|
11
|
+
xulbux/xx_json.py,sha256=q60lOj8Xg8c4L9cBu6SBZdJzFC7QbjDfFwcKKzBKj5w,5173
|
|
12
|
+
xulbux/xx_path.py,sha256=_xkH9cowPdi3nHw2q_TvN_i_5oG6GJut-QwPBLxnrAQ,4519
|
|
13
|
+
xulbux/xx_regex.py,sha256=zyxkS1bLlrSq26ErhO4UtrimIhW71_a7kox6ArCoK58,7670
|
|
14
|
+
xulbux/xx_string.py,sha256=Wa3qHxnk7AIpAVAn1vI_GBtkfYFwy4F_Xtj83ojEPKc,7168
|
|
15
|
+
xulbux/xx_system.py,sha256=uBpB1eKbR-bddO87jbWdoi5NScLnrxJONbyqU49LmCs,6426
|
|
16
|
+
xulbux-1.6.2.dist-info/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
|
|
17
|
+
xulbux-1.6.2.dist-info/METADATA,sha256=PyPr14LhmMVg952AQ-5uxp5jd3GSFRVCOxjPfsQ7ebo,6836
|
|
18
|
+
xulbux-1.6.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
19
|
+
xulbux-1.6.2.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
|
|
20
|
+
xulbux-1.6.2.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
|
|
21
|
+
xulbux-1.6.2.dist-info/RECORD,,
|
xulbux-1.6.1.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
xulbux/__init__.py,sha256=ayM2Gomwlns0lEDdbUZ6uxtrDIQRb0ldSOyqguQ_ON8,1658
|
|
2
|
-
xulbux/_cli_.py,sha256=U25ZrtpQgpKXtvOSTBBbh7-AJ_WTeZ95A66DQARAqzo,3558
|
|
3
|
-
xulbux/_consts_.py,sha256=2-swueg8B5retfsAQqmR8nFNc06tqAwpL__TOv3V4kw,4882
|
|
4
|
-
xulbux/xx_code.py,sha256=yBP5WxCxNxjBiS6nVAmUBJpD0hX6fgnh5RWq-NmrnaY,5222
|
|
5
|
-
xulbux/xx_color.py,sha256=sRW0Fr8CFfM2EsUubEO74aLRTseCu5VulXyjqDcahEw,44895
|
|
6
|
-
xulbux/xx_console.py,sha256=ydDPYyGZ5OVHIkCiJO_eaAJI-8k97L5vQ7v1SoUUmWI,15436
|
|
7
|
-
xulbux/xx_data.py,sha256=OEKLbI1XeNTrittdz3s3mvQk8YrBoSovj9O1H-b7ArY,25844
|
|
8
|
-
xulbux/xx_env_path.py,sha256=5CvKpItzA4n8nvF7RwOmZwgs4erGltZqRJ3VD-9dFzo,4388
|
|
9
|
-
xulbux/xx_file.py,sha256=-58YnqKvrs5idIF91UzEki7o7qnskFvnQYkBaRrp7Vw,3122
|
|
10
|
-
xulbux/xx_format_codes.py,sha256=TkdHaiKXpZxVoUN6oEcsGXITgxrxzkghMCde2Kw-kQg,14806
|
|
11
|
-
xulbux/xx_json.py,sha256=q60lOj8Xg8c4L9cBu6SBZdJzFC7QbjDfFwcKKzBKj5w,5173
|
|
12
|
-
xulbux/xx_path.py,sha256=_xkH9cowPdi3nHw2q_TvN_i_5oG6GJut-QwPBLxnrAQ,4519
|
|
13
|
-
xulbux/xx_regex.py,sha256=zyxkS1bLlrSq26ErhO4UtrimIhW71_a7kox6ArCoK58,7670
|
|
14
|
-
xulbux/xx_string.py,sha256=Wa3qHxnk7AIpAVAn1vI_GBtkfYFwy4F_Xtj83ojEPKc,7168
|
|
15
|
-
xulbux/xx_system.py,sha256=Eyf4MbZpaHHKv71SOcM_EK0bvT4PGG0KLwAXx4m60vo,3918
|
|
16
|
-
xulbux-1.6.1.dist-info/LICENSE,sha256=6NflEcvzFEe8_JFVNCPVwZBwBhlLLd4vqQi8WiX_Xk4,1084
|
|
17
|
-
xulbux-1.6.1.dist-info/METADATA,sha256=NLrtou5gw1j1FQ3PLIsI09UCoowzZg7xtJzVl5l9mi0,6836
|
|
18
|
-
xulbux-1.6.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
19
|
-
xulbux-1.6.1.dist-info/entry_points.txt,sha256=a3womfLIMZKnOFiyy-xnVb4g2qkZsHR5FbKKkljcGns,94
|
|
20
|
-
xulbux-1.6.1.dist-info/top_level.txt,sha256=FkK4EZajwfP36fnlrPaR98OrEvZpvdEOdW1T5zTj6og,7
|
|
21
|
-
xulbux-1.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|