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_data.py CHANGED
@@ -2,19 +2,17 @@ import math as _math
2
2
  import re as _re
3
3
 
4
4
 
5
-
6
-
7
5
  class Data:
8
6
 
9
7
  @staticmethod
10
- def chars_count(data:list|tuple|set|frozenset|dict) -> int:
8
+ def chars_count(data: list | tuple | set | frozenset | dict) -> int:
11
9
  """The sum of all the characters including the keys in dictionaries."""
12
10
  if isinstance(data, dict):
13
11
  return sum(len(str(k)) + len(str(v)) for k, v in data.items())
14
12
  return sum(len(str(item)) for item in data)
15
13
 
16
14
  @staticmethod
17
- def strip(data:list|tuple|dict) -> list|tuple|dict:
15
+ def strip(data: list | tuple | dict) -> list | tuple | dict:
18
16
  if isinstance(data, dict):
19
17
  return {k: v.strip() if isinstance(v, str) else Data.strip(v) for k, v in data.items()}
20
18
  elif isinstance(data, (list, tuple)):
@@ -23,7 +21,7 @@ class Data:
23
21
  return data.strip() if isinstance(data, str) else data
24
22
 
25
23
  @staticmethod
26
- def remove(data:list|tuple|dict, items:list[str]) -> list|tuple|dict:
24
+ def remove(data: list | tuple | dict, items: list[str]) -> list | tuple | dict:
27
25
  """Remove multiple items from lists and tuples or keys from dictionaries."""
28
26
  if isinstance(data, (list, tuple)):
29
27
  result = [k for k in data if k not in items]
@@ -32,14 +30,17 @@ class Data:
32
30
  return {k: v for k, v in data.items() if k not in items}
33
31
 
34
32
  @staticmethod
35
- def remove_empty_items(data:list|tuple|dict, spaces_are_empty:bool = False) -> list|tuple|dict:
33
+ def remove_empty_items(data: list | tuple | dict, spaces_are_empty: bool = False) -> list | tuple | dict:
36
34
  if isinstance(data, dict):
37
35
  filtered_dict = {}
38
36
  for key, value in data.items():
39
37
  if isinstance(value, (list, tuple, dict)):
40
38
  filtered_value = Data.remove_empty_items(value, spaces_are_empty)
41
- if filtered_value: filtered_dict[key] = filtered_value
42
- elif value not in (None, '') and not ((spaces_are_empty and isinstance(value, str)) and value.strip() in (None, '')):
39
+ if filtered_value:
40
+ filtered_dict[key] = filtered_value
41
+ elif value not in (None, "") and not (
42
+ (spaces_are_empty and isinstance(value, str)) and value.strip() in (None, "")
43
+ ):
43
44
  filtered_dict[key] = value
44
45
  return filtered_dict
45
46
  filtered = []
@@ -50,12 +51,12 @@ class Data:
50
51
  if isinstance(item, tuple):
51
52
  deduped_item = tuple(deduped_item)
52
53
  filtered.append(deduped_item)
53
- elif item not in (None, '') and not ((spaces_are_empty and isinstance(item, str)) and item.strip() in (None, '')):
54
+ elif item not in (None, "") and not ((spaces_are_empty and isinstance(item, str)) and item.strip() in (None, "")):
54
55
  filtered.append(item)
55
56
  return tuple(filtered) if isinstance(data, tuple) else filtered
56
57
 
57
58
  @staticmethod
58
- def remove_duplicates(data:list|tuple|dict) -> list|tuple|dict:
59
+ def remove_duplicates(data: list | tuple | dict) -> list | tuple | dict:
59
60
  if isinstance(data, dict):
60
61
  return {k: Data.remove_duplicates(v) for k, v in data.items()}
61
62
  elif isinstance(data, (list, tuple)):
@@ -71,7 +72,12 @@ class Data:
71
72
  return data
72
73
 
73
74
  @staticmethod
