xulbux 1.5.5__py3-none-any.whl → 1.5.7__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_file.py CHANGED
@@ -4,47 +4,53 @@ from .xx_path import *
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,201 @@ 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
71
  from .xx_string import *
74
72
  from .xx_regex import *
75
73
  from .xx_color import *
76
74
  from .xx_data import *
77
75
 
76
+ from functools import lru_cache
78
77
  import ctypes as _ctypes
79
78
  import regex as _rx
80
79
  import sys as _sys
81
80
  import re as _re
82
81
 
83
82
 
83
+ COMPILED = { # PRECOMPILE REGULAR EXPRESSIONS
84
+ "*": _re.compile(r"\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]"),
85
+ "*color": _re.compile(r"\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]"),
86
+ "format": _rx.compile(
87
+ Regex.brackets("[", "]", is_group=True) + r"(?:\s*([/\\]?)\s*" + Regex.brackets("(", ")", is_group=True) + r")?"
88
+ ),
89
+ "bg?_default": _re.compile(r"(?i)((?:BG\s*:)?)\s*default"),
90
+ "bg_default": _re.compile(r"(?i)BG\s*:\s*default"),
91
+ "modifier": _re.compile(
92
+ rf'(?i)((?:BG\s*:)?)\s*({"|".join([f"{_re.escape(m)}+" for m in ANSI.modifier["lighten"] + ANSI.modifier["darken"]])})$'
93
+ ),
94
+ "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*$"),
95
+ "hex": _re.compile(r"(?i)^\s*(BG\s*:)?\s*(?:#|0x)?([0-9A-F]{6}|[0-9A-F]{3})\s*$"),
96
+ }
84
97
 
85
98
 
86
99
  class FormatCodes:
87
100
 
88
101
  @staticmethod
89
- def print(*values:object, default_color:hexa|rgba = None, brightness_steps:int = 20, sep:str = ' ', end:str = '\n') -> None:
102
+ def print(
103
+ *values: object,
104
+ default_color: hexa | rgba = None,
105
+ brightness_steps: int = 20,
106
+ sep: str = " ",
107
+ end: str = "\n",
108
+ flush: bool = True,
109
+ ) -> None:
90
110
  FormatCodes.__config_console()
91
- _sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)), default_color, brightness_steps) + end)
92
- _sys.stdout.flush()
111
+ _sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)) + end, default_color, brightness_steps))
112
+ if flush:
113
+ _sys.stdout.flush()
93
114
 
94
115
  @staticmethod
95
- def input(prompt:object = '', default_color:hexa|rgba = None, brightness_steps:int = 20) -> str:
116
+ def input(
117
+ prompt: object = "",
118
+ default_color: hexa | rgba = None,
119
+ brightness_steps: int = 20,
120
+ ) -> str:
96
121
  FormatCodes.__config_console()
97
122
  return input(FormatCodes.to_ansi(prompt, default_color, brightness_steps))
98
123
 
99
124
  @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))
125
+ def to_ansi(
126
+ string: str, default_color: hexa | rgba = None, brightness_steps: int = 20, _default_start: bool = True
127
+ ) -> str:
128
+ result = ""
129
+ if Color.is_valid_rgba(default_color, False):
130
+ use_default = True
131
+ elif Color.is_valid_hexa(default_color, False):
132
+ use_default, default_color = True, Color.to_rgba(default_color)
133
+ else:
134
+ use_default = False
102
135
  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)
136
+ string = COMPILED["*"].sub(r"[\1_|default\2]", string) # REPLACE `[…|*|…]` WITH `[…|_|default|…]`
137
+ string = COMPILED["*color"].sub(r"[\1default\2]", string) # REPLACE `[…|*color|…]` WITH `[…|default|…]`
138
+
139
+ def replace_keys(match: _re.Match) -> str:
140
+ format_keys = match.group(1)
141
+ esc = match.group(2)
142
+ auto_reset_txt = match.group(3)
107
143
  if not format_keys:
108
144
  return match.group(0)
