cmd2 2.6.2__py3-none-any.whl → 3.0.0b1__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.
cmd2/__init__.py CHANGED
@@ -1,24 +1,17 @@
1
1
  """Import certain things for backwards compatibility."""
2
2
 
3
- import argparse
4
3
  import contextlib
5
4
  import importlib.metadata as importlib_metadata
6
- import sys
7
5
 
8
6
  with contextlib.suppress(importlib_metadata.PackageNotFoundError):
9
7
  __version__ = importlib_metadata.version(__name__)
10
8
 
11
- from .ansi import (
12
- Bg,
13
- Cursor,
14
- EightBitBg,
15
- EightBitFg,
16
- Fg,
17
- RgbBg,
18
- RgbFg,
19
- TextStyle,
20
- style,
9
+ from . import (
10
+ plugin,
11
+ rich_utils,
12
+ string_utils,
21
13
  )
14
+ from .argparse_completer import set_default_ap_completer_type
22
15
  from .argparse_custom import (
23
16
  Cmd2ArgumentParser,
24
17
  Cmd2AttributeWrapper,
@@ -26,21 +19,22 @@ from .argparse_custom import (
26
19
  register_argparse_argument_parameter,
27
20
  set_default_argument_parser_type,
28
21
  )
29
-
30
- # Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER.
31
- # Do this before loading cmd2.Cmd class so its commands use the custom parser.
32
- cmd2_parser_module = getattr(argparse, 'cmd2_parser_module', None)
33
- if cmd2_parser_module is not None:
34
- import importlib
35
-
36
- importlib.import_module(cmd2_parser_module)
37
-
38
- from . import plugin
39
- from .argparse_completer import set_default_ap_completer_type
40
22
  from .cmd2 import Cmd
41
- from .command_definition import CommandSet, with_default_category
42
- from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
43
- from .decorators import as_subcommand_to, with_argparser, with_argument_list, with_category
23
+ from .colors import Color
24
+ from .command_definition import (
25
+ CommandSet,
26
+ with_default_category,
27
+ )
28
+ from .constants import (
29
+ COMMAND_NAME,
30
+ DEFAULT_SHORTCUTS,
31
+ )
32
+ from .decorators import (
33
+ as_subcommand_to,
34
+ with_argparser,
35
+ with_argument_list,
36
+ with_category,
37
+ )
44
38
  from .exceptions import (
45
39
  Cmd2ArgparseError,
46
40
  CommandSetRegistrationError,
@@ -50,33 +44,33 @@ from .exceptions import (
50
44
  )
51
45
  from .parsing import Statement
52
46
  from .py_bridge import CommandResult
53
- from .utils import CompletionMode, CustomCompletionSettings, Settable, categorize
47
+ from .rich_utils import RichPrintKwargs
48
+ from .string_utils import stylize
49
+ from .styles import Cmd2Style
50
+ from .utils import (
51
+ CompletionMode,
52
+ CustomCompletionSettings,
53
+ Settable,
54
+ categorize,
55
+ )
54
56
 
55
57
  __all__: list[str] = [ # noqa: RUF022
56
58
  'COMMAND_NAME',
57
59
  'DEFAULT_SHORTCUTS',
58
- # ANSI Exports
59
- 'Cursor',
60
- 'Bg',
61
- 'Fg',
62
- 'EightBitBg',
63
- 'EightBitFg',
64
- 'RgbBg',
65
- 'RgbFg',
66
- 'TextStyle',
67
- 'style',
68
60
  # Argparse Exports
69
61
  'Cmd2ArgumentParser',
70
62
  'Cmd2AttributeWrapper',
71
63
  'CompletionItem',
72
64
  'register_argparse_argument_parameter',
73
- 'set_default_argument_parser_type',
74
65
  'set_default_ap_completer_type',
66
+ 'set_default_argument_parser_type',
75
67
  # Cmd2
76
68
  'Cmd',
77
69
  'CommandResult',
78
70
  'CommandSet',
79
71
  'Statement',
72
+ # Colors
73
+ "Color",
80
74
  # Decorators
81
75
  'with_argument_list',
82
76
  'with_argparser',
@@ -87,9 +81,18 @@ __all__: list[str] = [ # noqa: RUF022
87
81
  'Cmd2ArgparseError',
88
82
  'CommandSetRegistrationError',
89
83
  'CompletionError',
84
+ 'PassThroughException',
90
85
  'SkipPostcommandHooks',
91
86
  # modules
92
87
  'plugin',
88
+ 'rich_utils',
89
+ 'string_utils',
90
+ # Rich Utils
91
+ 'RichPrintKwargs',
92
+ # String Utils
93
+ 'stylize',
94
+ # Styles,
95
+ "Cmd2Style",
93
96
  # Utilities
94
97
  'categorize',
95
98
  'CompletionMode',
@@ -1,4 +1,4 @@
1
- """Module efines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps.
1
+ """Module defines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps.
2
2
 
3
3
  See the header of argparse_custom.py for instructions on how to use these features.
4
4
  """
@@ -9,25 +9,24 @@ import numbers
9
9
  from collections import (
10
10
  deque,
11
11
  )
12
+ from collections.abc import Sequence
12
13
  from typing import (
14
+ IO,
13
15
  TYPE_CHECKING,
14
- Optional,
15
- Union,
16
16
  cast,
17
17
  )
18
18
 
19
- from .ansi import (
20
- style_aware_wcswidth,
21
- widest_line,
22
- )
23
- from .constants import (
24
- INFINITY,
25
- )
19
+ from .constants import INFINITY
20
+ from .rich_utils import Cmd2GeneralConsole
26
21
 
27
22
  if TYPE_CHECKING: # pragma: no cover
28
- from .cmd2 import (
29
- Cmd,
30
- )
23
+ from .cmd2 import Cmd
24
+
25
+ from rich.box import SIMPLE_HEAD
26
+ from rich.table import (
27
+ Column,
28
+ Table,
29
+ )
31
30
 
32
31
  from .argparse_custom import (
33
32
  ChoicesCallable,
@@ -35,20 +34,12 @@ from .argparse_custom import (
35
34
  CompletionItem,
36
35
  generate_range_error,
37
36
  )
38
- from .command_definition import (
39
- CommandSet,
40
- )
41
- from .exceptions import (
42
- CompletionError,
43
- )
44
- from .table_creator import (
45
- Column,
46
- HorizontalAlignment,
47
- SimpleTable,
48
- )
37
+ from .command_definition import CommandSet
38
+ from .exceptions import CompletionError
39
+ from .styles import Cmd2Style
49
40
 
50
- # If no descriptive header is supplied, then this will be used instead
51
- DEFAULT_DESCRIPTIVE_HEADER = 'Description'
41
+ # If no descriptive headers are supplied, then this will be used instead
42
+ DEFAULT_DESCRIPTIVE_HEADERS: Sequence[str | Column] = ('Description',)
52
43
 
53
44
  # Name of the choice/completer function argument that, if present, will be passed a dictionary of
54
45
  # command line tokens up through the token being completed mapped to their argparse destination name.
@@ -104,8 +95,8 @@ class _ArgumentState:
104
95
 
105
96
  def __init__(self, arg_action: argparse.Action) -> None:
106
97
  self.action = arg_action
107
- self.min: Union[int, str]
108
- self.max: Union[float, int, str]
98
+ self.min: int | str
99
+ self.max: float | int | str
109
100
  self.count = 0
110
101
  self.is_remainder = self.action.nargs == argparse.REMAINDER
111
102
 
@@ -140,7 +131,7 @@ class _UnfinishedFlagError(CompletionError):
140
131
  :param flag_arg_state: information about the unfinished flag action.
141
132
  """
142
133
  arg = f'{argparse._get_action_name(flag_arg_state.action)}'
143
- err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(Union[int, float], flag_arg_state.max))}'
134
+ err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(int | float, flag_arg_state.max))}'
144
135
  error = f"Error: argument {arg}: {err} ({flag_arg_state.count} entered)"
145
136
  super().__init__(error)
146
137
 
@@ -162,7 +153,7 @@ class ArgparseCompleter:
162
153
  """Automatic command line tab completion based on argparse parameters."""
163
154
 
164
155
  def __init__(
165
- self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[dict[str, list[str]]] = None
156
+ self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: dict[str, list[str]] | None = None
166
157
  ) -> None:
167
158
  """Create an ArgparseCompleter.
168
159
 
@@ -202,7 +193,7 @@ class ArgparseCompleter:
202
193
  self._subcommand_action = action
203
194
 
204
195
  def complete(
205
- self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: Optional[CommandSet] = None
196
+ self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: CommandSet | None = None
206
197
  ) -> list[str]:
207
198
  """Complete text using argparse metadata.
208
199
 
@@ -227,10 +218,10 @@ class ArgparseCompleter:
227
218
  skip_remaining_flags = False
228
219
 
229
220
  # _ArgumentState of the current positional
230
- pos_arg_state: Optional[_ArgumentState] = None
221
+ pos_arg_state: _ArgumentState | None = None
231
222
 
232
223
  # _ArgumentState of the current flag
233
- flag_arg_state: Optional[_ArgumentState] = None
224
+ flag_arg_state: _ArgumentState | None = None
234
225
 
235
226
  # Non-reusable flags that we've parsed
236
227
  matched_flags: list[str] = []
@@ -522,7 +513,7 @@ class ArgparseCompleter:
522
513
 
523
514
  return matches
524
515
 
525
- def _format_completions(self, arg_state: _ArgumentState, completions: Union[list[str], list[CompletionItem]]) -> list[str]:
516
+ def _format_completions(self, arg_state: _ArgumentState, completions: list[str] | list[CompletionItem]) -> list[str]:
526
517
  """Format CompletionItems into hint table."""
527
518
  # Nothing to do if we don't have at least 2 completions which are all CompletionItems
528
519
  if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions):
