cmd2 2.5.11__py3-none-any.whl → 2.6.1__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/cmd2.py CHANGED
@@ -1,4 +1,3 @@
1
- # coding=utf-8
2
1
  """Variant on standard library's cmd with extra features.
3
2
 
4
3
  To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you
@@ -31,11 +30,13 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2
31
30
  # setting is True
32
31
  import argparse
33
32
  import cmd
33
+ import contextlib
34
34
  import copy
35
35
  import functools
36
36
  import glob
37
37
  import inspect
38
38
  import os
39
+ import pprint
39
40
  import pydoc
40
41
  import re
41
42
  import sys
@@ -48,9 +49,7 @@ from collections import (
48
49
  OrderedDict,
49
50
  namedtuple,
50
51
  )
51
- from contextlib import (
52
- redirect_stdout,
53
- )
52
+ from collections.abc import Callable, Iterable, Mapping
54
53
  from types import (
55
54
  FrameType,
56
55
  ModuleType,
@@ -59,16 +58,9 @@ from typing import (
59
58
  IO,
60
59
  TYPE_CHECKING,
61
60
  Any,
62
- Callable,
63
- Dict,
64
- Iterable,
65
- List,
66
- Mapping,
61
+ ClassVar,
67
62
  Optional,
68
- Set,
69
63
  TextIO,
70
- Tuple,
71
- Type,
72
64
  TypeVar,
73
65
  Union,
74
66
  cast,
@@ -130,10 +122,8 @@ from .parsing import (
130
122
  )
131
123
 
132
124
  # NOTE: When using gnureadline with Python 3.13, start_ipython needs to be imported before any readline-related stuff
133
- try:
125
+ with contextlib.suppress(ImportError):
134
126
  from IPython import start_ipython # type: ignore[import]
135
- except ImportError:
136
- pass
137
127
 
138
128
  from .rl_utils import (
139
129
  RlType,
@@ -154,6 +144,7 @@ from .table_creator import (
154
144
  from .utils import (
155
145
  Settable,
156
146
  get_defining_class,
147
+ get_types,
157
148
  strip_doc_annotations,
158
149
  suggest_similar,
159
150
  )
@@ -187,7 +178,7 @@ else:
187
178
 
188
179
 
189
180
  class _SavedReadlineSettings:
190
- """readline settings that are backed up when switching between readline environments"""
181
+ """readline settings that are backed up when switching between readline environments."""
191
182
 
192
183
  def __init__(self) -> None:
193
184
  self.completer = None
@@ -196,18 +187,18 @@ class _SavedReadlineSettings:
196
187
 
197
188
 
198
189
  class _SavedCmd2Env:
199
- """cmd2 environment settings that are backed up when entering an interactive Python shell"""
190
+ """cmd2 environment settings that are backed up when entering an interactive Python shell."""
200
191
 
201
192
  def __init__(self) -> None:
202
193
  self.readline_settings = _SavedReadlineSettings()
203
194
  self.readline_module: Optional[ModuleType] = None
204
- self.history: List[str] = []
195
+ self.history: list[str] = []
205
196
  self.sys_stdout: Optional[TextIO] = None
206
197
  self.sys_stdin: Optional[TextIO] = None
207
198
 
208
199
 
209
200
  # Contains data about a disabled command which is used to restore its original functions when the command is enabled
210
- DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function'])
201
+ DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function']) # noqa: PYI024
211
202
 
212
203
 
213
204
  if TYPE_CHECKING: # pragma: no cover
@@ -219,8 +210,7 @@ else:
219
210
 
220
211
 
221
212
  class _CommandParsers:
222
- """
223
- Create and store all command method argument parsers for a given Cmd instance.
213
+ """Create and store all command method argument parsers for a given Cmd instance.
224
214
 
225
215
  Parser creation and retrieval are accomplished through the get() method.
226
216
  """
@@ -230,7 +220,7 @@ class _CommandParsers:
230
220
 
231
221
  # Keyed by the fully qualified method names. This is more reliable than
232
222
  # the methods themselves, since wrapping a method will change its address.
233
- self._parsers: Dict[str, argparse.ArgumentParser] = {}
223
+ self._parsers: dict[str, argparse.ArgumentParser] = {}
234
224
 
235
225
  @staticmethod
236
226
  def _fully_qualified_name(command_method: CommandFunc) -> str:
@@ -241,8 +231,7 @@ class _CommandParsers:
241
231
  return ""
242
232
 
243
233
  def __contains__(self, command_method: CommandFunc) -> bool:
244
- """
245
- Return whether a given method's parser is in self.
234
+ """Return whether a given method's parser is in self.
246
235
 
247
236
  If the parser does not yet exist, it will be created if applicable.
248
237
  This is basically for checking if a method is argarse-based.
@@ -251,8 +240,7 @@ class _CommandParsers:
251
240
  return bool(parser)
252
241
 
253
242
  def get(self, command_method: CommandFunc) -> Optional[argparse.ArgumentParser]:
254
- """
255
- Return a given method's parser or None if the method is not argparse-based.
243
+ """Return a given method's parser or None if the method is not argparse-based.
256
244
 
257
245
  If the parser does not yet exist, it will be created.
258
246
  """
@@ -296,7 +284,7 @@ class _CommandParsers:
296
284
  class Cmd(cmd.Cmd):
297
285
  """An easy but powerful framework for writing line-oriented command interpreters.
298
286
 
299
- Extends the Python Standard Librarys cmd package by adding a lot of useful features
287
+ Extends the Python Standard Library's cmd package by adding a lot of useful features
300
288
  to the out of the box configuration.
301
289
 
302
290
  Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
@@ -312,6 +300,9 @@ class Cmd(cmd.Cmd):
312
300
  ALPHABETICAL_SORT_KEY = utils.norm_fold
313
301
  NATURAL_SORT_KEY = utils.natural_keys
314
302
 
303
+ # List for storing transcript test file names
304
+ testfiles: ClassVar[list[str]] = []
305
+
315
306
  def __init__(
316
307
  self,
317
308
  completekey: str = 'tab',
@@ -325,18 +316,17 @@ class Cmd(cmd.Cmd):
325
316
  include_py: bool = False,
326
317
  include_ipy: bool = False,
327
318
  allow_cli_args: bool = True,
328
- transcript_files: Optional[List[str]] = None,
319
+ transcript_files: Optional[list[str]] = None,
329
320
  allow_redirection: bool = True,
330
- multiline_commands: Optional[List[str]] = None,
331
- terminators: Optional[List[str]] = None,
332
- shortcuts: Optional[Dict[str, str]] = None,
321
+ multiline_commands: Optional[list[str]] = None,
322
+ terminators: Optional[list[str]] = None,
323
+ shortcuts: Optional[dict[str, str]] = None,
333
324
  command_sets: Optional[Iterable[CommandSet]] = None,
334
325
  auto_load_commands: bool = True,
335
326
  allow_clipboard: bool = True,
336
327
  suggest_similar_command: bool = False,
337
328
  ) -> None:
338
- """An easy but powerful framework for writing line-oriented command
339
- interpreters. Extends Python's cmd package.
329
+ """Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
340
330
 
341
331
  :param completekey: readline name of a completion key, default to Tab
342
332
  :param stdin: alternate input file object, if not specified, sys.stdin is used
@@ -388,9 +378,9 @@ class Cmd(cmd.Cmd):
388
378
  """
389
379
  # Check if py or ipy need to be disabled in this instance
390
380
  if not include_py:
391
- setattr(self, 'do_py', None)
381
+ setattr(self, 'do_py', None) # noqa: B010
392
382
  if not include_ipy:
393
- setattr(self, 'do_ipy', None)
383
+ setattr(self, 'do_ipy', None) # noqa: B010
394
384
 
395
385
  # initialize plugin system
396
386
  # needs to be done before we call __init__(0)
@@ -416,20 +406,20 @@ class Cmd(cmd.Cmd):
416
406
  # The maximum number of CompletionItems to display during tab completion. If the number of completion
417
407
  # suggestions exceeds this number, they will be displayed in the typical columnized format and will
418
408
  # not include the description value of the CompletionItems.
419
- self.max_completion_items = 50
409
+ self.max_completion_items: int = 50
420
410
 
421
411
  # A dictionary mapping settable names to their Settable instance
422
- self._settables: Dict[str, Settable] = dict()
412
+ self._settables: dict[str, Settable] = {}
423
413
  self._always_prefix_settables: bool = False
424
414
 
425
415
  # CommandSet containers
426
- self._installed_command_sets: Set[CommandSet] = set()
427
- self._cmd_to_command_sets: Dict[str, CommandSet] = {}
416
+ self._installed_command_sets: set[CommandSet] = set()
417
+ self._cmd_to_command_sets: dict[str, CommandSet] = {}
428
418
 
429
419
  self.build_settables()
430
420
 
431
421
  # Use as prompt for multiline commands on the 2nd+ line of input
432
- self.continuation_prompt = '> '
422
+ self.continuation_prompt: str = '> '
433
423
 
434
424
  # Allow access to your application in embedded Python shells and scripts py via self
435
425
  self.self_in_py = False
@@ -446,21 +436,21 @@ class Cmd(cmd.Cmd):
446
436
  self.exclude_from_history = ['eof', 'history']
447
437
 
448
438
  # Dictionary of macro names and their values
449
- self.macros: Dict[str, Macro] = dict()
439
+ self.macros: dict[str, Macro] = {}
450
440
 
451
441
  # Keeps track of typed command history in the Python shell
452
- self._py_history: List[str] = []
442
+ self._py_history: list[str] = []
453
443
 
454
444
  # The name by which Python environments refer to the PyBridge to call app commands
455
445
  self.py_bridge_name = 'app'
456
446
 
457
447
  # Defines app-specific variables/functions available in Python shells and pyscripts
458
- self.py_locals: Dict[str, Any] = dict()
448
+ self.py_locals: dict[str, Any] = {}
459
449
 
460
450
  # True if running inside a Python shell or pyscript, False otherwise
461
451
  self._in_py = False
462
452
 
463
- self.statement_parser = StatementParser(
453
+ self.statement_parser: StatementParser = StatementParser(
464
454
  terminators=terminators, multiline_commands=multiline_commands, shortcuts=shortcuts
465
455
  )
466
456
 
@@ -468,10 +458,10 @@ class Cmd(cmd.Cmd):
468
458
  self.last_result: Any = None
469
459
 
470
460
  # Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command
471
- self._script_dir: List[str] = []
461
+ self._script_dir: list[str] = []
472
462
 
473
463
  # Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt
474
- self.sigint_protection = utils.ContextFlag()
464
+ self.sigint_protection: utils.ContextFlag = utils.ContextFlag()
475
465
 
476
466
  # If the current command created a process to pipe to, then this will be a ProcReader object.
477
467
  # Otherwise it will be None. It's used to know when a pipe process can be killed and/or waited upon.
@@ -499,7 +489,7 @@ class Cmd(cmd.Cmd):
499
489
  self.broken_pipe_warning = ''
500
490
 
501
491
  # Commands that will run at the beginning of the command loop
502
- self._startup_commands: List[str] = []
492
+ self._startup_commands: list[str] = []
503
493
 
504
494
  # If a startup script is provided and exists, then execute it in the startup commands
505
495
  if startup_script:
@@ -511,7 +501,7 @@ class Cmd(cmd.Cmd):
511
501
  self._startup_commands.append(script_cmd)
512
502
 
513
503
  # Transcript files to run instead of interactive command loop
514
- self._transcript_files: Optional[List[str]] = None
504
+ self._transcript_files: Optional[list[str]] = None
515
505
 
516
506
  # Check for command line args
517
507
  if allow_cli_args:
@@ -554,7 +544,7 @@ class Cmd(cmd.Cmd):
554
544
  # Commands that have been disabled from use. This is to support commands that are only available
555
545
  # during specific states of the application. This dictionary's keys are the command names and its
556
546
  # values are DisabledCommand objects.
557
- self.disabled_commands: Dict[str, DisabledCommand] = dict()
547
+ self.disabled_commands: dict[str, DisabledCommand] = {}
558
548
 
559
549
  # If any command has been categorized, then all other commands that haven't been categorized
560
550
  # will display under this section in the help output.
@@ -566,7 +556,7 @@ class Cmd(cmd.Cmd):
566
556
  # command and category names
567
557
  # alias, macro, settable, and shortcut names
568
558
  # tab completion results when self.matches_sorted is False
569
- self.default_sort_key = Cmd.ALPHABETICAL_SORT_KEY
559
+ self.default_sort_key: Callable[[str], str] = Cmd.ALPHABETICAL_SORT_KEY
570
560
 
571
561
  ############################################################################################################
572
562
  # The following variables are used by tab completion functions. They are reset each time complete() is run
@@ -582,17 +572,17 @@ class Cmd(cmd.Cmd):
582
572
  self.allow_closing_quote = True
583
573
 
584
574
  # An optional hint which prints above tab completion suggestions
585
- self.completion_hint = ''
575
+ self.completion_hint: str = ''
586
576
 
587
577
  # Normally cmd2 uses readline's formatter to columnize the list of completion suggestions.
588
578
  # If a custom format is preferred, write the formatted completions to this string. cmd2 will
589
579
  # then print it instead of the readline format. ANSI style sequences and newlines are supported
590
580
  # when using this value. Even when using formatted_completions, the full matches must still be returned
591
581
  # from your completer function. ArgparseCompleter writes its tab completion tables to this string.
592
- self.formatted_completions = ''
582
+ self.formatted_completions: str = ''
593
583
 
594
584
  # Used by complete() for readline tab completion
595
- self.completion_matches: List[str] = []
585
+ self.completion_matches: list[str] = []
596
586
 
597
587
  # Use this list if you need to display tab completion suggestions that are different than the actual text
598
588
  # of the matches. For instance, if you are completing strings that contain a common delimiter and you only
@@ -600,7 +590,7 @@ class Cmd(cmd.Cmd):
600
590
  # still must be returned from your completer function. For an example, look at path_complete() which
601
591
  # uses this to show only the basename of paths as the suggestions. delimiter_complete() also populates
602
592
  # this list. These are ignored if self.formatted_completions is populated.
603
- self.display_matches: List[str] = []
593
+ self.display_matches: list[str] = []
604
594
 
605
595
  # Used by functions like path_complete() and delimiter_complete() to properly
606
596
  # quote matches that are completed in a delimited fashion
@@ -609,10 +599,10 @@ class Cmd(cmd.Cmd):
609
599
  # Set to True before returning matches to complete() in cases where matches have already been sorted.
610
600
  # If False, then complete() will sort the matches using self.default_sort_key before they are displayed.
611
601
  # This does not affect self.formatted_completions.
612
- self.matches_sorted = False
602
+ self.matches_sorted: bool = False
613
603
 
614
604
  # Command parsers for this Cmd instance.
615
- self._command_parsers = _CommandParsers(self)
605
+ self._command_parsers: _CommandParsers = _CommandParsers(self)
616
606
 
617
607
  # Add functions decorated to be subcommands
618
608
  self._register_subcommands(self)
@@ -642,9 +632,9 @@ class Cmd(cmd.Cmd):
642
632
  # the current command being executed
643
633
  self.current_command: Optional[Statement] = None
644
634
 
645
- def find_commandsets(self, commandset_type: Type[CommandSet], *, subclass_match: bool = False) -> List[CommandSet]:
646
- """
647
- Find all CommandSets that match the provided CommandSet type.
635
+ def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]:
636
+ """Find all CommandSets that match the provided CommandSet type.
637
+
648
638
  By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that
649
639
  are sub-classes of the provided type
650
640
  :param commandset_type: CommandSet sub-class type to search for
@@ -658,8 +648,8 @@ class Cmd(cmd.Cmd):
658
648
  ]
659
649
 
660
650
  def find_commandset_for_command(self, command_name: str) -> Optional[CommandSet]:
661
- """
662
- Finds the CommandSet that registered the command name
651
+ """Find the CommandSet that registered the command name.
652
+
663
653
  :param command_name: command name to search
664
654
  :return: CommandSet that provided the command
665
655
  """
@@ -671,7 +661,7 @@ class Cmd(cmd.Cmd):
671
661
  all_commandset_defs = CommandSet.__subclasses__()
672
662
  existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets]
673
663
 
674
- def load_commandset_by_type(commandset_types: List[Type[CommandSet]]) -> None:
664
+ def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None:
675
665
  for cmdset_type in commandset_types:
676
666
  # check if the type has sub-classes. We will only auto-load leaf class types.
677
667
  subclasses = cmdset_type.__subclasses__()
@@ -690,8 +680,7 @@ class Cmd(cmd.Cmd):
690
680
  load_commandset_by_type(all_commandset_defs)
691
681
 
692
682
  def register_command_set(self, cmdset: CommandSet) -> None:
693
- """
694
- Installs a CommandSet, loading all commands defined in the CommandSet
683
+ """Installs a CommandSet, loading all commands defined in the CommandSet.
695
684
 
696
685
  :param cmdset: CommandSet to load