74
- def remove_comments(data:list|tuple|dict, comment_start:str = '>>', comment_end:str = '<<', comment_sep:str = '') -> list|tuple|dict:
75
+ def remove_comments(
76
+ data: list | tuple | dict,
77
+ comment_start: str = ">>",
78
+ comment_end: str = "<<",
79
+ comment_sep: str = "",
80
+ ) -> list | tuple | dict:
75
81
  """Remove comments from a list, tuple or dictionary.\n
76
82
  -----------------------------------------------------------------------------------------------------------------
77
83
  The `data` parameter is your list, tuple or dictionary, where the comments should get removed from.<br>
@@ -109,8 +115,12 @@ class Data:
109
115
   `value3` The comment is removed and the parts left and right are joined through `comment_sep`.<br>
110
116
   `value4` The whole value is removed, since the whole value was a comment.<br>
111
117
  For `key2`, the key, including its whole values will be removed.<br>
112
- For `key3`, since all its values are just comments, the key will still exist, but with a value of `None`."""
113
- def process_item(item:dict|list|tuple|str) -> dict|list|tuple|str|None:
118
+ For `key3`, since all its values are just comments, the key will still exist, but with a value of `None`.
119
+ """
120
+
121
+ def process_item(
122
+ item: dict | list | tuple | str,
123
+ ) -> dict | list | tuple | str | None:
114
124
  if isinstance(item, dict):
115
125
  processed_dict = {}
116
126
  for key, val in item.items():
@@ -118,7 +128,8 @@ class Data:
118
128
  if processed_key is not None:
119
129
  processed_val = process_item(val)
120
130
  if isinstance(val, (list, tuple, dict)):
121
- if processed_val: processed_dict[processed_key] = processed_val
131
+ if processed_val:
132
+ processed_dict[processed_key] = processed_val
122
133
  elif processed_val is not None:
123
134
  processed_dict[processed_key] = processed_val
124
135
  else:
@@ -130,17 +141,28 @@ class Data:
130
141
  return tuple(v for v in (process_item(val) for val in item) if v is not None)
131
142
  elif isinstance(item, str):
132
143
  if comment_end:
133
- no_comments = _re.sub(rf'^((?:(?!{_re.escape(comment_start)}).)*){_re.escape(comment_start)}(?:(?:(?!{_re.escape(comment_end)}).)*)(?:{_re.escape(comment_end)})?(.*?)$',
134
- lambda m: f'{m.group(1).strip()}{comment_sep if (m.group(1).strip() not in ["", None]) and (m.group(2).strip() not in ["", None]) else ""}{m.group(2).strip()}', item)
144
+ no_comments = _re.sub(
145
+ rf"^((?:(?!{_re.escape(comment_start)}).)*){_re.escape(comment_start)}(?:(?:(?!{_re.escape(comment_end)}).)*)(?:{_re.escape(comment_end)})?(.*?)$",
146
+ lambda m: f'{m.group(1).strip()}{comment_sep if (m.group(1).strip() not in (None, "")) and (m.group(2).strip() not in (None, "")) else ""}{m.group(2).strip()}',
147
+ item,
148
+ )
135
149
  else:
136
150
  no_comments = None if item.lstrip().startswith(comment_start) else item
137
- return no_comments.strip() if no_comments and no_comments.strip() != '' else None
151
+ return no_comments.strip() if no_comments and no_comments.strip() != "" else None
138
152
  else:
139
153
  return item
154
+
140
155
  return process_item(data)
141
156
 
142
157
  @staticmethod
143
- def is_equal(data1:list|tuple|dict, data2:list|tuple|dict, ignore_paths:str|list[str] = '', comment_start:str = '>>', comment_end:str = '<<', sep:str = '->') -> bool:
158
+ def is_equal(
159
+ data1: list | tuple | dict,
160
+ data2: list | tuple | dict,
161
+ ignore_paths: str | list[str] = "",
162
+ comment_start: str = ">>",
163
+ comment_end: str = "<<",
164
+ sep: str = "->",
165
+ ) -> bool:
144
166
  """Compares two structures and returns `True` if they are equal and `False` otherwise.\n
145
167
  ⇾ **Will not detect, if a key-name has changed, only if removed or added.**\n
146
168
  ------------------------------------------------------------------------------------------------
@@ -149,13 +171,25 @@ class Data:
149
171
  ------------------------------------------------------------------------------------------------
150
172
  The paths from `ignore_paths` work exactly the same way as the paths from `value_paths`<br>
151
173
  in the function `Data.get_path_id()`, just like the `sep` parameter. For more detailed<br>
152
- explanation, see the documentation of the function `Data.get_path_id()`."""
153
- def process_ignore_paths(ignore_paths:str|list[str]) -> list[list[str]]:
174
+ explanation, see the documentation of the function `Data.get_path_id()`.
175
+ """
176
+
177
+ def process_ignore_paths(
178
+ ignore_paths: str | list[str],
179
+ ) -> list[list[str]]:
154
180
  if isinstance(ignore_paths, str):
155
181
  ignore_paths = [ignore_paths]
