xulbux 1.5.5__py3-none-any.whl → 1.5.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xulbux might be problematic. Click here for more details.
- xulbux/__init__.py +28 -18
- xulbux/{__help__.py → _cli_.py} +23 -45
- xulbux/_consts_.py +108 -111
- xulbux/xx_code.py +31 -28
- xulbux/xx_color.py +364 -205
- xulbux/xx_console.py +381 -0
- xulbux/xx_data.py +358 -261
- xulbux/xx_env_path.py +113 -0
- xulbux/xx_file.py +22 -16
- xulbux/xx_format_codes.py +156 -91
- xulbux/xx_json.py +38 -18
- xulbux/xx_path.py +38 -23
- xulbux/xx_regex.py +64 -29
- xulbux/xx_string.py +104 -47
- xulbux/xx_system.py +37 -26
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/METADATA +23 -19
- xulbux-1.5.8.dist-info/RECORD +20 -0
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/WHEEL +1 -1
- xulbux-1.5.8.dist-info/entry_points.txt +3 -0
- xulbux/xx_cmd.py +0 -240
- xulbux/xx_env_vars.py +0 -60
- xulbux-1.5.5.dist-info/RECORD +0 -20
- xulbux-1.5.5.dist-info/entry_points.txt +0 -2
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/licenses/LICENSE +0 -0
xulbux/xx_env_path.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions for modifying and checking the systems environment-variables:
|
|
3
|
+
- `EnvPath.paths()`
|
|
4
|
+
- `EnvPath.has_path()`
|
|
5
|
+
- `EnvPath.add_path()`
|
|
6
|
+
- `EnvPath.remove_path()`
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .xx_path import Path
|
|
10
|
+
|
|
11
|
+
import os as _os
|
|
12
|
+
import sys as _sys
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EnvPath:
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def paths(as_list: bool = False) -> str | list:
|
|
19
|
+
"""Get the PATH environment variable."""
|
|
20
|
+
paths = _os.environ.get("PATH", "")
|
|
21
|
+
return paths.split(_os.pathsep) if as_list else paths
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def has_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> bool:
|
|
25
|
+
"""Check if a path is present in the PATH environment variable."""
|
|
26
|
+
if cwd:
|
|
27
|
+
path = _os.getcwd()
|
|
28
|
+
elif base_dir:
|
|
29
|
+
path = Path.get(base_dir=True)
|
|
30
|
+
elif path is None:
|
|
31
|
+
raise ValueError("A path must be provided or either 'cwd' or 'base_dir' must be True.")
|
|
32
|
+
paths = EnvPath.paths(as_list=True)
|
|
33
|
+
return _os.path.normpath(path) in [_os.path.normpath(p) for p in paths]
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def add_path(
|
|
37
|
+
path: str = None,
|
|
38
|
+
cwd: bool = False,
|
|
39
|
+
base_dir: bool = False,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Add a path to the PATH environment variable."""
|
|
42
|
+
path = EnvPath.__get(path, cwd, base_dir)
|
|
43
|
+
if not EnvPath.has_path(path):
|
|
44
|
+
EnvPath.__persistent(path, add=True)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def remove_path(
|
|
48
|
+
path: str = None,
|
|
49
|
+
cwd: bool = False,
|
|
50
|
+
base_dir: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Remove a path from the PATH environment variable."""
|
|
53
|
+
path = EnvPath.__get(path, cwd, base_dir)
|
|
54
|
+
if EnvPath.has_path(path):
|
|
55
|
+
EnvPath.__persistent(path, remove=True)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def __get(
|
|
59
|
+
path: str = None,
|
|
60
|
+
cwd: bool = False,
|
|
61
|
+
base_dir: bool = False,
|
|
62
|
+
) -> list:
|
|
63
|
+
"""Get and/or normalize the paths.<br>
|
|
64
|
+
Raise an error if no path is provided and<br>
|
|
65
|
+
neither `cwd` or `base_dir` is `True`."""
|
|
66
|
+
if cwd:
|
|
67
|
+
path = _os.getcwd()
|
|
68
|
+
elif base_dir:
|
|
69
|
+
path = Path.get(base_dir=True)
|
|
70
|
+
elif path is None:
|
|
71
|
+
raise ValueError("A path must be provided or either 'cwd' or 'base_dir' must be True.")
|
|
72
|
+
return _os.path.normpath(path)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def __persistent(path: str, add: bool = False, remove: bool = False) -> None:
|
|
76
|
+
"""Add or remove a path from PATH persistently across sessions as well as the current session."""
|
|
77
|
+
if add == remove:
|
|
78
|
+
raise ValueError("Either add or remove must be True, but not both.")
|
|
79
|
+
current_paths = EnvPath.paths(as_list=True)
|
|
80
|
+
path = _os.path.normpath(path)
|
|
81
|
+
if remove:
|
|
82
|
+
current_paths = [p for p in current_paths if _os.path.normpath(p) != _os.path.normpath(path)]
|
|
83
|
+
elif add:
|
|
84
|
+
current_paths.append(path)
|
|
85
|
+
_os.environ["PATH"] = new_path = _os.pathsep.join(sorted(set(filter(bool, current_paths))))
|
|
86
|
+
if _sys.platform == "win32": # Windows
|
|
87
|
+
try:
|
|
88
|
+
import winreg as _winreg
|
|
89
|
+
|
|
90
|
+
key = _winreg.OpenKey(
|
|
91
|
+
_winreg.HKEY_CURRENT_USER,
|
|
92
|
+
"Environment",
|
|
93
|
+
0,
|
|
94
|
+
_winreg.KEY_ALL_ACCESS,
|
|
95
|
+
)
|
|
96
|
+
_winreg.SetValueEx(key, "PATH", 0, _winreg.REG_EXPAND_SZ, new_path)
|
|
97
|
+
_winreg.CloseKey(key)
|
|
98
|
+
except ImportError:
|
|
99
|
+
print("Warning: Unable to make persistent changes on Windows.")
|
|
100
|
+
else: # UNIX-like (Linux/macOS)
|
|
101
|
+
shell_rc_file = _os.path.expanduser(
|
|
102
|
+
"~/.bashrc" if _os.path.exists(_os.path.expanduser("~/.bashrc")) else "~/.zshrc"
|
|
103
|
+
)
|
|
104
|
+
with open(shell_rc_file, "r+") as f:
|
|
105
|
+
content = f.read()
|
|
106
|
+
f.seek(0)
|
|
107
|
+
if remove:
|
|
108
|
+
new_content = [line for line in content.splitlines() if not line.endswith(f':{path}"')]
|
|
109
|
+
f.write("\n".join(new_content))
|
|
110
|
+
else:
|
|
111
|
+
f.write(f'{content.rstrip()}\n# Added by XulbuX\nexport PATH="{new_path}"\n')
|
|
112
|
+
f.truncate()
|
|
113
|
+
_os.system(f"source {shell_rc_file}")
|
xulbux/xx_file.py
CHANGED
|
@@ -1,50 +1,56 @@
|
|
|
1
|
-
from .xx_string import
|
|
2
|
-
from .xx_path import
|
|
1
|
+
from .xx_string import String
|
|
2
|
+
from .xx_path import Path
|
|
3
3
|
|
|
4
4
|
import os as _os
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
7
|
class File:
|
|
10
8
|
|
|
11
9
|
@staticmethod
|
|
12
|
-
def _make_path(
|
|
10
|
+
def _make_path(
|
|
11
|
+
filename: str,
|
|
12
|
+
filetype: str,
|
|
13
|
+
search_in: str | list[str] = None,
|
|
14
|
+
prefer_base_dir: bool = True,
|
|
15
|
+
correct_path: bool = False,
|
|
16
|
+
) -> str:
|
|
13
17
|
"""Get the path to a file in the cwd, the base-dir, or predefined directories.\n
|
|
14
18
|
--------------------------------------------------------------------------------------
|
|
15
19
|
If the `filename` is not found in the above directories, it will be searched<br>
|
|
16
20
|
in the `search_in` directory/directories. If the file is still not found, it will<br>
|
|
17
21
|
return the path to the file in the base-dir per default or to the file in the<br>
|
|
18
22
|
cwd if `prefer_base_dir` is set to `False`."""
|
|
19
|
-
if not filename.lower().endswith(f
|
|
20
|
-
filename = f
|
|
23
|
+
if not filename.lower().endswith(f".{filetype.lower()}"):
|
|
24
|
+
filename = f"{filename}.{filetype.lower()}"
|
|
21
25
|
try:
|
|
22
26
|
return Path.extend(filename, search_in, True, correct_path)
|
|
23
27
|
except FileNotFoundError:
|
|
24
|
-
return
|
|
28
|
+
return (
|
|
29
|
+
_os.path.join(Path.get(base_dir=True), filename) if prefer_base_dir else _os.path.join(_os.getcwd(), filename)
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
@staticmethod
|
|
27
|
-
def rename_extension(file_path:str, new_extension:str) -> str:
|
|
33
|
+
def rename_extension(file_path: str, new_extension: str) -> str:
|
|
28
34
|
directory, filename_with_ext = _os.path.split(file_path)
|
|
29
|
-
filename = filename_with_ext.split(
|
|
35
|
+
filename = filename_with_ext.split(".")[0]
|
|
30
36
|
camel_case_filename = String.to_camel_case(filename)
|
|
31
|
-
new_filename = f
|
|
37
|
+
new_filename = f"{camel_case_filename}{new_extension}"
|
|
32
38
|
new_file_path = _os.path.join(directory, new_filename)
|
|
33
39
|
return new_file_path
|
|
34
40
|
|
|
35
41
|
@staticmethod
|
|
36
|
-
def create(content:str =
|
|
42
|
+
def create(content: str = "", file: str = "new_file.txt", force: bool = False) -> str:
|
|
37
43
|
"""Create a file with ot without content.\n
|
|
38
44
|
----------------------------------------------------------------------------
|
|
39
45
|
The function will throw a `FileExistsError` if the file already exists.<br>
|
|
40
46
|
To overwrite the file, set the `force` parameter to `True`."""
|
|
41
47
|
if _os.path.exists(file) and not force:
|
|
42
|
-
with open(file,
|
|
48
|
+
with open(file, "r", encoding="utf-8") as existing_file:
|
|
43
49
|
existing_content = existing_file.read()
|
|
44
50
|
if existing_content == content:
|
|
45
|
-
raise FileExistsError(
|
|
46
|
-
raise FileExistsError(
|
|
47
|
-
with open(file,
|
|
51
|
+
raise FileExistsError("Already created this file. (nothing changed)")
|
|
52
|
+
raise FileExistsError("File already exists.")
|
|
53
|
+
with open(file, "w", encoding="utf-8") as f:
|
|
48
54
|
f.write(content)
|
|
49
55
|
full_path = _os.path.abspath(file)
|
|
50
56
|
return full_path
|
xulbux/xx_format_codes.py
CHANGED
|
@@ -64,127 +64,200 @@ Unlike the standard cmd colors, the default color can be changed by using the fo
|
|
|
64
64
|
- `[dd]` will darken the `default_color` text by `2 × brightness_steps`%
|
|
65
65
|
- `[ddd]` will darken the `default_color` text by `3 × brightness_steps`%
|
|
66
66
|
- ... etc.\n
|
|
67
|
-
Per default, you can also use `+` and `-` to get lighter and darker `default_color` versions
|
|
68
|
-
This can also be changed by changing the param `_modifiers = ('+l', '-d')`.
|
|
67
|
+
Per default, you can also use `+` and `-` to get lighter and darker `default_color` versions.
|
|
69
68
|
"""
|
|
70
69
|
|
|
71
|
-
|
|
72
70
|
from ._consts_ import ANSI
|
|
73
|
-
from .xx_string import
|
|
74
|
-
from .xx_regex import
|
|
71
|
+
from .xx_string import String
|
|
72
|
+
from .xx_regex import Regex
|
|
75
73
|
from .xx_color import *
|
|
76
|
-
from .xx_data import *
|
|
77
74
|
|
|
75
|
+
from functools import lru_cache
|
|
78
76
|
import ctypes as _ctypes
|
|
79
77
|
import regex as _rx
|
|
80
78
|
import sys as _sys
|
|
81
79
|
import re as _re
|
|
82
80
|
|
|
83
81
|
|
|
82
|
+
COMPILED = { # PRECOMPILE REGULAR EXPRESSIONS
|
|
83
|
+
"*": _re.compile(r"\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]"),
|
|
84
|
+
"*color": _re.compile(r"\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]"),
|
|
85
|
+
"format": _rx.compile(
|
|
86
|
+
Regex.brackets("[", "]", is_group=True) + r"(?:\s*([/\\]?)\s*" + Regex.brackets("(", ")", is_group=True) + r")?"
|
|
87
|
+
),
|
|
88
|
+
"bg?_default": _re.compile(r"(?i)((?:BG\s*:)?)\s*default"),
|
|
89
|
+
"bg_default": _re.compile(r"(?i)BG\s*:\s*default"),
|
|
90
|
+
"modifier": _re.compile(
|
|
91
|
+
rf'(?i)((?:BG\s*:)?)\s*({"|".join([f"{_re.escape(m)}+" for m in ANSI.modifier["lighten"] + ANSI.modifier["darken"]])})$'
|
|
92
|
+
),
|
|
93
|
+
"rgb": _re.compile(r"(?i)^\s*(BG\s*:)?\s*(?:rgb|rgba)?\s*\(?\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)?\s*$"),
|
|
94
|
+
"hex": _re.compile(r"(?i)^\s*(BG\s*:)?\s*(?:#|0x)?([0-9A-F]{6}|[0-9A-F]{3})\s*$"),
|
|
95
|
+
}
|
|
84
96
|
|
|
85
97
|
|
|
86
98
|
class FormatCodes:
|
|
87
99
|
|
|
88
100
|
@staticmethod
|
|
89
|
-
def print(
|
|
101
|
+
def print(
|
|
102
|
+
*values: object,
|
|
103
|
+
default_color: hexa | rgba = None,
|
|
104
|
+
brightness_steps: int = 20,
|
|
105
|
+
sep: str = " ",
|
|
106
|
+
end: str = "\n",
|
|
107
|
+
flush: bool = True,
|
|
108
|
+
) -> None:
|
|
90
109
|
FormatCodes.__config_console()
|
|
91
|
-
_sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)), default_color, brightness_steps)
|
|
92
|
-
|
|
110
|
+
_sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)) + end, default_color, brightness_steps))
|
|
111
|
+
if flush:
|
|
112
|
+
_sys.stdout.flush()
|
|
93
113
|
|
|
94
114
|
@staticmethod
|
|
95
|
-
def input(
|
|
115
|
+
def input(
|
|
116
|
+
prompt: object = "",
|
|
117
|
+
default_color: hexa | rgba = None,
|
|
118
|
+
brightness_steps: int = 20,
|
|
119
|
+
) -> str:
|
|
96
120
|
FormatCodes.__config_console()
|
|
97
121
|
return input(FormatCodes.to_ansi(prompt, default_color, brightness_steps))
|
|
98
122
|
|
|
99
123
|
@staticmethod
|
|
100
|
-
def to_ansi(
|
|
101
|
-
|
|
124
|
+
def to_ansi(
|
|
125
|
+
string: str, default_color: hexa | rgba = None, brightness_steps: int = 20, _default_start: bool = True
|
|
126
|
+
) -> str:
|
|
127
|
+
result = ""
|
|
128
|
+
if Color.is_valid_rgba(default_color, False):
|
|
129
|
+
use_default = True
|
|
130
|
+
elif Color.is_valid_hexa(default_color, False):
|
|
131
|
+
use_default, default_color = True, Color.to_rgba(default_color)
|
|
132
|
+
else:
|
|
133
|
+
use_default = False
|
|
102
134
|
if use_default:
|
|
103
|
-
string =
|
|
104
|
-
string =
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
string = COMPILED["*"].sub(r"[\1_|default\2]", string) # REPLACE `[…|*|…]` WITH `[…|_|default|…]`
|
|
136
|
+
string = COMPILED["*color"].sub(r"[\1default\2]", string) # REPLACE `[…|*color|…]` WITH `[…|default|…]`
|
|
137
|
+
|
|
138
|
+
def replace_keys(match: _re.Match) -> str:
|
|
139
|
+
format_keys = match.group(1)
|
|
140
|
+
esc = match.group(2)
|
|
141
|
+
auto_reset_txt = match.group(3)
|
|
107
142
|
if not format_keys:
|
|
108
143
|
return match.group(0)
|
|
144
|
+
format_keys = [k.strip() for k in format_keys.split("|") if k.strip()]
|
|
145
|
+
ansi_formats = [FormatCodes.__get_replacement(k, default_color, brightness_steps) for k in format_keys]
|
|
146
|
+
if auto_reset_txt and not esc:
|
|
147
|
+
reset_keys = [
|
|
148
|
+
(
|
|
149
|
+
"_color"
|
|
150
|
+
if k in ANSI.color_map or Color.is_valid_rgba(k) or Color.is_valid_hexa(k)
|
|
151
|
+
else (
|
|
152
|
+
"_bg"
|
|
153
|
+
if ({"bg", "bright", "br"} & set(k.lower().split(":")))
|
|
154
|
+
and len(k.split(":")) <= 3
|
|
155
|
+
and any(
|
|
156
|
+
k[i:] in ANSI.color_map or Color.is_valid_rgba(k[i:]) or Color.is_valid_hexa(k[i:])
|
|
157
|
+
for i in range(len(k))
|
|
158
|
+
)
|
|
159
|
+
else f"_{k}"
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
for k in format_keys
|
|
163
|
+
]
|
|
164
|
+
ansi_resets = [
|
|
165
|
+
r
|
|
166
|
+
for k in reset_keys
|
|
167
|
+
if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)).startswith(
|
|
168
|
+
f"{ANSI.char}{ANSI.start}"
|
|
169
|
+
)
|
|
170
|
+
]
|
|
109
171
|
else:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
172
|
+
ansi_resets = []
|
|
173
|
+
if not all(f.startswith(f"{ANSI.char}{ANSI.start}") for f in ansi_formats):
|
|
174
|
+
return match.group(0)
|
|
175
|
+
return (
|
|
176
|
+
"".join(ansi_formats)
|
|
177
|
+
+ (
|
|
178
|
+
f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})"
|
|
179
|
+
if esc and auto_reset_txt
|
|
180
|
+
else auto_reset_txt if auto_reset_txt else ""
|
|
181
|
+
)
|
|
182
|
+
+ ("" if esc else "".join(ansi_resets))
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
(FormatCodes.__get_default_ansi(default_color) if _default_start else "")
|
|
187
|
+
+ (result := "\n".join(COMPILED["format"].sub(replace_keys, line) for line in string.split("\n")))
|
|
188
|
+
if use_default
|
|
189
|
+
else result
|
|
190
|
+
)
|
|
121
191
|
|
|
122
192
|
@staticmethod
|
|
193
|
+
@lru_cache(maxsize=64)
|
|
123
194
|
def __config_console() -> None:
|
|
124
195
|
_sys.stdout.flush()
|
|
125
196
|
kernel32 = _ctypes.windll.kernel32
|
|
126
197
|
h = kernel32.GetStdHandle(-11)
|
|
127
198
|
mode = _ctypes.c_ulong()
|
|
128
199
|
kernel32.GetConsoleMode(h, _ctypes.byref(mode))
|
|
129
|
-
kernel32.SetConsoleMode(h, mode.value | 0x0004)
|
|
200
|
+
kernel32.SetConsoleMode(h, mode.value | 0x0004)
|
|
130
201
|
|
|
131
202
|
@staticmethod
|
|
132
|
-
def __get_default_ansi(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
203
|
+
def __get_default_ansi(
|
|
204
|
+
default_color: tuple,
|
|
205
|
+
format_key: str = None,
|
|
206
|
+
brightness_steps: int = None,
|
|
207
|
+
_modifiers: tuple[str, str] = (ANSI.modifier["lighten"], ANSI.modifier["darken"]),
|
|
208
|
+
) -> str | None:
|
|
209
|
+
if not brightness_steps or (format_key and COMPILED["bg?_default"].search(format_key)):
|
|
210
|
+
return (ANSI.seq_bg_color if format_key and COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
|
|
211
|
+
*default_color[:3]
|
|
212
|
+
)
|
|
213
|
+
if not (format_key in _modifiers[0] or format_key in _modifiers[1]):
|
|
141
214
|
return None
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
215
|
+
match = COMPILED["modifier"].match(format_key)
|
|
216
|
+
if not match:
|
|
217
|
+
return None
|
|
218
|
+
is_bg, modifiers = match.groups()
|
|
219
|
+
adjust = 0
|
|
220
|
+
for mod in _modifiers[0] + _modifiers[1]:
|
|
221
|
+
adjust = String.single_char_repeats(modifiers, mod)
|
|
222
|
+
if adjust and adjust > 0:
|
|
223
|
+
modifiers = mod
|
|
148
224
|
break
|
|
149
|
-
if
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if new_rgb:
|
|
156
|
-
return ANSI.seq_bg_color.format(new_rgb[0], new_rgb[1], new_rgb[2]) if is_bg else ANSI.seq_color.format(new_rgb[0], new_rgb[1], new_rgb[2])
|
|
225
|
+
if adjust == 0:
|
|
226
|
+
return None
|
|
227
|
+
elif modifiers in _modifiers[0]:
|
|
228
|
+
new_rgb = Color.adjust_lightness(default_color, (brightness_steps / 100) * adjust)
|
|
229
|
+
elif modifiers in _modifiers[1]:
|
|
230
|
+
new_rgb = Color.adjust_lightness(default_color, -(brightness_steps / 100) * adjust)
|
|
231
|
+
return (ANSI.seq_bg_color if is_bg else ANSI.seq_color).format(*new_rgb[:3])
|
|
157
232
|
|
|
158
233
|
@staticmethod
|
|
159
|
-
def __get_replacement(format_key:str, default_color:
|
|
234
|
+
def __get_replacement(format_key: str, default_color: rgba = None, brightness_steps: int = 20) -> str:
|
|
160
235
|
"""Gives you the corresponding ANSI code for the given format key.<br>
|
|
161
236
|
If `default_color` is not `None`, the text color will be `default_color` if all formats<br>
|
|
162
|
-
are reset or you can get lighter or darker version of `default_color` (also as BG)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return True
|
|
170
|
-
return False
|
|
171
|
-
def get_value(key:str) -> any:
|
|
172
|
-
for map_key in ANSI.codes_map:
|
|
173
|
-
if isinstance(map_key, tuple) and key in map_key:
|
|
174
|
-
return ANSI.codes_map[map_key]
|
|
175
|
-
elif key == map_key:
|
|
176
|
-
return ANSI.codes_map[map_key]
|
|
177
|
-
return None
|
|
178
|
-
use_default = default_color and (Color.is_valid_rgba(default_color, False) or Color.is_valid_hexa(default_color, False))
|
|
179
|
-
_format_key, format_key = format_key, FormatCodes.__normalize(format_key)
|
|
237
|
+
are reset or you can get lighter or darker version of `default_color` (also as BG)"""
|
|
238
|
+
use_default = default_color and Color.is_valid_rgba(default_color, False)
|
|
239
|
+
_format_key, format_key = format_key, ( # NORMALIZE THE FORMAT KEY (+ SAVE ORIGINAL)
|
|
240
|
+
"bg:" if "bg" in (parts := format_key.replace(" ", "").lower().split(":")) else ""
|
|
241
|
+
) + ("bright:" if any(x in parts for x in ("bright", "br")) else "") + ":".join(
|
|
242
|
+
p for p in parts if p not in ("bg", "bright", "br")
|
|
243
|
+
)
|
|
180
244
|
if use_default:
|
|
181
|
-
new_default_color
|
|
182
|
-
if new_default_color:
|
|
245
|
+
if new_default_color := FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps):
|
|
183
246
|
return new_default_color
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
247
|
+
for map_key in ANSI.codes_map:
|
|
248
|
+
if (isinstance(map_key, tuple) and format_key in map_key) or format_key == map_key:
|
|
249
|
+
return ANSI.seq().format(
|
|
250
|
+
next(
|
|
251
|
+
(
|
|
252
|
+
v
|
|
253
|
+
for k, v in ANSI.codes_map.items()
|
|
254
|
+
if format_key == k or (isinstance(k, tuple) and format_key in k)
|
|
255
|
+
),
|
|
256
|
+
None,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
rgb_match = _re.match(COMPILED["rgb"], format_key)
|
|
260
|
+
hex_match = _re.match(COMPILED["hex"], format_key)
|
|
188
261
|
try:
|
|
189
262
|
if rgb_match:
|
|
190
263
|
is_bg = rgb_match.group(1)
|
|
@@ -194,19 +267,11 @@ class FormatCodes:
|
|
|
194
267
|
elif hex_match:
|
|
195
268
|
is_bg = hex_match.group(1)
|
|
196
269
|
rgb = Color.to_rgba(hex_match.group(2))
|
|
197
|
-
return
|
|
198
|
-
|
|
270
|
+
return (
|
|
271
|
+
ANSI.seq_bg_color.format(rgb[0], rgb[1], rgb[2])
|
|
272
|
+
if is_bg
|
|
273
|
+
else ANSI.seq_color.format(rgb[0], rgb[1], rgb[2])
|
|
274
|
+
)
|
|
275
|
+
except Exception:
|
|
276
|
+
pass
|
|
199
277
|
return _format_key
|
|
200
|
-
|
|
201
|
-
@staticmethod
|
|
202
|
-
def __normalize(format_key:str) -> str:
|
|
203
|
-
"""Put the given format key in the correct format:<br>
|
|
204
|
-
`1` put `BG:` as first key-part<br>
|
|
205
|
-
`2` put `bright:` or `br:` as second key-part<br>
|
|
206
|
-
`3` put everything else behind<br>
|
|
207
|
-
`4` everything in lower case"""
|
|
208
|
-
format_key = format_key.replace(' ', '').lower().strip()
|
|
209
|
-
if ':' in format_key:
|
|
210
|
-
key_parts = format_key.split(':')
|
|
211
|
-
format_key = ('bg:' if 'bg' in key_parts else '') + ('bright:' if 'bright' in key_parts or 'br' in key_parts else '') + ''.join(Data.remove(key_parts, ['bg', 'bright', 'br']))
|
|
212
|
-
return format_key
|
xulbux/xx_json.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
from .xx_data import
|
|
2
|
-
from .xx_file import
|
|
1
|
+
from .xx_data import Data
|
|
2
|
+
from .xx_file import File
|
|
3
3
|
|
|
4
4
|
import json as _json
|
|
5
5
|
import os as _os
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
8
|
class Json:
|
|
11
9
|
|
|
12
10
|
@staticmethod
|
|
13
|
-
def read(
|
|
11
|
+
def read(
|
|
12
|
+
json_file: str,
|
|
13
|
+
comment_start: str = ">>",
|
|
14
|
+
comment_end: str = "<<",
|
|
15
|
+
return_original: bool = False,
|
|
16
|
+
) -> dict | tuple[dict, dict]:
|
|
14
17
|
"""Read JSON files, ignoring comments.\n
|
|
15
18
|
-------------------------------------------------------------------------
|
|
16
19
|
If only `comment_start` is found at the beginning of an item,<br>
|
|
@@ -19,8 +22,8 @@ class Json:
|
|
|
19
22
|
the the section from `comment_start` to `comment_end` is ignored.<br>
|
|
20
23
|
If `return_original` is set to `True`, the original JSON is returned<br>
|
|
21
24
|
additionally. (returns: `[processed_json, original_json]`)"""
|
|
22
|
-
file_path = File._make_path(json_file,
|
|
23
|
-
with open(file_path,
|
|
25
|
+
file_path = File._make_path(json_file, "json", prefer_base_dir=True)
|
|
26
|
+
with open(file_path, "r") as f:
|
|
24
27
|
content = f.read()
|
|
25
28
|
try:
|
|
26
29
|
data = _json.loads(content)
|
|
@@ -32,21 +35,33 @@ class Json:
|
|
|
32
35
|
return (processed_data, data) if return_original else processed_data
|
|
33
36
|
|
|
34
37
|
@staticmethod
|
|
35
|
-
def create(
|
|
36
|
-
|
|
38
|
+
def create(
|
|
39
|
+
content: dict,
|
|
40
|
+
new_file: str = "config",
|
|
41
|
+
indent: int = 2,
|
|
42
|
+
compactness: int = 1,
|
|
43
|
+
force: bool = False,
|
|
44
|
+
) -> str:
|
|
45
|
+
file_path = File._make_path(new_file, "json", prefer_base_dir=True)
|
|
37
46
|
if _os.path.exists(file_path) and not force:
|
|
38
|
-
with open(file_path,
|
|
47
|
+
with open(file_path, "r", encoding="utf-8") as existing_f:
|
|
39
48
|
existing_content = _json.load(existing_f)
|
|
40
49
|
if existing_content == content:
|
|
41
|
-
raise FileExistsError(
|
|
42
|
-
raise FileExistsError(
|
|
43
|
-
with open(file_path,
|
|
50
|
+
raise FileExistsError("Already created this file. (nothing changed)")
|
|
51
|
+
raise FileExistsError("File already exists.")
|
|
52
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
44
53
|
f.write(Data.to_str(content, indent, compactness, as_json=True))
|
|
45
54
|
full_path = _os.path.abspath(file_path)
|
|
46
55
|
return full_path
|
|
47
56
|
|
|
48
57
|
@staticmethod
|
|
49
|
-
def update(
|
|
58
|
+
def update(
|
|
59
|
+
json_file: str,
|
|
60
|
+
update_values: str | list[str],
|
|
61
|
+
comment_start: str = ">>",
|
|
62
|
+
comment_end: str = "<<",
|
|
63
|
+
sep: tuple[str, str] = ("->", "::"),
|
|
64
|
+
) -> None:
|
|
50
65
|
"""Function to easily update single/multiple values inside JSON files.\n
|
|
51
66
|
--------------------------------------------------------------------------------------------------------
|
|
52
67
|
The param `json_file` is the path to the JSON file or just the name of the JSON file to be updated.\n
|
|
@@ -67,15 +82,20 @@ class Json:
|
|
|
67
82
|
--------------------------------------------------------------------------------------------------------
|
|
68
83
|
If only `comment_start` is found at the beginning of an item, the whole item is counted<br>
|
|
69
84
|
as a comment and therefore ignored. If `comment_start` and `comment_end` are found<br>
|
|
70
|
-
inside an item, the the section from `comment_start` to `comment_end` is ignored.
|
|
85
|
+
inside an item, the the section from `comment_start` to `comment_end` is ignored.
|
|
86
|
+
"""
|
|
71
87
|
if isinstance(update_values, str):
|
|
72
88
|
update_values = [update_values]
|
|
73
|
-
valid_entries = [
|
|
74
|
-
|
|
89
|
+
valid_entries = [
|
|
90
|
+
(parts[0].strip(), parts[1])
|
|
91
|
+
for update_value in update_values
|
|
92
|
+
if len(parts := update_value.split(str(sep[1]).strip())) == 2
|
|
93
|
+
]
|
|
94
|
+
value_paths, new_values = zip(*valid_entries) if valid_entries else ([], [])
|
|
75
95
|
processed_data, data = Json.read(json_file, comment_start, comment_end, return_original=True)
|
|
76
96
|
update = []
|
|
77
97
|
for value_path, new_value in zip(value_paths, new_values):
|
|
78
98
|
path_id = Data.get_path_id(processed_data, value_path)
|
|
79
|
-
update.append(f
|
|
99
|
+
update.append(f"{path_id}::{new_value}")
|
|
80
100
|
updated = Data.set_value_by_path_id(data, update)
|
|
81
101
|
Json.create(updated, json_file, force=True)
|