697
686
  """
@@ -703,19 +692,19 @@ class Cmd(cmd.Cmd):
703
692
  if self.always_prefix_settables:
704
693
  if not cmdset.settable_prefix.strip():
705
694
  raise CommandSetRegistrationError('CommandSet settable prefix must not be empty')
706
- for key in cmdset.settables.keys():
695
+ for key in cmdset.settables:
707
696
  prefixed_name = f'{cmdset.settable_prefix}.{key}'
708
697
  if prefixed_name in all_settables:
709
698
  raise CommandSetRegistrationError(f'Duplicate settable: {key}')
710
699
 
711
700
  else:
712
- for key in cmdset.settables.keys():
701
+ for key in cmdset.settables:
713
702
  if key in all_settables:
714
703
  raise CommandSetRegistrationError(f'Duplicate settable {key} is already registered')
715
704
 
716
705
  cmdset.on_register(self)
717
706
  methods = cast(
718
- List[Tuple[str, Callable[..., Any]]],
707
+ list[tuple[str, Callable[..., Any]]],
719
708
  inspect.getmembers(
720
709
  cmdset,
721
710
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
@@ -790,8 +779,7 @@ class Cmd(cmd.Cmd):
790
779
  return parser
791
780
 
792
781
  def _install_command_function(self, command_func_name: str, command_method: CommandFunc, context: str = '') -> None:
793
- """
794
- Install a new command function into the CLI.
782
+ """Install a new command function into the CLI.
795
783
 
796
784
  :param command_func_name: name of command function to add
797
785
  This points to the command method and may differ from the method's
@@ -800,7 +788,6 @@ class Cmd(cmd.Cmd):
800
788
  :param context: optional info to provide in error message. (e.g. class this function belongs to)
801
789
  :raises CommandSetRegistrationError: if the command function fails to install
802
790
  """
803
-
804
791
  # command_func_name must begin with COMMAND_FUNC_PREFIX to be identified as a command by cmd2.
805
792
  if not command_func_name.startswith(COMMAND_FUNC_PREFIX):
806
793
  raise CommandSetRegistrationError(f"{command_func_name} does not begin with '{COMMAND_FUNC_PREFIX}'")
@@ -847,8 +834,7 @@ class Cmd(cmd.Cmd):
847
834
  setattr(self, help_func_name, cmd_help)
848
835
 
849
836
  def unregister_command_set(self, cmdset: CommandSet) -> None:
850
- """
851
- Uninstalls a CommandSet and unloads all associated commands
837
+ """Uninstalls a CommandSet and unloads all associated commands.
852
838
 
853
839
  :param cmdset: CommandSet to uninstall
854
840
  """
@@ -857,7 +843,7 @@ class Cmd(cmd.Cmd):
857
843
  cmdset.on_unregister()
858
844
  self._unregister_subcommands(cmdset)
859
845
 
860
- methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
846
+ methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
861
847
  cmdset,
862
848
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
863
849
  and hasattr(meth, '__name__')
@@ -903,7 +889,7 @@ class Cmd(cmd.Cmd):
903
889
  check_parser_uninstallable(subparser)
904
890
  break
905
891
 
906
- methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
892
+ methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
907
893
  cmdset,
908
894
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
909
895
  and hasattr(meth, '__name__')
@@ -919,8 +905,7 @@ class Cmd(cmd.Cmd):
919
905
  check_parser_uninstallable(command_parser)
920
906
 
921
907
  def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
922
- """
923
- Register subcommands with their base command
908
+ """Register subcommands with their base command.
924
909
 
925
910
  :param cmdset: CommandSet or cmd2.Cmd subclass containing subcommands
926
911
  """
@@ -937,14 +922,14 @@ class Cmd(cmd.Cmd):
937
922
  )
938
923
 
939
924
  # iterate through all matching methods
940
- for method_name, method in methods:
925
+ for _method_name, method in methods:
941
926
  subcommand_name: str = getattr(method, constants.SUBCMD_ATTR_NAME)
942
927
  full_command_name: str = getattr(method, constants.SUBCMD_ATTR_COMMAND)
943
928
  subcmd_parser_builder = getattr(method, constants.CMD_ATTR_ARGPARSER)
944
929
 
945
930
  subcommand_valid, errmsg = self.statement_parser.is_valid_command(subcommand_name, is_subcommand=True)
946
931
  if not subcommand_valid:
947
- raise CommandSetRegistrationError(f'Subcommand {str(subcommand_name)} is not valid: {errmsg}')
932
+ raise CommandSetRegistrationError(f'Subcommand {subcommand_name!s} is not valid: {errmsg}')
948
933
 
949
934
  command_tokens = full_command_name.split()
950
935
  command_name = command_tokens[0]
@@ -957,16 +942,14 @@ class Cmd(cmd.Cmd):
957
942
  command_func = self.cmd_func(command_name)
958
943
 
959
944
  if command_func is None:
960
- raise CommandSetRegistrationError(
961
- f"Could not find command '{command_name}' needed by subcommand: {str(method)}"
962
- )
945
+ raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}")
963
946
  command_parser = self._command_parsers.get(command_func)
964
947
  if command_parser is None:
965
948
  raise CommandSetRegistrationError(
966
- f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}"
949
+ f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}"
967
950
  )
968
951
 
969
- def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser:
952
+ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser:
970
953
  if not subcmd_names:
971
954
  return action
972
955
  cur_subcmd = subcmd_names.pop(0)
@@ -976,7 +959,7 @@ class Cmd(cmd.Cmd):
976
959
  if choice_name == cur_subcmd:
977
960
  return find_subcommand(choice, subcmd_names)
978
961
  break
979
- raise CommandSetRegistrationError(f"Could not find subcommand '{full_command_name}'")
962
+ raise CommandSetRegistrationError(f"Could not find subcommand '{action}'")
980
963
 
981
964
  target_parser = find_subcommand(command_parser, subcommand_names)
982
965
 
@@ -1028,8 +1011,7 @@ class Cmd(cmd.Cmd):
1028
1011
  break
1029
1012
 
1030
1013
  def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
1031
- """
1032
- Unregister subcommands from their base command
1014
+ """Unregister subcommands from their base command.
1033
1015
 
1034
1016
  :param cmdset: CommandSet containing subcommands
1035
1017
  """
@@ -1046,7 +1028,7 @@ class Cmd(cmd.Cmd):
1046
1028
  )
1047
1029
 
1048
1030
  # iterate through all matching methods
1049
- for method_name, method in methods:
1031
+ for _method_name, method in methods:
1050
1032
  subcommand_name = getattr(method, constants.SUBCMD_ATTR_NAME)
1051
1033
  command_name = getattr(method, constants.SUBCMD_ATTR_COMMAND)
1052
1034
 
@@ -1059,15 +1041,13 @@ class Cmd(cmd.Cmd):
1059
1041
  if command_func is None: # pragma: no cover
1060
1042
  # This really shouldn't be possible since _register_subcommands would prevent this from happening
1061
1043
  # but keeping in case it does for some strange reason
1062
- raise CommandSetRegistrationError(
1063
- f"Could not find command '{command_name}' needed by subcommand: {str(method)}"
1064
- )
1044
+ raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}")
1065
1045
  command_parser = self._command_parsers.get(command_func)
1066
1046
  if command_parser is None: # pragma: no cover
1067
1047
  # This really shouldn't be possible since _register_subcommands would prevent this from happening
1068
1048
  # but keeping in case it does for some strange reason
1069
1049
  raise CommandSetRegistrationError(
1070
- f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}"
1050
+ f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}"
1071
1051
  )
1072
1052
 
1073
1053
  for action in command_parser._actions:
@@ -1077,8 +1057,7 @@ class Cmd(cmd.Cmd):
1077
1057
 
1078
1058
  @property
1079
1059
  def always_prefix_settables(self) -> bool:
1080
- """
1081
- Flags whether CommandSet settable values should always be prefixed
1060
+ """Flags whether CommandSet settable values should always be prefixed.
1082
1061
 
1083
1062
  :return: True if CommandSet settable values will always be prefixed. False if not.
1084
1063
  """
@@ -1086,8 +1065,7 @@ class Cmd(cmd.Cmd):
1086
1065
 
1087
1066
  @always_prefix_settables.setter
1088
1067
  def always_prefix_settables(self, new_value: bool) -> None:
1089
- """
1090
- Set whether CommandSet settable values should always be prefixed.
1068
+ """Set whether CommandSet settable values should always be prefixed.
1091
1069
 
1092
1070
  :param new_value: True if CommandSet settable values should always be prefixed. False if not.
1093
1071
  :raises ValueError: If a registered CommandSet does not have a defined prefix
@@ -1103,8 +1081,7 @@ class Cmd(cmd.Cmd):
1103
1081
 
1104
1082
  @property
1105
1083
  def settables(self) -> Mapping[str, Settable]:
1106
- """
1107
- Get all available user-settable attributes. This includes settables defined in installed CommandSets
1084
+ """Get all available user-settable attributes. This includes settables defined in installed CommandSets.
1108
1085
 
1109
1086
  :return: Mapping from attribute-name to Settable of all user-settable attributes from
1110
1087
  """
@@ -1119,44 +1096,41 @@ class Cmd(cmd.Cmd):
1119
1096
  return all_settables
1120
1097
 
1121
1098
  def add_settable(self, settable: Settable) -> None:
1122
- """
1123
- Add a settable parameter to ``self.settables``
1099
+ """Add a settable parameter to ``self.settables``.
1124
1100
 
1125
1101
  :param settable: Settable object being added
1126
1102
  """
1127
- if not self.always_prefix_settables:
1128
- if settable.name in self.settables.keys() and settable.name not in self._settables.keys():
1129
- raise KeyError(f'Duplicate settable: {settable.name}')
1103
+ if not self.always_prefix_settables and settable.name in self.settables and settable.name not in self._settables:
1104
+ raise KeyError(f'Duplicate settable: {settable.name}')
1130
1105
  self._settables[settable.name] = settable
1131
1106
 
1132
1107
  def remove_settable(self, name: str) -> None:
1133
- """
1134
- Convenience method for removing a settable parameter from ``self.settables``
1108
+ """Remove a settable parameter from ``self.settables``.
1135
1109
 
1136
1110
  :param name: name of the settable being removed
1137
1111
  :raises KeyError: if the Settable matches this name
1138
1112
  """
1139
1113
  try:
1140
1114
  del self._settables[name]
1141
- except KeyError:
1142
- raise KeyError(name + " is not a settable parameter")
1115
+ except KeyError as exc:
1116
+ raise KeyError(name + " is not a settable parameter") from exc
1143
1117
 
1144
1118
  def build_settables(self) -> None:
1145
- """Create the dictionary of user-settable parameters"""
1119
+ """Create the dictionary of user-settable parameters."""
1146
1120
 
1147
- def get_allow_style_choices(cli_self: Cmd) -> List[str]:
1148
- """Used to tab complete allow_style values"""
1121
+ def get_allow_style_choices(_cli_self: Cmd) -> list[str]:
1122
+ """Tab complete allow_style values."""
1149
1123
  return [val.name.lower() for val in ansi.AllowStyle]
1150
1124
 
1151
1125
  def allow_style_type(value: str) -> ansi.AllowStyle:
1152
- """Converts a string value into an ansi.AllowStyle"""
1126
+ """Convert a string value into an ansi.AllowStyle."""
1153
1127
  try:
1154
1128
  return ansi.AllowStyle[value.upper()]
1155
- except KeyError:
1129
+ except KeyError as esc:
1156
1130
  raise ValueError(
1157
1131
  f"must be {ansi.AllowStyle.ALWAYS}, {ansi.AllowStyle.NEVER}, or "
1158
1132
  f"{ansi.AllowStyle.TERMINAL} (case-insensitive)"
1159
- )
1133
+ ) from esc
1160
1134
 
1161
1135
  self.add_settable(
1162
1136
  Settable(
@@ -1187,16 +1161,16 @@ class Cmd(cmd.Cmd):
1187
1161
 
1188
1162
  @property
1189
1163
  def allow_style(self) -> ansi.AllowStyle:
1190
- """Read-only property needed to support do_set when it reads allow_style"""
1164
+ """Read-only property needed to support do_set when it reads allow_style."""
1191
1165
  return ansi.allow_style
1192
1166
 
1193
1167
  @allow_style.setter
1194
1168
  def allow_style(self, new_val: ansi.AllowStyle) -> None:
1195
- """Setter property needed to support do_set when it updates allow_style"""
1169
+ """Setter property needed to support do_set when it updates allow_style."""
1196
1170
  ansi.allow_style = new_val
1197
1171
 
1198
1172
  def _completion_supported(self) -> bool:
1199
- """Return whether tab completion is supported"""
1173
+ """Return whether tab completion is supported."""
1200
1174
  return self.use_rawinput and bool(self.completekey) and rl_type != RlType.NONE
1201
1175
 
1202
1176
  @property
@@ -1218,8 +1192,7 @@ class Cmd(cmd.Cmd):
1218
1192
  end: str = '\n',
1219
1193
  style: Optional[Callable[[str], str]] = None,
1220
1194
  ) -> None:
1221
- """
1222
- Print message to a given file object.
1195
+ """Print message to a given file object.
1223
1196
 
1224
1197
  :param dest: the file object being written to
1225
1198
  :param msg: object to print
@@ -1239,7 +1212,7 @@ class Cmd(cmd.Cmd):
1239
1212
  sys.stderr.write(self.broken_pipe_warning)
1240
1213
 
1241
1214
  def poutput(self, msg: Any = '', *, end: str = '\n') -> None:
1242
- """Print message to self.stdout and appends a newline by default
1215
+ """Print message to self.stdout and appends a newline by default.
1243
1216
 
1244
1217
  :param msg: object to print
1245
1218
  :param end: string appended after the end of the message, default a newline
@@ -1247,7 +1220,7 @@ class Cmd(cmd.Cmd):
1247
1220
  self.print_to(self.stdout, msg, end=end)
1248
1221
 
1249
1222
  def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1250
- """Print message to sys.stderr
1223
+ """Print message to sys.stderr.
1251
1224
 
1252
1225
  :param msg: object to print
1253
1226
  :param end: string appended after the end of the message, default a newline
@@ -1257,7 +1230,7 @@ class Cmd(cmd.Cmd):
1257
1230
  self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None)
1258
1231
 
1259
1232
  def psuccess(self, msg: Any = '', *, end: str = '\n') -> None:
1260
- """Wraps poutput, but applies ansi.style_success by default
1233
+ """Wrap poutput, but applies ansi.style_success by default.
1261
1234
 
1262
1235
  :param msg: object to print
1263
1236
  :param end: string appended after the end of the message, default a newline
@@ -1266,7 +1239,7 @@ class Cmd(cmd.Cmd):
1266
1239
  self.poutput(msg, end=end)
1267
1240
 
1268
1241
  def pwarning(self, msg: Any = '', *, end: str = '\n') -> None:
1269
- """Wraps perror, but applies ansi.style_warning by default
1242
+ """Wrap perror, but applies ansi.style_warning by default.
1270
1243
 
1271
1244
  :param msg: object to print
1272
1245
  :param end: string appended after the end of the message, default a newline
@@ -1302,7 +1275,8 @@ class Cmd(cmd.Cmd):
1302
1275
  self.perror(final_msg, end=end, apply_style=False)
1303
1276
 
1304
1277
  def pfeedback(self, msg: Any, *, end: str = '\n') -> None:
1305
- """For printing nonessential feedback. Can be silenced with `quiet`.
1278
+ """Print nonessential feedback. Can be silenced with `quiet`.
1279
+
1306
1280
  Inclusion in redirected output is controlled by `feedback_to_output`.
1307
1281
 
1308
1282
  :param msg: object to print
@@ -1334,7 +1308,7 @@ class Cmd(cmd.Cmd):
1334
1308
  # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
1335
1309
  functional_terminal = False
1336
1310
 
1337
- if self.stdin.isatty() and self.stdout.isatty():
1311
+ if self.stdin.isatty() and self.stdout.isatty(): # noqa: SIM102
1338
1312
  if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
1339
1313
  functional_terminal = True
1340
1314
 
@@ -1355,7 +1329,7 @@ class Cmd(cmd.Cmd):
1355
1329
  with self.sigint_protection:
1356
1330
  import subprocess
1357
1331
 
1358
- pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout)
1332
+ pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout) # noqa: S602
1359
1333
  pipe_proc.communicate(final_msg.encode('utf-8', 'replace'))
1360
1334
  except BrokenPipeError:
1361
1335
  # This occurs if a command's output is being piped to another process and that process closes before the
@@ -1366,12 +1340,23 @@ class Cmd(cmd.Cmd):
1366
1340
  else:
1367
1341
  self.poutput(msg, end=end)
1368
1342
 
1343
+ def ppretty(self, data: Any, *, indent: int = 2, width: int = 80, depth: Optional[int] = None, end: str = '\n') -> None:
1344
+ """Pretty print arbitrary Python data structures to self.stdout and appends a newline by default.
1345
+
1346
+ :param data: object to print
1347
+ :param indent: the amount of indentation added for each nesting level
1348
+ :param width: the desired maximum number of characters per line in the output, a best effort will be made for long data
1349
+ :param depth: the number of nesting levels which may be printed, if data is too deep, the next level replaced by ...
1350
+ :param end: string appended after the end of the message, default a newline
1351
+ """
1352
+ self.print_to(self.stdout, pprint.pformat(data, indent, width, depth), end=end)
1353
+
1369
1354
  # ----- Methods related to tab completion -----
1370
1355
 
1371
1356
  def _reset_completion_defaults(self) -> None:
1372
- """
1373
- Resets tab completion settings
1374
- Needs to be called each time readline runs tab completion
1357
+ """Reset tab completion settings.
1358
+
1359
+ Needs to be called each time readline runs tab completion.
1375
1360
  """