156
182
  return [path.split(sep) for path in ignore_paths if path]
157
- def compare(d1:dict|list|tuple, d2:dict|list|tuple, ignore_paths:list[list[str]], current_path:list = []) -> bool:
158
- if ignore_paths and any(current_path == path[:len(current_path)] and len(current_path) == len(path) for path in ignore_paths):
183
+
184
+ def compare(
185
+ d1: dict | list | tuple,
186
+ d2: dict | list | tuple,
187
+ ignore_paths: list[list[str]],
188
+ current_path: list = [],
189
+ ) -> bool:
190
+ if ignore_paths and any(
191
+ current_path == path[: len(current_path)] and len(current_path) == len(path) for path in ignore_paths
192
+ ):
159
193
  return True
160
194
  if isinstance(d1, dict) and isinstance(d2, dict):
161
195
  if set(d1.keys()) != set(d2.keys()):
@@ -164,13 +198,23 @@ class Data:
164
198
  elif isinstance(d1, (list, tuple)) and isinstance(d2, (list, tuple)):
165
199
  if len(d1) != len(d2):
166
200
  return False
167
- return all(compare(item1, item2, ignore_paths, current_path + [str(i)]) for i, (item1, item2) in enumerate(zip(d1, d2)))
201
+ return all(
202
+ compare(item1, item2, ignore_paths, current_path + [str(i)])
203
+ for i, (item1, item2) in enumerate(zip(d1, d2))
204
+ )
168
205
  else:
169
206
  return d1 == d2
170
- return compare(Data.remove_comments(data1, comment_start, comment_end), Data.remove_comments(data2, comment_start, comment_end), process_ignore_paths(ignore_paths))
207
+
208
+ return compare(
209
+ Data.remove_comments(data1, comment_start, comment_end),
210
+ Data.remove_comments(data2, comment_start, comment_end),
211
+ process_ignore_paths(ignore_paths),
212
+ )
171
213
 
172
214
  @staticmethod
173
- def get_fingerprint(data:list|tuple|dict) -> list|tuple|dict|None:
215
+ def get_fingerprint(
216
+ data: list | tuple | dict,
217
+ ) -> list | tuple | dict | None:
174
218
  if isinstance(data, dict):
175
219
  return {i: type(v).__name__ for i, v in enumerate(data.values())}
176
220
  elif isinstance(data, (list, tuple)):
@@ -178,7 +222,12 @@ class Data:
178
222
  return None
179
223
 
180
224
  @staticmethod
181
- def get_path_id(data:list|tuple|dict, value_paths:str|list[str], sep:str = '->', ignore_not_found:bool = False) -> str|list[str]:
225
+ def get_path_id(
226
+ data: list | tuple | dict,
227
+ value_paths: str | list[str],
228
+ sep: str = "->",
229
+ ignore_not_found: bool = False,
230
+ ) -> str | list[str]:
182
231
  """Generates a unique ID based on the path to a specific value within a nested data structure.\n
183
232
  -------------------------------------------------------------------------------------------------
184
233
  The `data` parameter is the list, tuple, or dictionary, which the id should be generated for.\n
@@ -200,16 +249,17 @@ class Data:
200
249
  -------------------------------------------------------------------------------------------------
201
250
  If `ignore_not_found` is `True`, the function will return `None` if the value is not<br>
202
251
  found instead of raising an error."""
203
- if isinstance(value_paths, str): value_paths = [value_paths]
252
+ if isinstance(value_paths, str):
253
+ value_paths = [value_paths]
204
254
  path_ids = []
205
255
  for path in value_paths:
206
- keys = [k.strip() for k in path.split(str(sep).strip()) if k.strip() != '']
256
+ keys = [k.strip() for k in path.split(str(sep).strip()) if k.strip() != ""]
207
257
  id_part_len, _path_ids, _obj = 0, [], data
208
258
  try:
209
259
  for k in keys:
210
260
  if isinstance(_obj, dict):
211
261
  if k.isdigit():
212
- raise TypeError(f'Key \'{k}\' is invalid for a dict type.')
262
+ raise TypeError(f"Key '{k}' is invalid for a dict type.")
213
263
  try:
214
264
  idx = list(_obj.keys()).index(k)
215
265
  _path_ids.append(idx)
@@ -218,7 +268,7 @@ class Data:
218
268
  if ignore_not_found:
219
269
  _path_ids = None
220
270
  break