145
+ format_keys = [k.strip() for k in format_keys.split("|") if k.strip()]
146
+ ansi_formats = [FormatCodes.__get_replacement(k, default_color, brightness_steps) for k in format_keys]
147
+ if auto_reset_txt and not esc:
148
+ reset_keys = [
149
+ (
150
+ "_color"
151
+ if k in ANSI.color_map or Color.is_valid_rgba(k) or Color.is_valid_hexa(k)
152
+ else (
153
+ "_bg"
154
+ if ({"bg", "bright", "br"} & set(k.lower().split(":")))
155
+ and len(k.split(":")) <= 3
156
+ and any(
157
+ k[i:] in ANSI.color_map or Color.is_valid_rgba(k[i:]) or Color.is_valid_hexa(k[i:])
158
+ for i in range(len(k))
159
+ )
160
+ else f"_{k}"
161
+ )
162
+ )
163
+ for k in format_keys
164
+ ]
165
+ ansi_resets = [
166
+ r
167
+ for k in reset_keys
168
+ if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)).startswith(
169
+ f"{ANSI.char}{ANSI.start}"
170
+ )
171
+ ]
109
172
  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
173
+ ansi_resets = []
174
+ if not all(f.startswith(f"{ANSI.char}{ANSI.start}") for f in ansi_formats):
175
+ return match.group(0)
176
+ return (
177
+ "".join(ansi_formats)
178
+ + (
179
+ f"({FormatCodes.to_ansi(auto_reset_txt, default_color, brightness_steps, False)})"
180
+ if esc and auto_reset_txt
181
+ else auto_reset_txt if auto_reset_txt else ""
182
+ )
183
+ + ("" if esc else "".join(ansi_resets))
184
+ )
185
+
186
+ return (
187
+ (FormatCodes.__get_default_ansi(default_color) if _default_start else "")
188
+ + (result := "\n".join(COMPILED["format"].sub(replace_keys, line) for line in string.split("\n")))
189
+ if use_default
190
+ else result
191
+ )
121
192
 
122
193
  @staticmethod
194
+ @lru_cache(maxsize=64)
123
195
  def __config_console() -> None:
124
196
  _sys.stdout.flush()
125
197
  kernel32 = _ctypes.windll.kernel32
126
198
  h = kernel32.GetStdHandle(-11)
127
199
  mode = _ctypes.c_ulong()
128
200
  kernel32.GetConsoleMode(h, _ctypes.byref(mode))
129
- kernel32.SetConsoleMode(h, mode.value | 0x0004) # ENABLE VIRTUAL TERMINAL PROCESSING
201
+ kernel32.SetConsoleMode(h, mode.value | 0x0004)
130
202
 
131
203
  @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):
204
+ def __get_default_ansi(
205
+ default_color: tuple,
206
+ format_key: str = None,
207
+ brightness_steps: int = None,
208
+ _modifiers: tuple[str, str] = (ANSI.modifier["lighten"], ANSI.modifier["darken"]),
209
+ ) -> str | None:
210
+ if not brightness_steps or (format_key and COMPILED["bg?_default"].search(format_key)):
211
+ return (ANSI.seq_bg_color if format_key and COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
212
+ *default_color[:3]
213
+ )
214
+ if not (format_key in _modifiers[0] or format_key in _modifiers[1]):
141
215
  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)
216
+ match = COMPILED["modifier"].match(format_key)
217
+ if not match:
218
+ return None
219
+ is_bg, modifiers = match.groups()
220
+ adjust = 0
221
+ for mod in _modifiers[0] + _modifiers[1]:
222
+ adjust = String.single_char_repeats(modifiers, mod)
223
+ if adjust and adjust > 0:
224
+ modifiers = mod
148
225
  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])
226
+ if adjust == 0:
227
+ return None
228
+ elif modifiers in _modifiers[0]:
229
+ new_rgb = Color.adjust_lightness(default_color, (brightness_steps / 100) * adjust)
230
+ elif modifiers in _modifiers[1]:
231
+ new_rgb = Color.adjust_lightness(default_color, -(brightness_steps / 100) * adjust)
232
+ return (ANSI.seq_bg_color if is_bg else ANSI.seq_color).format(*new_rgb[:3])
157
233
 
