xulbux 1.6.9__py3-none-any.whl → 1.7.0__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_console.py CHANGED
@@ -8,7 +8,7 @@ For more detailed information about formatting codes, see the the `xx_format_cod
8
8
  from ._consts_ import COLOR, CHARS
9
9
  from .xx_format_codes import FormatCodes, _COMPILED
10
10
  from .xx_string import String
11
- from .xx_color import Color, rgba, hexa
11
+ from .xx_color import Color, Rgba, Hexa
12
12
 
13
13
  from prompt_toolkit.key_binding.key_bindings import KeyBindings
14
14
  from typing import Optional, Any
@@ -103,14 +103,14 @@ class Args:
103
103
 
104
104
  class Console:
105
105
 
106
- w: int = _ConsoleWidth()
106
+ w: int = _ConsoleWidth() # type: ignore[assignment]
107
107
  """The width of the console in characters."""
108
- h: int = _ConsoleHeight()
108
+ h: int = _ConsoleHeight() # type: ignore[assignment]
109
109
  """The height of the console in lines."""
110
- wh: tuple[int, int] = _ConsoleSize()
110
+ wh: tuple[int, int] = _ConsoleSize() # type: ignore[assignment]
111
111
  """A tuple with the width and height of
112
112
  the console in characters and lines."""
113
- usr: str = _ConsoleUser()
113
+ usr: str = _ConsoleUser() # type: ignore[assignment]
114
114
  """The name of the current user."""
115
115
 
116
116
  @staticmethod
@@ -249,8 +249,8 @@ class Console:
249
249
  format_linebreaks: bool = True,
250
250
  start: str = "",
251
251
  end: str = "\n",
252
- title_bg_color: hexa | rgba = None,
253
- default_color: hexa | rgba = None,
252
+ title_bg_color: Optional[Rgba | Hexa] = None,
253
+ default_color: Optional[Rgba | Hexa] = None,
254
254
  _console_tabsize: int = 8,
255
255
  ) -> None:
256
256
  """Will print a formatted log message:
@@ -271,8 +271,12 @@ class Console:
271
271
  if format_linebreaks:
272
272
  clean_prompt, removals = FormatCodes.remove_formatting(str(prompt), get_removals=True, _ignore_linebreaks=True)
273
273
  prompt_lst = (String.split_count(l, Console.w - (title_len + tab_len)) for l in str(clean_prompt).splitlines())
274
- prompt_lst = (item for lst in prompt_lst for item in (lst if isinstance(lst, list) else [lst]))
275
- prompt = f"\n{' ' * title_len}\t".join(Console.__add_back_removed_parts(list(prompt_lst), removals))
274
+ prompt_lst = (
275
+ item for lst in prompt_lst for item in ([""] if lst == [] else (lst if isinstance(lst, list) else [lst]))
276
+ )
277
+ prompt = f"\n{' ' * title_len}\t".join(
278
+ Console.__add_back_removed_parts(list(prompt_lst), removals) # type: ignore[assignment]
279
+ )
276
280
  else:
277
281
  prompt = str(prompt)
278
282
  if title == "":
@@ -328,8 +332,8 @@ class Console:
328
332
  format_linebreaks: bool = True,
329
333
  start: str = "",
330
334
  end: str = "\n",
331
- title_bg_color: hexa | rgba = COLOR.yellow,
332
- default_color: hexa | rgba = COLOR.text,
335
+ title_bg_color: Rgba | Hexa = COLOR.yellow,
336
+ default_color: Rgba | Hexa = COLOR.text,
333
337
  pause: bool = False,
334
338
  exit: bool = False,
335
339
  ) -> None:
@@ -346,8 +350,8 @@ class Console:
346
350
  format_linebreaks: bool = True,
347
351
  start: str = "",
348
352
  end: str = "\n",
349
- title_bg_color: hexa | rgba = COLOR.blue,
350
- default_color: hexa | rgba = COLOR.text,
353
+ title_bg_color: Rgba | Hexa = COLOR.blue,
354
+ default_color: Rgba | Hexa = COLOR.text,
351
355
  pause: bool = False,
352
356
  exit: bool = False,
353
357
  ) -> None:
@@ -362,8 +366,8 @@ class Console:
362
366
  format_linebreaks: bool = True,
363
367
  start: str = "",
364
368
  end: str = "\n",
365
- title_bg_color: hexa | rgba = COLOR.teal,
366
- default_color: hexa | rgba = COLOR.text,
369
+ title_bg_color: Rgba | Hexa = COLOR.teal,
370
+ default_color: Rgba | Hexa = COLOR.text,
367
371
  pause: bool = False,
368
372
  exit: bool = False,
369
373
  ) -> None:
@@ -378,8 +382,8 @@ class Console:
378
382
  format_linebreaks: bool = True,
379
383
  start: str = "",
380
384
  end: str = "\n",
381
- title_bg_color: hexa | rgba = COLOR.orange,
382
- default_color: hexa | rgba = COLOR.text,
385
+ title_bg_color: Rgba | Hexa = COLOR.orange,
386
+ default_color: Rgba | Hexa = COLOR.text,
383
387
  pause: bool = False,
384
388
  exit: bool = False,
385
389
  ) -> None:
@@ -394,8 +398,8 @@ class Console:
394
398
  format_linebreaks: bool = True,
395
399
  start: str = "",
396
400
  end: str = "\n",
397
- title_bg_color: hexa | rgba = COLOR.red,
398
- default_color: hexa | rgba = COLOR.text,
401
+ title_bg_color: Rgba | Hexa = COLOR.red,
402
+ default_color: Rgba | Hexa = COLOR.text,
399
403
  pause: bool = False,
400
404
  exit: bool = True,
401
405
  reset_ansi=True,
@@ -411,8 +415,8 @@ class Console:
411
415
  format_linebreaks: bool = True,
412
416
  start: str = "",
413
417
  end: str = "\n",
414
- title_bg_color: hexa | rgba = COLOR.magenta,
415
- default_color: hexa | rgba = COLOR.text,
418
+ title_bg_color: Rgba | Hexa = COLOR.magenta,
419
+ default_color: Rgba | Hexa = COLOR.text,
416
420
  pause: bool = False,
417
421
  exit: bool = True,
418
422
  reset_ansi=True,
@@ -427,8 +431,8 @@ class Console:
427
431
  *values: object,
428
432
  start: str = "",
429
433
  end: str = "\n",
430
- box_bg_color: str | hexa | rgba = "green",
431
- default_color: hexa | rgba = "#000",
434
+ box_bg_color: str | Rgba | Hexa = "green",
435
+ default_color: Rgba | Hexa = "#000",
432
436
  w_padding: int = 2,
433
437
  w_full: bool = False,
434
438
  ) -> None:
@@ -443,7 +447,7 @@ class Console:
443
447
  -----------------------------------------------------------------------------------
444
448
  The box content can be formatted with special formatting codes. For more detailed
445
449
  information about formatting codes, see `xx_format_codes` module documentation."""
446
- lines = [line.rstrip() for val in values for line in val.splitlines()]
450
+ lines = [line.rstrip() for val in values for line in str(val).splitlines()]
447
451
  unfmt_lines = [FormatCodes.remove_formatting(line) for line in lines]
448
452
  max_line_len = max(len(line) for line in unfmt_lines)
449
453
  pad_w_full = (Console.w - (max_line_len + (2 * w_padding))) if w_full else 0
@@ -466,7 +470,7 @@ class Console:
466
470
  prompt: object = "Do you want to continue?",
467
471
  start="",
468
472
  end="\n",
469
- default_color: hexa | rgba = COLOR.cyan,
473
+ default_color: Rgba | Hexa = COLOR.cyan,
470
474
  default_is_yes: bool = True,
471
475
  ) -> bool:
472
476
  """Ask a yes/no question.\n
@@ -488,7 +492,7 @@ class Console:
488
492
  prompt: object = "",
489
493
  start="",
490
494
  end="\n",
491
- default_color: hexa | rgba = COLOR.cyan,
495
+ default_color: Rgba | Hexa = COLOR.cyan,
492
496
  show_keybindings=True,
493
497
  input_prefix=" ⤷ ",
494
498
  reset_ansi=True,
@@ -511,7 +515,7 @@ class Console:
511
515
  def _(event):
512
516
  event.app.exit(result=event.app.current_buffer.document.text)
513
517
 
514
- FormatCodes.print(start + prompt, default_color=default_color)
518
+ FormatCodes.print(start + str(prompt), default_color=default_color)
515
519
  if show_keybindings:
516
520
  FormatCodes.print("[dim][[b](CTRL+D)[dim] : end of input][_dim]")
517
521
  input_string = _prompt_toolkit.prompt(input_prefix, multiline=True, wrap_lines=True, key_bindings=kb)
@@ -523,11 +527,11 @@ class Console:
523
527
  prompt: object = "",
524
528
  start="",
525
529
  end="\n",
526
- default_color: hexa | rgba = COLOR.cyan,
527
- allowed_chars: str = CHARS.all,
528
- min_len: int = None,
529
- max_len: int = None,
530
- mask_char: str = None,
530
+ default_color: Rgba | Hexa = COLOR.cyan,
531
+ allowed_chars: str = CHARS.all, # type: ignore[assignment]
532
+ min_len: Optional[int] = None,
533
+ max_len: Optional[int] = None,
534
+ mask_char: Optional[str] = None,
531
535
  reset_ansi: bool = True,
532
536
  ) -> Optional[str]:
533
537
  """Acts like a standard Python `input()` with the advantage, that you can specify:
@@ -538,7 +542,7 @@ class Console:
538
542
  ---------------------------------------------------------------------------------------
539
543
  The input can be formatted with special formatting codes. For more detailed
540
544
  information about formatting codes, see the `xx_format_codes` module documentation."""
541
- FormatCodes.print(start + prompt, default_color=default_color, end="")
545
+ FormatCodes.print(start + str(prompt), default_color=default_color, end="")
542
546
  result = ""
543
547
  select_all = False
544
548
  last_line_count = 1
@@ -594,7 +598,8 @@ class Console:
594
598
 
595
599
  def handle_character_input():
596
600
  nonlocal result
597
- if (allowed_chars == CHARS.all or event.name in allowed_chars) and (max_len is None or len(result) < max_len):
601
+ if event.name is not None and ((allowed_chars == CHARS.all or event.name in allowed_chars) and
602
+ (max_len is None or len(result) < max_len)):
598
603
  result += event.name
599
604
  update_display(Console.w)
600
605
 
@@ -615,7 +620,7 @@ class Console:
615
620
  return None
616
621
  elif event.name == "space":
617
622
  handle_character_input()
618
- elif len(event.name) == 1:
623
+ elif event.name is not None and len(event.name) == 1:
619
624
  handle_character_input()
620
625
  else:
621
626
  select_all = False
@@ -626,12 +631,22 @@ class Console:
626
631
  prompt: object = "Password: ",
627
632
  start="",
628
633
  end="\n",
629
- default_color: hexa | rgba = COLOR.cyan,
634
+ default_color: Rgba | Hexa = COLOR.cyan,
630
635
  allowed_chars: str = CHARS.standard_ascii,
631
- min_len: int = None,
632
- max_len: int = None,
636
+ min_len: Optional[int] = None,
637
+ max_len: Optional[int] = None,
633
638
  reset_ansi: bool = True,
634
- ) -> str:
639
+ ) -> Optional[str]:
635
640
  """Password input (preset for `Console.restricted_input()`)
636
641
  that always masks the entered characters with asterisks."""
637
- return Console.restricted_input(prompt, start, end, default_color, allowed_chars, min_len, max_len, "*", reset_ansi)
642
+ return Console.restricted_input(
643
+ prompt=prompt,
644
+ start=start,
645
+ end=end,
646
+ default_color=default_color,
647
+ allowed_chars=allowed_chars,
648
+ min_len=min_len,
649
+ max_len=max_len,
650
+ mask_char="*",
651
+ reset_ansi=reset_ansi,
652
+ )
xulbux/xx_data.py CHANGED
@@ -166,7 +166,7 @@ class Data:
166
166
 
167
167
  def process_string(s: str) -> Optional[str]:
168
168
  if comment_end:
169
- match = pattern.match(s)
169
+ match = pattern.match(s) # type: ignore[unbound]
170
170
  if match:
171
171
  start, end = match.group(1).strip(), match.group(2).strip()
172
172
  return f"{start}{comment_sep if start and end else ''}{end}" or None
@@ -223,7 +223,7 @@ class Data:
223
223
  return True
224
224
  if type(d1) is not type(d2):
225
225
  return False
226
- if isinstance(d1, dict):
226
+ if isinstance(d1, dict) and isinstance(d2, dict):
227
227
  if set(d1.keys()) != set(d2.keys()):
228
228
  return False
229
229
  return all(compare(d1[key], d2[key], ignore_paths, current_path + [key]) for key in d1)
@@ -251,7 +251,7 @@ class Data:
251
251
  comment_start: str = ">>",
252
252
  comment_end: str = "<<",
253
253
  ignore_not_found: bool = False,
254
- ) -> str | list[str]:
254
+ ) -> Optional[str | list[Optional[str]]]:
255
255
  """Generates a unique ID based on the path to a specific value within a nested data structure.\n
256
256
  -------------------------------------------------------------------------------------------------
257
257
  The `data` parameter is the list, tuple, or dictionary, which the id should be generated for.\n
@@ -355,12 +355,12 @@ class Data:
355
355
  return get_nested(data, Data.__sep_path_id(path_id), get_key)
356
356
 
357
357
  @staticmethod
358
- def set_value_by_path_id(data: DataStructure, update_values: dict[str, Any]) -> list | tuple | dict:
358
+ def set_value_by_path_id(data: DataStructure, update_values: dict[str, Any]) -> DataStructure:
359
359
  """Updates the value/s from `update_values` in the `data`.\n
360
360
  --------------------------------------------------------------------------------
361
361
  Input a list, tuple or dict as `data`, along with `update_values`, which is a
362
362
  dictionary where keys are path IDs and values are the new values to insert:
363
- { "1>": "new value", "path_id2": ["new value 1", "new value 2"], ... }
363
+ { "1>012": "new value", "1>31": ["new value 1", "new value 2"], ... }
364
364
  The path IDs should have been created using `Data.get_path_id()`.\n
365
365
  --------------------------------------------------------------------------------
366
366
  The value from path ID will be changed to the new value, as long as the
@@ -413,7 +413,8 @@ class Data:
413
413
  - `2` keeps everything collapsed (all on one line)\n
414
414
  ------------------------------------------------------------------------------
415
415
  If `as_json` is set to `True`, the output will be in valid JSON format."""
416
- if syntax_hl := _syntax_highlighting not in (None, False):
416
+ _syntax_hl = {}
417
+ if do_syntax_hl := _syntax_highlighting not in (None, False):
417
418
  if _syntax_highlighting is True:
418
419
  _syntax_highlighting = {}
419
420
  elif not isinstance(_syntax_highlighting, dict):
@@ -426,62 +427,62 @@ class Data:
426
427
  "punctuation": (f"[{COLOR.darkgray}]", "[_c]"),
427
428
  }
428
429
  _syntax_hl.update({
429
- k: [f"[{v}]", "[_]"] if k in _syntax_hl and v not in ("", None) else ["", ""]
430
+ k: (f"[{v}]", "[_]") if k in _syntax_hl and v not in ("", None) else ("", "")
430
431
  for k, v in _syntax_highlighting.items()
431
432
  })
432
433
  sep = f"{_syntax_hl['punctuation'][0]}{sep}{_syntax_hl['punctuation'][1]}"
433
434
  punct_map = {"(": ("/(", "("), **{char: char for char in "'\":)[]{}"}}
434
435
  punct = {
435
- k: ((f"{_syntax_hl['punctuation'][0]}{v[0]}{_syntax_hl['punctuation'][1]}" if syntax_hl else v[1])
436
+ k: ((f"{_syntax_hl['punctuation'][0]}{v[0]}{_syntax_hl['punctuation'][1]}" if do_syntax_hl else v[1])
436
437
  if isinstance(v, (list, tuple)) else
437
- (f"{_syntax_hl['punctuation'][0]}{v}{_syntax_hl['punctuation'][1]}" if syntax_hl else v))
438
+ (f"{_syntax_hl['punctuation'][0]}{v}{_syntax_hl['punctuation'][1]}" if do_syntax_hl else v))
438
439
  for k, v in punct_map.items()
439
440
  }
440
441
 
441
- def format_value(value: Any, current_indent: int = None) -> str:
442
+ def format_value(value: Any, current_indent: Optional[int] = None) -> str:
442
443
  if current_indent is not None and isinstance(value, dict):
443
444
  return format_dict(value, current_indent + indent)
444
445
  elif current_indent is not None and hasattr(value, "__dict__"):
445
446
  return format_dict(value.__dict__, current_indent + indent)
446
447
  elif current_indent is not None and isinstance(value, IndexIterable):
447
448
  return format_sequence(value, current_indent + indent)
448
- elif isinstance(value, (bytes, bytearray)):
449
+ elif current_indent is not None and isinstance(value, (bytes, bytearray)):
449
450
  obj_dict = Data.serialize_bytes(value)
450
451
  return (
451
452
  format_dict(obj_dict, current_indent + indent) if as_json else (
452
453
  f"{_syntax_hl['type'][0]}{(k := next(iter(obj_dict)))}{_syntax_hl['type'][1]}"
453
- + format_sequence((obj_dict[k], obj_dict["encoding"]), current_indent + indent) if syntax_hl else
454
+ + format_sequence((obj_dict[k], obj_dict["encoding"]), current_indent + indent) if do_syntax_hl else
454
455
  (k := next(iter(obj_dict)))
455
456
  + format_sequence((obj_dict[k], obj_dict["encoding"]), current_indent + indent)
456
457
  )
457
458
  )
458
459
  elif isinstance(value, bool):
459
460
  val = str(value).lower() if as_json else str(value)
460
- return f"{_syntax_hl['literal'][0]}{val}{_syntax_hl['literal'][1]}" if syntax_hl else val
461
+ return f"{_syntax_hl['literal'][0]}{val}{_syntax_hl['literal'][1]}" if do_syntax_hl else val
461
462
  elif isinstance(value, (int, float)):
462
463
  val = "null" if as_json and (_math.isinf(value) or _math.isnan(value)) else str(value)
463
- return f"{_syntax_hl['number'][0]}{val}{_syntax_hl['number'][1]}" if syntax_hl else val
464
- elif isinstance(value, complex):
464
+ return f"{_syntax_hl['number'][0]}{val}{_syntax_hl['number'][1]}" if do_syntax_hl else val
465
+ elif current_indent is not None and isinstance(value, complex):
465
466
  return (
466
467
  format_value(str(value).strip("()")) if as_json else (
467
468
  f"{_syntax_hl['type'][0]}complex{_syntax_hl['type'][1]}"
468
469
  + format_sequence((value.real, value.imag), current_indent + indent)
469
- if syntax_hl else f"complex{format_sequence((value.real, value.imag), current_indent + indent)}"
470
+ if do_syntax_hl else f"complex{format_sequence((value.real, value.imag), current_indent + indent)}"
470
471
  )
471
472
  )
472
473
  elif value is None:
473
474
  val = "null" if as_json else "None"
474
- return f"{_syntax_hl['literal'][0]}{val}{_syntax_hl['literal'][1]}" if syntax_hl else val
475
+ return f"{_syntax_hl['literal'][0]}{val}{_syntax_hl['literal'][1]}" if do_syntax_hl else val
475
476
  else:
476
477
  return ((
477
478
  punct['"'] + _syntax_hl["str"][0] + String.escape(str(value), '"') + _syntax_hl["str"][1]
478
- + punct['"'] if syntax_hl else punct['"'] + String.escape(str(value), '"') + punct['"']
479
+ + punct['"'] if do_syntax_hl else punct['"'] + String.escape(str(value), '"') + punct['"']
479
480
  ) if as_json else (
480
481
  punct["'"] + _syntax_hl["str"][0] + String.escape(str(value), "'") + _syntax_hl["str"][1]
481
- + punct["'"] if syntax_hl else punct["'"] + String.escape(str(value), "'") + punct["'"]
482
+ + punct["'"] if do_syntax_hl else punct["'"] + String.escape(str(value), "'") + punct["'"]
482
483
  ))
483
484
 
484
- def should_expand(seq: list | tuple | dict) -> bool:
485
+ def should_expand(seq: IndexIterable) -> bool:
485
486
  if compactness == 0:
486
487
  return True
487
488
  if compactness == 2:
@@ -500,7 +501,7 @@ class Data:
500
501
  + sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
501
502
  for k, v in d.items()) + punct["}"]
502
503
  )
503
- if not should_expand(d.values()):
504
+ if not should_expand(list(d.values())):
504
505
  return (
505
506
  punct["{"]
506
507
  + sep.join(f"{format_value(k)}{punct[':']} {format_value(v, current_indent)}"
xulbux/xx_env_path.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from .xx_path import Path
2
2
 
3
+ from typing import Optional
3
4
  import sys as _sys
4
5
  import os as _os
5
6
 
@@ -13,7 +14,7 @@ class EnvPath:
13
14
  return paths.split(_os.pathsep) if as_list else paths
14
15
 
15
16
  @staticmethod
16
- def has_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> bool:
17
+ def has_path(path: Optional[str] = None, cwd: bool = False, base_dir: bool = False) -> bool:
17
18
  """Check if a path is present in the PATH environment variable."""
18
19
  if cwd:
19
20
  path = _os.getcwd()
@@ -25,21 +26,21 @@ class EnvPath:
25
26
  return _os.path.normpath(path) in [_os.path.normpath(p) for p in paths]
26
27
 
27
28
  @staticmethod
28
- def add_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> None:
29
+ def add_path(path: Optional[str] = None, cwd: bool = False, base_dir: bool = False) -> None:
29
30
  """Add a path to the PATH environment variable."""
30
31
  path = EnvPath.__get(path, cwd, base_dir)
31
32
  if not EnvPath.has_path(path):
32
33
  EnvPath.__persistent(path, add=True)
33
34
 
34
35
  @staticmethod
35
- def remove_path(path: str = None, cwd: bool = False, base_dir: bool = False) -> None:
36
+ def remove_path(path: Optional[str] = None, cwd: bool = False, base_dir: bool = False) -> None:
36
37
  """Remove a path from the PATH environment variable."""
37
38
  path = EnvPath.__get(path, cwd, base_dir)
38
39
  if EnvPath.has_path(path):
39
40
  EnvPath.__persistent(path, remove=True)
40
41
 
41
42
  @staticmethod
42
- def __get(path: str = None, cwd: bool = False, base_dir: bool = False) -> list:
43
+ def __get(path: Optional[str] = None, cwd: bool = False, base_dir: bool = False) -> str:
43
44
  """Get and/or normalize the paths.\n
44
45
  ------------------------------------------------------------------------------------
45
46
  Raise an error if no path is provided and neither `cwd` or `base_dir` is `True`."""
@@ -56,7 +57,7 @@ class EnvPath:
56
57
  """Add or remove a path from PATH persistently across sessions as well as the current session."""
57
58
  if add == remove:
58
59
  raise ValueError("Either add or remove must be True, but not both.")
59
- current_paths = EnvPath.paths(as_list=True)
60
+ current_paths = list(EnvPath.paths(as_list=True))
60
61
  path = _os.path.normpath(path)
61
62
  if remove:
62
63
  current_paths = [p for p in current_paths if _os.path.normpath(p) != _os.path.normpath(path)]
xulbux/xx_file.py CHANGED
@@ -19,8 +19,8 @@ class File:
19
19
  """Rename the extension of a file.\n
20
20
  --------------------------------------------------------------------------
21
21
  If `full_extension` is true, everything after the first dot in the
22
- filename will be treated as the extension to replace. Otherwise, only the
23
- part after the last dot is replaced.\n
22
+ filename will be treated as the extension to replace (e.g. `.tar.gz`).
23
+ Otherwise, only the part after the last dot is replaced (e.g. `.gz`).\n
24
24
  If the `camel_case_filename` parameter is true, the filename will be made
25
25
  CamelCase in addition to changing the files extension."""
26
26
  normalized_file = _os.path.normpath(file)
xulbux/xx_format_codes.py CHANGED
@@ -152,10 +152,10 @@ Per default, you can also use `+` and `-` to get lighter and darker `default_col
152
152
 