221
- raise KeyError(f'Key \'{k}\' not found in dict.')
271
+ raise KeyError(f"Key '{k}' not found in dict.")
222
272
  elif isinstance(_obj, (list, tuple)):
223
273
  try:
224
274
  idx = int(k)
@@ -233,7 +283,7 @@ class Data:
233
283
  if ignore_not_found:
234
284
  _path_ids = None
235
285
  break
236
- raise ValueError(f'Value \'{k}\' not found in list/tuple.')
286
+ raise ValueError(f"Value '{k}' not found in list/tuple.")
237
287
  else:
238
288
  break
239
289
  if _path_ids:
@@ -245,11 +295,12 @@ class Data:
245
295
  except (KeyError, ValueError, TypeError) as e:
246
296
  if ignore_not_found:
247
297
  path_ids.append(None)
248
- else: raise e
298
+ else:
299
+ raise e
249
300
  return path_ids if len(path_ids) > 1 else path_ids[0] if len(path_ids) == 1 else None
250
301
 
251
302
  @staticmethod
252
- def get_value_by_path_id(data:list|tuple|dict, path_id:str, get_key:bool = False) -> any:
303
+ def get_value_by_path_id(data: list | tuple | dict, path_id: str, get_key: bool = False) -> any:
253
304
  """Retrieves the value from `data` using the provided `path_id`.\n
254
305
  ------------------------------------------------------------------------------------
255
306
  Input a list, tuple or dict as `data`, along with `path_id`, which is a path-id<br>
@@ -257,8 +308,10 @@ class Data:
257
308
  and the final item is in a dict, it returns the key instead of the value.\n
258
309
  ------------------------------------------------------------------------------------
259
310
  The function will return the value (or key) from the path-id location, as long as<br>
260
- the structure of `data` hasn't changed since creating the path-id to that value."""
261
- def get_nested(data:list|tuple|dict, path:list[int], get_key:bool) -> any:
311
+ the structure of `data` hasn't changed since creating the path-id to that value.
312
+ """
313
+
314
+ def get_nested(data: list | tuple | dict, path: list[int], get_key: bool) -> any:
262
315
  parent = None
263
316
  for i, idx in enumerate(path):
264
317
  if isinstance(data, dict):
@@ -270,18 +323,23 @@ class Data:
270
323
  elif isinstance(data, (list, tuple)):
271
324
  if i == len(path) - 1 and get_key:
272
325
  if parent is None or not isinstance(parent, dict):
273
- raise ValueError('Cannot get key from list or tuple without a parent dictionary')
326
+ raise ValueError("Cannot get key from list or tuple without a parent dictionary")
274
327
  return next(key for key, value in parent.items() if value is data)
275
328
  parent = data
276
329
  data = data[idx]
277
330
  else:
278
- raise TypeError(f'Unsupported type {type(data)} at path {path[:i+1]}')
331
+ raise TypeError(f"Unsupported type {type(data)} at path {path[:i+1]}")
279
332
  return data
333
+
280
334
  path = Data._sep_path_id(path_id)
281
335
  return get_nested(data, path, get_key)
282
336
 
283
337
  @staticmethod
284
- def set_value_by_path_id(data:list|tuple|dict, update_values:str|list[str], sep:str = '::') -> list|tuple|dict:
338
+ def set_value_by_path_id(
339
+ data: list | tuple | dict,
340
+ update_values: str | list[str],
341
+ sep: str = "::",
342
+ ) -> list | tuple | dict:
285
343
  """Updates the value/s from `update_values` in the `data`.\n
286
344
  --------------------------------------------------------------------------------
287
345
  Input a list, tuple or dict as `data`, along with `update_values`, which is<br>
@@ -290,8 +348,10 @@ class Data:
290
348
  and the new value are separated by `sep`, which per default is `::`.\n
291
349
  --------------------------------------------------------------------------------
292
350
  The value from path-id will be changed to the new value, as long as the<br>