1376
1361
  self.allow_appended_space = True
1377
1362
  self.allow_closing_quote = True
@@ -1387,8 +1372,8 @@ class Cmd(cmd.Cmd):
1387
1372
  elif rl_type == RlType.PYREADLINE:
1388
1373
  readline.rl.mode._display_completions = self._display_matches_pyreadline
1389
1374
 
1390
- def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[List[str], List[str]]:
1391
- """Used by tab completion functions to get all tokens through the one being completed.
1375
+ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[list[str], list[str]]:
1376
+ """Get all tokens through the one being completed, used by tab completion functions.
1392
1377
 
1393
1378
  :param line: the current input line with leading whitespace removed
1394
1379
  :param begidx: the beginning index of the prefix text
@@ -1454,14 +1439,14 @@ class Cmd(cmd.Cmd):
1454
1439
  def basic_complete(
1455
1440
  self,
1456
1441
  text: str,
1457
- line: str,
1458
- begidx: int,
1459
- endidx: int,
1442
+ line: str, # noqa: ARG002
1443
+ begidx: int, # noqa: ARG002
1444
+ endidx: int, # noqa: ARG002
1460
1445
  match_against: Iterable[str],
1461
- ) -> List[str]:
1462
- """
1463
- Basic tab completion function that matches against a list of strings without considering line contents
1464
- or cursor position. The args required by this function are defined in the header of Python's cmd.py.
1446
+ ) -> list[str]:
1447
+ """Tab completion function that matches against a list of strings without considering line contents or cursor position.
1448
+
1449
+ The args required by this function are defined in the header of Python's cmd.py.
1465
1450
 
1466
1451
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1467
1452
  :param line: the current input line with leading whitespace removed
@@ -1480,10 +1465,10 @@ class Cmd(cmd.Cmd):
1480
1465
  endidx: int,
1481
1466
  match_against: Iterable[str],
1482
1467
  delimiter: str,
1483
- ) -> List[str]:
1484
- """
1485
- Performs tab completion against a list but each match is split on a delimiter and only
1486
- the portion of the match being tab completed is shown as the completion suggestions.
1468
+ ) -> list[str]:
1469
+ """Perform tab completion against a list but each match is split on a delimiter.
1470
+
1471
+ Only the portion of the match being tab completed is shown as the completion suggestions.
1487
1472
  This is useful if you match against strings that are hierarchical in nature and have a
1488
1473
  common delimiter.
1489
1474
 
@@ -1546,10 +1531,10 @@ class Cmd(cmd.Cmd):
1546
1531
  line: str,
1547
1532
  begidx: int,
1548
1533
  endidx: int,
1549
- flag_dict: Dict[str, Union[Iterable[str], CompleterFunc]],
1534
+ flag_dict: dict[str, Union[Iterable[str], CompleterFunc]],
1550
1535
  *,
1551
1536
  all_else: Union[None, Iterable[str], CompleterFunc] = None,
1552
- ) -> List[str]:
1537
+ ) -> list[str]:
1553
1538
  """Tab completes based on a particular flag preceding the token being completed.
1554
1539
 
1555
1540
  :param text: the string prefix we are attempting to match (all matches must begin with it)
@@ -1598,7 +1583,7 @@ class Cmd(cmd.Cmd):
1598
1583
  index_dict: Mapping[int, Union[Iterable[str], CompleterFunc]],
1599
1584
  *,
1600
1585
  all_else: Optional[Union[Iterable[str], CompleterFunc]] = None,
1601
- ) -> List[str]:
1586
+ ) -> list[str]:
1602
1587
  """Tab completes based on a fixed position in the input string.
1603
1588
 
1604
1589
  :param text: the string prefix we are attempting to match (all matches must begin with it)
@@ -1626,10 +1611,7 @@ class Cmd(cmd.Cmd):
1626
1611
 
1627
1612
  # Check if token is at an index in the dictionary
1628
1613
  match_against: Optional[Union[Iterable[str], CompleterFunc]]
1629
- if index in index_dict:
1630
- match_against = index_dict[index]
1631
- else:
1632
- match_against = all_else
1614
+ match_against = index_dict.get(index, all_else)
1633
1615
 
1634
1616
  # Perform tab completion using a Iterable
1635
1617
  if isinstance(match_against, Iterable):
@@ -1642,9 +1624,15 @@ class Cmd(cmd.Cmd):
1642
1624
  return matches
1643
1625
 
1644
1626
  def path_complete(
1645
- self, text: str, line: str, begidx: int, endidx: int, *, path_filter: Optional[Callable[[str], bool]] = None
1646
- ) -> List[str]:
1647
- """Performs completion of local file system paths
1627
+ self,
1628
+ text: str,
1629
+ line: str,
1630
+ begidx: int, # noqa: ARG002
1631
+ endidx: int,
1632
+ *,
1633
+ path_filter: Optional[Callable[[str], bool]] = None,
1634
+ ) -> list[str]:
1635
+ """Perform completion of local file system paths.
1648
1636
 
1649
1637
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1650
1638
  :param line: the current input line with leading whitespace removed
@@ -1657,7 +1645,7 @@ class Cmd(cmd.Cmd):
1657
1645
  """
1658
1646
 
1659
1647
  # Used to complete ~ and ~user strings
1660
- def complete_users() -> List[str]:
1648
+ def complete_users() -> list[str]:
1661
1649
  users = []
1662
1650
 
1663
1651
  # Windows lacks the pwd module so we can't get a list of users.
@@ -1728,12 +1716,11 @@ class Cmd(cmd.Cmd):
1728
1716
  return complete_users()
1729
1717
 
1730
1718
  # Otherwise expand the user dir
1731
- else:
1732
- search_str = os.path.expanduser(search_str)
1719
+ search_str = os.path.expanduser(search_str)
1733
1720
 
1734
- # Get what we need to restore the original tilde path later
1735
- orig_tilde_path = text[:sep_index]
1736
- expanded_tilde_path = os.path.expanduser(orig_tilde_path)
1721
+ # Get what we need to restore the original tilde path later
1722
+ orig_tilde_path = text[:sep_index]
1723
+ expanded_tilde_path = os.path.expanduser(orig_tilde_path)
1737
1724
 
1738
1725
  # If the search text does not have a directory, then use the cwd
1739
1726
  elif not os.path.dirname(text):
@@ -1772,10 +1759,7 @@ class Cmd(cmd.Cmd):
1772
1759
 
1773
1760
  # Remove cwd if it was added to match the text readline expects
1774
1761
  if cwd_added:
1775
- if cwd == os.path.sep:
1776
- to_replace = cwd
1777
- else:
1778
- to_replace = cwd + os.path.sep
1762
+ to_replace = cwd if cwd == os.path.sep else cwd + os.path.sep
1779
1763
  matches = [cur_path.replace(to_replace, '', 1) for cur_path in matches]
1780
1764
 
1781
1765
  # Restore the tilde string if we expanded one to match the text readline expects
@@ -1784,8 +1768,8 @@ class Cmd(cmd.Cmd):
1784
1768
 
1785
1769
  return matches
1786
1770
 
1787
- def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> List[str]:
1788
- """Performs completion of executables either in a user's path or a given path
1771
+ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]:
1772
+ """Perform completion of executables either in a user's path or a given path.
1789
1773
 
1790
1774
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1791
1775
  :param line: the current input line with leading whitespace removed
@@ -1804,15 +1788,15 @@ class Cmd(cmd.Cmd):
1804
1788
  return utils.get_exes_in_path(text)
1805
1789
 
1806
1790
  # Otherwise look for executables in the given path
1807
- else:
1808
- return self.path_complete(
1809
- text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK)
1810
- )
1791
+ return self.path_complete(
1792
+ text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK)
1793
+ )
1794
+
1795
+ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]:
1796
+ """First tab completion function for all commands, called by complete().
1811
1797
 
1812
- def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> List[str]:
1813
- """Called by complete() as the first tab completion function for all commands
1814
1798
  It determines if it should tab complete for redirection (|, >, >>) or use the
1815
- completer function for the current command
1799
+ completer function for the current command.
1816
1800
 
1817
1801
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1818
1802
  :param line: the current input line with leading whitespace removed
@@ -1878,20 +1862,21 @@ class Cmd(cmd.Cmd):
1878
1862
  if do_shell_completion:
1879
1863
  return self.shell_cmd_complete(text, line, begidx, endidx)
1880
1864
 
1881
- elif do_path_completion:
1865
+ if do_path_completion:
1882
1866
  return self.path_complete(text, line, begidx, endidx)
1883
1867
 
1884
1868
  # If there were redirection strings anywhere on the command line, then we
1885
1869
  # are no longer tab completing for the current command
1886
- elif has_redirection:
1870
+ if has_redirection:
1887
1871
  return []
1888
1872
 
1889
1873
  # Call the command's completer function
1890
1874
  return compfunc(text, line, begidx, endidx)
1891
1875
 
1892
1876
  @staticmethod
1893
- def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], int]: # pragma: no cover
1894
- """Adds padding to the matches being displayed as tab completion suggestions.
1877
+ def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], int]: # pragma: no cover
1878
+ """Add padding to the matches being displayed as tab completion suggestions.
1879
+
1895
1880
  The default padding of readline/pyreadine is small and not visually appealing
1896
1881
  especially if matches have spaces. It appears very squished together.
1897
1882
 
@@ -1912,9 +1897,9 @@ class Cmd(cmd.Cmd):
1912
1897
  return [cur_match + padding for cur_match in matches_to_display], len(padding)
1913
1898
 
1914
1899
  def _display_matches_gnu_readline(
1915
- self, substitution: str, matches: List[str], longest_match_length: int
1900
+ self, substitution: str, matches: list[str], longest_match_length: int
1916
1901
  ) -> None: # pragma: no cover
1917
- """Prints a match list using GNU readline's rl_display_match_list()
1902
+ """Print a match list using GNU readline's rl_display_match_list().
1918
1903
 
1919
1904
  :param substitution: the substitution written to the command line
1920
1905
  :param matches: the tab completion matches to display
@@ -1944,8 +1929,7 @@ class Cmd(cmd.Cmd):
1944
1929
 
1945
1930
  for cur_match in matches_to_display:
1946
1931
  cur_length = ansi.style_aware_wcswidth(cur_match)
1947
- if cur_length > longest_match_length:
1948
- longest_match_length = cur_length
1932
+ longest_match_length = max(longest_match_length, cur_length)
1949
1933
  else:
1950
1934
  matches_to_display = matches
1951
1935
 
@@ -1960,7 +1944,7 @@ class Cmd(cmd.Cmd):
1960
1944
 
1961
1945
  # rl_display_match_list() expects matches to be in argv format where
1962
1946
  # substitution is the first element, followed by the matches, and then a NULL.
1963
- strings_array = cast(List[Optional[bytes]], (ctypes.c_char_p * (1 + len(encoded_matches) + 1))())
1947
+ strings_array = cast(list[Optional[bytes]], (ctypes.c_char_p * (1 + len(encoded_matches) + 1))())
1964
1948
 
1965
1949
  # Copy in the encoded strings and add a NULL to the end
1966
1950
  strings_array[0] = encoded_substitution
@@ -1973,8 +1957,8 @@ class Cmd(cmd.Cmd):
1973
1957
  # Redraw prompt and input line
1974
1958
  rl_force_redisplay()
1975
1959
 
1976
- def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no cover
1977
- """Prints a match list using pyreadline3's _display_completions()
1960
+ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover
1961
+ """Print a match list using pyreadline3's _display_completions().
1978
1962
 
1979
1963
  :param matches: the tab completion matches to display
1980
1964
  """
@@ -1997,10 +1981,7 @@ class Cmd(cmd.Cmd):
1997
1981
  # Otherwise use pyreadline3's formatter
1998
1982
  else:
1999
1983
  # Check if we should show display_matches
2000
- if self.display_matches:
2001
- matches_to_display = self.display_matches
2002
- else:
2003
- matches_to_display = matches
1984
+ matches_to_display = self.display_matches if self.display_matches else matches
2004
1985
 
2005
1986
  # Add padding for visual appeal
2006
1987
  matches_to_display, _ = self._pad_matches_to_display(matches_to_display)
@@ -2009,15 +1990,15 @@ class Cmd(cmd.Cmd):
2009
1990
  orig_pyreadline_display(matches_to_display)
2010
1991
 
2011
1992
  @staticmethod
2012
- def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argparse_completer.ArgparseCompleter]:
2013
- """
2014
- Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one
2015
- set, then use argparse_completer.DEFAULT_AP_COMPLETER.
1993
+ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]:
1994
+ """Determine what type of ArgparseCompleter to use on a given parser.
1995
+
1996
+ If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER.
2016
1997
 
2017
1998
  :param parser: the parser to examine
2018
1999
  :return: type of ArgparseCompleter
