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/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(filename:str, filetype:str, search_in:str|list[str] = None, prefer_base_dir:bool = True, correct_path:bool = False) -> str:
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'.{filetype.lower()}'):
20
- filename = f'{filename}.{filetype.lower()}'
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 _os.path.join(Path.get(base_dir=True), filename) if prefer_base_dir else _os.path.join(_os.getcwd(), filename)
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('.')[0]
35
+ filename = filename_with_ext.split(".")[0]
30
36
  camel_case_filename = String.to_camel_case(filename)
31
- new_filename = f'{camel_case_filename}{new_extension}'
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 = '', file:str = 'new_file.txt', force:bool = False) -> 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, 'r', encoding='utf-8') as existing_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('Already created this file. (nothing changed)')
46
- raise FileExistsError('File already exists.')
47
- with open(file, 'w', encoding='utf-8') as f:
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.<br>
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(*values:object, default_color:hexa|rgba = None, brightness_steps:int = 20, sep:str = ' ', end:str = '\n') -> None:
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) + end)
92
- _sys.stdout.flush()
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(prompt:object = '', default_color:hexa|rgba = None, brightness_steps:int = 20) -> str:
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(string:str, default_color:hexa|rgba = None, brightness_steps:int = 20, _default_start:bool = True) -> str:
101
- result, use_default = '', default_color and (Color.is_valid_rgba(default_color, False) or Color.is_valid_hexa(default_color, False))
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 = _re.sub(r'\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]', r'[\1_|default\2]', string) # REPLACE `[…|*|…]` WITH `[…|_|default|…]`
104
- string = _re.sub(r'\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]', r'[\1default\2]', string) # REPLACE `[…|*color|…]` WITH `[…|default|…]`
105
- def replace_keys(match:_rx.Match) -> str:
106
- format_keys, esc, auto_reset_txt = match.group(1), match.group(2), match.group(3)
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
- format_keys = [k.replace(' ', '') for k in format_keys.split('|') if k.replace(' ', '')]
111
- ansi_resets, ansi_formats = [], [FormatCodes.__get_replacement(k, default_color, brightness_steps) for k in format_keys]
112
- if auto_reset_txt and not esc:
113
- reset_keys = ['_color' if Color.is_valid(k) or k in ANSI.color_map
114
- else '_bg' if (set(k.lower().split(':')) & {'bg', 'bright', 'br'} and len(k.split(':')) <= 3 and any(Color.is_valid(k[i:]) or k[i:] in ANSI.color_map for i in range(len(k))))
115
- else f'_{k}' for k in format_keys]
116
- ansi_resets = [r for k in reset_keys if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)).startswith(f'{ANSI.char}{ANSI.start}')]
117
- if not all(f.startswith(f'{ANSI.char}{ANSI.start}') for f in ansi_formats): return match.group(0)
118
- return ''.join(ansi_formats) + ((f'({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})' if esc else auto_reset_txt) if auto_reset_txt else '') + ('' if esc else ''.join(ansi_resets))
119
- result = '\n'.join(_rx.sub(Regex.brackets('[', ']', is_group=True) + r'(?:\s*([/\\]?)\s*' + Regex.brackets('(', ')', is_group=True) + r')?', replace_keys, line) for line in string.splitlines())
120
- return (FormatCodes.__get_default_ansi(default_color) if _default_start else '') + result if use_default else result
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) # ENABLE VIRTUAL TERMINAL PROCESSING
200
+ kernel32.SetConsoleMode(h, mode.value | 0x0004)
130
201
 
131
202
  @staticmethod
132
- def __get_default_ansi(default_color:hexa|rgba, format_key:str = None, brightness_steps:int = None, _modifiers:tuple[str,str] = ('+l', '-d')) -> str|None:
133
- if Color.is_valid_hexa(default_color, False):
134
- default_color = Color.to_rgba(default_color)
135
- if not brightness_steps or (format_key and _re.search(r'(?i)((?:BG\s*:)?)\s*default', format_key)):
136
- if format_key and _re.search(r'(?i)BG\s*:\s*default', format_key):
137
- return ANSI.seq_bg_color.format(default_color[0], default_color[1], default_color[2])
138
- return ANSI.seq_color.format(default_color[0], default_color[1], default_color[2])
139
- match = _re.match(rf'(?i)((?:BG\s*:)?)\s*({"|".join([f"{_re.escape(m)}+" for m in _modifiers[0] + _modifiers[1]])})$', format_key)
140
- if not match or not match.group(2):
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
- is_bg, modifier = match.group(1), match.group(2)
143
- new_rgb, lighten, darken = None, None, None
144
- for mod in _modifiers[0]:
145
- lighten = String.get_repeated_symbol(modifier, mod)
146
- if lighten and lighten > 0:
147
- new_rgb = Color.adjust_lightness(default_color, (brightness_steps / 100) * lighten)
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 not new_rgb:
150
- for mod in _modifiers[1]:
151
- darken = String.get_repeated_symbol(modifier, mod)
152
- if darken and darken > 0:
153
- new_rgb = Color.adjust_lightness(default_color, -(brightness_steps / 100) * darken)
154
- break
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:hexa|rgba = None, brightness_steps:int = 20, _modifiers:tuple[str, str] = ('+l', '-d')) -> str:
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) by<br>
163
- using one or more `_modifiers` symbols as a format key ()"""
164
- def key_exists(key:str) -> bool:
165
- for map_key in ANSI.codes_map:
166
- if isinstance(map_key, tuple) and key in map_key:
167
- return True
168
- elif key == map_key:
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 = FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps, _modifiers)
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
- if key_exists(format_key):
185
- return ANSI.seq().format(get_value(format_key))
186
- rgb_match = _re.match(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*', format_key)
187
- hex_match = _re.match(r'(?i)\s*(BG\s*:)?\s*(?:#|0x)?([0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{4}|[0-9A-F]{3})\s*', format_key)
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 ANSI.seq_bg_color.format(rgb[0], rgb[1], rgb[2]) if is_bg else ANSI.seq_color.format(rgb[0], rgb[1], rgb[2])
198
- except Exception: pass
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(json_file:str, comment_start:str = '>>', comment_end:str = '<<', return_original:bool = False) -> dict|tuple[dict,dict]:
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, 'json', prefer_base_dir=True)
23
- with open(file_path, 'r') as f:
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(content:dict, new_file:str = 'config', indent:int = 2, compactness:int = 1, force:bool = False) -> str:
36
- file_path = File._make_path(new_file, 'json', prefer_base_dir=True)
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, 'r', encoding='utf-8') as existing_f:
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(f'Already created this file. (nothing changed)')
42
- raise FileExistsError('File already exists.')
43
- with open(file_path, 'w', encoding='utf-8') as f:
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(json_file:str, update_values:str|list[str], comment_start:str = '>>', comment_end:str = '<<', sep:tuple[str,str] = ('->', '::')) -> None:
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 = [(parts[0].strip(), parts[1]) for update_value in update_values if len(parts := update_value.split(str(sep[1]).strip())) == 2]
74
- value_paths, new_values = (zip(*valid_entries) if valid_entries else ([], []))
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'{path_id}::{new_value}')
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)