293
- structure of `data` hasn't changed since creating the path-id to that value."""
294
- def update_nested(data:list|tuple|dict, path:list[int], value:any) -> list|tuple|dict:
351
+ structure of `data` hasn't changed since creating the path-id to that value.
352
+ """
353
+
354
+ def update_nested(data: list | tuple | dict, path: list[int], value: any) -> list | tuple | dict:
295
355
  if len(path) == 1:
296
356
  if isinstance(data, dict):
297
357
  keys = list(data.keys())
@@ -309,33 +369,58 @@ class Data:
309
369
  data[path[0]] = update_nested(data[path[0]], path[1:], value)
310
370
  data = type(data)(data)
311
371
  return data
372
+
312
373
  if isinstance(update_values, str):
313
374
  update_values = [update_values]
314
- valid_entries = [(parts[0].strip(), parts[1]) for update_value in update_values if len(parts := update_value.split(str(sep).strip())) == 2]
375
+ valid_entries = [
376
+ (parts[0].strip(), parts[1])
377
+ for update_value in update_values
378
+ if len(parts := update_value.split(str(sep).strip())) == 2
379
+ ]
315
380
  if not valid_entries:
316
- raise ValueError(f'No valid update_values found: {update_values}')
317
- path, new_values = (zip(*valid_entries) if valid_entries else ([], []))
381
+ raise ValueError(f"No valid update_values found: {update_values}")
382
+ path, new_values = zip(*valid_entries) if valid_entries else ([], [])
318
383
  for path_id, new_val in zip(path, new_values):
319
384
  path = Data._sep_path_id(path_id)
320
385
  data = update_nested(data, path, new_val)
321
386
  return data
322
387
 
323
388
  @staticmethod
324
- def print(data:list|tuple|dict, indent:int = 2, compactness:int = 1, sep:str = ', ', max_width:int = 140, as_json:bool = False, end:str = '\n') -> None:
325
- """Print nicely formatted data structures.\n
326
- ------------------------------------------------------------------------------------
327
- The indentation spaces-amount can be set with with `indent`.<br>
328
- There are three different levels of `compactness`:<br>
329
- `0` expands everything possible<br>
330
- `1` only expands if there's other lists, tuples or dicts inside of data or,<br>
331
-  ⠀if the data's content is longer than `max_width`<br>
332
- `2` keeps everything collapsed (all on one line)\n
333
- ------------------------------------------------------------------------------------
334
- If `as_json` is set to `True`, the output will be in valid JSON format."""
335
- print(Data.to_str(data, indent, compactness, sep, max_width, as_json), end=end, flush=True)
389
+ def print(
390
+ data: list | tuple | dict,
391
+ indent: int = 2,
392
+ compactness: int = 1,
393
+ sep: str = ", ",
394
+ max_width: int = 140,
395
+ as_json: bool = False,
396
+ end: str = "\n",
397
+ ) -> None:
398
+ """Print nicely formatted data structures.\n
399
+ ------------------------------------------------------------------------------------
400
+ The indentation spaces-amount can be set with with `indent`.<br>
401
+ There are three different levels of `compactness`:<br>
402
+ `0` expands everything possible<br>
403
+ `1` only expands if there's other lists, tuples or dicts inside of data or,<br>
404
+  ⠀if the data's content is longer than `max_width`<br>
405
+ `2` keeps everything collapsed (all on one line)\n
406
+ ------------------------------------------------------------------------------------
407
+ If `as_json` is set to `True`, the output will be in valid JSON format.
408
+ """
409
+ print(
410
+ Data.to_str(data, indent, compactness, sep, max_width, as_json),
411
+ end=end,
412
+ flush=True,
413
+ )
336
414
 
337
415
  @staticmethod
338
- def to_str(data:list|tuple|dict, indent:int = 2, compactness:int = 1, sep:str = ', ', max_width:int = 140, as_json:bool = False) -> str:
416
+ def to_str(
417
+ data: list | tuple | dict,
418
+ indent: int = 2,
419
+ compactness: int = 1,
420
+ sep: str = ", ",
421
+ max_width: int = 140,
422
+ as_json: bool = False,
423
+ ) -> str:
339
424
  """Get nicely formatted data structure-strings.\n
340
425
  ------------------------------------------------------------------------------------
341
426
  The indentation spaces-amount can be set with with `indent`.<br>
@@ -345,68 +430,105 @@ class Data:
345
430
   ⠀if the data's content is longer than `max_width`<br>
346
431
  `2` keeps everything collapsed (all on one line)\n
347
432
  ------------------------------------------------------------------------------------