2019
2000
  """
2020
- Completer = Optional[Type[argparse_completer.ArgparseCompleter]]
2001
+ Completer = Optional[type[argparse_completer.ArgparseCompleter]] # noqa: N806
2021
2002
  completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined]
2022
2003
 
2023
2004
  if completer_type is None:
@@ -2027,8 +2008,7 @@ class Cmd(cmd.Cmd):
2027
2008
  def _perform_completion(
2028
2009
  self, text: str, line: str, begidx: int, endidx: int, custom_settings: Optional[utils.CustomCompletionSettings] = None
2029
2010
  ) -> None:
2030
- """
2031
- Helper function for complete() that performs the actual completion
2011
+ """Perform the actual completion, helper function for complete().
2032
2012
 
2033
2013
  :param text: the string prefix we are attempting to match (all matches must begin with it)
2034
2014
  :param line: the current input line with leading whitespace removed
@@ -2106,12 +2086,11 @@ class Cmd(cmd.Cmd):
2106
2086
  completer_func = self.completedefault # type: ignore[assignment]
2107
2087
 
2108
2088
  # Not a recognized macro or command
2089
+ # Check if this command should be run as a shell command
2090
+ elif self.default_to_shell and command in utils.get_exes_in_path(command):
2091
+ completer_func = self.path_complete
2109
2092
  else:
2110
- # Check if this command should be run as a shell command
2111
- if self.default_to_shell and command in utils.get_exes_in_path(command):
2112
- completer_func = self.path_complete
2113
- else:
2114
- completer_func = self.completedefault # type: ignore[assignment]
2093
+ completer_func = self.completedefault # type: ignore[assignment]
2115
2094
 
2116
2095
  # Otherwise we are completing the command token or performing custom completion
2117
2096
  else:
@@ -2192,10 +2171,7 @@ class Cmd(cmd.Cmd):
2192
2171
 
2193
2172
  if add_quote:
2194
2173
  # Figure out what kind of quote to add and save it as the unclosed_quote
2195
- if any('"' in match for match in self.completion_matches):
2196
- completion_token_quote = "'"
2197
- else:
2198
- completion_token_quote = '"'
2174
+ completion_token_quote = "'" if any('"' in match for match in self.completion_matches) else '"'
2199
2175
 
2200
2176
  self.completion_matches = [completion_token_quote + match for match in self.completion_matches]
2201
2177
 
@@ -2210,7 +2186,7 @@ class Cmd(cmd.Cmd):
2210
2186
  def complete( # type: ignore[override]
2211
2187
  self, text: str, state: int, custom_settings: Optional[utils.CustomCompletionSettings] = None
2212
2188
  ) -> Optional[str]:
2213
- """Override of cmd's complete method which returns the next possible completion for 'text'
2189
+ """Override of cmd's complete method which returns the next possible completion for 'text'.
2214
2190
 
2215
2191
  This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …,
2216
2192
  until it returns a non-string value. It should return the next possible completion starting with text.
@@ -2304,7 +2280,7 @@ class Cmd(cmd.Cmd):
2304
2280
  ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n')
2305
2281
  rl_force_redisplay()
2306
2282
  return None
2307
- except Exception as ex:
2283
+ except Exception as ex: # noqa: BLE001
2308
2284
  # Insert a newline so the exception doesn't print in the middle of the command line being tab completed
2309
2285
  self.perror()
2310
2286
  self.pexcept(ex)
@@ -2312,32 +2288,32 @@ class Cmd(cmd.Cmd):
2312
2288
  return None
2313
2289
 
2314
2290
  def in_script(self) -> bool:
2315
- """Return whether a text script is running"""
2291
+ """Return whether a text script is running."""
2316
2292
  return self._current_script_dir is not None
2317
2293
 
2318
2294
  def in_pyscript(self) -> bool:
2319
- """Return whether running inside a Python shell or pyscript"""
2295
+ """Return whether running inside a Python shell or pyscript."""
2320
2296
  return self._in_py
2321
2297
 
2322
2298
  @property
2323
- def aliases(self) -> Dict[str, str]:
2324
- """Read-only property to access the aliases stored in the StatementParser"""
2299
+ def aliases(self) -> dict[str, str]:
2300
+ """Read-only property to access the aliases stored in the StatementParser."""
2325
2301
  return self.statement_parser.aliases
2326
2302
 
2327
- def get_names(self) -> List[str]:
2303
+ def get_names(self) -> list[str]:
2328
2304
  """Return an alphabetized list of names comprising the attributes of the cmd2 class instance."""
2329
2305
  return dir(self)
2330
2306
 
2331
- def get_all_commands(self) -> List[str]:
2332
- """Return a list of all commands"""
2307
+ def get_all_commands(self) -> list[str]:
2308
+ """Return a list of all commands."""
2333
2309
  return [
2334
2310
  name[len(constants.COMMAND_FUNC_PREFIX) :]
2335
2311
  for name in self.get_names()
2336
2312
  if name.startswith(constants.COMMAND_FUNC_PREFIX) and callable(getattr(self, name))
2337
2313
  ]
2338
2314
 
2339
- def get_visible_commands(self) -> List[str]:
2340
- """Return a list of commands that have not been hidden or disabled"""
2315
+ def get_visible_commands(self) -> list[str]:
2316
+ """Return a list of commands that have not been hidden or disabled."""
2341
2317
  return [
2342
2318
  command
2343
2319
  for command in self.get_all_commands()
@@ -2347,9 +2323,9 @@ class Cmd(cmd.Cmd):
2347
2323
  # Table displayed when tab completing aliases
2348
2324
  _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
2349
2325
 
2350
- def _get_alias_completion_items(self) -> List[CompletionItem]:
2351
- """Return list of alias names and values as CompletionItems"""
2352
- results: List[CompletionItem] = []
2326
+ def _get_alias_completion_items(self) -> list[CompletionItem]:
2327
+ """Return list of alias names and values as CompletionItems."""
2328
+ results: list[CompletionItem] = []
2353
2329
 
2354
2330
  for cur_key in self.aliases:
2355
2331
  row_data = [self.aliases[cur_key]]
@@ -2360,9 +2336,9 @@ class Cmd(cmd.Cmd):
2360
2336
  # Table displayed when tab completing macros
2361
2337
  _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
2362
2338
 
2363
- def _get_macro_completion_items(self) -> List[CompletionItem]:
2364
- """Return list of macro names and values as CompletionItems"""
2365
- results: List[CompletionItem] = []
2339
+ def _get_macro_completion_items(self) -> list[CompletionItem]:
2340
+ """Return list of macro names and values as CompletionItems."""
2341
+ results: list[CompletionItem] = []
2366
2342
 
2367
2343
  for cur_key in self.macros:
2368
2344
  row_data = [self.macros[cur_key].value]
@@ -2373,9 +2349,9 @@ class Cmd(cmd.Cmd):
2373
2349
  # Table displayed when tab completing Settables
2374
2350
  _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None)
2375
2351
 
2376
- def _get_settable_completion_items(self) -> List[CompletionItem]:
2377
- """Return list of Settable names, values, and descriptions as CompletionItems"""
2378
- results: List[CompletionItem] = []
2352
+ def _get_settable_completion_items(self) -> list[CompletionItem]:
2353
+ """Return list of Settable names, values, and descriptions as CompletionItems."""
2354
+ results: list[CompletionItem] = []
2379
2355
 
2380
2356
  for cur_key in self.settables:
2381
2357
  row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description]
@@ -2383,15 +2359,15 @@ class Cmd(cmd.Cmd):
2383
2359
 
2384
2360
  return results
2385
2361
 
2386
- def _get_commands_aliases_and_macros_for_completion(self) -> List[str]:
2387
- """Return a list of visible commands, aliases, and macros for tab completion"""
2362
+ def _get_commands_aliases_and_macros_for_completion(self) -> list[str]:
2363
+ """Return a list of visible commands, aliases, and macros for tab completion."""
2388
2364
  visible_commands = set(self.get_visible_commands())
2389
2365
  alias_names = set(self.aliases)
2390
2366
  macro_names = set(self.macros)
2391
2367
  return list(visible_commands | alias_names | macro_names)
2392
2368
 
2393
- def get_help_topics(self) -> List[str]:
2394
- """Return a list of help topics"""
2369
+ def get_help_topics(self) -> list[str]:
2370
+ """Return a list of help topics."""
2395
2371
  all_topics = [
2396
2372
  name[len(constants.HELP_FUNC_PREFIX) :]
2397
2373
  for name in self.get_names()
@@ -2401,7 +2377,7 @@ class Cmd(cmd.Cmd):
2401
2377
  # Filter out hidden and disabled commands
2402
2378
  return [topic for topic in all_topics if topic not in self.hidden_commands and topic not in self.disabled_commands]
2403
2379
 
2404
- def sigint_handler(self, signum: int, _: Optional[FrameType]) -> None:
2380
+ def sigint_handler(self, signum: int, _: Optional[FrameType]) -> None: # noqa: ARG002
2405
2381
  """Signal handler for SIGINTs which typically come from Ctrl-C events.
2406
2382
 
2407
2383
  If you need custom SIGINT behavior, then override this method.
@@ -2424,8 +2400,7 @@ class Cmd(cmd.Cmd):
2424
2400
  self._raise_keyboard_interrupt()
2425
2401
 
2426
2402
  def termination_signal_handler(self, signum: int, _: Optional[FrameType]) -> None:
2427
- """
2428
- Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac.
2403
+ """Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac.
2429
2404
 
2430
2405
  SIGHUP - received when terminal window is closed
2431
2406
  SIGTERM - received when this app has been requested to terminate
@@ -2441,56 +2416,50 @@ class Cmd(cmd.Cmd):
2441
2416
  sys.exit(128 + signum)
2442
2417
 
2443
2418
  def _raise_keyboard_interrupt(self) -> None:
2444
- """Helper function to raise a KeyboardInterrupt"""
2419
+ """Raise a KeyboardInterrupt."""
2445
2420
  raise KeyboardInterrupt("Got a keyboard interrupt")
2446
2421
 
2447
2422
  def precmd(self, statement: Union[Statement, str]) -> Statement:
2448
- """Hook method executed just before the command is executed by
2449
- [cmd2.Cmd.onecmd][] and after adding it to history.
2423
+ """Ran just before the command is executed by [cmd2.Cmd.onecmd][] and after adding it to history (cmd Hook method).
2450
2424
 
2451
2425
  :param statement: subclass of str which also contains the parsed input
2452
2426
  :return: a potentially modified version of the input Statement object
2453
2427
 
2454
- See [cmd2.Cmd.register_postparsing_hook][] and
2455
- [cmd2.Cmd.register_precmd_hook][] for more robust ways
2456
- to run hooks before the command is executed. See
2457
- [Hooks](../features/hooks.md) for more information.
2428
+ See [cmd2.Cmd.register_postparsing_hook][] and [cmd2.Cmd.register_precmd_hook][] for more robust ways
2429
+ to run hooks before the command is executed. See [Hooks](../features/hooks.md) for more information.
2458
2430
  """
2459
2431
  return Statement(statement) if not isinstance(statement, Statement) else statement
2460
2432
 
2461
- def postcmd(self, stop: bool, statement: Union[Statement, str]) -> bool:
2462
- """Hook method executed just after a command is executed by
2463
- [cmd2.Cmd.onecmd][].
2433
+ def postcmd(self, stop: bool, statement: Union[Statement, str]) -> bool: # noqa: ARG002
2434
+ """Ran just after a command is executed by [cmd2.Cmd.onecmd][] (cmd inherited Hook method).
2464
2435
 
2465
2436
  :param stop: return `True` to request the command loop terminate
2466
2437
  :param statement: subclass of str which also contains the parsed input
2467
2438
 
2468
2439
  See [cmd2.Cmd.register_postcmd_hook][] and [cmd2.Cmd.register_cmdfinalization_hook][] for more robust ways
2469
- to run hooks after the command is executed. See
2470
- [Hooks](../features/hooks.md) for more information.
2440
+ to run hooks after the command is executed. See [Hooks](../features/hooks.md) for more information.
2471
2441
  """
2472
2442
  return stop
2473
2443
 
2474
2444
  def preloop(self) -> None:
2475
- """Hook method executed once when the [cmd2.Cmd.cmdloop][]
2476
- method is called.
2445
+ """Ran once when the [cmd2.Cmd.cmdloop][] method is called (cmd inherited Hook method).
2446
+
2447
+ This method is a stub that does nothing and exists to be overridden by subclasses.
2477
2448
 
2478
- See [cmd2.Cmd.register_preloop_hook][] for a more robust way
2479
- to run hooks before the command loop begins. See
2480
- [Hooks](../features/hooks.md) for more information.
2449
+ See [cmd2.Cmd.register_preloop_hook][] for a more robust wayto run hooks before the command loop begins.
2450
+ See [Hooks](../features/hooks.md) for more information.
2481
2451
  """
2482
- pass
2483
2452
 
2484
2453
  def postloop(self) -> None:
2485
- """Hook method executed once when the [cmd2.Cmd.cmdloop][] method is about to return.
2454
+ """Ran once when the [cmd2.Cmd.cmdloop][] method is about to return (cmd inherited Hook Method).
2486
2455
 
2487
- See [cmd2.Cmd.register_postloop_hook][] for a more robust way
2488
- to run hooks after the command loop completes. See
2489
- [Hooks](../features/hooks.md) for more information.
2456
+ This method is a stub that does nothing and exists to be overridden by subclasses.
2457
+
2458
+ See [cmd2.Cmd.register_postloop_hook][] for a more robust way to run hooks after the command loop completes.
2459
+ See [Hooks](../features/hooks.md) for more information.
2490
2460
  """
2491
- pass
2492
2461
 
2493
- def parseline(self, line: str) -> Tuple[str, str, str]:
2462
+ def parseline(self, line: str) -> tuple[str, str, str]:
2494
2463
  """Parse the line into a command name and a string containing the arguments.
2495
2464
 
2496
2465
  NOTE: This is an override of a parent class method. It is only used by other parent class methods.
@@ -2549,7 +2518,7 @@ class Cmd(cmd.Cmd):
2549
2518
  if stop:
2550
2519
  # we should not run the command, but
2551
2520
  # we need to run the finalization hooks
2552
- raise EmptyStatement
2521
+ raise EmptyStatement # noqa: TRY301
2553
2522
 
2554
2523
  redir_saved_state: Optional[utils.RedirectionSavedState] = None
2555
2524
 
@@ -2562,7 +2531,7 @@ class Cmd(cmd.Cmd):
2562
2531
 
2563
2532
  redir_saved_state = self._redirect_output(statement)
2564
2533
 
2565
- timestart = datetime.datetime.now()
2534
+ timestart = datetime.datetime.now(tz=datetime.timezone.utc)
2566
2535
 
2567
2536
  # precommand hooks
2568
2537
  precmd_data = plugin.PrecommandData(statement)
@@ -2588,7 +2557,7 @@ class Cmd(cmd.Cmd):
2588
2557
  stop = self.postcmd(stop, statement)
2589
2558
 
2590
2559
  if self.timing:
2591
- self.pfeedback(f'Elapsed: {datetime.datetime.now() - timestart}')
2560
+ self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}')
2592
2561
  finally:
2593
2562
  # Get sigint protection while we restore stuff
2594
2563
  with self.sigint_protection:
@@ -2605,43 +2574,43 @@ class Cmd(cmd.Cmd):
2605
2574
  self.perror(f"Invalid syntax: {ex}")
2606
2575
  except RedirectionError as ex:
2607
2576
  self.perror(ex)
2608
- except KeyboardInterrupt as ex:
2577
+ except KeyboardInterrupt:
2609
2578
  if raise_keyboard_interrupt and not stop:
2610
- raise ex
2579
+ raise
2611
2580
  except SystemExit as ex:
2612
2581
  if isinstance(ex.code, int):
2613
2582
  self.exit_code = ex.code
2614
2583
  stop = True
2615
2584
  except PassThroughException as ex:
2616
- raise ex.wrapped_ex
2617
- except Exception as ex:
2585
+ raise ex.wrapped_ex from None
2586
+ except Exception as ex: # noqa: BLE001
2618
2587
  self.pexcept(ex)
2619
2588
  finally:
2620
2589
  try:
2621
2590
  stop = self._run_cmdfinalization_hooks(stop, statement)
2622
- except KeyboardInterrupt as ex:
2591
+ except KeyboardInterrupt:
2623
2592
  if raise_keyboard_interrupt and not stop:
2624
- raise ex
2593
+ raise
2625
2594
  except SystemExit as ex:
2626
2595
  if isinstance(ex.code, int):
2627
2596
  self.exit_code = ex.code
2628
2597
  stop = True
2629
2598
  except PassThroughException as ex:
2630
- raise ex.wrapped_ex
2631
- except Exception as ex:
2599
+ raise ex.wrapped_ex from None
2600
+ except Exception as ex: # noqa: BLE001
2632
2601
  self.pexcept(ex)
2633
2602
 
2634
2603
  return stop
2635
2604
 
2636
2605
  def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool:
2637
- """Run the command finalization hooks"""
2606
+ """Run the command finalization hooks."""
2638
2607
  with self.sigint_protection:
2639
2608
  if not sys.platform.startswith('win') and self.stdin.isatty():
2640
2609
  # Before the next command runs, fix any terminal problems like those
2641
2610
  # caused by certain binary characters having been printed to it.
2642
2611
  import subprocess
2643
2612
 
2644
- proc = subprocess.Popen(['stty', 'sane'])
2613
+ proc = subprocess.Popen(['stty', 'sane']) # noqa: S603, S607
2645
2614
  proc.communicate()
2646
2615
 
2647
2616
  data = plugin.CommandFinalizationData(stop, statement)
@@ -2653,13 +2622,13 @@ class Cmd(cmd.Cmd):
2653
2622
 
2654
2623
  def runcmds_plus_hooks(
2655
2624
  self,
2656
- cmds: Union[List[HistoryItem], List[str]],
2625
+ cmds: Union[list[HistoryItem], list[str]],
2657
2626
  *,
2658
2627
  add_to_history: bool = True,
2659
2628
  stop_on_keyboard_interrupt: bool = False,
2660
2629
  ) -> bool:
2661
- """
2662
- Used when commands are being run in an automated fashion like text scripts or history replays.
2630
+ """Run commands in an automated fashion from sources like text scripts or history replays.
2631
+
2663
2632
  The prompt and command line for each command will be printed if echo is True.
2664
2633
 
2665
2634
  :param cmds: commands to run
@@ -2671,7 +2640,7 @@ class Cmd(cmd.Cmd):
2671
2640
  """
2672
2641
  for line in cmds:
2673
2642
  if isinstance(line, HistoryItem):
2674
- line = line.raw
2643
+ line = line.raw # noqa: PLW2901
2675
2644
 
2676
2645
  if self.echo:
2677
2646
  self.poutput(f'{self.prompt}{line}')
@@ -2706,7 +2675,7 @@ class Cmd(cmd.Cmd):
2706
2675
  """
2707
2676
 
2708
2677
  def combine_rl_history(statement: Statement) -> None:
2709
- """Combine all lines of a multiline command into a single readline history entry"""
2678
+ """Combine all lines of a multiline command into a single readline history entry."""
2710
2679
  if orig_rl_history_length is None or not statement.multiline_command:
2711
2680
  return
2712
2681
 
@@ -2773,15 +2742,13 @@ class Cmd(cmd.Cmd):
2773
2742
 
2774
2743
  if not statement.command:
2775
2744
  raise EmptyStatement
2776
- else:
2777
- # If necessary, update history with completed multiline command.
2778
- combine_rl_history(statement)
2745
+ # If necessary, update history with completed multiline command.
2746
+ combine_rl_history(statement)
2779
2747
 
2780
2748
  return statement
2781
2749
 
2782
2750
  def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optional[int] = None) -> Statement:
2783
- """
2784
- Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved
2751
+ """Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved.
2785
2752
 
2786
2753
  :param line: the line being parsed
2787
2754
  :param orig_rl_history_length: Optional length of the readline history before the current command was typed.
@@ -2806,7 +2773,7 @@ class Cmd(cmd.Cmd):
2806
2773
  orig_rl_history_length = None
2807
2774
 
2808
2775
  # Check if this command matches a macro and wasn't already processed to avoid an infinite loop
2809
- if statement.command in self.macros.keys() and statement.command not in used_macros:
2776
+ if statement.command in self.macros and statement.command not in used_macros:
2810
2777
  used_macros.append(statement.command)
2811
2778
  resolve_result = self._resolve_macro(statement)
2812
2779
  if resolve_result is None:
@@ -2834,13 +2801,12 @@ class Cmd(cmd.Cmd):
2834
2801
  return statement
2835
2802
 
2836
2803
  def _resolve_macro(self, statement: Statement) -> Optional[str]:
2837
- """
2838
- Resolve a macro and return the resulting string
2804
+ """Resolve a macro and return the resulting string.
2839
2805
 
2840
2806
  :param statement: the parsed statement from the command line
2841
2807
  :return: the resolved macro or None on error
2842
2808
  """