158
234
  @staticmethod
159
- def __get_replacement(format_key:str, default_color:hexa|rgba = None, brightness_steps:int = 20, _modifiers:tuple[str, str] = ('+l', '-d')) -> str:
235
+ def __get_replacement(format_key: str, default_color: rgba = None, brightness_steps: int = 20) -> str:
160
236
  """Gives you the corresponding ANSI code for the given format key.<br>
161
237
  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)
238
+ are reset or you can get lighter or darker version of `default_color` (also as BG)"""
239
+ use_default = default_color and Color.is_valid_rgba(default_color, False)
240
+ _format_key, format_key = format_key, ( # NORMALIZE THE FORMAT KEY (+ SAVE ORIGINAL)
241
+ "bg:" if "bg" in (parts := format_key.replace(" ", "").lower().split(":")) else ""
242
+ ) + ("bright:" if any(x in parts for x in ("bright", "br")) else "") + ":".join(
243
+ p for p in parts if p not in ("bg", "bright", "br")
244
+ )
180
245
  if use_default:
181
- new_default_color = FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps, _modifiers)
182
- if new_default_color:
246
+ if new_default_color := FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps):
183
247
  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)
248
+ for map_key in ANSI.codes_map:
249
+ if (isinstance(map_key, tuple) and format_key in map_key) or format_key == map_key:
250
+ return ANSI.seq().format(
251
+ next(
252
+ (
253
+ v
254
+ for k, v in ANSI.codes_map.items()
255
+ if format_key == k or (isinstance(k, tuple) and format_key in k)
256
+ ),
257
+ None,
258
+ )
259
+ )
260
+ rgb_match = _re.match(COMPILED["rgb"], format_key)
261
+ hex_match = _re.match(COMPILED["hex"], format_key)
188
262
  try:
189
263
  if rgb_match:
190
264
  is_bg = rgb_match.group(1)
@@ -194,19 +268,11 @@ class FormatCodes:
194
268
  elif hex_match:
195
269
  is_bg = hex_match.group(1)
196
270
  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
271
+ return (
272
+ ANSI.seq_bg_color.format(rgb[0], rgb[1], rgb[2])
273
+ if is_bg
274
+ else ANSI.seq_color.format(rgb[0], rgb[1], rgb[2])
275
+ )
276
+ except Exception:
277
+ pass
199
278
  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
@@ -5,12 +5,15 @@ 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)
xulbux/xx_path.py CHANGED
@@ -5,41 +5,48 @@ import sys as _sys
5
5
  import os as _os
6
6
 
7
7
 
8
-
9
-
10
8
  class Path:
11
9
 
12
10
  @staticmethod
13
- def get(cwd:bool = False, base_dir:bool = False) -> str|list:
11
+ def get(cwd: bool = False, base_dir: bool = False) -> str | list:
14
12
  paths = []
15
13
  if cwd:
16
14
  paths.append(_os.getcwd())
17
15
  if base_dir:
18
- if getattr(_sys, 'frozen', False):
16
+ if getattr(_sys, "frozen", False):
19
17
  base_path = _os.path.dirname(_sys.executable)
20
18
  else:
21
- main_module = _sys.modules['__main__']
22
- if hasattr(main_module, '__file__'):
19
+ main_module = _sys.modules["__main__"]
20
+ if hasattr(main_module, "__file__"):
23
21
  base_path = _os.path.dirname(_os.path.abspath(main_module.__file__))
24
- elif hasattr(main_module, '__spec__') and main_module.__spec__ and getattr(main_module.__spec__, 'origin', None):
22
+ elif (
23
+ hasattr(main_module, "__spec__") and main_module.__spec__ and getattr(main_module.__spec__, "origin", None)
24
+ ):
25
25
  base_path = _os.path.dirname(_os.path.abspath(main_module.__spec__.origin))
26
26
  else:
27
- raise RuntimeError('Can only get base directory if ran from a file.')
27
+ raise RuntimeError("Can only get base directory if ran from a file.")
28
28
  paths.append(base_path)