348
- If `as_json` is set to `True`, the output will be in valid JSON format."""
349
- def escape_string(s:str, str_quotes:str = '"') -> str:
350
- s = s.replace('\\', r'\\').replace('\n', r'\n').replace('\r', r'\r').replace('\t', r'\t').replace('\b', r'\b').replace('\f', r'\f').replace('\a', r'\a')
351
- if str_quotes == '"': s = s.replace(r"\\'", "'").replace(r'"', r'\"')
352
- elif str_quotes == "'": s = s.replace(r'\\"', '"').replace(r"'", r"\'")
433
+ If `as_json` is set to `True`, the output will be in valid JSON format.
434
+ """
435
+
436
+ def escape_string(s: str, str_quotes: str = '"') -> str:
437
+ s = (
438
+ s.replace("\\", r"\\")
439
+ .replace("\n", r"\n")
440
+ .replace("\r", r"\r")
441
+ .replace("\t", r"\t")
442
+ .replace("\b", r"\b")
443
+ .replace("\f", r"\f")
444
+ .replace("\a", r"\a")
445
+ )
446
+ if str_quotes == '"':
447
+ s = s.replace(r"\\'", "'").replace(r'"', r"\"")
448
+ elif str_quotes == "'":
449
+ s = s.replace(r'\\"', '"').replace(r"'", r"\'")
353
450
  return s
354
- def format_value(value:any, current_indent:int) -> str:
451
+
452
+ def format_value(value: any, current_indent: int) -> str:
355
453
  if isinstance(value, dict):
356
454
  return format_dict(value, current_indent + indent)
357
- elif hasattr(value, '__dict__'):
455
+ elif hasattr(value, "__dict__"):
358
456
  return format_dict(value.__dict__, current_indent + indent)
359
457
  elif isinstance(value, (list, tuple, set, frozenset)):
360
458
  return format_sequence(value, current_indent + indent)
361
459
  elif isinstance(value, bool):
362
460
  return str(value).lower() if as_json else str(value)
363
461
  elif isinstance(value, (int, float)):
364
- return 'null' if as_json and (_math.isinf(value) or _math.isnan(value)) else str(value)
462
+ return "null" if as_json and (_math.isinf(value) or _math.isnan(value)) else str(value)
365
463
  elif isinstance(value, complex):
366
- return f'[{value.real}, {value.imag}]' if as_json else str(value)
464
+ return f"[{value.real}, {value.imag}]" if as_json else str(value)
367
465
  elif value is None:
368
- return 'null' if as_json else 'None'
466
+ return "null" if as_json else "None"
369
467
  else:
370
468
  return '"' + escape_string(str(value), '"') + '"' if as_json else "'" + escape_string(str(value), "'") + "'"
371
- def should_expand(seq:list|tuple|dict) -> bool:
372
- if compactness == 0: return True
373
- if compactness == 2: return False
469
+
470
+ def should_expand(seq: list | tuple | dict) -> bool:
471
+ if compactness == 0:
472
+ return True
473
+ if compactness == 2:
474
+ return False
374
475
  complex_items = sum(1 for item in seq if isinstance(item, (list, tuple, dict, set, frozenset)))
375
- return complex_items > 1 or (complex_items == 1 and len(seq) > 1) or Data.chars_count(seq) + (len(seq) * len(sep)) > max_width
376
- def format_key(k:any) -> str:
377
- return '"' + escape_string(str(k), '"') + '"' if as_json else "'" + escape_string(str(k), "'") + "'" if isinstance(k, str) else str(k)
378
- def format_dict(d:dict, current_indent:int) -> str:
476
+ return (
477
+ complex_items > 1
478
+ or (complex_items == 1 and len(seq) > 1)
479
+ or Data.chars_count(seq) + (len(seq) * len(sep)) > max_width
480
+ )
481
+
482
+ def format_key(k: any) -> str:
483
+ return (
484
+ '"' + escape_string(str(k), '"') + '"'
485
+ if as_json
486
+ else ("'" + escape_string(str(k), "'") + "'" if isinstance(k, str) else str(k))
487
+ )
488
+
489
+ def format_dict(d: dict, current_indent: int) -> str:
379
490
  if not d or compactness == 2:
380
- return '{' + sep.join(f'{format_key(k)}: {format_value(v, current_indent)}' for k, v in d.items()) + '}'
491
+ return "{" + sep.join(f"{format_key(k)}: {format_value(v, current_indent)}" for k, v in d.items()) + "}"
381
492
  if not should_expand(d.values()):
382
- return '{' + sep.join(f'{format_key(k)}: {format_value(v, current_indent)}' for k, v in d.items()) + '}'
493
+ return "{" + sep.join(f"{format_key(k)}: {format_value(v, current_indent)}" for k, v in d.items()) + "}"
383
494
  items = []
384
495
  for key, value in d.items():
385
496
  formatted_value = format_value(value, current_indent)
386
497
  items.append(f'{" " * (current_indent + indent)}{format_key(key)}: {formatted_value}')