2843
- if statement.command not in self.macros.keys():
2809
+ if statement.command not in self.macros:
2844
2810
  raise KeyError(f"{statement.command} is not a macro")
2845
2811
 
2846
2812
  macro = self.macros[statement.command]
@@ -2881,7 +2847,6 @@ class Cmd(cmd.Cmd):
2881
2847
  :return: A bool telling if an error occurred and a utils.RedirectionSavedState object
2882
2848
  :raises RedirectionError: if an error occurs trying to pipe or redirect
2883
2849
  """
2884
- import io
2885
2850
  import subprocess
2886
2851
 
2887
2852
  # Initialize the redirection saved state
@@ -2901,13 +2866,13 @@ class Cmd(cmd.Cmd):
2901
2866
  read_fd, write_fd = os.pipe()
2902
2867
 
2903
2868
  # Open each side of the pipe
2904
- subproc_stdin = io.open(read_fd, 'r')
2905
- new_stdout: TextIO = cast(TextIO, io.open(write_fd, 'w'))
2869
+ subproc_stdin = open(read_fd) # noqa: SIM115
2870
+ new_stdout: TextIO = cast(TextIO, open(write_fd, 'w')) # noqa: SIM115
2906
2871
 
2907
2872
  # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs,
2908
2873
  # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe
2909
2874
  # processes close in the right order (most recent first).
2910
- kwargs: Dict[str, Any] = dict()
2875
+ kwargs: dict[str, Any] = {}
2911
2876
  if sys.platform == 'win32':
2912
2877
  kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
2913
2878
  else:
@@ -2919,7 +2884,7 @@ class Cmd(cmd.Cmd):
2919
2884
  kwargs['executable'] = shell
2920
2885
 
2921
2886
  # For any stream that is a StdSim, we will use a pipe so we can capture its output
2922
- proc = subprocess.Popen( # type: ignore[call-overload]
2887
+ proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602
2923
2888
  statement.pipe_to,
2924
2889
  stdin=subproc_stdin,
2925
2890
  stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
@@ -2932,20 +2897,17 @@ class Cmd(cmd.Cmd):
2932
2897
  # like: !ls -l | grep user | wc -l > out.txt. But this makes it difficult to know if the pipe process
2933
2898
  # started OK, since the shell itself always starts. Therefore, we will wait a short time and check
2934
2899
  # if the pipe process is still running.
2935
- try:
2900
+ with contextlib.suppress(subprocess.TimeoutExpired):
2936
2901
  proc.wait(0.2)
2937
- except subprocess.TimeoutExpired:
2938
- pass
2939
2902
 
2940
2903
  # Check if the pipe process already exited
2941
2904
  if proc.returncode is not None:
2942
2905
  subproc_stdin.close()
2943
2906
  new_stdout.close()
2944
2907
  raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run')
2945
- else:
2946
- redir_saved_state.redirecting = True # type: ignore[unreachable]
2947
- cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)
2948
- sys.stdout = self.stdout = new_stdout
2908
+ redir_saved_state.redirecting = True # type: ignore[unreachable]
2909
+ cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)
2910
+ sys.stdout = self.stdout = new_stdout
2949
2911
 
2950
2912
  elif statement.output:
2951
2913
  if statement.output_to:
@@ -2954,9 +2916,9 @@ class Cmd(cmd.Cmd):
2954
2916
  mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w'
2955
2917
  try:
2956
2918
  # Use line buffering
2957
- new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1))
2919
+ new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) # noqa: SIM115
2958
2920
  except OSError as ex:
2959
- raise RedirectionError(f'Failed to redirect because: {ex}')
2921
+ raise RedirectionError('Failed to redirect output') from ex
2960
2922
 
2961
2923
  redir_saved_state.redirecting = True
2962
2924
  sys.stdout = self.stdout = new_stdout
@@ -2975,7 +2937,7 @@ class Cmd(cmd.Cmd):
2975
2937
  # no point opening up the temporary file
2976
2938
  current_paste_buffer = get_paste_buffer()
2977
2939
  # create a temporary file to store output
2978
- new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+"))
2940
+ new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+")) # noqa: SIM115
2979
2941
  redir_saved_state.redirecting = True
2980
2942
  sys.stdout = self.stdout = new_stdout
2981
2943
 
@@ -2990,7 +2952,7 @@ class Cmd(cmd.Cmd):
2990
2952
  return redir_saved_state
2991
2953
 
2992
2954
  def _restore_output(self, statement: Statement, saved_redir_state: utils.RedirectionSavedState) -> None:
2993
- """Handles restoring state after output redirection
2955
+ """Handle restoring state after output redirection.
2994
2956
 
2995
2957
  :param statement: Statement object which contains the parsed input from the user
2996
2958
  :param saved_redir_state: contains information needed to restore state data
@@ -3001,11 +2963,9 @@ class Cmd(cmd.Cmd):
3001
2963
  self.stdout.seek(0)
3002
2964
  write_to_paste_buffer(self.stdout.read())
3003
2965
 
3004
- try:
2966
+ with contextlib.suppress(BrokenPipeError):
3005
2967
  # Close the file or pipe that stdout was redirected to
3006
2968
  self.stdout.close()
3007
- except BrokenPipeError:
3008
- pass
3009
2969
 
3010
2970
  # Restore the stdout values
3011
2971
  self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout)
@@ -3020,25 +2980,24 @@ class Cmd(cmd.Cmd):
3020
2980
  self._redirecting = saved_redir_state.saved_redirecting
3021
2981
 
3022
2982
  def cmd_func(self, command: str) -> Optional[CommandFunc]:
3023
- """
3024
- Get the function for a command
2983
+ """Get the function for a command.
3025
2984
 
3026
2985
  :param command: the name of the command
3027
2986
 
3028
2987
  Example:
3029
-
3030
2988
  ```py
3031
2989
  helpfunc = self.cmd_func('help')
3032
2990
  ```
3033
2991
 
3034
2992
  helpfunc now contains a reference to the ``do_help`` method
2993
+
3035
2994
  """
3036
2995
  func_name = constants.COMMAND_FUNC_PREFIX + command
3037
2996
  func = getattr(self, func_name, None)
3038
2997
  return cast(CommandFunc, func) if callable(func) else None
3039
2998
 
3040
2999
  def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool:
3041
- """This executes the actual do_* method for a command.
3000
+ """Execute the actual do_* method for a command.
3042
3001
 
3043
3002
  If the command provided doesn't exist, then it executes default() instead.
3044
3003
 
@@ -3073,7 +3032,7 @@ class Cmd(cmd.Cmd):
3073
3032
  return stop if stop is not None else False
3074
3033
 
3075
3034
  def default(self, statement: Statement) -> Optional[bool]: # type: ignore[override]
3076
- """Executed when the command given isn't a recognized command implemented by a do_* method.
3035
+ """Execute when the command given isn't a recognized command implemented by a do_* method.
3077
3036
 
3078
3037
  :param statement: Statement object with parsed input
3079
3038
  """
@@ -3082,14 +3041,13 @@ class Cmd(cmd.Cmd):
3082
3041
  self.history.append(statement)
3083
3042
 
3084
3043
  return self.do_shell(statement.command_and_args)
3085
- else:
3086
- err_msg = self.default_error.format(statement.command)
3087
- if self.suggest_similar_command and (suggested_command := self._suggest_similar_command(statement.command)):
3088
- err_msg += f"\n{self.default_suggestion_message.format(suggested_command)}"
3044
+ err_msg = self.default_error.format(statement.command)
3045
+ if self.suggest_similar_command and (suggested_command := self._suggest_similar_command(statement.command)):
3046
+ err_msg += f"\n{self.default_suggestion_message.format(suggested_command)}"
3089
3047
 
3090
- # Set apply_style to False so styles for default_error and default_suggestion_message are not overridden
3091
- self.perror(err_msg, apply_style=False)
3092
- return None
3048
+ # Set apply_style to False so styles for default_error and default_suggestion_message are not overridden
3049
+ self.perror(err_msg, apply_style=False)
3050
+ return None
3093
3051
 
3094
3052
  def _suggest_similar_command(self, command: str) -> Optional[str]:
3095
3053
  return suggest_similar(command, self.get_visible_commands())
@@ -3098,7 +3056,7 @@ class Cmd(cmd.Cmd):
3098
3056
  self,
3099
3057
  prompt: str,
3100
3058
  *,
3101
- history: Optional[List[str]] = None,
3059
+ history: Optional[list[str]] = None,
3102
3060
  completion_mode: utils.CompletionMode = utils.CompletionMode.NONE,
3103
3061
  preserve_quotes: bool = False,
3104
3062
  choices: Optional[Iterable[Any]] = None,
@@ -3106,9 +3064,9 @@ class Cmd(cmd.Cmd):
3106
3064
  completer: Optional[CompleterFunc] = None,
3107
3065
  parser: Optional[argparse.ArgumentParser] = None,
3108
3066
  ) -> str:
3109
- """
3110
- Read input from appropriate stdin value. Also supports tab completion and up-arrow history while
3111
- input is being entered.
3067
+ """Read input from appropriate stdin value.
3068
+
3069
+ Also supports tab completion and up-arrow history while input is being entered.
3112
3070
 
3113
3071
  :param prompt: prompt to display to user
3114
3072
  :param history: optional list of strings to use for up-arrow history. If completion_mode is
@@ -3139,10 +3097,10 @@ class Cmd(cmd.Cmd):
3139
3097
  """
3140
3098
  readline_configured = False
3141
3099
  saved_completer: Optional[CompleterFunc] = None
3142
- saved_history: Optional[List[str]] = None
3100
+ saved_history: Optional[list[str]] = None
3143
3101
 
3144
3102
  def configure_readline() -> None:
3145
- """Configure readline tab completion and history"""
3103
+ """Configure readline tab completion and history."""
3146
3104
  nonlocal readline_configured
3147
3105
  nonlocal saved_completer
3148
3106
  nonlocal saved_history
@@ -3158,7 +3116,7 @@ class Cmd(cmd.Cmd):
3158
3116
  # Disable completion
3159
3117
  if completion_mode == utils.CompletionMode.NONE:
3160
3118
 
3161
- def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover
3119
+ def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover # noqa: ARG001
3162
3120
  return None
3163
3121
 
3164
3122
  complete_func = complete_none
@@ -3198,7 +3156,7 @@ class Cmd(cmd.Cmd):
3198
3156
  readline_configured = True
3199
3157
 
3200
3158
  def restore_readline() -> None:
3201
- """Restore readline tab completion and history"""
3159
+ """Restore readline tab completion and history."""
3202
3160
  nonlocal readline_configured
3203
3161
  if not readline_configured or rl_type == RlType.NONE: # pragma: no cover
3204
3162
  return
@@ -3232,31 +3190,29 @@ class Cmd(cmd.Cmd):
3232
3190
  sys.stdout.write(f'{prompt}{line}\n')
3233
3191
 
3234
3192
  # Otherwise read from self.stdin
3193
+ elif self.stdin.isatty():
3194
+ # on a tty, print the prompt first, then read the line
3195
+ self.poutput(prompt, end='')
3196
+ self.stdout.flush()
3197
+ line = self.stdin.readline()
3198
+ if len(line) == 0:
3199
+ line = 'eof'
3235
3200
  else:
3236
- if self.stdin.isatty():
3237
- # on a tty, print the prompt first, then read the line
3238
- self.poutput(prompt, end='')
3239
- self.stdout.flush()
3240
- line = self.stdin.readline()
3241
- if len(line) == 0:
3242
- line = 'eof'
3201
+ # we are reading from a pipe, read the line to see if there is
3202
+ # anything there, if so, then decide whether to print the
3203
+ # prompt or not
3204
+ line = self.stdin.readline()
3205
+ if len(line):
3206
+ # we read something, output the prompt and the something
3207
+ if self.echo:
3208
+ self.poutput(f'{prompt}{line}')
3243
3209
  else:
3244
- # we are reading from a pipe, read the line to see if there is
3245
- # anything there, if so, then decide whether to print the
3246
- # prompt or not
3247
- line = self.stdin.readline()
3248
- if len(line):
3249
- # we read something, output the prompt and the something
3250
- if self.echo:
3251
- self.poutput(f'{prompt}{line}')
3252
- else:
3253
- line = 'eof'
3210
+ line = 'eof'
3254
3211
 
3255
3212
  return line.rstrip('\r\n')
3256
3213
 
3257
3214
  def _read_command_line(self, prompt: str) -> str:
3258
- """
3259
- Read command line from appropriate stdin
3215
+ """Read command line from appropriate stdin.
3260
3216
 
3261
3217
  :param prompt: prompt to display to user
3262
3218
  :return: command line text of 'eof' if an EOFError was caught
@@ -3264,11 +3220,9 @@ class Cmd(cmd.Cmd):
3264
3220
  """
3265
3221
  try:
3266
3222
  # Wrap in try since terminal_lock may not be locked
3267
- try:
3223
+ with contextlib.suppress(RuntimeError):
3268
3224
  # Command line is about to be drawn. Allow asynchronous changes to the terminal.
3269
3225
  self.terminal_lock.release()
3270
- except RuntimeError:
3271
- pass
3272
3226
  return self.read_input(prompt, completion_mode=utils.CompletionMode.COMMANDS)
3273
3227
  except EOFError:
3274
3228
  return 'eof'
@@ -3277,8 +3231,7 @@ class Cmd(cmd.Cmd):
3277
3231
  self.terminal_lock.acquire()
3278
3232
 
3279
3233
  def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
3280
- """
3281
- Called at beginning of command loop to set up readline with cmd2-specific settings
3234
+ """Set up readline with cmd2-specific settings, called at beginning of command loop.
3282
3235
 
3283
3236
  :return: Class containing saved readline settings
3284
3237
  """
@@ -3318,8 +3271,7 @@ class Cmd(cmd.Cmd):
3318
3271
  return readline_settings
3319
3272
 
3320
3273
  def _restore_readline(self, readline_settings: _SavedReadlineSettings) -> None:
3321
- """
3322
- Called at end of command loop to restore saved readline settings
3274
+ """Restore saved readline settings, called at end of command loop.
3323
3275
 
3324
3276
  :param readline_settings: the readline settings to restore