@@ -537,7 +528,7 @@ class ArgparseCompleter:
537
528
  if not self._cmd2_app.matches_sorted:
538
529
  # If all orig_value types are numbers, then sort by that value
539
530
  if all_nums:
540
- completion_items.sort(key=lambda c: c.orig_value) # type: ignore[no-any-return]
531
+ completion_items.sort(key=lambda c: c.orig_value)
541
532
 
542
533
  # Otherwise sort as strings
543
534
  else:
@@ -547,8 +538,6 @@ class ArgparseCompleter:
547
538
 
548
539
  # Check if there are too many CompletionItems to display as a table
549
540
  if len(completions) <= self._cmd2_app.max_completion_items:
550
- four_spaces = 4 * ' '
551
-
552
541
  # If a metavar was defined, use that instead of the dest field
553
542
  destination = arg_state.action.metavar if arg_state.action.metavar else arg_state.action.dest
554
543
 
@@ -561,39 +550,45 @@ class ArgparseCompleter:
561
550
  tuple_index = min(len(destination) - 1, arg_state.count)
562
551
  destination = destination[tuple_index]
563
552
 
564
- desc_header = arg_state.action.get_descriptive_header() # type: ignore[attr-defined]
565
- if desc_header is None:
566
- desc_header = DEFAULT_DESCRIPTIVE_HEADER
567
-
568
- # Replace tabs with 4 spaces so we can calculate width
569
- desc_header = desc_header.replace('\t', four_spaces)
570
-
571
- # Calculate needed widths for the token and description columns of the table
572
- token_width = style_aware_wcswidth(destination)
573
- desc_width = widest_line(desc_header)
574
-
575
- for item in completion_items:
576
- token_width = max(style_aware_wcswidth(item), token_width)
577
-
578
- # Replace tabs with 4 spaces so we can calculate width
579
- item.description = item.description.replace('\t', four_spaces)
580
- desc_width = max(widest_line(item.description), desc_width)
553
+ desc_headers = cast(Sequence[str | Column] | None, arg_state.action.get_descriptive_headers()) # type: ignore[attr-defined]
554
+ if desc_headers is None:
555
+ desc_headers = DEFAULT_DESCRIPTIVE_HEADERS
581
556
 