387
- return '{\n' + ',\n'.join(items) + f'\n{" " * current_indent}}}'
388
- def format_sequence(seq, current_indent:int) -> str:
498
+ return "{\n" + ",\n".join(items) + f'\n{" " * current_indent}}}'
499
+
500
+ def format_sequence(seq, current_indent: int) -> str:
389
501
  if as_json:
390
502
  seq = list(seq)
391
503
  if not seq or compactness == 2:
392
- return '[' + sep.join(format_value(item, current_indent) for item in seq) + ']' if isinstance(seq, list) else '(' + sep.join(format_value(item, current_indent) for item in seq) + ')'
504
+ return (
505
+ "[" + sep.join(format_value(item, current_indent) for item in seq) + "]"
506
+ if isinstance(seq, list)
507
+ else "(" + sep.join(format_value(item, current_indent) for item in seq) + ")"
508
+ )
393
509
  if not should_expand(seq):
394
- return '[' + sep.join(format_value(item, current_indent) for item in seq) + ']' if isinstance(seq, list) else '(' + sep.join(format_value(item, current_indent) for item in seq) + ')'
510
+ return (
511
+ "[" + sep.join(format_value(item, current_indent) for item in seq) + "]"
512
+ if isinstance(seq, list)
513
+ else "(" + sep.join(format_value(item, current_indent) for item in seq) + ")"
514
+ )
395
515
  items = [format_value(item, current_indent) for item in seq]
396
- formatted_items = ',\n'.join(f'{" " * (current_indent + indent)}{item}' for item in items)
516
+ formatted_items = ",\n".join(f'{" " * (current_indent + indent)}{item}' for item in items)
397
517
  if isinstance(seq, list):
398
- return '[\n' + formatted_items + f'\n{" " * current_indent}]'
518
+ return "[\n" + formatted_items + f'\n{" " * current_indent}]'
399
519
  else:
400
- return '(\n' + formatted_items + f'\n{" " * current_indent})'
520
+ return "(\n" + formatted_items + f'\n{" " * current_indent})'
521
+
401
522
  return format_dict(data, 0) if isinstance(data, dict) else format_sequence(data, 0)
402
523
 
403
524
  @staticmethod
404
- def _is_key(data:list|tuple|dict, path_id:str) -> bool:
525
+ def _is_key(data: list | tuple | dict, path_id: str) -> bool:
405
526
  """Returns `True` if the path-id points to a key in `data` and `False` otherwise.\n
406
527
  ------------------------------------------------------------------------------------
407
528
  Input a list, tuple or dict as `data`, along with `path_id`, which is a path-id<br>
408
529
  that was created before using `Object.get_path_id()`."""
409
- def check_nested(data:list|tuple|dict, path:list[int]) -> bool:
530
+
531
+ def check_nested(data: list | tuple | dict, path: list[int]) -> bool:
410
532
  for i, idx in enumerate(path):
411
533
  if isinstance(data, dict):
412
534
  keys = list(data.keys())
@@ -416,16 +538,18 @@ class Data:
416
538
  elif isinstance(data, (list, tuple)):
417
539
  return False
418
540
  else:
419
- raise TypeError(f'Unsupported type {type(data)} at path {path[:i+1]}')
541
+ raise TypeError(f"Unsupported type {type(data)} at path {path[:i+1]}")
420
542
  return False
543
+
421
544
  if isinstance(data, (list, tuple)):
422
545
  return False
423
546
  path = Data._sep_path_id(path_id)
424
547
  return check_nested(data, path)
425
548
 
426
549
  @staticmethod
427
- def _sep_path_id(path_id:str) -> list[int]:
428
- if path_id.count('>') != 1:
429
- raise ValueError(f'Invalid path-id: {path_id}')
430
- id_part_len, path_ids_str = int(path_id.split('>')[0]), path_id.split('>')[1]
431
- return [int(path_ids_str[i:i+id_part_len]) for i in range(0, len(path_ids_str), id_part_len)]
550
+ def _sep_path_id(path_id: str) -> list[int]:
551
+ if path_id.count(">") != 1:
552
+ raise ValueError(f"Invalid path-id: {path_id}")
553
+ id_part_len = int(path_id.split(">")[0])
554
+ path_ids_str = path_id.split(">")[1]
555
+ return [int(path_ids_str[i : i + id_part_len]) for i in range(0, len(path_ids_str), id_part_len)]
xulbux/xx_env_vars.py CHANGED
@@ -5,56 +5,69 @@ Functions for modifying and checking the systems environment-variables:
5
5
  - `EnvVars.add_path()`