3325
3277
  """
@@ -3335,8 +3287,9 @@ class Cmd(cmd.Cmd):
3335
3287
  readline.rl.mode._display_completions = orig_pyreadline_display
3336
3288
 
3337
3289
  def _cmdloop(self) -> None:
3338
- """Repeatedly issue a prompt, accept input, parse an initial prefix
3339
- off the received input, and dispatch to action methods, passing them
3290
+ """Repeatedly issue a prompt, accept input, parse it, and dispatch to apporpriate commands.
3291
+
3292
+ Parse an initial prefix off the received input and dispatch to action methods, passing them
3340
3293
  the remainder of the line as argument.
3341
3294
 
3342
3295
  This serves the same role as cmd.cmdloop().
@@ -3388,7 +3341,7 @@ class Cmd(cmd.Cmd):
3388
3341
  # Preserve quotes since we are passing strings to other commands
3389
3342
  @with_argparser(alias_parser, preserve_quotes=True)
3390
3343
  def do_alias(self, args: argparse.Namespace) -> None:
3391
- """Manage aliases"""
3344
+ """Manage aliases."""
3392
3345
  # Call handler for whatever subcommand was selected
3393
3346
  handler = args.cmd2_handler.get()
3394
3347
  handler(args)
@@ -3423,7 +3376,7 @@ class Cmd(cmd.Cmd):
3423
3376
 
3424
3377
  @as_subcommand_to('alias', 'create', alias_create_parser, help=alias_create_description.lower())
3425
3378
  def _alias_create(self, args: argparse.Namespace) -> None:
3426
- """Create or overwrite an alias"""
3379
+ """Create or overwrite an alias."""
3427
3380
  self.last_result = False
3428
3381
 
3429
3382
  # Validate the alias name
@@ -3473,7 +3426,7 @@ class Cmd(cmd.Cmd):
3473
3426
 
3474
3427
  @as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help)
3475
3428
  def _alias_delete(self, args: argparse.Namespace) -> None:
3476
- """Delete aliases"""
3429
+ """Delete aliases."""
3477
3430
  self.last_result = True
3478
3431
 
3479
3432
  if args.all:
@@ -3510,18 +3463,15 @@ class Cmd(cmd.Cmd):
3510
3463
 
3511
3464
  @as_subcommand_to('alias', 'list', alias_list_parser, help=alias_list_help)
3512
3465
  def _alias_list(self, args: argparse.Namespace) -> None:
3513
- """List some or all aliases as 'alias create' commands"""
3514
- self.last_result = {} # Dict[alias_name, alias_value]
3466
+ """List some or all aliases as 'alias create' commands."""
3467
+ self.last_result = {} # dict[alias_name, alias_value]
3515
3468
 
3516
3469
  tokens_to_quote = constants.REDIRECTION_TOKENS
3517
3470
  tokens_to_quote.extend(self.statement_parser.terminators)
3518
3471
 
3519
- if args.names:
3520
- to_list = utils.remove_duplicates(args.names)
3521
- else:
3522
- to_list = sorted(self.aliases, key=self.default_sort_key)
3472
+ to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.aliases, key=self.default_sort_key)
3523
3473
 
3524
- not_found: List[str] = []
3474
+ not_found: list[str] = []
3525
3475
  for name in to_list:
3526
3476
  if name not in self.aliases:
3527
3477
  not_found.append(name)
@@ -3556,7 +3506,7 @@ class Cmd(cmd.Cmd):
3556
3506
  # Preserve quotes since we are passing strings to other commands
3557
3507
  @with_argparser(macro_parser, preserve_quotes=True)
3558
3508
  def do_macro(self, args: argparse.Namespace) -> None:
3559
- """Manage macros"""
3509
+ """Manage macros."""
3560
3510
  # Call handler for whatever subcommand was selected
3561
3511
  handler = args.cmd2_handler.get()
3562
3512
  handler(args)
@@ -3615,7 +3565,7 @@ class Cmd(cmd.Cmd):
3615
3565
 
3616
3566
  @as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help)
3617
3567
  def _macro_create(self, args: argparse.Namespace) -> None:
3618
- """Create or overwrite a macro"""
3568
+ """Create or overwrite a macro."""
3619
3569
  self.last_result = False
3620
3570
 
3621
3571
  # Validate the macro name
@@ -3648,8 +3598,8 @@ class Cmd(cmd.Cmd):
3648
3598
  max_arg_num = 0
3649
3599
  arg_nums = set()
3650
3600
 
3651
- while True:
3652
- try:
3601
+ try:
3602
+ while True:
3653
3603
  cur_match = normal_matches.__next__()
3654
3604
 
3655
3605
  # Get the number string between the braces
@@ -3660,13 +3610,11 @@ class Cmd(cmd.Cmd):
3660
3610
  return
3661
3611
 
3662
3612
  arg_nums.add(cur_num)
3663
- if cur_num > max_arg_num:
3664
- max_arg_num = cur_num
3613
+ max_arg_num = max(max_arg_num, cur_num)
3665
3614
 
3666
3615
  arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False))
3667
-
3668
- except StopIteration:
3669
- break
3616
+ except StopIteration:
3617
+ pass
3670
3618
 
3671
3619
  # Make sure the argument numbers are continuous
3672
3620
  if len(arg_nums) != max_arg_num:
@@ -3676,16 +3624,16 @@ class Cmd(cmd.Cmd):
3676
3624
  # Find all escaped arguments
3677
3625
  escaped_matches = re.finditer(MacroArg.macro_escaped_arg_pattern, value)
3678
3626
 
3679
- while True:
3680
- try:
3627
+ try:
3628
+ while True:
3681
3629
  cur_match = escaped_matches.__next__()
3682
3630
 
3683
3631
  # Get the number string between the braces
3684
3632
  cur_num_str = re.findall(MacroArg.digit_pattern, cur_match.group())[0]
3685
3633
 
3686
3634
  arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=True))
3687
- except StopIteration:
3688
- break
3635
+ except StopIteration:
3636
+ pass
3689
3637
 
3690
3638
  # Set the macro
3691
3639
  result = "overwritten" if args.name in self.macros else "created"
@@ -3709,7 +3657,7 @@ class Cmd(cmd.Cmd):
3709
3657
 
3710
3658
  @as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help)
3711
3659
  def _macro_delete(self, args: argparse.Namespace) -> None:
3712
- """Delete macros"""
3660
+ """Delete macros."""
3713
3661
  self.last_result = True
3714
3662
 
3715
3663
  if args.all:
@@ -3746,18 +3694,15 @@ class Cmd(cmd.Cmd):
3746
3694
 
3747
3695
  @as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help)
3748
3696
  def _macro_list(self, args: argparse.Namespace) -> None:
3749
- """List some or all macros as 'macro create' commands"""
3750
- self.last_result = {} # Dict[macro_name, macro_value]
3697
+ """List some or all macros as 'macro create' commands."""
3698
+ self.last_result = {} # dict[macro_name, macro_value]
3751
3699
 
3752
3700
  tokens_to_quote = constants.REDIRECTION_TOKENS
3753
3701
  tokens_to_quote.extend(self.statement_parser.terminators)
3754
3702
 
3755
- if args.names:
3756
- to_list = utils.remove_duplicates(args.names)
3757
- else:
3758
- to_list = sorted(self.macros, key=self.default_sort_key)
3703
+ to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.macros, key=self.default_sort_key)
3759
3704
 
3760
- not_found: List[str] = []
3705
+ not_found: list[str] = []
3761
3706
  for name in to_list:
3762
3707
  if name not in self.macros:
3763
3708
  not_found.append(name)
@@ -3779,9 +3724,8 @@ class Cmd(cmd.Cmd):
3779
3724
  for name in not_found:
3780
3725
  self.perror(f"Macro '{name}' not found")
3781
3726
 
3782
- def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
3783
- """Completes the command argument of help"""
3784
-
3727
+ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]:
3728
+ """Completes the command argument of help."""
3785
3729
  # Complete token against topics and visible commands
3786
3730
  topics = set(self.get_help_topics())
3787
3731
  visible_commands = set(self.get_visible_commands())
@@ -3789,10 +3733,9 @@ class Cmd(cmd.Cmd):
3789
3733
  return self.basic_complete(text, line, begidx, endidx, strs_to_match)
3790
3734
 
3791
3735
  def complete_help_subcommands(
3792
- self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]]
3793
- ) -> List[str]:
3794
- """Completes the subcommands argument of help"""
3795
-
3736
+ self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
3737
+ ) -> list[str]:
3738
+ """Completes the subcommands argument of help."""
3796
3739
  # Make sure we have a command whose subcommands we will complete
3797
3740
  command = arg_tokens['command'][0]
3798
3741
  if not command:
@@ -3824,7 +3767,7 @@ class Cmd(cmd.Cmd):
3824
3767
 
3825
3768
  @with_argparser(help_parser)
3826
3769
  def do_help(self, args: argparse.Namespace) -> None:
3827
- """List available commands or provide detailed help for a specific command"""
3770
+ """List available commands or provide detailed help for a specific command."""
3828
3771
  self.last_result = True
3829
3772
 
3830
3773
  if not args.command or args.verbose:
@@ -3859,10 +3802,10 @@ class Cmd(cmd.Cmd):
3859
3802
  self.perror(err_msg, apply_style=False)
3860
3803
  self.last_result = False
3861
3804
 
3862
- def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxcol: int) -> None:
3863
- """
3864
- Print groups of commands and topics in columns and an optional header
3865
- Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters
3805
+ def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: # noqa: ARG002
3806
+ """Print groups of commands and topics in columns and an optional header.
3807
+
3808
+ Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters.
3866
3809
 
3867
3810
  :param header: string to print above commands being printed
3868
3811
  :param cmds: list of topics to print
@@ -3877,9 +3820,10 @@ class Cmd(cmd.Cmd):
3877
3820
  self.columnize(cmds, maxcol - 1)
3878
3821
  self.poutput()
3879
3822
 
3880
- def columnize(self, str_list: Optional[List[str]], display_width: int = 80) -> None:
3823
+ def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> None:
3881
3824
  """Display a list of single-line strings as a compact set of columns.
3882
- Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters
3825
+
3826
+ Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters.
3883
3827
 
3884
3828
  Each column is only as wide as necessary.
3885
3829
  Columns are separated by two spaces (one was not legible enough).
@@ -3923,10 +3867,7 @@ class Cmd(cmd.Cmd):
3923
3867
  texts = []
3924
3868
  for col in range(ncols):
3925
3869
  i = row + nrows * col
3926
- if i >= size:
3927
- x = ""
3928
- else:
3929
- x = str_list[i]
3870
+ x = "" if i >= size else str_list[i]
3930
3871
  texts.append(x)
3931
3872
  while texts and not texts[-1]:
3932
3873
  del texts[-1]
@@ -3935,7 +3876,7 @@ class Cmd(cmd.Cmd):
3935
3876
  self.poutput(" ".join(texts))
3936
3877
 
3937
3878
  def _help_menu(self, verbose: bool = False) -> None:
3938
- """Show a list of commands which help can be displayed for"""
3879
+ """Show a list of commands which help can be displayed for."""
3939
3880
  cmds_cats, cmds_doc, cmds_undoc, help_topics = self._build_command_info()
3940
3881
 
3941
3882
  if not cmds_cats:
@@ -3953,15 +3894,15 @@ class Cmd(cmd.Cmd):
3953
3894
  self.print_topics(self.misc_header, help_topics, 15, 80)
3954
3895
  self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
3955
3896
 
3956
- def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]:
3897
+ def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str], list[str]]:
3957
3898
  # Get a sorted list of help topics
3958
3899
  help_topics = sorted(self.get_help_topics(), key=self.default_sort_key)
3959
3900
 
3960
3901
  # Get a sorted list of visible command names
3961
3902
  visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key)
3962
- cmds_doc: List[str] = []
3963
- cmds_undoc: List[str] = []
3964
- cmds_cats: Dict[str, List[str]] = {}
3903
+ cmds_doc: list[str] = []
3904
+ cmds_undoc: list[str] = []
3905
+ cmds_cats: dict[str, list[str]] = {}
3965
3906
  for command in visible_commands:
3966
3907
  func = cast(CommandFunc, self.cmd_func(command))
3967
3908
  has_help_func = False
@@ -3984,8 +3925,8 @@ class Cmd(cmd.Cmd):
3984
3925
  cmds_undoc.append(command)
3985
3926
  return cmds_cats, cmds_doc, cmds_undoc, help_topics
3986
3927
 
3987
- def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None:
3988
- """Customized version of print_topics that can switch between verbose or traditional output"""
3928
+ def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
3929
+ """Print topics, switching between verbose or traditional output."""
3989
3930
  import io
3990
3931
 
3991
3932
  if cmds:
@@ -4028,7 +3969,7 @@ class Cmd(cmd.Cmd):
4028
3969
  result = io.StringIO()
4029
3970
 
4030
3971
  # try to redirect system stdout
4031
- with redirect_stdout(result):
3972
+ with contextlib.redirect_stdout(result):
4032
3973
  # save our internal stdout
4033
3974
  stdout_orig = self.stdout
4034
3975
  try:
@@ -4056,10 +3997,10 @@ class Cmd(cmd.Cmd):
4056
3997
 
4057
3998
  @with_argparser(shortcuts_parser)
4058
3999
  def do_shortcuts(self, _: argparse.Namespace) -> None:
4059
- """List available shortcuts"""
4000
+ """List available shortcuts."""
4060
4001
  # Sort the shortcut tuples by name
4061
4002
  sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0]))
4062
- result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts)
4003
+ result = "\n".join(f'{sc[0]}: {sc[1]}' for sc in sorted_shortcuts)
4063
4004
  self.poutput(f"Shortcuts for other commands:\n{result}")
4064
4005
  self.last_result = True
4065
4006
 
@@ -4069,8 +4010,8 @@ class Cmd(cmd.Cmd):
4069
4010
 
4070
4011
  @with_argparser(eof_parser)
4071
4012
  def do_eof(self, _: argparse.Namespace) -> Optional[bool]:
4072
- """
4073
- Called when Ctrl-D is pressed and calls quit with no arguments.
4013
+ """Quit with no arguments, called when Ctrl-D is pressed.
4014
+
4074
4015
  This can be overridden if quit should be called differently.
4075
4016
  """
4076
4017
  self.poutput()
@@ -4082,14 +4023,15 @@ class Cmd(cmd.Cmd):
4082
4023
 
4083
4024
  @with_argparser(quit_parser)
4084
4025
  def do_quit(self, _: argparse.Namespace) -> Optional[bool]:
4085
- """Exit this application"""
4026
+ """Exit this application."""
4086
4027
  # Return True to stop the command loop
4087
4028
  self.last_result = True
4088
4029
  return True
4089
4030
 
4090
- def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any:
4091
- """Presents a numbered menu to the user. Modeled after
4092
- the bash shell's SELECT. Returns the item chosen.
4031
+ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any:
4032
+ """Present a numbered menu to the user.
4033
+
4034
+ Modeled after the bash shell's SELECT. Returns the item chosen.
4093
4035
 
4094
4036
  Argument ``opts`` can be:
4095
4037
 
@@ -4097,13 +4039,14 @@ class Cmd(cmd.Cmd):
4097
4039
  | a list of strings -> will be offered as options
4098
4040
  | a list of tuples -> interpreted as (value, text), so
4099
4041
  that the return value can differ from
4100
- the text advertised to the user"""
4101
- local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]]
4042
+ the text advertised to the user
4043
+ """
4044
+ local_opts: Union[list[str], list[tuple[Any, Optional[str]]]]
4102
4045
  if isinstance(opts, str):
4103
- local_opts = cast(List[Tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split())))
4046
+ local_opts = cast(list[tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split())))
4104
4047
  else:
4105
4048
  local_opts = opts
4106
- fulloptions: List[Tuple[Any, Optional[str]]] = []
4049
+ fulloptions: list[tuple[Any, Optional[str]]] = []
4107
4050
  for opt in local_opts:
4108
4051
  if isinstance(opt, str):
4109
4052
  fulloptions.append((opt, opt))
@@ -4113,7 +4056,7 @@ class Cmd(cmd.Cmd):
4113
4056
  except IndexError:
4114
4057
  fulloptions.append((opt[0], opt[0]))
4115
4058
  for idx, (_, text) in enumerate(fulloptions):
4116
- self.poutput(' %2d. %s' % (idx + 1, text))
4059
+ self.poutput(' %2d. %s' % (idx + 1, text)) # noqa: UP031
4117
4060
 
4118
4061
  while True:
4119
4062
  try:
@@ -4121,9 +4064,9 @@ class Cmd(cmd.Cmd):
4121
4064
  except EOFError:
4122
4065
  response = ''
4123
4066
  self.poutput()
4124
- except KeyboardInterrupt as ex:
4067
+ except KeyboardInterrupt:
4125
4068
  self.poutput('^C')
4126
- raise ex
4069
+ raise
4127
4070
 
4128
4071
  if not response:
4129
4072
  continue
@@ -4131,20 +4074,20 @@ class Cmd(cmd.Cmd):
4131
4074
  try:
4132
4075
  choice = int(response)
4133
4076
  if choice < 1:
4134
- raise IndexError
4077
+ raise IndexError # noqa: TRY301
4135
4078
  return fulloptions[choice - 1][0]
4136
4079
  except (ValueError, IndexError):
4137
4080
  self.poutput(f"'{response}' isn't a valid choice. Pick a number between 1 and {len(fulloptions)}:")
4138
4081
 
4139
4082
  def complete_set_value(
4140
- self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]]
4141
- ) -> List[str]:
4142
- """Completes the value argument of set"""
4083
+ self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
4084
+ ) -> list[str]:
4085
+ """Completes the value argument of set."""
4143
4086
  param = arg_tokens['param'][0]
4144
4087
  try:
4145
4088
  settable = self.settables[param]
4146
- except KeyError:
4147
- raise CompletionError(param + " is not a settable parameter")
4089
+ except KeyError as exc:
4090
+ raise CompletionError(param + " is not a settable parameter") from exc
4148
4091
 
4149
4092
  # Create a parser with a value field based on this settable
4150
4093
  settable_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(parents=[Cmd.set_parser_parent])
@@ -4192,7 +4135,7 @@ class Cmd(cmd.Cmd):
4192
4135
  # Preserve quotes so users can pass in quoted empty strings and flags (e.g. -h) as the value
4193
4136
  @with_argparser(set_parser, preserve_quotes=True)
4194
4137
  def do_set(self, args: argparse.Namespace) -> None:
4195
- """Set a settable parameter or show current settings of parameters"""
4138
+ """Set a settable parameter or show current settings of parameters."""
4196
4139
  self.last_result = False
4197
4140
 
4198
4141
  if not self.settables:
@@ -4211,7 +4154,7 @@ class Cmd(cmd.Cmd):
4211
4154
  try:
4212
4155
  orig_value = settable.get_value()
4213
4156
  settable.set_value(utils.strip_quotes(args.value))
4214
- except Exception as ex:
4157
+ except ValueError as ex:
4215
4158
  self.perror(f"Error setting {args.param}: {ex}")
4216
4159
  else:
4217
4160
  self.poutput(f"{args.param} - was: {orig_value!r}\nnow: {settable.get_value()!r}")
@@ -4229,7 +4172,7 @@ class Cmd(cmd.Cmd):
4229
4172
  max_name_width = max([ansi.style_aware_wcswidth(param) for param in to_show])
4230
4173
  max_name_width = max(max_name_width, ansi.style_aware_wcswidth(name_label))
4231
4174
 
4232
- cols: List[Column] = [
4175
+ cols: list[Column] = [
4233
4176
  Column(name_label, width=max_name_width),
4234
4177
  Column('Value', width=30),
4235
4178
  Column('Description', width=60),
@@ -4239,7 +4182,7 @@ class Cmd(cmd.Cmd):
4239
4182
  self.poutput(table.generate_header())
4240
4183
 
4241
4184
  # Build the table and populate self.last_result
4242
- self.last_result = {} # Dict[settable_name, settable_value]
4185
+ self.last_result = {} # dict[settable_name, settable_value]
4243
4186
 
4244
4187
  for param in sorted(to_show, key=self.default_sort_key):
4245
4188
  settable = self.settables[param]
@@ -4256,11 +4199,11 @@ class Cmd(cmd.Cmd):
4256
4199
  # Preserve quotes since we are passing these strings to the shell
4257
4200
  @with_argparser(shell_parser, preserve_quotes=True)
4258
4201
  def do_shell(self, args: argparse.Namespace) -> None:
4259
- """Execute a command as if at the OS prompt"""
4202
+ """Execute a command as if at the OS prompt."""
4260
4203
  import signal
4261
4204
  import subprocess
4262
4205
 
4263
- kwargs: Dict[str, Any] = dict()
4206
+ kwargs: dict[str, Any] = {}
4264
4207
 
4265
4208
  # Set OS-specific parameters
4266
4209
  if sys.platform.startswith('win'):
@@ -4282,7 +4225,7 @@ class Cmd(cmd.Cmd):
4282
4225
  kwargs['executable'] = shell
4283
4226
 
4284
4227
  # Create a list of arguments to shell
4285
- tokens = [args.command] + args.command_args
4228
+ tokens = [args.command, *args.command_args]
4286
4229
 
4287
4230
  # Expand ~ where needed
4288
4231
  utils.expand_user_in_tokens(tokens)
@@ -4292,7 +4235,7 @@ class Cmd(cmd.Cmd):
4292
4235
  # still receive the SIGINT since it is in the same process group as us.
4293
4236
  with self.sigint_protection:
4294
4237
  # For any stream that is a StdSim, we will use a pipe so we can capture its output
4295
- proc = subprocess.Popen( # type: ignore[call-overload]
4238
+ proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602
4296
4239
  expanded_command,
4297
4240
  stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
4298
4241
  stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr, # type: ignore[unreachable]
@@ -4313,8 +4256,8 @@ class Cmd(cmd.Cmd):
4313
4256
 
4314
4257
  @staticmethod
4315
4258
  def _reset_py_display() -> None:
4316
- """
4317
- Resets the dynamic objects in the sys module that the py and ipy consoles fight over.
4259
+ """Reset the dynamic objects in the sys module that the py and ipy consoles fight over.
4260
+
4318
4261
  When a Python console starts it adopts certain display settings if they've already been set.
4319
4262
  If an ipy console has previously been run, then py uses its settings and ends up looking
4320
4263
  like an ipy console in terms of prompt and exception text. This method forces the Python
@@ -4326,19 +4269,17 @@ class Cmd(cmd.Cmd):
4326
4269
  # Delete any prompts that have been set
4327
4270
  attributes = ['ps1', 'ps2', 'ps3']
4328
4271
  for cur_attr in attributes:
4329
- try:
4272
+ with contextlib.suppress(KeyError):
4330
4273
  del sys.__dict__[cur_attr]
4331
- except KeyError:
4332
- pass
4333
4274
 
4334
4275
  # Reset functions
4335
4276
  sys.displayhook = sys.__displayhook__
4336
4277
  sys.excepthook = sys.__excepthook__
4337
4278
 
4338
4279
  def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env:
4339
- """
4340
- Set up interactive Python shell environment
4341
- :return: Class containing saved up cmd2 environment
4280
+ """Set up interactive Python shell environment.
4281
+
4282
+ :return: Class containing saved up cmd2 environment.
4342
4283
  """