582
- cols = []
583
- dest_alignment = HorizontalAlignment.RIGHT if all_nums else HorizontalAlignment.LEFT
584
- cols.append(
557
+ # Build all headers for the hint table
558
+ headers: list[Column] = []
559
+ headers.append(
585
560
  Column(
586
561
  destination.upper(),
587
- width=token_width,
588
- header_horiz_align=dest_alignment,
589
- data_horiz_align=dest_alignment,
562
+ justify="right" if all_nums else "left",
563
+ no_wrap=True,
564
+ )
565
+ )
566
+ for desc_header in desc_headers:
567
+ header = (
568
+ desc_header
569
+ if isinstance(desc_header, Column)
570
+ else Column(
571
+ desc_header,
572
+ overflow="fold",
573
+ )
590
574
  )
575
+ headers.append(header)
576
+
577
+ # Build the hint table
578
+ hint_table = Table(
579
+ *headers,
580
+ box=SIMPLE_HEAD,
581
+ show_edge=False,
582
+ border_style=Cmd2Style.TABLE_BORDER,
591
583
  )
592
- cols.append(Column(desc_header, width=desc_width))
584
+ for item in completion_items:
585
+ hint_table.add_row(item, *item.descriptive_data)
593
586
 
594
- hint_table = SimpleTable(cols, divider_char=self._cmd2_app.ruler)
595
- table_data = [[item, item.description] for item in completion_items]
596
- self._cmd2_app.formatted_completions = hint_table.generate_table(table_data, row_spacing=0)
587
+ # Generate the hint table string
588
+ console = Cmd2GeneralConsole()
589
+ with console.capture() as capture:
590
+ console.print(hint_table, end="")
591
+ self._cmd2_app.formatted_completions = capture.get()
597
592
 
598
593
  # Return sorted list of completions
599
594
  return cast(list[str], completions)
@@ -624,24 +619,28 @@ class ArgparseCompleter:
624
619
  break
625
620
  return []
626
621
 
627
- def format_help(self, tokens: list[str]) -> str:
628
- """Supports cmd2's help command in the retrieval of help text.
622
+ def print_help(self, tokens: list[str], file: IO[str] | None = None) -> None:
623
+ """Supports cmd2's help command in the printing of help text.
629
624
 
630
625
  :param tokens: arguments passed to help command
631
- :return: help text of the command being queried.
626
+ :param file: optional file object where the argparse should write help text
627
+ If not supplied, argparse will write to sys.stdout.
632
628
  """
633
- # If our parser has subcommands, we must examine the tokens and check if they are subcommands
629
+ # If our parser has subcommands, we must examine the tokens and check if they are subcommands.
634
630
  # If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter.
635
- if self._subcommand_action is not None:
636
- for token_index, token in enumerate(tokens):
637
- if token in self._subcommand_action.choices:
638
- parser: argparse.ArgumentParser = self._subcommand_action.choices[token]
639
- completer_type = self._cmd2_app._determine_ap_completer_type(parser)
631
+ if tokens and self._subcommand_action is not None:
632
+ parser = cast(
633
+ argparse.ArgumentParser | None,
634
+ self._subcommand_action.choices.get(tokens[0]),
635
+ )
640
636
 
641
- completer = completer_type(parser, self._cmd2_app)
642
- return completer.format_help(tokens[token_index + 1 :])
643
- break
644
- return self._parser.format_help()
637
+ if parser:
638
+ completer_type = self._cmd2_app._determine_ap_completer_type(parser)
639
+ completer = completer_type(parser, self._cmd2_app)
640
+ completer.print_help(tokens[1:])
641
+ return
642
+
643
+ self._parser.print_help(file=file)
645
644
 
646
645
  def _complete_arg(
647
646
  self,
@@ -652,7 +651,7 @@ class ArgparseCompleter:
652
651
  arg_state: _ArgumentState,
653
652
  consumed_arg_values: dict[str, list[str]],
654
653
  *,
655
- cmd_set: Optional[CommandSet] = None,
654
+ cmd_set: CommandSet | None = None,
656
655
  ) -> list[str]:
657
656
  """Tab completion routine for an argparse argument.
658
657
 
@@ -660,7 +659,7 @@ class ArgparseCompleter:
660
659
  :raises CompletionError: if the completer or choices function this calls raises one.
661
660
  """
662
661
  # Check if the arg provides choices to the user
663
- arg_choices: Union[list[str], ChoicesCallable]
662
+ arg_choices: list[str] | ChoicesCallable
664
663
  if arg_state.action.choices is not None:
665
664
  arg_choices = list(arg_state.action.choices)
666
665
  if not arg_choices:
@@ -722,12 +721,12 @@ class ArgparseCompleter:
722
721
  if not arg_choices.is_completer:
723
722
  choices_func = arg_choices.choices_provider
724
723
  if isinstance(choices_func, ChoicesProviderFuncWithTokens):
725
- completion_items = choices_func(*args, **kwargs) # type: ignore[arg-type]
724
+ completion_items = choices_func(*args, **kwargs)
726
725
  else: # pragma: no cover
727
726
  # This won't hit because runtime checking doesn't check function argument types and will always
728
727
  # resolve true above. Mypy, however, does see the difference and gives an error that can't be
729
728
  # ignored. Mypy issue #5485 discusses this problem
730
- completion_items = choices_func(*args) # type: ignore[arg-type]
729
+ completion_items = choices_func(*args)
731
730
  # else case is already covered above
732
731
  else:
733
732
  completion_items = arg_choices