6
6
  """
7
7
 
8
-
9
8
  from .xx_data import *
10
9
  from .xx_path import *
11
10
 
12
11
  import os as _os
13
12
 
14
13
 
15
-
16
-
17
14
  class EnvVars:
18
15
 
19
16
  @staticmethod
20
- def get_paths(as_list:bool = False) -> str|list:
21
- paths = _os.environ.get('PATH')
17
+ def get_paths(as_list: bool = False) -> str | list:
18
+ paths = _os.environ.get("PATH")
22
19
  return paths.split(_os.pathsep) if as_list else paths
23
20
 
24
21
  @staticmethod
25
- def has_path(path:str = None, cwd:bool = False, base_dir:bool = False) -> bool:
26
- if cwd: path = _os.getcwd()
27
- if base_dir: path = Path.get(base_dir=True)
22
+ def has_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> bool:
23
+ if cwd:
24
+ path = _os.getcwd()
25
+ if base_dir:
26
+ path = Path.get(base_dir=True)
28
27
  paths = EnvVars.get_paths()
29
28
  return path in paths
30
29
 
31
30
  @staticmethod
32
- def __add_sort_paths(add_path:str, current_paths:str) -> str:
33
- final_paths = Data.remove_empty_items(Data.remove_duplicates(f'{add_path};{current_paths}'.split(_os.pathsep)))
31
+ def __add_sort_paths(add_path: str, current_paths: str) -> str:
32
+ final_paths = Data.remove_empty_items(Data.remove_duplicates(f"{add_path};{current_paths}".split(_os.pathsep)))
34
33
  final_paths.sort()
35
- return f'{_os.pathsep.join(final_paths)};'
34
+ return f"{_os.pathsep.join(final_paths)};"
36
35
 
37
36
  @staticmethod
38
- def add_path(add_path:str = None, cwd:bool = False, base_dir:bool = False, persistent:bool = True) -> None:
37
+ def add_path(
38
+ add_path: str = None,
39
+ cwd: bool = False,
40
+ base_dir: bool = False,
41
+ persistent: bool = True,
42
+ ) -> None:
39
43
  if cwd:
40
44
  add_path = _os.getcwd()
41
45
  if base_dir:
42
46
  add_path = Path.get(base_dir=True)
43
47
  if not EnvVars.has_path(add_path):
44
48
  final_paths = EnvVars.__add_sort_paths(add_path, EnvVars.get_paths())
45
- _os.environ['PATH'] = final_paths
49
+ _os.environ["PATH"] = final_paths
46
50
  if persistent:
47
- if _os.name == 'nt': # Windows
51
+ if _os.name == "nt": # Windows
48
52
  try:
49
- import winreg
50
- key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS)
51
- winreg.SetValueEx(key, 'PATH', 0, winreg.REG_EXPAND_SZ, final_paths)
52
- winreg.CloseKey(key)
53
- except ImportError: raise ImportError('Unable to make persistent changes on Windows.')
53
+ import winreg as _winreg
54
+
55
+ key = _winreg.OpenKey(
56
+ _winreg.HKEY_CURRENT_USER,
57
+ "Environment",
58
+ 0,
59
+ _winreg.KEY_ALL_ACCESS,
60
+ )
61
+ _winreg.SetValueEx(key, "PATH", 0, _winreg.REG_EXPAND_SZ, final_paths)
62
+ _winreg.CloseKey(key)
63
+ except ImportError:
64
+ raise ImportError("Unable to make persistent changes on Windows.")
54
65
  else: # UNIX-LIKE (Linux/macOS)
55
- shell_rc_file = _os.path.expanduser('~/.bashrc' if _os.path.exists(_os.path.expanduser('~/.bashrc')) else '~/.zshrc')
56
- with open(shell_rc_file, 'a') as f:
66
+ shell_rc_file = _os.path.expanduser(
67
+ "~/.bashrc" if _os.path.exists(_os.path.expanduser("~/.bashrc")) else "~/.zshrc"
68
+ )
69
+ with open(shell_rc_file, "a") as f:
57
70
  f.write(f'\n# Added by XulbuX\nexport PATH="$PATH:{add_path}"\n')
58
- _os.system(f'source {shell_rc_file}')
71
+ _os.system(f"source {shell_rc_file}")
59
72
  else:
60
- raise ValueError(f'{add_path} is already in PATH.')
73
+ raise ValueError(f"{add_path} is already in PATH.")