153
153
  from ._consts_ import ANSI
154
154
  from .xx_string import String
155
- from .xx_regex import Regex
156
- from .xx_color import Color, rgba, hexa
155
+ from .xx_regex import Regex, Match, Pattern
156
+ from .xx_color import Color, rgba, Rgba, Hexa
157
157
 
158
- from typing import Optional, Pattern
158
+ from typing import Optional
159
159
  import ctypes as _ctypes
160
160
  import regex as _rx
161
161
  import sys as _sys
@@ -205,7 +205,7 @@ class FormatCodes:
205
205
  @staticmethod
206
206
  def print(
207
207
  *values: object,
208
- default_color: rgba | hexa = None,
208
+ default_color: Optional[Rgba | Hexa] = None,
209
209
  brightness_steps: int = 20,
210
210
  sep: str = " ",
211
211
  end: str = "\n",
@@ -223,7 +223,7 @@ class FormatCodes:
223
223
  @staticmethod
224
224
  def input(
225
225
  prompt: object = "",
226
- default_color: rgba | hexa = None,
226
+ default_color: Optional[Rgba | Hexa] = None,
227
227
  brightness_steps: int = 20,
228
228
  reset_ansi: bool = False,
229
229
  ) -> str:
@@ -243,7 +243,7 @@ class FormatCodes:
243
243
  @staticmethod
244
244
  def to_ansi(
245
245
  string: str,
246
- default_color: rgba | hexa = None,
246
+ default_color: Optional[Rgba | Hexa] = None,
247
247
  brightness_steps: int = 20,
248
248
  _default_start: bool = True,
249
249
  ) -> str:
@@ -253,9 +253,9 @@ class FormatCodes:
253
253
  `xx_format_codes` module documentation."""
254
254
  if not isinstance(string, str):
255
255
  string = str(string)
256
- if Color.is_valid_rgba(default_color, False):
256
+ if default_color and Color.is_valid_rgba(default_color, False): # type: ignore[assignment]
257
257
  use_default = True
258
- elif Color.is_valid_hexa(default_color, False):
258
+ elif default_color and Color.is_valid_hexa(default_color, False): # type: ignore[assignment]
259
259
  use_default, default_color = True, Color.to_rgba(default_color)
260
260
  else:
261
261
  use_default = False
@@ -264,9 +264,9 @@ class FormatCodes:
264
264
  string = _COMPILED["*color"].sub(r"[\1default\2]", string) # REPLACE `[…|*color|…]` WITH `[…|default|…]`
265
265
 
266
266
  def is_valid_color(color: str) -> bool:
267
- return color in ANSI.color_map or Color.is_valid_rgba(color) or Color.is_valid_hexa(color)
267
+ return bool((color in ANSI.color_map) or Color.is_valid_rgba(color) or Color.is_valid_hexa(color))
268
268
 
269
- def replace_keys(match: _re.Match) -> str:
269
+ def replace_keys(match: Match) -> str:
270
270
  _formats = formats = match.group(1)
271
271
  auto_reset_escaped = match.group(2)
272
272
  auto_reset_txt = match.group(3)
@@ -280,8 +280,8 @@ class FormatCodes:
280
280
  formats = FormatCodes.to_ansi(formats, default_color, brightness_steps, False)
281
281
  format_keys = [k.strip() for k in formats.split("|") if k.strip()]
282
282
  ansi_formats = [
283
- r if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)) != k else f"[{k}]"
284
- for k in format_keys
283
+ r if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)) != k # type: ignore[assignment]
284
+ else f"[{k}]" for k in format_keys
285
285
  ]
286
286
  if auto_reset_txt and not auto_reset_escaped:
287
287
  reset_keys = []
@@ -306,8 +306,9 @@ class FormatCodes:
306
306
  else:
307
307
  reset_keys.append(f"_{k}")
308
308
  ansi_resets = [
309
- r for k in reset_keys if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps)
310
- ).startswith(f"{ANSI.char}{ANSI.start}")
309
+ r for k in reset_keys
310
+ if (r := FormatCodes.__get_replacement(k, default_color, brightness_steps) # type: ignore[assignment]
311
+ ).startswith(f"{ANSI.char}{ANSI.start}")
311
312
  ]
312
313
  else:
313
314
  ansi_resets = []
@@ -325,7 +326,8 @@ class FormatCodes:
325
326
  )
326
327
 
327
328
  string = "\n".join(_COMPILED["formatting"].sub(replace_keys, line) for line in string.split("\n"))
328
- return ((FormatCodes.__get_default_ansi(default_color) if _default_start else "") + string) if use_default else string
329
+ return (((FormatCodes.__get_default_ansi(default_color) or "") if _default_start else "") # type: ignore[assignment]
330
+ + string) if use_default else string
329
331
 
330
332
  @staticmethod
331
333
  def escape_ansi(ansi_string: str) -> str:
@@ -346,7 +348,7 @@ class FormatCodes:
346
348
  if get_removals:
347
349
  removals = []
348
350
 
349
- def replacement(match: _re.Match) -> str:
351
+ def replacement(match: Match) -> str:
350
352
  start_pos = match.start() - sum(len(removed) for _, removed in removals)
351
353
  if removals and removals[-1][0] == start_pos:
352
354
  start_pos = removals[-1][0]
@@ -394,8 +396,8 @@ class FormatCodes:
394
396
  @staticmethod
395
397
  def __get_default_ansi(
396
398
  default_color: tuple,
397
- format_key: str = None,
398
- brightness_steps: int = None,
399
+ format_key: Optional[str] = None,
400
+ brightness_steps: Optional[int] = None,
399
401
  _modifiers: tuple[str, str] = (ANSI.default_color_modifiers["lighten"], ANSI.default_color_modifiers["darken"]),
400
402
  ) -> Optional[str]:
401
403
  """Get the `default_color` and lighter/darker versions of it as ANSI code."""
@@ -403,8 +405,9 @@ class FormatCodes:
403
405
  return (ANSI.seq_bg_color if format_key and _COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
404
406
  *default_color[:3]
405
407
  )
406
- if not (format_key in _modifiers[0] or format_key in _modifiers[1]):
408
+ if format_key is None or not (format_key in _modifiers[0] or format_key in _modifiers[1]):
407
409
  return None
410
+ assert format_key is not None
408
411
  match = _COMPILED["modifier"].match(format_key)
409
412
  if not match:
410
413
  return None
@@ -415,23 +418,25 @@ class FormatCodes:
415
418
  if adjust and adjust > 0:
416
419
  modifiers = mod
417
420
  break
421
+ new_rgb = default_color
418
422
  if adjust == 0:
419
423
  return None
420
424
  elif modifiers in _modifiers[0]:
421
- new_rgb = Color.adjust_lightness(default_color, (brightness_steps / 100) * adjust)
425
+ new_rgb = tuple(Color.adjust_lightness(default_color, (brightness_steps / 100) * adjust))
422
426
  elif modifiers in _modifiers[1]:
423
- new_rgb = Color.adjust_lightness(default_color, -(brightness_steps / 100) * adjust)
427
+ new_rgb = tuple(Color.adjust_lightness(default_color, -(brightness_steps / 100) * adjust))
424
428
  return (ANSI.seq_bg_color if is_bg else ANSI.seq_color).format(*new_rgb[:3])
425
429
 
426
430
  @staticmethod
427
- def __get_replacement(format_key: str, default_color: rgba = None, brightness_steps: int = 20) -> str:
431
+ def __get_replacement(format_key: str, default_color: Optional[Rgba] = None, brightness_steps: int = 20) -> str:
428
432
  """Gives you the corresponding ANSI code for the given format key.
429
433
  If `default_color` is not `None`, the text color will be `default_color` if all formats
430
434
  are reset or you can get lighter or darker version of `default_color` (also as BG)"""
431
435
  use_default = default_color and Color.is_valid_rgba(default_color, False)
436
+ _default_color = tuple(Color.to_rgba(default_color)) if use_default else () # type: ignore[assignment]
432
437
  _format_key, format_key = format_key, FormatCodes.__normalize_key(format_key) # NORMALIZE KEY AND SAVE ORIGINAL
433
438
  if use_default:
434
- if new_default_color := FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps):
439
+ if new_default_color := FormatCodes.__get_default_ansi(_default_color, format_key, brightness_steps):
435
440
  return new_default_color
436
441
  for map_key in ANSI.codes_map:
437
442
  if (isinstance(map_key, tuple) and format_key in map_key) or format_key == map_key:
@@ -440,8 +445,8 @@ class FormatCodes:
440
445
  v for k, v in ANSI.codes_map.items() if format_key == k or (isinstance(k, tuple) and format_key in k)
441
446
  ), None)
442
447
  )
443
- rgb_match = _re.match(_COMPILED["rgb"], format_key)
444
- hex_match = _re.match(_COMPILED["hex"], format_key)
448
+ rgb_match = _COMPILED["rgb"].match(format_key)
449
+ hex_match = _COMPILED["hex"].match(format_key)
445
450
  try:
446
451
  if rgb_match:
447
452
  is_bg = rgb_match.group(1)
xulbux/xx_json.py CHANGED
@@ -26,13 +26,15 @@ class Json:
26
26
  if not json_file.endswith(".json"):
27
27
  json_file += ".json"
28
28
  file_path = Path.extend_or_make(json_file, prefer_script_dir=True)
29
+ if file_path is None:
30
+ raise FileNotFoundError(f"Could not find JSON file: {json_file}")
29
31
  with open(file_path, "r") as f:
30
32
  content = f.read()
31
33
  try:
32
34
  data = _json.loads(content)
33
35
  except _json.JSONDecodeError as e:
34
36
  raise ValueError(f"Error parsing JSON in '{file_path}': {str(e)}")
35
- processed_data = Data.remove_comments(data, comment_start, comment_end)
37
+ processed_data = dict(Data.remove_comments(data, comment_start, comment_end))
36
38
  if not processed_data:
37
39
  raise ValueError(f"The JSON file '{file_path}' is empty or contains only comments.")
38
40
  return (processed_data, data) if return_original else processed_data
@@ -135,24 +137,23 @@ class Json:
135
137
  raise TypeError(f"Cannot navigate through {type(current).__name__}")
136
138
  return data_obj
137
139
 
140
+ update = {}
138
141
  for value_path, new_value in update_values.items():
139
142
  try:
140
143
  path_id = Data.get_path_id(
141
144
  data=processed_data,
142
145
  value_paths=value_path,
143
146
  path_sep=path_sep,
144
- ignore_not_found=True,
145
147
  )
146
148
  if path_id is not None:
147
- if 'update' not in locals():
148
- update = {}
149
149
  update[path_id] = new_value
150
150
  else:
151
+ keys = value_path.split(path_sep)
151
152
  keys = value_path.split(path_sep)
152
153
  data = create_nested_path(data, keys, new_value)
153
154
  except Exception:
154
155
  keys = value_path.split(path_sep)
155
156
  data = create_nested_path(data, keys, new_value)
156
- if 'update' in locals() and update:
157
+ if "update" in locals() and update:
157
158
  data = Data.set_value_by_path_id(data, update)
158
- Json.create(json_file=json_file, data=data, force=True)
159
+ Json.create(json_file=json_file, data=dict(data), force=True)