4343
4284
  cmd2_env = _SavedCmd2Env()
4344
4285
 
@@ -4400,8 +4341,7 @@ class Cmd(cmd.Cmd):
4400
4341
  return cmd2_env
4401
4342
 
4402
4343
  def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None:
4403
- """
4404
- Restore cmd2 environment after exiting an interactive Python shell
4344
+ """Restore cmd2 environment after exiting an interactive Python shell.
4405
4345
 
4406
4346
  :param cmd2_env: the environment settings to restore
4407
4347
  """
@@ -4437,8 +4377,10 @@ class Cmd(cmd.Cmd):
4437
4377
  sys.modules['readline'] = cmd2_env.readline_module
4438
4378
 
4439
4379
  def _run_python(self, *, pyscript: Optional[str] = None) -> Optional[bool]:
4440
- """
4380
+ """Run an interactive Python shell or execute a pyscript file.
4381
+
4441
4382
  Called by do_py() and do_run_pyscript().
4383
+
4442
4384
  If pyscript is None, then this function runs an interactive Python shell.
4443
4385
  Otherwise, it runs the pyscript file.
4444
4386
 
@@ -4449,7 +4391,7 @@ class Cmd(cmd.Cmd):
4449
4391
  self.last_result = False
4450
4392
 
4451
4393
  def py_quit() -> None:
4452
- """Function callable from the interactive Python console to exit that environment"""
4394
+ """Exit an interactive Python environment, callable from the interactive Python console."""
4453
4395
  raise EmbeddedConsoleExit
4454
4396
 
4455
4397
  from .py_bridge import (
@@ -4509,9 +4451,9 @@ class Cmd(cmd.Cmd):
4509
4451
 
4510
4452
  # Check if we are running Python code
4511
4453
  if py_code_to_run:
4512
- try:
4454
+ try: # noqa: SIM105
4513
4455
  interp.runcode(py_code_to_run) # type: ignore[arg-type]
4514
- except BaseException:
4456
+ except BaseException: # noqa: BLE001, S110
4515
4457
  # We don't care about any exception that happened in the Python code
4516
4458
  pass
4517
4459
 
@@ -4534,7 +4476,7 @@ class Cmd(cmd.Cmd):
4534
4476
  # Since quit() or exit() raise an EmbeddedConsoleExit, interact() exits before printing
4535
4477
  # the exitmsg. Therefore, we will not provide it one and print it manually later.
4536
4478
  interp.interact(banner=banner, exitmsg='')
4537
- except BaseException:
4479
+ except BaseException: # noqa: BLE001, S110
4538
4480
  # We don't care about any exception that happened in the interactive console
4539
4481
  pass
4540
4482
  finally:
@@ -4556,11 +4498,11 @@ class Cmd(cmd.Cmd):
4556
4498
 
4557
4499
  @with_argparser(py_parser)
4558
4500
  def do_py(self, _: argparse.Namespace) -> Optional[bool]:
4501
+ """Run an interactive Python shell.
4502
+
4503
+ :return: True if running of commands should stop.
4559
4504
  """
4560
- Run an interactive Python shell
4561
- :return: True if running of commands should stop
4562
- """
4563
- # self.last_resort will be set by _run_python()
4505
+ # self.last_result will be set by _run_python()
4564
4506
  return self._run_python()
4565
4507
 
4566
4508
  run_pyscript_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description="Run a Python script file inside the console")
@@ -4571,8 +4513,7 @@ class Cmd(cmd.Cmd):
4571
4513
 
4572
4514
  @with_argparser(run_pyscript_parser)
4573
4515
  def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]:
4574
- """
4575
- Run a Python script file inside the console
4516
+ """Run a Python script file inside the console.
4576
4517
 
4577
4518
  :return: True if running of commands should stop
4578
4519
  """
@@ -4594,9 +4535,9 @@ class Cmd(cmd.Cmd):
4594
4535
 
4595
4536
  try:
4596
4537
  # Overwrite sys.argv to allow the script to take command line arguments
4597
- sys.argv = [args.script_path] + args.script_arguments
4538
+ sys.argv = [args.script_path, *args.script_arguments]
4598
4539
 
4599
- # self.last_resort will be set by _run_python()
4540
+ # self.last_result will be set by _run_python()
4600
4541
  py_return = self._run_python(pyscript=args.script_path)
4601
4542
  finally:
4602
4543
  # Restore command line arguments to original state
@@ -4608,8 +4549,7 @@ class Cmd(cmd.Cmd):
4608
4549
 
4609
4550
  @with_argparser(ipython_parser)
4610
4551
  def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover
4611
- """
4612
- Enter an interactive IPython shell
4552
+ """Enter an interactive IPython shell.
4613
4553
 
4614
4554
  :return: True if running of commands should stop
4615
4555
  """
@@ -4617,11 +4557,11 @@ class Cmd(cmd.Cmd):
4617
4557
 
4618
4558
  # Detect whether IPython is installed
4619
4559
  try:
4620
- import traitlets.config.loader as TraitletsLoader # type: ignore[import]
4560
+ import traitlets.config.loader as traitlets_loader # type: ignore[import]
4621
4561
 
4622
4562
  # Allow users to install ipython from a cmd2 prompt when needed and still have ipy command work
4623
4563
  try:
4624
- start_ipython # noqa F823
4564
+ _dummy = start_ipython # noqa: F823
4625
4565
  except NameError:
4626
4566
  from IPython import start_ipython # type: ignore[import]
4627
4567
 
@@ -4658,7 +4598,7 @@ class Cmd(cmd.Cmd):
4658
4598
  local_vars['self'] = self
4659
4599
 
4660
4600
  # Configure IPython
4661
- config = TraitletsLoader.Config() # type: ignore
4601
+ config = traitlets_loader.Config()
4662
4602
  config.InteractiveShell.banner2 = (
4663
4603
  'Entering an IPython shell. Type exit, quit, or Ctrl-D to exit.\n'
4664
4604
  f'Run CLI commands with: {self.py_bridge_name}("command ...")\n'
@@ -4692,7 +4632,7 @@ class Cmd(cmd.Cmd):
4692
4632
  '-t',
4693
4633
  '--transcript',
4694
4634
  metavar='TRANSCRIPT_FILE',
4695
- help='output commands and results to a transcript file,\nimplies -s',
4635
+ help='create a transcript file by re-running the commands,\nimplies both -r and -s',
4696
4636
  completer=path_complete,
4697
4637
  )
4698
4638
  history_action_group.add_argument('-c', '--clear', action='store_true', help='clear all history')
@@ -4728,15 +4668,14 @@ class Cmd(cmd.Cmd):
4728
4668
 
4729
4669
  @with_argparser(history_parser)
4730
4670
  def do_history(self, args: argparse.Namespace) -> Optional[bool]:
4731
- """
4732
- View, run, edit, save, or clear previously entered commands
4671
+ """View, run, edit, save, or clear previously entered commands.
4733
4672
 
4734
4673
  :return: True if running of commands should stop
4735
4674
  """
4736
4675
  self.last_result = False
4737
4676
 
4738
4677
  # -v must be used alone with no other options
4739
- if args.verbose:
4678
+ if args.verbose: # noqa: SIM102
4740
4679
  if args.clear or args.edit or args.output_file or args.run or args.transcript or args.expanded or args.script:
4741
4680
  self.poutput("-v cannot be used with any other options")
4742
4681
  self.poutput(self.history_parser.format_usage())
@@ -4791,7 +4730,7 @@ class Cmd(cmd.Cmd):
4791
4730
  try:
4792
4731
  self.run_editor(fname)
4793
4732
 
4794
- # self.last_resort will be set by do_run_script()
4733
+ # self.last_result will be set by do_run_script()
4795
4734
  return self.do_run_script(utils.quote_string(fname))
4796
4735
  finally:
4797
4736
  os.remove(fname)
@@ -4811,7 +4750,7 @@ class Cmd(cmd.Cmd):
4811
4750
  self.pfeedback(f"{len(history)} command{plural} saved to {full_path}")
4812
4751
  self.last_result = True
4813
4752
  elif args.transcript:
4814
- # self.last_resort will be set by _generate_transcript()
4753
+ # self.last_result will be set by _generate_transcript()
4815
4754
  self._generate_transcript(list(history.values()), args.transcript)
4816
4755
  else:
4817
4756
  # Display the history items retrieved
@@ -4845,7 +4784,7 @@ class Cmd(cmd.Cmd):
4845
4784
  return history
4846
4785
 
4847
4786
  def _initialize_history(self, hist_file: str) -> None:
4848
- """Initialize history using history related attributes
4787
+ """Initialize history using history related attributes.
4849
4788
 
4850
4789
  :param hist_file: optional path to persistent history file. If specified, then history from
4851
4790
  previous sessions will be included. Additionally, all history will be written
@@ -4879,7 +4818,7 @@ class Cmd(cmd.Cmd):
4879
4818
  with open(hist_file, 'rb') as fobj:
4880
4819
  compressed_bytes = fobj.read()
4881
4820
  except FileNotFoundError:
4882
- compressed_bytes = bytes()
4821
+ compressed_bytes = b""
4883
4822
  except OSError as ex:
4884
4823
  self.perror(f"Cannot read persistent history file '{hist_file}': {ex}")
4885
4824
  return
@@ -4898,11 +4837,11 @@ class Cmd(cmd.Cmd):
4898
4837
  try:
4899
4838
  import lzma as decompress_lib
4900
4839
 
4901
- decompress_exceptions: Tuple[type[Exception]] = (decompress_lib.LZMAError,)
4840
+ decompress_exceptions: tuple[type[Exception]] = (decompress_lib.LZMAError,)
4902
4841
  except ModuleNotFoundError: # pragma: no cover
4903
4842
  import bz2 as decompress_lib # type: ignore[no-redef]
4904
4843
 
4905
- decompress_exceptions: Tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef]
4844
+ decompress_exceptions: tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef]
4906
4845
 
4907
4846
  try:
4908
4847
  history_json = decompress_lib.decompress(compressed_bytes).decode(encoding='utf-8')
@@ -4938,7 +4877,7 @@ class Cmd(cmd.Cmd):
4938
4877
  readline.add_history(formatted_command)
4939
4878
 
4940
4879
  def _persist_history(self) -> None:
4941
- """Write history out to the persistent history file as compressed JSON"""
4880
+ """Write history out to the persistent history file as compressed JSON."""
4942
4881
  if not self.persistent_history_file:
4943
4882
  return
4944
4883
 
@@ -4959,12 +4898,12 @@ class Cmd(cmd.Cmd):
4959
4898
 
4960
4899
  def _generate_transcript(
4961
4900
  self,
4962
- history: Union[List[HistoryItem], List[str]],
4901
+ history: Union[list[HistoryItem], list[str]],
4963
4902
  transcript_file: str,
4964
4903
  *,
4965
4904
  add_to_history: bool = True,
4966
4905
  ) -> None:
4967
- """Generate a transcript file from a given history of commands"""
4906
+ """Generate a transcript file from a given history of commands."""
4968
4907
  self.last_result = False
4969
4908
 
4970
4909
  # Validate the transcript file path to make sure directory exists and write access is available
@@ -4998,7 +4937,7 @@ class Cmd(cmd.Cmd):
4998
4937
  first = True
4999
4938
  command = ''
5000
4939
  if isinstance(history_item, HistoryItem):
5001
- history_item = history_item.raw
4940
+ history_item = history_item.raw # noqa: PLW2901
5002
4941
  for line in history_item.splitlines():
5003
4942
  if first:
5004
4943
  command += f"{self.prompt}{line}\n"
@@ -5048,10 +4987,7 @@ class Cmd(cmd.Cmd):
5048
4987
  self.perror(f"Error saving transcript file '{transcript_path}': {ex}")
5049
4988
  else:
5050
4989
  # and let the user know what we did
5051
- if commands_run == 1:
5052
- plural = 'command and its output'
5053
- else:
5054
- plural = 'commands and their outputs'
4990
+ plural = 'command and its output' if commands_run == 1 else 'commands and their outputs'
5055
4991
  self.pfeedback(f"{commands_run} {plural} saved to transcript file '{transcript_path}'")
5056
4992
  self.last_result = True
5057
4993
 
@@ -5070,20 +5006,18 @@ class Cmd(cmd.Cmd):
5070
5006
 
5071
5007
  @with_argparser(edit_parser)
5072
5008
  def do_edit(self, args: argparse.Namespace) -> None:
5073
- """Run a text editor and optionally open a file with it"""
5074
-
5009
+ """Run a text editor and optionally open a file with it."""
5075
5010
  # self.last_result will be set by do_shell() which is called by run_editor()
5076
5011
  self.run_editor(args.file_path)
5077
5012
 
5078
5013
  def run_editor(self, file_path: Optional[str] = None) -> None:
5079
- """
5080
- Run a text editor and optionally open a file with it
5014
+ """Run a text editor and optionally open a file with it.
5081
5015
 
5082
5016
  :param file_path: optional path of the file to edit. Defaults to None.
5083
5017
  :raises EnvironmentError: if self.editor is not set
5084
5018
  """
5085
5019
  if not self.editor:
5086
- raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.")
5020
+ raise OSError("Please use 'set editor' to specify your text editing program of choice.")
5087
5021
 
5088
5022
  command = utils.quote_string(os.path.expanduser(self.editor))
5089
5023
  if file_path:
@@ -5096,8 +5030,7 @@ class Cmd(cmd.Cmd):
5096
5030
  """Accessor to get the current script directory from the _script_dir LIFO queue."""
5097
5031
  if self._script_dir:
5098
5032
  return self._script_dir[-1]
5099
- else:
5100
- return None
5033
+ return None
5101
5034
 