29
29
  return paths[0] if len(paths) == 1 else paths
30
30
 
31
31
  @staticmethod
32
- def extend(path:str, search_in:str|list[str] = None, raise_error:bool = False, correct_path:bool = False) -> str:
33
- if path in (None, ''):
32
+ def extend(
33
+ path: str,
34
+ search_in: str | list[str] = None,
35
+ raise_error: bool = False,
36
+ correct_path: bool = False,
37
+ ) -> str:
38
+ if path in (None, ""):
34
39
  return path
35
- def get_closest_match(dir:str, part:str) -> str|None:
40
+
41
+ def get_closest_match(dir: str, part: str) -> str | None:
36
42
  try:
37
43
  files_and_dirs = _os.listdir(dir)
38
44
  matches = _difflib.get_close_matches(part, files_and_dirs, n=1, cutoff=0.6)
39
45
  return matches[0] if matches else None
40
- except:
46
+ except Exception:
41
47
  return None
42
- def find_path(start:str, parts:list[str]) -> str|None:
48
+
49
+ def find_path(start: str, parts: list[str]) -> str | None:
43
50
  current = start
44
51
  for part in parts:
45
52
  if _os.path.isfile(current):
@@ -49,23 +56,30 @@ class Path:
49
56
  if current is None:
50
57
  return None
51
58
  return current if _os.path.exists(current) and current != start else None
52
- def expand_env_path(p:str) -> str:
53
- if not '%' in p:
59
+
60
+ def expand_env_path(p: str) -> str:
61
+ if "%" not in p:
54
62
  return p
55
- parts = p.split('%')
63
+ parts = p.split("%")
56
64
  for i in range(1, len(parts), 2):
57
65
  if parts[i].upper() in _os.environ:
58
66
  parts[i] = _os.environ[parts[i].upper()]
59
- return ''.join(parts)
67
+ return "".join(parts)
68
+
60
69
  path = _os.path.normpath(expand_env_path(path))
61
70
  if _os.path.isabs(path):
62
71
  drive, rel_path = _os.path.splitdrive(path)
63
72
  rel_path = rel_path.lstrip(_os.sep)
64
- search_dirs = [drive + _os.sep] if drive else [_os.sep]
73
+ search_dirs = (drive + _os.sep) if drive else [_os.sep]
65
74
  else:
66
75
  rel_path = path.lstrip(_os.sep)
67
76
  base_dir = Path.get(base_dir=True)
68
- search_dirs = [_os.getcwd(), base_dir, _os.path.expanduser('~'), _tempfile.gettempdir()]
77
+ search_dirs = (
78
+ _os.getcwd(),
79
+ base_dir,
80
+ _os.path.expanduser("~"),
81
+ _tempfile.gettempdir(),
82
+ )
69
83
  if search_in:
70
84
  search_dirs.extend([search_in] if isinstance(search_in, str) else search_in)
71
85
  path_parts = rel_path.split(_os.sep)
@@ -74,13 +88,14 @@ class Path:
74
88
  if _os.path.exists(full_path):
75
89
  return full_path
76
90
  match = find_path(search_dir, path_parts) if correct_path else None
77
- if match: return match
91
+ if match:
92
+ return match
78
93
  if raise_error:
79
- raise FileNotFoundError(f'Path \'{path}\' not found in specified directories.')
94
+ raise FileNotFoundError(f"Path '{path}' not found in specified directories.")
80
95
  return _os.path.join(search_dirs[0], rel_path)
81
96
 
82
97
  @staticmethod
83
- def remove(path:str, only_content:bool = False) -> None:
98
+ def remove(path: str, only_content: bool = False) -> None:
84
99
  if not _os.path.exists(path):
85
100
  return None
86
101
  if not only_content:
@@ -94,4 +109,4 @@ class Path:
94
109
  elif _os.path.isdir(file_path):
95
110
  _shutil.rmtree(file_path)
96
111
  except Exception as e:
97
- raise Exception(f'Failed to delete {file_path}. Reason: {e}')
112
+ raise Exception(f"Failed to delete {file_path}. Reason: {e}")