5102
5035
  run_script_description = (
5103
5036
  "Run commands in script file that is encoded as either ASCII or UTF-8 text\n"
@@ -5160,7 +5093,7 @@ class Cmd(cmd.Cmd):
5160
5093
  self._script_dir.append(os.path.dirname(expanded_path))
5161
5094
 
5162
5095
  if args.transcript:
5163
- # self.last_resort will be set by _generate_transcript()
5096
+ # self.last_result will be set by _generate_transcript()
5164
5097
  self._generate_transcript(
5165
5098
  script_commands,
5166
5099
  os.path.expanduser(args.transcript),
@@ -5198,8 +5131,7 @@ class Cmd(cmd.Cmd):
5198
5131
 
5199
5132
  @with_argparser(relative_run_script_parser)
5200
5133
  def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]:
5201
- """
5202
- Run commands in script file that is encoded as either ASCII or UTF-8 text
5134
+ """Run commands in script file that is encoded as either ASCII or UTF-8 text.
5203
5135
 
5204
5136
  :return: True if running of commands should stop
5205
5137
  """
@@ -5210,8 +5142,8 @@ class Cmd(cmd.Cmd):
5210
5142
  # self.last_result will be set by do_run_script()
5211
5143
  return self.do_run_script(utils.quote_string(relative_path))
5212
5144
 
5213
- def _run_transcript_tests(self, transcript_paths: List[str]) -> None:
5214
- """Runs transcript tests for provided file(s).
5145
+ def _run_transcript_tests(self, transcript_paths: list[str]) -> None:
5146
+ """Run transcript tests for provided file(s).
5215
5147
 
5216
5148
  This is called when either -t is provided on the command line or the transcript_files argument is provided
5217
5149
  during construction of the cmd2.Cmd instance.
@@ -5246,7 +5178,7 @@ class Cmd(cmd.Cmd):
5246
5178
  self.poutput(f'cmd2 app: {sys.argv[0]}')
5247
5179
  self.poutput(ansi.style(f'collected {num_transcripts} transcript{plural}', bold=True))
5248
5180
 
5249
- setattr(self.__class__, 'testfiles', transcripts_expanded)
5181
+ self.__class__.testfiles = transcripts_expanded
5250
5182
  sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
5251
5183
  testcase = TestMyAppCase()
5252
5184
  stream = cast(TextIO, utils.StdSim(sys.stderr))
@@ -5273,8 +5205,8 @@ class Cmd(cmd.Cmd):
5273
5205
  self.exit_code = 1
5274
5206
 
5275
5207
  def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None: # pragma: no cover
5276
- """
5277
- Display an important message to the user while they are at a command line prompt.
5208
+ """Display an important message to the user while they are at a command line prompt.
5209
+
5278
5210
  To the user it appears as if an alert message is printed above the prompt and their
5279
5211
  current input text and cursor location is left alone.
5280
5212
 
@@ -5345,8 +5277,7 @@ class Cmd(cmd.Cmd):
5345
5277
  raise RuntimeError("another thread holds terminal_lock")
5346
5278
 
5347
5279
  def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover
5348
- """
5349
- Update the command line prompt while the user is still typing at it.
5280
+ """Update the command line prompt while the user is still typing at it.
5350
5281
 
5351
5282
  This is good for alerting the user to system changes dynamically in between commands.
5352
5283
  For instance you could alter the color of the prompt to indicate a system status or increase a
@@ -5365,8 +5296,7 @@ class Cmd(cmd.Cmd):
5365
5296
  self.async_alert('', new_prompt)
5366
5297
 
5367
5298
  def async_refresh_prompt(self) -> None: # pragma: no cover
5368
- """
5369
- Refresh the oncreen prompt to match self.prompt.
5299
+ """Refresh the oncreen prompt to match self.prompt.
5370
5300
 
5371
5301
  One case where the onscreen prompt and self.prompt can get out of sync is
5372
5302
  when async_alert() is called while a user is in search mode (e.g. Ctrl-r).
@@ -5392,8 +5322,7 @@ class Cmd(cmd.Cmd):
5392
5322
 
5393
5323
  @staticmethod
5394
5324
  def set_window_title(title: str) -> None: # pragma: no cover
5395
- """
5396
- Set the terminal window title.
5325
+ """Set the terminal window title.
5397
5326
 
5398
5327
  NOTE: This function writes to stderr. Therefore, if you call this during a command run by a pyscript,
5399
5328
  the string which updates the title will appear in that command's CommandResult.stderr data.
@@ -5411,8 +5340,7 @@ class Cmd(cmd.Cmd):
5411
5340
  pass
5412
5341
 
5413
5342
  def enable_command(self, command: str) -> None:
5414
- """
5415
- Enable a command by restoring its functions
5343
+ """Enable a command by restoring its functions.
5416
5344
 
5417
5345
  :param command: the command being enabled
5418
5346
  """
@@ -5444,8 +5372,7 @@ class Cmd(cmd.Cmd):
5444
5372
  del self.disabled_commands[command]
5445
5373
 
5446
5374
  def enable_category(self, category: str) -> None:
5447
- """
5448
- Enable an entire category of commands
5375
+ """Enable an entire category of commands.
5449
5376
 
5450
5377
  :param category: the category to enable
5451
5378
  """
@@ -5455,8 +5382,7 @@ class Cmd(cmd.Cmd):
5455
5382
  self.enable_command(cmd_name)
5456
5383
 
5457
5384
  def disable_command(self, command: str, message_to_print: str) -> None:
5458
- """
5459
- Disable a command and overwrite its functions
5385
+ """Disable a command and overwrite its functions.
5460
5386
 
5461
5387
  :param command: the command being disabled
5462
5388
  :param message_to_print: what to print when this command is run or help is called on it while disabled
@@ -5493,7 +5419,7 @@ class Cmd(cmd.Cmd):
5493
5419
  setattr(self, help_func_name, new_func)
5494
5420
 
5495
5421
  # Set the completer to a function that returns a blank list
5496
- setattr(self, completer_func_name, lambda *args, **kwargs: [])
5422
+ setattr(self, completer_func_name, lambda *_args, **_kwargs: [])
5497
5423
 
5498
5424
  def disable_category(self, category: str, message_to_print: str) -> None:
5499
5425
  """Disable an entire category of commands.
@@ -5512,8 +5438,7 @@ class Cmd(cmd.Cmd):
5512
5438
  self.disable_command(cmd_name, message_to_print)
5513
5439
 
5514
5440
  def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_kwargs: Any) -> None:
5515
- """
5516
- Report when a disabled command has been run or had help called on it
5441
+ """Report when a disabled command has been run or had help called on it.
5517
5442
 
5518
5443
  :param _args: not used
5519
5444
  :param message_to_print: the message reporting that the command is disabled
@@ -5523,7 +5448,7 @@ class Cmd(cmd.Cmd):
5523
5448
  self.perror(message_to_print, apply_style=False)
5524
5449
 
5525
5450
  def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override]
5526
- """This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
5451
+ """Deal with extra features provided by cmd2, this is an outer wrapper around _cmdloop().
5527
5452
 
5528
5453
  _cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with
5529
5454
  the following extra features provided by cmd2:
@@ -5598,13 +5523,13 @@ class Cmd(cmd.Cmd):
5598
5523
  #
5599
5524
  ###
5600
5525
  def _initialize_plugin_system(self) -> None:
5601
- """Initialize the plugin system"""
5602
- self._preloop_hooks: List[Callable[[], None]] = []
5603
- self._postloop_hooks: List[Callable[[], None]] = []
5604
- self._postparsing_hooks: List[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = []
5605
- self._precmd_hooks: List[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = []
5606
- self._postcmd_hooks: List[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = []
5607
- self._cmdfinalization_hooks: List[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = []
5526
+ """Initialize the plugin system."""
5527
+ self._preloop_hooks: list[Callable[[], None]] = []
5528
+ self._postloop_hooks: list[Callable[[], None]] = []
5529
+ self._postparsing_hooks: list[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = []
5530
+ self._precmd_hooks: list[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = []
5531
+ self._postcmd_hooks: list[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = []
5532
+ self._cmdfinalization_hooks: list[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = []
5608
5533
 
5609
5534
  @classmethod
5610
5535
  def _validate_callable_param_count(cls, func: Callable[..., Any], count: int) -> None:
@@ -5620,10 +5545,10 @@ class Cmd(cmd.Cmd):
5620
5545
  def _validate_prepostloop_callable(cls, func: Callable[[], None]) -> None:
5621
5546
  """Check parameter and return types for preloop and postloop hooks."""
5622
5547
  cls._validate_callable_param_count(func, 0)
5623
- # make sure there is no return notation
5624
- signature = inspect.signature(func)
5625
- if signature.return_annotation is not None:
5626
- raise TypeError(f"{func.__name__} must declare return a return type of 'None'")
5548
+ # make sure there is no return annotation or the return is specified as None
5549
+ _, ret_ann = get_types(func)
5550
+ if ret_ann is not None:
5551
+ raise TypeError(f"{func.__name__} must have a return type of 'None', got: {ret_ann}")
5627
5552
 
5628
5553
  def register_preloop_hook(self, func: Callable[[], None]) -> None:
5629
5554
  """Register a function to be called at the beginning of the command loop."""
@@ -5637,17 +5562,19 @@ class Cmd(cmd.Cmd):
5637
5562
 
5638
5563
  @classmethod
5639
5564
  def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None:
5640
- """Check parameter and return types for postparsing hooks"""
5565
+ """Check parameter and return types for postparsing hooks."""
5641
5566
  cls._validate_callable_param_count(cast(Callable[..., Any], func), 1)
5642
- signature = inspect.signature(func)
5643
- _, param = list(signature.parameters.items())[0]
5644
- if param.annotation != plugin.PostparsingData:
5567
+ type_hints, ret_ann = get_types(func)
5568
+ if not type_hints:
5569
+ raise TypeError(f"{func.__name__} parameter is missing a type hint, expected: 'cmd2.plugin.PostparsingData'")
5570
+ par_ann = next(iter(type_hints.values()))
5571
+ if par_ann != plugin.PostparsingData:
5645
5572
  raise TypeError(f"{func.__name__} must have one parameter declared with type 'cmd2.plugin.PostparsingData'")
5646
- if signature.return_annotation != plugin.PostparsingData:
5573
+ if ret_ann != plugin.PostparsingData:
5647
5574
  raise TypeError(f"{func.__name__} must declare return a return type of 'cmd2.plugin.PostparsingData'")
5648
5575
 
5649
5576
  def register_postparsing_hook(self, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None:
5650
- """Register a function to be called after parsing user input but before running the command"""
5577
+ """Register a function to be called after parsing user input but before running the command."""
5651
5578
  self._validate_postparsing_callable(func)
5652
5579
  self._postparsing_hooks.append(func)
5653
5580
 
@@ -5655,24 +5582,24 @@ class Cmd(cmd.Cmd):
5655
5582
 
5656
5583
  @classmethod
5657
5584
  def _validate_prepostcmd_hook(
5658
- cls, func: Callable[[CommandDataType], CommandDataType], data_type: Type[CommandDataType]
5585
+ cls, func: Callable[[CommandDataType], CommandDataType], data_type: type[CommandDataType]
5659
5586
  ) -> None:
5660
5587
  """Check parameter and return types for pre and post command hooks."""
5661
- signature = inspect.signature(func)
5662
5588
  # validate that the callable has the right number of parameters
5663
5589
  cls._validate_callable_param_count(cast(Callable[..., Any], func), 1)
5590
+
5591
+ type_hints, ret_ann = get_types(func)
5592
+ if not type_hints:
5593
+ raise TypeError(f"{func.__name__} parameter is missing a type hint, expected: {data_type}")
5594
+ param_name, par_ann = next(iter(type_hints.items()))
5664
5595
  # validate the parameter has the right annotation
5665
- paramname = list(signature.parameters.keys())[0]
5666
- param = signature.parameters[paramname]
5667
- if param.annotation != data_type:
5668
- raise TypeError(f'argument 1 of {func.__name__} has incompatible type {param.annotation}, expected {data_type}')
5596
+ if par_ann != data_type:
5597
+ raise TypeError(f'argument 1 of {func.__name__} has incompatible type {par_ann}, expected {data_type}')
5669
5598
  # validate the return value has the right annotation
5670
- if signature.return_annotation == signature.empty:
5599
+ if ret_ann is None:
5671
5600
  raise TypeError(f'{func.__name__} does not have a declared return type, expected {data_type}')
5672
- if signature.return_annotation != data_type:
5673
- raise TypeError(
5674
- f'{func.__name__} has incompatible return type {signature.return_annotation}, expected {data_type}'
5675
- )
5601
+ if ret_ann != data_type:
5602
+ raise TypeError(f'{func.__name__} has incompatible return type {ret_ann}, expected {data_type}')
5676
5603
 
5677
5604
  def register_precmd_hook(self, func: Callable[[plugin.PrecommandData], plugin.PrecommandData]) -> None:
5678
5605
  """Register a hook to be called before the command function."""
@@ -5690,12 +5617,16 @@ class Cmd(cmd.Cmd):
5690
5617
  ) -> None:
5691
5618
  """Check parameter and return types for command finalization hooks."""
5692
5619
  cls._validate_callable_param_count(func, 1)
5693
- signature = inspect.signature(func)
5694
- _, param = list(signature.parameters.items())[0]
5695
- if param.annotation != plugin.CommandFinalizationData:
5696
- raise TypeError(f"{func.__name__} must have one parameter declared with type {plugin.CommandFinalizationData}")
5697
- if signature.return_annotation != plugin.CommandFinalizationData:
5698
- raise TypeError("{func.__name__} must declare return a return type of {plugin.CommandFinalizationData}")
5620
+ type_hints, ret_ann = get_types(func)
5621
+ if not type_hints:
5622
+ raise TypeError(f"{func.__name__} parameter is missing a type hint, expected: {plugin.CommandFinalizationData}")
5623
+ _, par_ann = next(iter(type_hints.items()))
5624
+ if par_ann != plugin.CommandFinalizationData:
5625
+ raise TypeError(
5626
+ f"{func.__name__} must have one parameter declared with type {plugin.CommandFinalizationData}, got: {par_ann}"
5627
+ )
5628
+ if ret_ann != plugin.CommandFinalizationData:
5629
+ raise TypeError(f"{func.__name__} must declare return a return type of {plugin.CommandFinalizationData}")
5699
5630
 
5700
5631
  def register_cmdfinalization_hook(
5701
5632
  self, func: Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]
@@ -5709,16 +5640,18 @@ class Cmd(cmd.Cmd):
5709
5640
  cmd_support_func: Callable[..., Any],
5710
5641
  cmd_self: Union[CommandSet, 'Cmd', None],
5711
5642
  ) -> Optional[object]:
5712
- """
5713
- Attempt to resolve a candidate instance to pass as 'self' for an unbound class method that was
5714
- used when defining command's argparse object. Since we restrict registration to only a single CommandSet
5715
- instance of each type, using type is a reasonably safe way to resolve the correct object instance
5643
+ """Attempt to resolve a candidate instance to pass as 'self'.
5644
+
5645
+ Used for an unbound class method that was used when defining command's argparse object.
5646
+
5647
+ Since we restrict registration to only a single CommandSet
5648
+ instance of each type, using type is a reasonably safe way to resolve the correct object instance.
5716
5649
 
5717
5650
  :param cmd_support_func: command support function. This could be a completer or namespace provider
5718
5651
  :param cmd_self: The `self` associated with the command or subcommand
5719
5652
  """
5720
5653
  # figure out what class the command support function was defined in
5721
- func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func)
5654
+ func_class: Optional[type[Any]] = get_defining_class(cmd_support_func)
5722
5655
 
5723
5656
  # Was there a defining class identified? If so, is it a sub-class of CommandSet?
5724
5657
  if func_class is not None and issubclass(func_class, CommandSet):
@@ -5729,7 +5662,7 @@ class Cmd(cmd.Cmd):
5729
5662
  # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type?
5730
5663
  # 3. Is there a registered CommandSet that is is the only matching subclass?
5731
5664
 
5732
- func_self: Optional[Union[CommandSet, 'Cmd']]
5665
+ func_self: Optional[Union[CommandSet, Cmd]]
5733
5666
 
5734
5667
  # check if the command's CommandSet is a sub-class of the support function's defining class
5735
5668
  if isinstance(cmd_self, func_class):
@@ -5738,7 +5671,7 @@ class Cmd(cmd.Cmd):
5738
5671
  else:
5739
5672
  # Search all registered CommandSets
5740
5673
  func_self = None
5741
- candidate_sets: List[CommandSet] = []
5674
+ candidate_sets: list[CommandSet] = []
5742
5675
  for installed_cmd_set in self._installed_command_sets:
5743
5676
  if type(installed_cmd_set) == func_class: # noqa: E721
5744
5677
  # Case 2: CommandSet is an exact type match for the function's CommandSet
@@ -5752,5 +5685,4 @@ class Cmd(cmd.Cmd):
5752
5685
  # Case 3: There exists exactly 1 CommandSet that is a sub-class match of the function's CommandSet
5753
5686
  func_self = candidate_sets[0]
5754
5687
  return func_self
5755
- else:
5756
- return self
5688
+ return self