cmd2 2.5.10__py3-none-any.whl → 2.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,
@@ -187,7 +177,7 @@ else:
187
177
 
188
178
 
189
179
  class _SavedReadlineSettings:
190
- """readline settings that are backed up when switching between readline environments"""
180
+ """readline settings that are backed up when switching between readline environments."""
191
181
 
192
182
  def __init__(self) -> None:
193
183
  self.completer = None
@@ -196,18 +186,18 @@ class _SavedReadlineSettings:
196
186
 
197
187
 
198
188
  class _SavedCmd2Env:
199
- """cmd2 environment settings that are backed up when entering an interactive Python shell"""
189
+ """cmd2 environment settings that are backed up when entering an interactive Python shell."""
200
190
 
201
191
  def __init__(self) -> None:
202
192
  self.readline_settings = _SavedReadlineSettings()
203
193
  self.readline_module: Optional[ModuleType] = None
204
- self.history: List[str] = []
194
+ self.history: list[str] = []
205
195
  self.sys_stdout: Optional[TextIO] = None
206
196
  self.sys_stdin: Optional[TextIO] = None
207
197
 
208
198
 
209
199
  # 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'])
200
+ DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function']) # noqa: PYI024
211
201
 
212
202
 
213
203
  if TYPE_CHECKING: # pragma: no cover
@@ -219,8 +209,7 @@ else:
219
209
 
220
210
 
221
211
  class _CommandParsers:
222
- """
223
- Create and store all command method argument parsers for a given Cmd instance.
212
+ """Create and store all command method argument parsers for a given Cmd instance.
224
213
 
225
214
  Parser creation and retrieval are accomplished through the get() method.
226
215
  """
@@ -230,7 +219,7 @@ class _CommandParsers:
230
219
 
231
220
  # Keyed by the fully qualified method names. This is more reliable than
232
221
  # the methods themselves, since wrapping a method will change its address.
233
- self._parsers: Dict[str, argparse.ArgumentParser] = {}
222
+ self._parsers: dict[str, argparse.ArgumentParser] = {}
234
223
 
235
224
  @staticmethod
236
225
  def _fully_qualified_name(command_method: CommandFunc) -> str:
@@ -241,8 +230,7 @@ class _CommandParsers:
241
230
  return ""
242
231
 
243
232
  def __contains__(self, command_method: CommandFunc) -> bool:
244
- """
245
- Return whether a given method's parser is in self.
233
+ """Return whether a given method's parser is in self.
246
234
 
247
235
  If the parser does not yet exist, it will be created if applicable.
248
236
  This is basically for checking if a method is argarse-based.
@@ -251,8 +239,7 @@ class _CommandParsers:
251
239
  return bool(parser)
252
240
 
253
241
  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.
242
+ """Return a given method's parser or None if the method is not argparse-based.
256
243
 
257
244
  If the parser does not yet exist, it will be created.
258
245
  """
@@ -296,7 +283,7 @@ class _CommandParsers:
296
283
  class Cmd(cmd.Cmd):
297
284
  """An easy but powerful framework for writing line-oriented command interpreters.
298
285
 
299
- Extends the Python Standard Librarys cmd package by adding a lot of useful features
286
+ Extends the Python Standard Library's cmd package by adding a lot of useful features
300
287
  to the out of the box configuration.
301
288
 
302
289
  Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
@@ -312,6 +299,9 @@ class Cmd(cmd.Cmd):
312
299
  ALPHABETICAL_SORT_KEY = utils.norm_fold
313
300
  NATURAL_SORT_KEY = utils.natural_keys
314
301
 
302
+ # List for storing transcript test file names
303
+ testfiles: ClassVar[list[str]] = []
304
+
315
305
  def __init__(
316
306
  self,
317
307
  completekey: str = 'tab',
@@ -325,18 +315,17 @@ class Cmd(cmd.Cmd):
325
315
  include_py: bool = False,
326
316
  include_ipy: bool = False,
327
317
  allow_cli_args: bool = True,
328
- transcript_files: Optional[List[str]] = None,
318
+ transcript_files: Optional[list[str]] = None,
329
319
  allow_redirection: bool = True,
330
- multiline_commands: Optional[List[str]] = None,
331
- terminators: Optional[List[str]] = None,
332
- shortcuts: Optional[Dict[str, str]] = None,
320
+ multiline_commands: Optional[list[str]] = None,
321
+ terminators: Optional[list[str]] = None,
322
+ shortcuts: Optional[dict[str, str]] = None,
333
323
  command_sets: Optional[Iterable[CommandSet]] = None,
334
324
  auto_load_commands: bool = True,
335
325
  allow_clipboard: bool = True,
336
326
  suggest_similar_command: bool = False,
337
327
  ) -> None:
338
- """An easy but powerful framework for writing line-oriented command
339
- interpreters. Extends Python's cmd package.
328
+ """Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
340
329
 
341
330
  :param completekey: readline name of a completion key, default to Tab
342
331
  :param stdin: alternate input file object, if not specified, sys.stdin is used
@@ -388,9 +377,9 @@ class Cmd(cmd.Cmd):
388
377
  """
389
378
  # Check if py or ipy need to be disabled in this instance
390
379
  if not include_py:
391
- setattr(self, 'do_py', None)
380
+ setattr(self, 'do_py', None) # noqa: B010
392
381
  if not include_ipy:
393
- setattr(self, 'do_ipy', None)
382
+ setattr(self, 'do_ipy', None) # noqa: B010
394
383
 
395
384
  # initialize plugin system
396
385
  # needs to be done before we call __init__(0)
@@ -416,20 +405,20 @@ class Cmd(cmd.Cmd):
416
405
  # The maximum number of CompletionItems to display during tab completion. If the number of completion
417
406
  # suggestions exceeds this number, they will be displayed in the typical columnized format and will
418
407
  # not include the description value of the CompletionItems.
419
- self.max_completion_items = 50
408
+ self.max_completion_items: int = 50
420
409
 
421
410
  # A dictionary mapping settable names to their Settable instance
422
- self._settables: Dict[str, Settable] = dict()
411
+ self._settables: dict[str, Settable] = {}
423
412
  self._always_prefix_settables: bool = False
424
413
 
425
414
  # CommandSet containers
426
- self._installed_command_sets: Set[CommandSet] = set()
427
- self._cmd_to_command_sets: Dict[str, CommandSet] = {}
415
+ self._installed_command_sets: set[CommandSet] = set()
416
+ self._cmd_to_command_sets: dict[str, CommandSet] = {}
428
417
 
429
418
  self.build_settables()
430
419
 
431
420
  # Use as prompt for multiline commands on the 2nd+ line of input
432
- self.continuation_prompt = '> '
421
+ self.continuation_prompt: str = '> '
433
422
 
434
423
  # Allow access to your application in embedded Python shells and scripts py via self
435
424
  self.self_in_py = False
@@ -446,21 +435,21 @@ class Cmd(cmd.Cmd):
446
435
  self.exclude_from_history = ['eof', 'history']
447
436
 
448
437
  # Dictionary of macro names and their values
449
- self.macros: Dict[str, Macro] = dict()
438
+ self.macros: dict[str, Macro] = {}
450
439
 
451
440
  # Keeps track of typed command history in the Python shell
452
- self._py_history: List[str] = []
441
+ self._py_history: list[str] = []
453
442
 
454
443
  # The name by which Python environments refer to the PyBridge to call app commands
455
444
  self.py_bridge_name = 'app'
456
445
 
457
446
  # Defines app-specific variables/functions available in Python shells and pyscripts
458
- self.py_locals: Dict[str, Any] = dict()
447
+ self.py_locals: dict[str, Any] = {}
459
448
 
460
449
  # True if running inside a Python shell or pyscript, False otherwise
461
450
  self._in_py = False
462
451
 
463
- self.statement_parser = StatementParser(
452
+ self.statement_parser: StatementParser = StatementParser(
464
453
  terminators=terminators, multiline_commands=multiline_commands, shortcuts=shortcuts
465
454
  )
466
455
 
@@ -468,10 +457,10 @@ class Cmd(cmd.Cmd):
468
457
  self.last_result: Any = None
469
458
 
470
459
  # 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] = []
460
+ self._script_dir: list[str] = []
472
461
 
473
462
  # Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt
474
- self.sigint_protection = utils.ContextFlag()
463
+ self.sigint_protection: utils.ContextFlag = utils.ContextFlag()
475
464
 
476
465
  # If the current command created a process to pipe to, then this will be a ProcReader object.
477
466
  # Otherwise it will be None. It's used to know when a pipe process can be killed and/or waited upon.
@@ -499,7 +488,7 @@ class Cmd(cmd.Cmd):
499
488
  self.broken_pipe_warning = ''
500
489
 
501
490
  # Commands that will run at the beginning of the command loop
502
- self._startup_commands: List[str] = []
491
+ self._startup_commands: list[str] = []
503
492
 
504
493
  # If a startup script is provided and exists, then execute it in the startup commands
505
494
  if startup_script:
@@ -511,7 +500,7 @@ class Cmd(cmd.Cmd):
511
500
  self._startup_commands.append(script_cmd)
512
501
 
513
502
  # Transcript files to run instead of interactive command loop
514
- self._transcript_files: Optional[List[str]] = None
503
+ self._transcript_files: Optional[list[str]] = None
515
504
 
516
505
  # Check for command line args
517
506
  if allow_cli_args:
@@ -554,7 +543,7 @@ class Cmd(cmd.Cmd):
554
543
  # Commands that have been disabled from use. This is to support commands that are only available
555
544
  # during specific states of the application. This dictionary's keys are the command names and its
556
545
  # values are DisabledCommand objects.
557
- self.disabled_commands: Dict[str, DisabledCommand] = dict()
546
+ self.disabled_commands: dict[str, DisabledCommand] = {}
558
547
 
559
548
  # If any command has been categorized, then all other commands that haven't been categorized
560
549
  # will display under this section in the help output.
@@ -566,7 +555,7 @@ class Cmd(cmd.Cmd):
566
555
  # command and category names
567
556
  # alias, macro, settable, and shortcut names
568
557
  # tab completion results when self.matches_sorted is False
569
- self.default_sort_key = Cmd.ALPHABETICAL_SORT_KEY
558
+ self.default_sort_key: Callable[[str], str] = Cmd.ALPHABETICAL_SORT_KEY
570
559
 
571
560
  ############################################################################################################
572
561
  # The following variables are used by tab completion functions. They are reset each time complete() is run
@@ -582,17 +571,17 @@ class Cmd(cmd.Cmd):
582
571
  self.allow_closing_quote = True
583
572
 
584
573
  # An optional hint which prints above tab completion suggestions
585
- self.completion_hint = ''
574
+ self.completion_hint: str = ''
586
575
 
587
576
  # Normally cmd2 uses readline's formatter to columnize the list of completion suggestions.
588
577
  # If a custom format is preferred, write the formatted completions to this string. cmd2 will
589
578
  # then print it instead of the readline format. ANSI style sequences and newlines are supported
590
579
  # when using this value. Even when using formatted_completions, the full matches must still be returned
591
580
  # from your completer function. ArgparseCompleter writes its tab completion tables to this string.
592
- self.formatted_completions = ''
581
+ self.formatted_completions: str = ''
593
582
 
594
583
  # Used by complete() for readline tab completion
595
- self.completion_matches: List[str] = []
584
+ self.completion_matches: list[str] = []
596
585
 
597
586
  # Use this list if you need to display tab completion suggestions that are different than the actual text
598
587
  # of the matches. For instance, if you are completing strings that contain a common delimiter and you only
@@ -600,7 +589,7 @@ class Cmd(cmd.Cmd):
600
589
  # still must be returned from your completer function. For an example, look at path_complete() which
601
590
  # uses this to show only the basename of paths as the suggestions. delimiter_complete() also populates
602
591
  # this list. These are ignored if self.formatted_completions is populated.
603
- self.display_matches: List[str] = []
592
+ self.display_matches: list[str] = []
604
593
 
605
594
  # Used by functions like path_complete() and delimiter_complete() to properly
606
595
  # quote matches that are completed in a delimited fashion
@@ -609,10 +598,10 @@ class Cmd(cmd.Cmd):
609
598
  # Set to True before returning matches to complete() in cases where matches have already been sorted.
610
599
  # If False, then complete() will sort the matches using self.default_sort_key before they are displayed.
611
600
  # This does not affect self.formatted_completions.
612
- self.matches_sorted = False
601
+ self.matches_sorted: bool = False
613
602
 
614
603
  # Command parsers for this Cmd instance.
615
- self._command_parsers = _CommandParsers(self)
604
+ self._command_parsers: _CommandParsers = _CommandParsers(self)
616
605
 
617
606
  # Add functions decorated to be subcommands
618
607
  self._register_subcommands(self)
@@ -642,9 +631,9 @@ class Cmd(cmd.Cmd):
642
631
  # the current command being executed
643
632
  self.current_command: Optional[Statement] = None
644
633
 
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.
634
+ def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]:
635
+ """Find all CommandSets that match the provided CommandSet type.
636
+
648
637
  By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that
649
638
  are sub-classes of the provided type
650
639
  :param commandset_type: CommandSet sub-class type to search for
@@ -658,8 +647,8 @@ class Cmd(cmd.Cmd):
658
647
  ]
659
648
 
660
649
  def find_commandset_for_command(self, command_name: str) -> Optional[CommandSet]:
661
- """
662
- Finds the CommandSet that registered the command name
650
+ """Find the CommandSet that registered the command name.
651
+
663
652
  :param command_name: command name to search
664
653
  :return: CommandSet that provided the command
665
654
  """
@@ -671,7 +660,7 @@ class Cmd(cmd.Cmd):
671
660
  all_commandset_defs = CommandSet.__subclasses__()
672
661
  existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets]
673
662
 
674
- def load_commandset_by_type(commandset_types: List[Type[CommandSet]]) -> None:
663
+ def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None:
675
664
  for cmdset_type in commandset_types:
676
665
  # check if the type has sub-classes. We will only auto-load leaf class types.
677
666
  subclasses = cmdset_type.__subclasses__()
@@ -690,8 +679,7 @@ class Cmd(cmd.Cmd):
690
679
  load_commandset_by_type(all_commandset_defs)
691
680
 
692
681
  def register_command_set(self, cmdset: CommandSet) -> None:
693
- """
694
- Installs a CommandSet, loading all commands defined in the CommandSet
682
+ """Installs a CommandSet, loading all commands defined in the CommandSet.
695
683
 
696
684
  :param cmdset: CommandSet to load
697
685
  """
@@ -703,19 +691,19 @@ class Cmd(cmd.Cmd):
703
691
  if self.always_prefix_settables:
704
692
  if not cmdset.settable_prefix.strip():
705
693
  raise CommandSetRegistrationError('CommandSet settable prefix must not be empty')
706
- for key in cmdset.settables.keys():
694
+ for key in cmdset.settables:
707
695
  prefixed_name = f'{cmdset.settable_prefix}.{key}'
708
696
  if prefixed_name in all_settables:
709
697
  raise CommandSetRegistrationError(f'Duplicate settable: {key}')
710
698
 
711
699
  else:
712
- for key in cmdset.settables.keys():
700
+ for key in cmdset.settables:
713
701
  if key in all_settables:
714
702
  raise CommandSetRegistrationError(f'Duplicate settable {key} is already registered')
715
703
 
716
704
  cmdset.on_register(self)
717
705
  methods = cast(
718
- List[Tuple[str, Callable[..., Any]]],
706
+ list[tuple[str, Callable[..., Any]]],
719
707
  inspect.getmembers(
720
708
  cmdset,
721
709
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
@@ -790,8 +778,7 @@ class Cmd(cmd.Cmd):
790
778
  return parser
791
779
 
792
780
  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.
781
+ """Install a new command function into the CLI.
795
782
 
796
783
  :param command_func_name: name of command function to add
797
784
  This points to the command method and may differ from the method's
@@ -800,7 +787,6 @@ class Cmd(cmd.Cmd):
800
787
  :param context: optional info to provide in error message. (e.g. class this function belongs to)
801
788
  :raises CommandSetRegistrationError: if the command function fails to install
802
789
  """
803
-
804
790
  # command_func_name must begin with COMMAND_FUNC_PREFIX to be identified as a command by cmd2.
805
791
  if not command_func_name.startswith(COMMAND_FUNC_PREFIX):
806
792
  raise CommandSetRegistrationError(f"{command_func_name} does not begin with '{COMMAND_FUNC_PREFIX}'")
@@ -847,8 +833,7 @@ class Cmd(cmd.Cmd):
847
833
  setattr(self, help_func_name, cmd_help)
848
834
 
849
835
  def unregister_command_set(self, cmdset: CommandSet) -> None:
850
- """
851
- Uninstalls a CommandSet and unloads all associated commands
836
+ """Uninstalls a CommandSet and unloads all associated commands.
852
837
 
853
838
  :param cmdset: CommandSet to uninstall
854
839
  """
@@ -857,7 +842,7 @@ class Cmd(cmd.Cmd):
857
842
  cmdset.on_unregister()
858
843
  self._unregister_subcommands(cmdset)
859
844
 
860
- methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
845
+ methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
861
846
  cmdset,
862
847
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
863
848
  and hasattr(meth, '__name__')
@@ -903,7 +888,7 @@ class Cmd(cmd.Cmd):
903
888
  check_parser_uninstallable(subparser)
904
889
  break
905
890
 
906
- methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
891
+ methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers(
907
892
  cmdset,
908
893
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
909
894
  and hasattr(meth, '__name__')
@@ -919,8 +904,7 @@ class Cmd(cmd.Cmd):
919
904
  check_parser_uninstallable(command_parser)
920
905
 
921
906
  def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
922
- """
923
- Register subcommands with their base command
907
+ """Register subcommands with their base command.
924
908
 
925
909
  :param cmdset: CommandSet or cmd2.Cmd subclass containing subcommands
926
910
  """
@@ -937,14 +921,14 @@ class Cmd(cmd.Cmd):
937
921
  )
938
922
 
939
923
  # iterate through all matching methods
940
- for method_name, method in methods:
924
+ for _method_name, method in methods:
941
925
  subcommand_name: str = getattr(method, constants.SUBCMD_ATTR_NAME)
942
926
  full_command_name: str = getattr(method, constants.SUBCMD_ATTR_COMMAND)
943
927
  subcmd_parser_builder = getattr(method, constants.CMD_ATTR_ARGPARSER)
944
928
 
945
929
  subcommand_valid, errmsg = self.statement_parser.is_valid_command(subcommand_name, is_subcommand=True)
946
930
  if not subcommand_valid:
947
- raise CommandSetRegistrationError(f'Subcommand {str(subcommand_name)} is not valid: {errmsg}')
931
+ raise CommandSetRegistrationError(f'Subcommand {subcommand_name!s} is not valid: {errmsg}')
948
932
 
949
933
  command_tokens = full_command_name.split()
950
934
  command_name = command_tokens[0]
@@ -957,16 +941,14 @@ class Cmd(cmd.Cmd):
957
941
  command_func = self.cmd_func(command_name)
958
942
 
959
943
  if command_func is None:
960
- raise CommandSetRegistrationError(
961
- f"Could not find command '{command_name}' needed by subcommand: {str(method)}"
962
- )
944
+ raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}")
963
945
  command_parser = self._command_parsers.get(command_func)
964
946
  if command_parser is None:
965
947
  raise CommandSetRegistrationError(
966
- f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}"
948
+ f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}"
967
949
  )
968
950
 
969
- def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser:
951
+ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser:
970
952
  if not subcmd_names:
971
953
  return action
972
954
  cur_subcmd = subcmd_names.pop(0)
@@ -976,7 +958,7 @@ class Cmd(cmd.Cmd):
976
958
  if choice_name == cur_subcmd:
977
959
  return find_subcommand(choice, subcmd_names)
978
960
  break
979
- raise CommandSetRegistrationError(f"Could not find subcommand '{full_command_name}'")
961
+ raise CommandSetRegistrationError(f"Could not find subcommand '{action}'")
980
962
 
981
963
  target_parser = find_subcommand(command_parser, subcommand_names)
982
964
 
@@ -1028,8 +1010,7 @@ class Cmd(cmd.Cmd):
1028
1010
  break
1029
1011
 
1030
1012
  def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
1031
- """
1032
- Unregister subcommands from their base command
1013
+ """Unregister subcommands from their base command.
1033
1014
 
1034
1015
  :param cmdset: CommandSet containing subcommands
1035
1016
  """
@@ -1046,7 +1027,7 @@ class Cmd(cmd.Cmd):
1046
1027
  )
1047
1028
 
1048
1029
  # iterate through all matching methods
1049
- for method_name, method in methods:
1030
+ for _method_name, method in methods:
1050
1031
  subcommand_name = getattr(method, constants.SUBCMD_ATTR_NAME)
1051
1032
  command_name = getattr(method, constants.SUBCMD_ATTR_COMMAND)
1052
1033
 
@@ -1059,15 +1040,13 @@ class Cmd(cmd.Cmd):
1059
1040
  if command_func is None: # pragma: no cover
1060
1041
  # This really shouldn't be possible since _register_subcommands would prevent this from happening
1061
1042
  # 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
- )
1043
+ raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}")
1065
1044
  command_parser = self._command_parsers.get(command_func)
1066
1045
  if command_parser is None: # pragma: no cover
1067
1046
  # This really shouldn't be possible since _register_subcommands would prevent this from happening
1068
1047
  # but keeping in case it does for some strange reason
1069
1048
  raise CommandSetRegistrationError(
1070
- f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}"
1049
+ f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}"
1071
1050
  )
1072
1051
 
1073
1052
  for action in command_parser._actions:
@@ -1077,8 +1056,7 @@ class Cmd(cmd.Cmd):
1077
1056
 
1078
1057
  @property
1079
1058
  def always_prefix_settables(self) -> bool:
1080
- """
1081
- Flags whether CommandSet settable values should always be prefixed
1059
+ """Flags whether CommandSet settable values should always be prefixed.
1082
1060
 
1083
1061
  :return: True if CommandSet settable values will always be prefixed. False if not.
1084
1062
  """
@@ -1086,8 +1064,7 @@ class Cmd(cmd.Cmd):
1086
1064
 
1087
1065
  @always_prefix_settables.setter
1088
1066
  def always_prefix_settables(self, new_value: bool) -> None:
1089
- """
1090
- Set whether CommandSet settable values should always be prefixed.
1067
+ """Set whether CommandSet settable values should always be prefixed.
1091
1068
 
1092
1069
  :param new_value: True if CommandSet settable values should always be prefixed. False if not.
1093
1070
  :raises ValueError: If a registered CommandSet does not have a defined prefix
@@ -1103,8 +1080,7 @@ class Cmd(cmd.Cmd):
1103
1080
 
1104
1081
  @property
1105
1082
  def settables(self) -> Mapping[str, Settable]:
1106
- """
1107
- Get all available user-settable attributes. This includes settables defined in installed CommandSets
1083
+ """Get all available user-settable attributes. This includes settables defined in installed CommandSets.
1108
1084
 
1109
1085
  :return: Mapping from attribute-name to Settable of all user-settable attributes from
1110
1086
  """
@@ -1119,44 +1095,41 @@ class Cmd(cmd.Cmd):
1119
1095
  return all_settables
1120
1096
 
1121
1097
  def add_settable(self, settable: Settable) -> None:
1122
- """
1123
- Add a settable parameter to ``self.settables``
1098
+ """Add a settable parameter to ``self.settables``.
1124
1099
 
1125
1100
  :param settable: Settable object being added
1126
1101
  """
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}')
1102
+ if not self.always_prefix_settables and settable.name in self.settables and settable.name not in self._settables:
1103
+ raise KeyError(f'Duplicate settable: {settable.name}')
1130
1104
  self._settables[settable.name] = settable
1131
1105
 
1132
1106
  def remove_settable(self, name: str) -> None:
1133
- """
1134
- Convenience method for removing a settable parameter from ``self.settables``
1107
+ """Remove a settable parameter from ``self.settables``.
1135
1108
 
1136
1109
  :param name: name of the settable being removed
1137
1110
  :raises KeyError: if the Settable matches this name
1138
1111
  """
1139
1112
  try:
1140
1113
  del self._settables[name]
1141
- except KeyError:
1142
- raise KeyError(name + " is not a settable parameter")
1114
+ except KeyError as exc:
1115
+ raise KeyError(name + " is not a settable parameter") from exc
1143
1116
 
1144
1117
  def build_settables(self) -> None:
1145
- """Create the dictionary of user-settable parameters"""
1118
+ """Create the dictionary of user-settable parameters."""
1146
1119
 
1147
- def get_allow_style_choices(cli_self: Cmd) -> List[str]:
1148
- """Used to tab complete allow_style values"""
1120
+ def get_allow_style_choices(_cli_self: Cmd) -> list[str]:
1121
+ """Tab complete allow_style values."""
1149
1122
  return [val.name.lower() for val in ansi.AllowStyle]
1150
1123
 
1151
1124
  def allow_style_type(value: str) -> ansi.AllowStyle:
1152
- """Converts a string value into an ansi.AllowStyle"""
1125
+ """Convert a string value into an ansi.AllowStyle."""
1153
1126
  try:
1154
1127
  return ansi.AllowStyle[value.upper()]
1155
- except KeyError:
1128
+ except KeyError as esc:
1156
1129
  raise ValueError(
1157
1130
  f"must be {ansi.AllowStyle.ALWAYS}, {ansi.AllowStyle.NEVER}, or "
1158
1131
  f"{ansi.AllowStyle.TERMINAL} (case-insensitive)"
1159
- )
1132
+ ) from esc
1160
1133
 
1161
1134
  self.add_settable(
1162
1135
  Settable(
@@ -1187,16 +1160,16 @@ class Cmd(cmd.Cmd):
1187
1160
 
1188
1161
  @property
1189
1162
  def allow_style(self) -> ansi.AllowStyle:
1190
- """Read-only property needed to support do_set when it reads allow_style"""
1163
+ """Read-only property needed to support do_set when it reads allow_style."""
1191
1164
  return ansi.allow_style
1192
1165
 
1193
1166
  @allow_style.setter
1194
1167
  def allow_style(self, new_val: ansi.AllowStyle) -> None:
1195
- """Setter property needed to support do_set when it updates allow_style"""
1168
+ """Setter property needed to support do_set when it updates allow_style."""
1196
1169
  ansi.allow_style = new_val
1197
1170
 
1198
1171
  def _completion_supported(self) -> bool:
1199
- """Return whether tab completion is supported"""
1172
+ """Return whether tab completion is supported."""
1200
1173
  return self.use_rawinput and bool(self.completekey) and rl_type != RlType.NONE
1201
1174
 
1202
1175
  @property
@@ -1218,8 +1191,7 @@ class Cmd(cmd.Cmd):
1218
1191
  end: str = '\n',
1219
1192
  style: Optional[Callable[[str], str]] = None,
1220
1193
  ) -> None:
1221
- """
1222
- Print message to a given file object.
1194
+ """Print message to a given file object.
1223
1195
 
1224
1196
  :param dest: the file object being written to
1225
1197
  :param msg: object to print
@@ -1239,7 +1211,7 @@ class Cmd(cmd.Cmd):
1239
1211
  sys.stderr.write(self.broken_pipe_warning)
1240
1212
 
1241
1213
  def poutput(self, msg: Any = '', *, end: str = '\n') -> None:
1242
- """Print message to self.stdout and appends a newline by default
1214
+ """Print message to self.stdout and appends a newline by default.
1243
1215
 
1244
1216
  :param msg: object to print
1245
1217
  :param end: string appended after the end of the message, default a newline
@@ -1247,7 +1219,7 @@ class Cmd(cmd.Cmd):
1247
1219
  self.print_to(self.stdout, msg, end=end)
1248
1220
 
1249
1221
  def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1250
- """Print message to sys.stderr
1222
+ """Print message to sys.stderr.
1251
1223
 
1252
1224
  :param msg: object to print
1253
1225
  :param end: string appended after the end of the message, default a newline
@@ -1257,7 +1229,7 @@ class Cmd(cmd.Cmd):
1257
1229
  self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None)
1258
1230
 
1259
1231
  def psuccess(self, msg: Any = '', *, end: str = '\n') -> None:
1260
- """Wraps poutput, but applies ansi.style_success by default
1232
+ """Wrap poutput, but applies ansi.style_success by default.
1261
1233
 
1262
1234
  :param msg: object to print
1263
1235
  :param end: string appended after the end of the message, default a newline
@@ -1266,7 +1238,7 @@ class Cmd(cmd.Cmd):
1266
1238
  self.poutput(msg, end=end)
1267
1239
 
1268
1240
  def pwarning(self, msg: Any = '', *, end: str = '\n') -> None:
1269
- """Wraps perror, but applies ansi.style_warning by default
1241
+ """Wrap perror, but applies ansi.style_warning by default.
1270
1242
 
1271
1243
  :param msg: object to print
1272
1244
  :param end: string appended after the end of the message, default a newline
@@ -1302,7 +1274,8 @@ class Cmd(cmd.Cmd):
1302
1274
  self.perror(final_msg, end=end, apply_style=False)
1303
1275
 
1304
1276
  def pfeedback(self, msg: Any, *, end: str = '\n') -> None:
1305
- """For printing nonessential feedback. Can be silenced with `quiet`.
1277
+ """Print nonessential feedback. Can be silenced with `quiet`.
1278
+
1306
1279
  Inclusion in redirected output is controlled by `feedback_to_output`.
1307
1280
 
1308
1281
  :param msg: object to print
@@ -1334,7 +1307,7 @@ class Cmd(cmd.Cmd):
1334
1307
  # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
1335
1308
  functional_terminal = False
1336
1309
 
1337
- if self.stdin.isatty() and self.stdout.isatty():
1310
+ if self.stdin.isatty() and self.stdout.isatty(): # noqa: SIM102
1338
1311
  if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
1339
1312
  functional_terminal = True
1340
1313
 
@@ -1355,7 +1328,7 @@ class Cmd(cmd.Cmd):
1355
1328
  with self.sigint_protection:
1356
1329
  import subprocess
1357
1330
 
1358
- pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout)
1331
+ pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout) # noqa: S602
1359
1332
  pipe_proc.communicate(final_msg.encode('utf-8', 'replace'))
1360
1333
  except BrokenPipeError:
1361
1334
  # This occurs if a command's output is being piped to another process and that process closes before the
@@ -1366,12 +1339,23 @@ class Cmd(cmd.Cmd):
1366
1339
  else:
1367
1340
  self.poutput(msg, end=end)
1368
1341
 
1342
+ def ppretty(self, data: Any, *, indent: int = 2, width: int = 80, depth: Optional[int] = None, end: str = '\n') -> None:
1343
+ """Pretty print arbitrary Python data structures to self.stdout and appends a newline by default.
1344
+
1345
+ :param data: object to print
1346
+ :param indent: the amount of indentation added for each nesting level
1347
+ :param width: the desired maximum number of characters per line in the output, a best effort will be made for long data
1348
+ :param depth: the number of nesting levels which may be printed, if data is too deep, the next level replaced by ...
1349
+ :param end: string appended after the end of the message, default a newline
1350
+ """
1351
+ self.print_to(self.stdout, pprint.pformat(data, indent, width, depth), end=end)
1352
+
1369
1353
  # ----- Methods related to tab completion -----
1370
1354
 
1371
1355
  def _reset_completion_defaults(self) -> None:
1372
- """
1373
- Resets tab completion settings
1374
- Needs to be called each time readline runs tab completion
1356
+ """Reset tab completion settings.
1357
+
1358
+ Needs to be called each time readline runs tab completion.
1375
1359
  """
1376
1360
  self.allow_appended_space = True
1377
1361
  self.allow_closing_quote = True
@@ -1387,8 +1371,8 @@ class Cmd(cmd.Cmd):
1387
1371
  elif rl_type == RlType.PYREADLINE:
1388
1372
  readline.rl.mode._display_completions = self._display_matches_pyreadline
1389
1373
 
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.
1374
+ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[list[str], list[str]]:
1375
+ """Get all tokens through the one being completed, used by tab completion functions.
1392
1376
 
1393
1377
  :param line: the current input line with leading whitespace removed
1394
1378
  :param begidx: the beginning index of the prefix text
@@ -1454,14 +1438,14 @@ class Cmd(cmd.Cmd):
1454
1438
  def basic_complete(
1455
1439
  self,
1456
1440
  text: str,
1457
- line: str,
1458
- begidx: int,
1459
- endidx: int,
1441
+ line: str, # noqa: ARG002
1442
+ begidx: int, # noqa: ARG002
1443
+ endidx: int, # noqa: ARG002
1460
1444
  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.
1445
+ ) -> list[str]:
1446
+ """Tab completion function that matches against a list of strings without considering line contents or cursor position.
1447
+
1448
+ The args required by this function are defined in the header of Python's cmd.py.
1465
1449
 
1466
1450
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1467
1451
  :param line: the current input line with leading whitespace removed
@@ -1480,10 +1464,10 @@ class Cmd(cmd.Cmd):
1480
1464
  endidx: int,
1481
1465
  match_against: Iterable[str],
1482
1466
  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.
1467
+ ) -> list[str]:
1468
+ """Perform tab completion against a list but each match is split on a delimiter.
1469
+
1470
+ Only the portion of the match being tab completed is shown as the completion suggestions.
1487
1471
  This is useful if you match against strings that are hierarchical in nature and have a
1488
1472
  common delimiter.
1489
1473
 
@@ -1546,10 +1530,10 @@ class Cmd(cmd.Cmd):
1546
1530
  line: str,
1547
1531
  begidx: int,
1548
1532
  endidx: int,
1549
- flag_dict: Dict[str, Union[Iterable[str], CompleterFunc]],
1533
+ flag_dict: dict[str, Union[Iterable[str], CompleterFunc]],
1550
1534
  *,
1551
1535
  all_else: Union[None, Iterable[str], CompleterFunc] = None,
1552
- ) -> List[str]:
1536
+ ) -> list[str]:
1553
1537
  """Tab completes based on a particular flag preceding the token being completed.
1554
1538
 
1555
1539
  :param text: the string prefix we are attempting to match (all matches must begin with it)
@@ -1598,7 +1582,7 @@ class Cmd(cmd.Cmd):
1598
1582
  index_dict: Mapping[int, Union[Iterable[str], CompleterFunc]],
1599
1583
  *,
1600
1584
  all_else: Optional[Union[Iterable[str], CompleterFunc]] = None,
1601
- ) -> List[str]:
1585
+ ) -> list[str]:
1602
1586
  """Tab completes based on a fixed position in the input string.
1603
1587
 
1604
1588
  :param text: the string prefix we are attempting to match (all matches must begin with it)
@@ -1626,10 +1610,7 @@ class Cmd(cmd.Cmd):
1626
1610
 
1627
1611
  # Check if token is at an index in the dictionary
1628
1612
  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
1613
+ match_against = index_dict.get(index, all_else)
1633
1614
 
1634
1615
  # Perform tab completion using a Iterable
1635
1616
  if isinstance(match_against, Iterable):
@@ -1642,9 +1623,15 @@ class Cmd(cmd.Cmd):
1642
1623
  return matches
1643
1624
 
1644
1625
  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
1626
+ self,
1627
+ text: str,
1628
+ line: str,
1629
+ begidx: int, # noqa: ARG002
1630
+ endidx: int,
1631
+ *,
1632
+ path_filter: Optional[Callable[[str], bool]] = None,
1633
+ ) -> list[str]:
1634
+ """Perform completion of local file system paths.
1648
1635
 
1649
1636
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1650
1637
  :param line: the current input line with leading whitespace removed
@@ -1657,7 +1644,7 @@ class Cmd(cmd.Cmd):
1657
1644
  """
1658
1645
 
1659
1646
  # Used to complete ~ and ~user strings
1660
- def complete_users() -> List[str]:
1647
+ def complete_users() -> list[str]:
1661
1648
  users = []
1662
1649
 
1663
1650
  # Windows lacks the pwd module so we can't get a list of users.
@@ -1728,12 +1715,11 @@ class Cmd(cmd.Cmd):
1728
1715
  return complete_users()
1729
1716
 
1730
1717
  # Otherwise expand the user dir
1731
- else:
1732
- search_str = os.path.expanduser(search_str)
1718
+ search_str = os.path.expanduser(search_str)
1733
1719
 
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)
1720
+ # Get what we need to restore the original tilde path later
1721
+ orig_tilde_path = text[:sep_index]
1722
+ expanded_tilde_path = os.path.expanduser(orig_tilde_path)
1737
1723
 
1738
1724
  # If the search text does not have a directory, then use the cwd
1739
1725
  elif not os.path.dirname(text):
@@ -1772,10 +1758,7 @@ class Cmd(cmd.Cmd):
1772
1758
 
1773
1759
  # Remove cwd if it was added to match the text readline expects
1774
1760
  if cwd_added:
1775
- if cwd == os.path.sep:
1776
- to_replace = cwd
1777
- else:
1778
- to_replace = cwd + os.path.sep
1761
+ to_replace = cwd if cwd == os.path.sep else cwd + os.path.sep
1779
1762
  matches = [cur_path.replace(to_replace, '', 1) for cur_path in matches]
1780
1763
 
1781
1764
  # Restore the tilde string if we expanded one to match the text readline expects
@@ -1784,8 +1767,8 @@ class Cmd(cmd.Cmd):
1784
1767
 
1785
1768
  return matches
1786
1769
 
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
1770
+ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]:
1771
+ """Perform completion of executables either in a user's path or a given path.
1789
1772
 
1790
1773
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1791
1774
  :param line: the current input line with leading whitespace removed
@@ -1804,15 +1787,15 @@ class Cmd(cmd.Cmd):
1804
1787
  return utils.get_exes_in_path(text)
1805
1788
 
1806
1789
  # 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
- )
1790
+ return self.path_complete(
1791
+ text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK)
1792
+ )
1793
+
1794
+ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]:
1795
+ """First tab completion function for all commands, called by complete().
1811
1796
 
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
1797
  It determines if it should tab complete for redirection (|, >, >>) or use the
1815
- completer function for the current command
1798
+ completer function for the current command.
1816
1799
 
1817
1800
  :param text: the string prefix we are attempting to match (all matches must begin with it)
1818
1801
  :param line: the current input line with leading whitespace removed
@@ -1878,20 +1861,21 @@ class Cmd(cmd.Cmd):
1878
1861
  if do_shell_completion:
1879
1862
  return self.shell_cmd_complete(text, line, begidx, endidx)
1880
1863
 
1881
- elif do_path_completion:
1864
+ if do_path_completion:
1882
1865
  return self.path_complete(text, line, begidx, endidx)
1883
1866
 
1884
1867
  # If there were redirection strings anywhere on the command line, then we
1885
1868
  # are no longer tab completing for the current command
1886
- elif has_redirection:
1869
+ if has_redirection:
1887
1870
  return []
1888
1871
 
1889
1872
  # Call the command's completer function
1890
1873
  return compfunc(text, line, begidx, endidx)
1891
1874
 
1892
1875
  @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.
1876
+ def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], int]: # pragma: no cover
1877
+ """Add padding to the matches being displayed as tab completion suggestions.
1878
+
1895
1879
  The default padding of readline/pyreadine is small and not visually appealing
1896
1880
  especially if matches have spaces. It appears very squished together.
1897
1881
 
@@ -1912,9 +1896,9 @@ class Cmd(cmd.Cmd):
1912
1896
  return [cur_match + padding for cur_match in matches_to_display], len(padding)
1913
1897
 
1914
1898
  def _display_matches_gnu_readline(
1915
- self, substitution: str, matches: List[str], longest_match_length: int
1899
+ self, substitution: str, matches: list[str], longest_match_length: int
1916
1900
  ) -> None: # pragma: no cover
1917
- """Prints a match list using GNU readline's rl_display_match_list()
1901
+ """Print a match list using GNU readline's rl_display_match_list().
1918
1902
 
1919
1903
  :param substitution: the substitution written to the command line
1920
1904
  :param matches: the tab completion matches to display
@@ -1944,8 +1928,7 @@ class Cmd(cmd.Cmd):
1944
1928
 
1945
1929
  for cur_match in matches_to_display:
1946
1930
  cur_length = ansi.style_aware_wcswidth(cur_match)
1947
- if cur_length > longest_match_length:
1948
- longest_match_length = cur_length
1931
+ longest_match_length = max(longest_match_length, cur_length)
1949
1932
  else:
1950
1933
  matches_to_display = matches
1951
1934
 
@@ -1960,7 +1943,7 @@ class Cmd(cmd.Cmd):
1960
1943
 
1961
1944
  # rl_display_match_list() expects matches to be in argv format where
1962
1945
  # 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))())
1946
+ strings_array = cast(list[Optional[bytes]], (ctypes.c_char_p * (1 + len(encoded_matches) + 1))())
1964
1947
 
1965
1948
  # Copy in the encoded strings and add a NULL to the end
1966
1949
  strings_array[0] = encoded_substitution
@@ -1973,8 +1956,8 @@ class Cmd(cmd.Cmd):
1973
1956
  # Redraw prompt and input line
1974
1957
  rl_force_redisplay()
1975
1958
 
1976
- def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no cover
1977
- """Prints a match list using pyreadline3's _display_completions()
1959
+ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover
1960
+ """Print a match list using pyreadline3's _display_completions().
1978
1961
 
1979
1962
  :param matches: the tab completion matches to display
1980
1963
  """
@@ -1997,10 +1980,7 @@ class Cmd(cmd.Cmd):
1997
1980
  # Otherwise use pyreadline3's formatter
1998
1981
  else:
1999
1982
  # 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
1983
+ matches_to_display = self.display_matches if self.display_matches else matches
2004
1984
 
2005
1985
  # Add padding for visual appeal
2006
1986
  matches_to_display, _ = self._pad_matches_to_display(matches_to_display)
@@ -2009,15 +1989,15 @@ class Cmd(cmd.Cmd):
2009
1989
  orig_pyreadline_display(matches_to_display)
2010
1990
 
2011
1991
  @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.
1992
+ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]:
1993
+ """Determine what type of ArgparseCompleter to use on a given parser.
1994
+
1995
+ If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER.
2016
1996
 
2017
1997
  :param parser: the parser to examine
2018
1998
  :return: type of ArgparseCompleter
2019
1999
  """
2020
- Completer = Optional[Type[argparse_completer.ArgparseCompleter]]
2000
+ Completer = Optional[type[argparse_completer.ArgparseCompleter]] # noqa: N806
2021
2001
  completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined]
2022
2002
 
2023
2003
  if completer_type is None:
@@ -2027,8 +2007,7 @@ class Cmd(cmd.Cmd):
2027
2007
  def _perform_completion(
2028
2008
  self, text: str, line: str, begidx: int, endidx: int, custom_settings: Optional[utils.CustomCompletionSettings] = None
2029
2009
  ) -> None:
2030
- """
2031
- Helper function for complete() that performs the actual completion
2010
+ """Perform the actual completion, helper function for complete().
2032
2011
 
2033
2012
  :param text: the string prefix we are attempting to match (all matches must begin with it)
2034
2013
  :param line: the current input line with leading whitespace removed
@@ -2106,12 +2085,11 @@ class Cmd(cmd.Cmd):
2106
2085
  completer_func = self.completedefault # type: ignore[assignment]
2107
2086
 
2108
2087
  # Not a recognized macro or command
2088
+ # Check if this command should be run as a shell command
2089
+ elif self.default_to_shell and command in utils.get_exes_in_path(command):
2090
+ completer_func = self.path_complete
2109
2091
  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]
2092
+ completer_func = self.completedefault # type: ignore[assignment]
2115
2093
 
2116
2094
  # Otherwise we are completing the command token or performing custom completion
2117
2095
  else:
@@ -2192,10 +2170,7 @@ class Cmd(cmd.Cmd):
2192
2170
 
2193
2171
  if add_quote:
2194
2172
  # 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 = '"'
2173
+ completion_token_quote = "'" if any('"' in match for match in self.completion_matches) else '"'
2199
2174
 
2200
2175
  self.completion_matches = [completion_token_quote + match for match in self.completion_matches]
2201
2176
 
@@ -2210,7 +2185,7 @@ class Cmd(cmd.Cmd):
2210
2185
  def complete( # type: ignore[override]
2211
2186
  self, text: str, state: int, custom_settings: Optional[utils.CustomCompletionSettings] = None
2212
2187
  ) -> Optional[str]:
2213
- """Override of cmd's complete method which returns the next possible completion for 'text'
2188
+ """Override of cmd's complete method which returns the next possible completion for 'text'.
2214
2189
 
2215
2190
  This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …,
2216
2191
  until it returns a non-string value. It should return the next possible completion starting with text.
@@ -2304,7 +2279,7 @@ class Cmd(cmd.Cmd):
2304
2279
  ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n')
2305
2280
  rl_force_redisplay()
2306
2281
  return None
2307
- except Exception as ex:
2282
+ except Exception as ex: # noqa: BLE001
2308
2283
  # Insert a newline so the exception doesn't print in the middle of the command line being tab completed
2309
2284
  self.perror()
2310
2285
  self.pexcept(ex)
@@ -2312,32 +2287,32 @@ class Cmd(cmd.Cmd):
2312
2287
  return None
2313
2288
 
2314
2289
  def in_script(self) -> bool:
2315
- """Return whether a text script is running"""
2290
+ """Return whether a text script is running."""
2316
2291
  return self._current_script_dir is not None
2317
2292
 
2318
2293
  def in_pyscript(self) -> bool:
2319
- """Return whether running inside a Python shell or pyscript"""
2294
+ """Return whether running inside a Python shell or pyscript."""
2320
2295
  return self._in_py
2321
2296
 
2322
2297
  @property
2323
- def aliases(self) -> Dict[str, str]:
2324
- """Read-only property to access the aliases stored in the StatementParser"""
2298
+ def aliases(self) -> dict[str, str]:
2299
+ """Read-only property to access the aliases stored in the StatementParser."""
2325
2300
  return self.statement_parser.aliases
2326
2301
 
2327
- def get_names(self) -> List[str]:
2302
+ def get_names(self) -> list[str]:
2328
2303
  """Return an alphabetized list of names comprising the attributes of the cmd2 class instance."""
2329
2304
  return dir(self)
2330
2305
 
2331
- def get_all_commands(self) -> List[str]:
2332
- """Return a list of all commands"""
2306
+ def get_all_commands(self) -> list[str]:
2307
+ """Return a list of all commands."""
2333
2308
  return [
2334
2309
  name[len(constants.COMMAND_FUNC_PREFIX) :]
2335
2310
  for name in self.get_names()
2336
2311
  if name.startswith(constants.COMMAND_FUNC_PREFIX) and callable(getattr(self, name))
2337
2312
  ]
2338
2313
 
2339
- def get_visible_commands(self) -> List[str]:
2340
- """Return a list of commands that have not been hidden or disabled"""
2314
+ def get_visible_commands(self) -> list[str]:
2315
+ """Return a list of commands that have not been hidden or disabled."""
2341
2316
  return [
2342
2317
  command
2343
2318
  for command in self.get_all_commands()
@@ -2347,9 +2322,9 @@ class Cmd(cmd.Cmd):
2347
2322
  # Table displayed when tab completing aliases
2348
2323
  _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
2349
2324
 
2350
- def _get_alias_completion_items(self) -> List[CompletionItem]:
2351
- """Return list of alias names and values as CompletionItems"""
2352
- results: List[CompletionItem] = []
2325
+ def _get_alias_completion_items(self) -> list[CompletionItem]:
2326
+ """Return list of alias names and values as CompletionItems."""
2327
+ results: list[CompletionItem] = []
2353
2328
 
2354
2329
  for cur_key in self.aliases:
2355
2330
  row_data = [self.aliases[cur_key]]
@@ -2360,9 +2335,9 @@ class Cmd(cmd.Cmd):
2360
2335
  # Table displayed when tab completing macros
2361
2336
  _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
2362
2337
 
2363
- def _get_macro_completion_items(self) -> List[CompletionItem]:
2364
- """Return list of macro names and values as CompletionItems"""
2365
- results: List[CompletionItem] = []
2338
+ def _get_macro_completion_items(self) -> list[CompletionItem]:
2339
+ """Return list of macro names and values as CompletionItems."""
2340
+ results: list[CompletionItem] = []
2366
2341
 
2367
2342
  for cur_key in self.macros:
2368
2343
  row_data = [self.macros[cur_key].value]
@@ -2373,9 +2348,9 @@ class Cmd(cmd.Cmd):
2373
2348
  # Table displayed when tab completing Settables
2374
2349
  _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None)
2375
2350
 
2376
- def _get_settable_completion_items(self) -> List[CompletionItem]:
2377
- """Return list of Settable names, values, and descriptions as CompletionItems"""
2378
- results: List[CompletionItem] = []
2351
+ def _get_settable_completion_items(self) -> list[CompletionItem]:
2352
+ """Return list of Settable names, values, and descriptions as CompletionItems."""
2353
+ results: list[CompletionItem] = []
2379
2354
 
2380
2355
  for cur_key in self.settables:
2381
2356
  row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description]
@@ -2383,15 +2358,15 @@ class Cmd(cmd.Cmd):
2383
2358
 
2384
2359
  return results
2385
2360
 
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"""
2361
+ def _get_commands_aliases_and_macros_for_completion(self) -> list[str]:
2362
+ """Return a list of visible commands, aliases, and macros for tab completion."""
2388
2363
  visible_commands = set(self.get_visible_commands())
2389
2364
  alias_names = set(self.aliases)
2390
2365
  macro_names = set(self.macros)
2391
2366
  return list(visible_commands | alias_names | macro_names)
2392
2367
 
2393
- def get_help_topics(self) -> List[str]:
2394
- """Return a list of help topics"""
2368
+ def get_help_topics(self) -> list[str]:
2369
+ """Return a list of help topics."""
2395
2370
  all_topics = [
2396
2371
  name[len(constants.HELP_FUNC_PREFIX) :]
2397
2372
  for name in self.get_names()
@@ -2401,7 +2376,7 @@ class Cmd(cmd.Cmd):
2401
2376
  # Filter out hidden and disabled commands
2402
2377
  return [topic for topic in all_topics if topic not in self.hidden_commands and topic not in self.disabled_commands]
2403
2378
 
2404
- def sigint_handler(self, signum: int, _: Optional[FrameType]) -> None:
2379
+ def sigint_handler(self, signum: int, _: Optional[FrameType]) -> None: # noqa: ARG002
2405
2380
  """Signal handler for SIGINTs which typically come from Ctrl-C events.
2406
2381
 
2407
2382
  If you need custom SIGINT behavior, then override this method.
@@ -2424,8 +2399,7 @@ class Cmd(cmd.Cmd):
2424
2399
  self._raise_keyboard_interrupt()
2425
2400
 
2426
2401
  def termination_signal_handler(self, signum: int, _: Optional[FrameType]) -> None:
2427
- """
2428
- Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac.
2402
+ """Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac.
2429
2403
 
2430
2404
  SIGHUP - received when terminal window is closed
2431
2405
  SIGTERM - received when this app has been requested to terminate
@@ -2441,56 +2415,50 @@ class Cmd(cmd.Cmd):
2441
2415
  sys.exit(128 + signum)
2442
2416
 
2443
2417
  def _raise_keyboard_interrupt(self) -> None:
2444
- """Helper function to raise a KeyboardInterrupt"""
2418
+ """Raise a KeyboardInterrupt."""
2445
2419
  raise KeyboardInterrupt("Got a keyboard interrupt")
2446
2420
 
2447
2421
  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.
2422
+ """Ran just before the command is executed by [cmd2.Cmd.onecmd][] and after adding it to history (cmd Hook method).
2450
2423
 
2451
2424
  :param statement: subclass of str which also contains the parsed input
2452
2425
  :return: a potentially modified version of the input Statement object
2453
2426
 
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.
2427
+ See [cmd2.Cmd.register_postparsing_hook][] and [cmd2.Cmd.register_precmd_hook][] for more robust ways
2428
+ to run hooks before the command is executed. See [Hooks](../features/hooks.md) for more information.
2458
2429
  """
2459
2430
  return Statement(statement) if not isinstance(statement, Statement) else statement
2460
2431
 
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][].
2432
+ def postcmd(self, stop: bool, statement: Union[Statement, str]) -> bool: # noqa: ARG002
2433
+ """Ran just after a command is executed by [cmd2.Cmd.onecmd][] (cmd inherited Hook method).
2464
2434
 
2465
2435
  :param stop: return `True` to request the command loop terminate
2466
2436
  :param statement: subclass of str which also contains the parsed input
2467
2437
 
2468
2438
  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.
2439
+ to run hooks after the command is executed. See [Hooks](../features/hooks.md) for more information.
2471
2440
  """
2472
2441
  return stop
2473
2442
 
2474
2443
  def preloop(self) -> None:
2475
- """Hook method executed once when the [cmd2.Cmd.cmdloop][]
2476
- method is called.
2444
+ """Ran once when the [cmd2.Cmd.cmdloop][] method is called (cmd inherited Hook method).
2445
+
2446
+ This method is a stub that does nothing and exists to be overridden by subclasses.
2477
2447
 
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.
2448
+ See [cmd2.Cmd.register_preloop_hook][] for a more robust wayto run hooks before the command loop begins.
2449
+ See [Hooks](../features/hooks.md) for more information.
2481
2450
  """
2482
- pass
2483
2451
 
2484
2452
  def postloop(self) -> None:
2485
- """Hook method executed once when the [cmd2.Cmd.cmdloop][] method is about to return.
2453
+ """Ran once when the [cmd2.Cmd.cmdloop][] method is about to return (cmd inherited Hook Method).
2486
2454
 
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.
2455
+ This method is a stub that does nothing and exists to be overridden by subclasses.
2456
+
2457
+ See [cmd2.Cmd.register_postloop_hook][] for a more robust way to run hooks after the command loop completes.
2458
+ See [Hooks](../features/hooks.md) for more information.
2490
2459
  """
2491
- pass
2492
2460
 
2493
- def parseline(self, line: str) -> Tuple[str, str, str]:
2461
+ def parseline(self, line: str) -> tuple[str, str, str]:
2494
2462
  """Parse the line into a command name and a string containing the arguments.
2495
2463
 
2496
2464
  NOTE: This is an override of a parent class method. It is only used by other parent class methods.
@@ -2549,7 +2517,7 @@ class Cmd(cmd.Cmd):
2549
2517
  if stop:
2550
2518
  # we should not run the command, but
2551
2519
  # we need to run the finalization hooks
2552
- raise EmptyStatement
2520
+ raise EmptyStatement # noqa: TRY301
2553
2521
 
2554
2522
  redir_saved_state: Optional[utils.RedirectionSavedState] = None
2555
2523
 
@@ -2562,7 +2530,7 @@ class Cmd(cmd.Cmd):
2562
2530
 
2563
2531
  redir_saved_state = self._redirect_output(statement)
2564
2532
 
2565
- timestart = datetime.datetime.now()
2533
+ timestart = datetime.datetime.now(tz=datetime.timezone.utc)
2566
2534
 
2567
2535
  # precommand hooks
2568
2536
  precmd_data = plugin.PrecommandData(statement)
@@ -2588,7 +2556,7 @@ class Cmd(cmd.Cmd):
2588
2556
  stop = self.postcmd(stop, statement)
2589
2557
 
2590
2558
  if self.timing:
2591
- self.pfeedback(f'Elapsed: {datetime.datetime.now() - timestart}')
2559
+ self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}')
2592
2560
  finally:
2593
2561
  # Get sigint protection while we restore stuff
2594
2562
  with self.sigint_protection:
@@ -2605,43 +2573,43 @@ class Cmd(cmd.Cmd):
2605
2573
  self.perror(f"Invalid syntax: {ex}")
2606
2574
  except RedirectionError as ex:
2607
2575
  self.perror(ex)
2608
- except KeyboardInterrupt as ex:
2576
+ except KeyboardInterrupt:
2609
2577
  if raise_keyboard_interrupt and not stop:
2610
- raise ex
2578
+ raise
2611
2579
  except SystemExit as ex:
2612
2580
  if isinstance(ex.code, int):
2613
2581
  self.exit_code = ex.code
2614
2582
  stop = True
2615
2583
  except PassThroughException as ex:
2616
- raise ex.wrapped_ex
2617
- except Exception as ex:
2584
+ raise ex.wrapped_ex from None
2585
+ except Exception as ex: # noqa: BLE001
2618
2586
  self.pexcept(ex)
2619
2587
  finally:
2620
2588
  try:
2621
2589
  stop = self._run_cmdfinalization_hooks(stop, statement)
2622
- except KeyboardInterrupt as ex:
2590
+ except KeyboardInterrupt:
2623
2591
  if raise_keyboard_interrupt and not stop:
2624
- raise ex
2592
+ raise
2625
2593
  except SystemExit as ex:
2626
2594
  if isinstance(ex.code, int):
2627
2595
  self.exit_code = ex.code
2628
2596
  stop = True
2629
2597
  except PassThroughException as ex:
2630
- raise ex.wrapped_ex
2631
- except Exception as ex:
2598
+ raise ex.wrapped_ex from None
2599
+ except Exception as ex: # noqa: BLE001
2632
2600
  self.pexcept(ex)
2633
2601
 
2634
2602
  return stop
2635
2603
 
2636
2604
  def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool:
2637
- """Run the command finalization hooks"""
2605
+ """Run the command finalization hooks."""
2638
2606
  with self.sigint_protection:
2639
2607
  if not sys.platform.startswith('win') and self.stdin.isatty():
2640
2608
  # Before the next command runs, fix any terminal problems like those
2641
2609
  # caused by certain binary characters having been printed to it.
2642
2610
  import subprocess
2643
2611
 
2644
- proc = subprocess.Popen(['stty', 'sane'])
2612
+ proc = subprocess.Popen(['stty', 'sane']) # noqa: S603, S607
2645
2613
  proc.communicate()
2646
2614
 
2647
2615
  data = plugin.CommandFinalizationData(stop, statement)
@@ -2653,13 +2621,13 @@ class Cmd(cmd.Cmd):
2653
2621
 
2654
2622
  def runcmds_plus_hooks(
2655
2623
  self,
2656
- cmds: Union[List[HistoryItem], List[str]],
2624
+ cmds: Union[list[HistoryItem], list[str]],
2657
2625
  *,
2658
2626
  add_to_history: bool = True,
2659
2627
  stop_on_keyboard_interrupt: bool = False,
2660
2628
  ) -> bool:
2661
- """
2662
- Used when commands are being run in an automated fashion like text scripts or history replays.
2629
+ """Run commands in an automated fashion from sources like text scripts or history replays.
2630
+
2663
2631
  The prompt and command line for each command will be printed if echo is True.
2664
2632
 
2665
2633
  :param cmds: commands to run
@@ -2671,7 +2639,7 @@ class Cmd(cmd.Cmd):
2671
2639
  """
2672
2640
  for line in cmds:
2673
2641
  if isinstance(line, HistoryItem):
2674
- line = line.raw
2642
+ line = line.raw # noqa: PLW2901
2675
2643
 
2676
2644
  if self.echo:
2677
2645
  self.poutput(f'{self.prompt}{line}')
@@ -2706,7 +2674,7 @@ class Cmd(cmd.Cmd):
2706
2674
  """
2707
2675
 
2708
2676
  def combine_rl_history(statement: Statement) -> None:
2709
- """Combine all lines of a multiline command into a single readline history entry"""
2677
+ """Combine all lines of a multiline command into a single readline history entry."""
2710
2678
  if orig_rl_history_length is None or not statement.multiline_command:
2711
2679
  return
2712
2680
 
@@ -2773,15 +2741,13 @@ class Cmd(cmd.Cmd):
2773
2741
 
2774
2742
  if not statement.command:
2775
2743
  raise EmptyStatement
2776
- else:
2777
- # If necessary, update history with completed multiline command.
2778
- combine_rl_history(statement)
2744
+ # If necessary, update history with completed multiline command.
2745
+ combine_rl_history(statement)
2779
2746
 
2780
2747
  return statement
2781
2748
 
2782
2749
  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
2750
+ """Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved.
2785
2751
 
2786
2752
  :param line: the line being parsed
2787
2753
  :param orig_rl_history_length: Optional length of the readline history before the current command was typed.
@@ -2806,7 +2772,7 @@ class Cmd(cmd.Cmd):
2806
2772
  orig_rl_history_length = None
2807
2773
 
2808
2774
  # 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:
2775
+ if statement.command in self.macros and statement.command not in used_macros:
2810
2776
  used_macros.append(statement.command)
2811
2777
  resolve_result = self._resolve_macro(statement)
2812
2778
  if resolve_result is None:
@@ -2834,13 +2800,12 @@ class Cmd(cmd.Cmd):
2834
2800
  return statement
2835
2801
 
2836
2802
  def _resolve_macro(self, statement: Statement) -> Optional[str]:
2837
- """
2838
- Resolve a macro and return the resulting string
2803
+ """Resolve a macro and return the resulting string.
2839
2804
 
2840
2805
  :param statement: the parsed statement from the command line
2841
2806
  :return: the resolved macro or None on error
2842
2807
  """
2843
- if statement.command not in self.macros.keys():
2808
+ if statement.command not in self.macros:
2844
2809
  raise KeyError(f"{statement.command} is not a macro")
2845
2810
 
2846
2811
  macro = self.macros[statement.command]
@@ -2881,7 +2846,6 @@ class Cmd(cmd.Cmd):
2881
2846
  :return: A bool telling if an error occurred and a utils.RedirectionSavedState object
2882
2847
  :raises RedirectionError: if an error occurs trying to pipe or redirect
2883
2848
  """
2884
- import io
2885
2849
  import subprocess
2886
2850
 
2887
2851
  # Initialize the redirection saved state
@@ -2901,13 +2865,13 @@ class Cmd(cmd.Cmd):
2901
2865
  read_fd, write_fd = os.pipe()
2902
2866
 
2903
2867
  # 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'))
2868
+ subproc_stdin = open(read_fd) # noqa: SIM115
2869
+ new_stdout: TextIO = cast(TextIO, open(write_fd, 'w')) # noqa: SIM115
2906
2870
 
2907
2871
  # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs,
2908
2872
  # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe
2909
2873
  # processes close in the right order (most recent first).
2910
- kwargs: Dict[str, Any] = dict()
2874
+ kwargs: dict[str, Any] = {}
2911
2875
  if sys.platform == 'win32':
2912
2876
  kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
2913
2877
  else:
@@ -2919,7 +2883,7 @@ class Cmd(cmd.Cmd):
2919
2883
  kwargs['executable'] = shell
2920
2884
 
2921
2885
  # 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]
2886
+ proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602
2923
2887
  statement.pipe_to,
2924
2888
  stdin=subproc_stdin,
2925
2889
  stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
@@ -2932,20 +2896,17 @@ class Cmd(cmd.Cmd):
2932
2896
  # like: !ls -l | grep user | wc -l > out.txt. But this makes it difficult to know if the pipe process
2933
2897
  # started OK, since the shell itself always starts. Therefore, we will wait a short time and check
2934
2898
  # if the pipe process is still running.
2935
- try:
2899
+ with contextlib.suppress(subprocess.TimeoutExpired):
2936
2900
  proc.wait(0.2)
2937
- except subprocess.TimeoutExpired:
2938
- pass
2939
2901
 
2940
2902
  # Check if the pipe process already exited
2941
2903
  if proc.returncode is not None:
2942
2904
  subproc_stdin.close()
2943
2905
  new_stdout.close()
2944
2906
  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
2907
+ redir_saved_state.redirecting = True # type: ignore[unreachable]
2908
+ cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)
2909
+ sys.stdout = self.stdout = new_stdout
2949
2910
 
2950
2911
  elif statement.output:
2951
2912
  if statement.output_to:
@@ -2954,9 +2915,9 @@ class Cmd(cmd.Cmd):
2954
2915
  mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w'
2955
2916
  try:
2956
2917
  # Use line buffering
2957
- new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1))
2918
+ new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) # noqa: SIM115
2958
2919
  except OSError as ex:
2959
- raise RedirectionError(f'Failed to redirect because: {ex}')
2920
+ raise RedirectionError('Failed to redirect output') from ex
2960
2921
 
2961
2922
  redir_saved_state.redirecting = True
2962
2923
  sys.stdout = self.stdout = new_stdout
@@ -2975,7 +2936,7 @@ class Cmd(cmd.Cmd):
2975
2936
  # no point opening up the temporary file
2976
2937
  current_paste_buffer = get_paste_buffer()
2977
2938
  # create a temporary file to store output
2978
- new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+"))
2939
+ new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+")) # noqa: SIM115
2979
2940
  redir_saved_state.redirecting = True
2980
2941
  sys.stdout = self.stdout = new_stdout
2981
2942
 
@@ -2990,7 +2951,7 @@ class Cmd(cmd.Cmd):
2990
2951
  return redir_saved_state
2991
2952
 
2992
2953
  def _restore_output(self, statement: Statement, saved_redir_state: utils.RedirectionSavedState) -> None:
2993
- """Handles restoring state after output redirection
2954
+ """Handle restoring state after output redirection.
2994
2955
 
2995
2956
  :param statement: Statement object which contains the parsed input from the user
2996
2957
  :param saved_redir_state: contains information needed to restore state data
@@ -3001,11 +2962,9 @@ class Cmd(cmd.Cmd):
3001
2962
  self.stdout.seek(0)
3002
2963
  write_to_paste_buffer(self.stdout.read())
3003
2964
 
3004
- try:
2965
+ with contextlib.suppress(BrokenPipeError):
3005
2966
  # Close the file or pipe that stdout was redirected to
3006
2967
  self.stdout.close()
3007
- except BrokenPipeError:
3008
- pass
3009
2968
 
3010
2969
  # Restore the stdout values
3011
2970
  self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout)
@@ -3020,25 +2979,24 @@ class Cmd(cmd.Cmd):
3020
2979
  self._redirecting = saved_redir_state.saved_redirecting
3021
2980
 
3022
2981
  def cmd_func(self, command: str) -> Optional[CommandFunc]:
3023
- """
3024
- Get the function for a command
2982
+ """Get the function for a command.
3025
2983
 
3026
2984
  :param command: the name of the command
3027
2985
 
3028
2986
  Example:
3029
-
3030
2987
  ```py
3031
2988
  helpfunc = self.cmd_func('help')
3032
2989
  ```
3033
2990
 
3034
2991
  helpfunc now contains a reference to the ``do_help`` method
2992
+
3035
2993
  """
3036
2994
  func_name = constants.COMMAND_FUNC_PREFIX + command
3037
2995
  func = getattr(self, func_name, None)
3038
2996
  return cast(CommandFunc, func) if callable(func) else None
3039
2997
 
3040
2998
  def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool:
3041
- """This executes the actual do_* method for a command.
2999
+ """Execute the actual do_* method for a command.
3042
3000
 
3043
3001
  If the command provided doesn't exist, then it executes default() instead.
3044
3002
 
@@ -3073,7 +3031,7 @@ class Cmd(cmd.Cmd):
3073
3031
  return stop if stop is not None else False
3074
3032
 
3075
3033
  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.
3034
+ """Execute when the command given isn't a recognized command implemented by a do_* method.
3077
3035
 
3078
3036
  :param statement: Statement object with parsed input
3079
3037
  """
@@ -3082,14 +3040,13 @@ class Cmd(cmd.Cmd):
3082
3040
  self.history.append(statement)
3083
3041
 
3084
3042
  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)}"
3043
+ err_msg = self.default_error.format(statement.command)
3044
+ if self.suggest_similar_command and (suggested_command := self._suggest_similar_command(statement.command)):
3045
+ err_msg += f"\n{self.default_suggestion_message.format(suggested_command)}"
3089
3046
 
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
3047
+ # Set apply_style to False so styles for default_error and default_suggestion_message are not overridden
3048
+ self.perror(err_msg, apply_style=False)
3049
+ return None
3093
3050
 
3094
3051
  def _suggest_similar_command(self, command: str) -> Optional[str]:
3095
3052
  return suggest_similar(command, self.get_visible_commands())
@@ -3098,7 +3055,7 @@ class Cmd(cmd.Cmd):
3098
3055
  self,
3099
3056
  prompt: str,
3100
3057
  *,
3101
- history: Optional[List[str]] = None,
3058
+ history: Optional[list[str]] = None,
3102
3059
  completion_mode: utils.CompletionMode = utils.CompletionMode.NONE,
3103
3060
  preserve_quotes: bool = False,
3104
3061
  choices: Optional[Iterable[Any]] = None,
@@ -3106,9 +3063,9 @@ class Cmd(cmd.Cmd):
3106
3063
  completer: Optional[CompleterFunc] = None,
3107
3064
  parser: Optional[argparse.ArgumentParser] = None,
3108
3065
  ) -> str:
3109
- """
3110
- Read input from appropriate stdin value. Also supports tab completion and up-arrow history while
3111
- input is being entered.
3066
+ """Read input from appropriate stdin value.
3067
+
3068
+ Also supports tab completion and up-arrow history while input is being entered.
3112
3069
 
3113
3070
  :param prompt: prompt to display to user
3114
3071
  :param history: optional list of strings to use for up-arrow history. If completion_mode is
@@ -3139,10 +3096,10 @@ class Cmd(cmd.Cmd):
3139
3096
  """
3140
3097
  readline_configured = False
3141
3098
  saved_completer: Optional[CompleterFunc] = None
3142
- saved_history: Optional[List[str]] = None
3099
+ saved_history: Optional[list[str]] = None
3143
3100
 
3144
3101
  def configure_readline() -> None:
3145
- """Configure readline tab completion and history"""
3102
+ """Configure readline tab completion and history."""
3146
3103
  nonlocal readline_configured
3147
3104
  nonlocal saved_completer
3148
3105
  nonlocal saved_history
@@ -3158,7 +3115,7 @@ class Cmd(cmd.Cmd):
3158
3115
  # Disable completion
3159
3116
  if completion_mode == utils.CompletionMode.NONE:
3160
3117
 
3161
- def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover
3118
+ def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover # noqa: ARG001
3162
3119
  return None
3163
3120
 
3164
3121
  complete_func = complete_none
@@ -3198,7 +3155,7 @@ class Cmd(cmd.Cmd):
3198
3155
  readline_configured = True
3199
3156
 
3200
3157
  def restore_readline() -> None:
3201
- """Restore readline tab completion and history"""
3158
+ """Restore readline tab completion and history."""
3202
3159
  nonlocal readline_configured
3203
3160
  if not readline_configured or rl_type == RlType.NONE: # pragma: no cover
3204
3161
  return
@@ -3232,31 +3189,29 @@ class Cmd(cmd.Cmd):
3232
3189
  sys.stdout.write(f'{prompt}{line}\n')
3233
3190
 
3234
3191
  # Otherwise read from self.stdin
3192
+ elif self.stdin.isatty():
3193
+ # on a tty, print the prompt first, then read the line
3194
+ self.poutput(prompt, end='')
3195
+ self.stdout.flush()
3196
+ line = self.stdin.readline()
3197
+ if len(line) == 0:
3198
+ line = 'eof'
3235
3199
  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'
3200
+ # we are reading from a pipe, read the line to see if there is
3201
+ # anything there, if so, then decide whether to print the
3202
+ # prompt or not
3203
+ line = self.stdin.readline()
3204
+ if len(line):
3205
+ # we read something, output the prompt and the something
3206
+ if self.echo:
3207
+ self.poutput(f'{prompt}{line}')
3243
3208
  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'
3209
+ line = 'eof'
3254
3210
 
3255
3211
  return line.rstrip('\r\n')
3256
3212
 
3257
3213
  def _read_command_line(self, prompt: str) -> str:
3258
- """
3259
- Read command line from appropriate stdin
3214
+ """Read command line from appropriate stdin.
3260
3215
 
3261
3216
  :param prompt: prompt to display to user
3262
3217
  :return: command line text of 'eof' if an EOFError was caught
@@ -3264,11 +3219,9 @@ class Cmd(cmd.Cmd):
3264
3219
  """
3265
3220
  try:
3266
3221
  # Wrap in try since terminal_lock may not be locked
3267
- try:
3222
+ with contextlib.suppress(RuntimeError):
3268
3223
  # Command line is about to be drawn. Allow asynchronous changes to the terminal.
3269
3224
  self.terminal_lock.release()
3270
- except RuntimeError:
3271
- pass
3272
3225
  return self.read_input(prompt, completion_mode=utils.CompletionMode.COMMANDS)
3273
3226
  except EOFError:
3274
3227
  return 'eof'
@@ -3277,8 +3230,7 @@ class Cmd(cmd.Cmd):
3277
3230
  self.terminal_lock.acquire()
3278
3231
 
3279
3232
  def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
3280
- """
3281
- Called at beginning of command loop to set up readline with cmd2-specific settings
3233
+ """Set up readline with cmd2-specific settings, called at beginning of command loop.
3282
3234
 
3283
3235
  :return: Class containing saved readline settings
3284
3236
  """
@@ -3318,8 +3270,7 @@ class Cmd(cmd.Cmd):
3318
3270
  return readline_settings
3319
3271
 
3320
3272
  def _restore_readline(self, readline_settings: _SavedReadlineSettings) -> None:
3321
- """
3322
- Called at end of command loop to restore saved readline settings
3273
+ """Restore saved readline settings, called at end of command loop.
3323
3274
 
3324
3275
  :param readline_settings: the readline settings to restore
3325
3276
  """
@@ -3335,8 +3286,9 @@ class Cmd(cmd.Cmd):
3335
3286
  readline.rl.mode._display_completions = orig_pyreadline_display
3336
3287
 
3337
3288
  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
3289
+ """Repeatedly issue a prompt, accept input, parse it, and dispatch to apporpriate commands.
3290
+
3291
+ Parse an initial prefix off the received input and dispatch to action methods, passing them
3340
3292
  the remainder of the line as argument.
3341
3293
 
3342
3294
  This serves the same role as cmd.cmdloop().
@@ -3388,7 +3340,7 @@ class Cmd(cmd.Cmd):
3388
3340
  # Preserve quotes since we are passing strings to other commands
3389
3341
  @with_argparser(alias_parser, preserve_quotes=True)
3390
3342
  def do_alias(self, args: argparse.Namespace) -> None:
3391
- """Manage aliases"""
3343
+ """Manage aliases."""
3392
3344
  # Call handler for whatever subcommand was selected
3393
3345
  handler = args.cmd2_handler.get()
3394
3346
  handler(args)
@@ -3423,7 +3375,7 @@ class Cmd(cmd.Cmd):
3423
3375
 
3424
3376
  @as_subcommand_to('alias', 'create', alias_create_parser, help=alias_create_description.lower())
3425
3377
  def _alias_create(self, args: argparse.Namespace) -> None:
3426
- """Create or overwrite an alias"""
3378
+ """Create or overwrite an alias."""
3427
3379
  self.last_result = False
3428
3380
 
3429
3381
  # Validate the alias name
@@ -3473,7 +3425,7 @@ class Cmd(cmd.Cmd):
3473
3425
 
3474
3426
  @as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help)
3475
3427
  def _alias_delete(self, args: argparse.Namespace) -> None:
3476
- """Delete aliases"""
3428
+ """Delete aliases."""
3477
3429
  self.last_result = True
3478
3430
 
3479
3431
  if args.all:
@@ -3510,18 +3462,15 @@ class Cmd(cmd.Cmd):
3510
3462
 
3511
3463
  @as_subcommand_to('alias', 'list', alias_list_parser, help=alias_list_help)
3512
3464
  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]
3465
+ """List some or all aliases as 'alias create' commands."""
3466
+ self.last_result = {} # dict[alias_name, alias_value]
3515
3467
 
3516
3468
  tokens_to_quote = constants.REDIRECTION_TOKENS
3517
3469
  tokens_to_quote.extend(self.statement_parser.terminators)
3518
3470
 
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)
3471
+ to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.aliases, key=self.default_sort_key)
3523
3472
 
3524
- not_found: List[str] = []
3473
+ not_found: list[str] = []
3525
3474
  for name in to_list:
3526
3475
  if name not in self.aliases:
3527
3476
  not_found.append(name)
@@ -3556,7 +3505,7 @@ class Cmd(cmd.Cmd):
3556
3505
  # Preserve quotes since we are passing strings to other commands
3557
3506
  @with_argparser(macro_parser, preserve_quotes=True)
3558
3507
  def do_macro(self, args: argparse.Namespace) -> None:
3559
- """Manage macros"""
3508
+ """Manage macros."""
3560
3509
  # Call handler for whatever subcommand was selected
3561
3510
  handler = args.cmd2_handler.get()
3562
3511
  handler(args)
@@ -3615,7 +3564,7 @@ class Cmd(cmd.Cmd):
3615
3564
 
3616
3565
  @as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help)
3617
3566
  def _macro_create(self, args: argparse.Namespace) -> None:
3618
- """Create or overwrite a macro"""
3567
+ """Create or overwrite a macro."""
3619
3568
  self.last_result = False
3620
3569
 
3621
3570
  # Validate the macro name
@@ -3660,8 +3609,7 @@ class Cmd(cmd.Cmd):
3660
3609
  return
3661
3610
 
3662
3611
  arg_nums.add(cur_num)
3663
- if cur_num > max_arg_num:
3664
- max_arg_num = cur_num
3612
+ max_arg_num = max(max_arg_num, cur_num)
3665
3613
 
3666
3614
  arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False))
3667
3615
 
@@ -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:
@@ -5637,17 +5562,17 @@ 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
5567
  signature = inspect.signature(func)
5643
- _, param = list(signature.parameters.items())[0]
5568
+ _, param = next(iter(signature.parameters.items()))
5644
5569
  if param.annotation != plugin.PostparsingData:
5645
5570
  raise TypeError(f"{func.__name__} must have one parameter declared with type 'cmd2.plugin.PostparsingData'")
5646
5571
  if signature.return_annotation != plugin.PostparsingData:
5647
5572
  raise TypeError(f"{func.__name__} must declare return a return type of 'cmd2.plugin.PostparsingData'")
5648
5573
 
5649
5574
  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"""
5575
+ """Register a function to be called after parsing user input but before running the command."""
5651
5576
  self._validate_postparsing_callable(func)
5652
5577
  self._postparsing_hooks.append(func)
5653
5578
 
@@ -5655,14 +5580,14 @@ class Cmd(cmd.Cmd):
5655
5580
 
5656
5581
  @classmethod
5657
5582
  def _validate_prepostcmd_hook(
5658
- cls, func: Callable[[CommandDataType], CommandDataType], data_type: Type[CommandDataType]
5583
+ cls, func: Callable[[CommandDataType], CommandDataType], data_type: type[CommandDataType]
5659
5584
  ) -> None:
5660
5585
  """Check parameter and return types for pre and post command hooks."""
5661
5586
  signature = inspect.signature(func)
5662
5587
  # validate that the callable has the right number of parameters
5663
5588
  cls._validate_callable_param_count(cast(Callable[..., Any], func), 1)
5664
5589
  # validate the parameter has the right annotation
5665
- paramname = list(signature.parameters.keys())[0]
5590
+ paramname = next(iter(signature.parameters.keys()))
5666
5591
  param = signature.parameters[paramname]
5667
5592
  if param.annotation != data_type:
5668
5593
  raise TypeError(f'argument 1 of {func.__name__} has incompatible type {param.annotation}, expected {data_type}')
@@ -5691,7 +5616,7 @@ class Cmd(cmd.Cmd):
5691
5616
  """Check parameter and return types for command finalization hooks."""
5692
5617
  cls._validate_callable_param_count(func, 1)
5693
5618
  signature = inspect.signature(func)
5694
- _, param = list(signature.parameters.items())[0]
5619
+ _, param = next(iter(signature.parameters.items()))
5695
5620
  if param.annotation != plugin.CommandFinalizationData:
5696
5621
  raise TypeError(f"{func.__name__} must have one parameter declared with type {plugin.CommandFinalizationData}")
5697
5622
  if signature.return_annotation != plugin.CommandFinalizationData:
@@ -5709,16 +5634,18 @@ class Cmd(cmd.Cmd):
5709
5634
  cmd_support_func: Callable[..., Any],
5710
5635
  cmd_self: Union[CommandSet, 'Cmd', None],
5711
5636
  ) -> 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
5637
+ """Attempt to resolve a candidate instance to pass as 'self'.
5638
+
5639
+ Used for an unbound class method that was used when defining command's argparse object.
5640
+
5641
+ Since we restrict registration to only a single CommandSet
5642
+ instance of each type, using type is a reasonably safe way to resolve the correct object instance.
5716
5643
 
5717
5644
  :param cmd_support_func: command support function. This could be a completer or namespace provider
5718
5645
  :param cmd_self: The `self` associated with the command or subcommand
5719
5646
  """
5720
5647
  # figure out what class the command support function was defined in
5721
- func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func)
5648
+ func_class: Optional[type[Any]] = get_defining_class(cmd_support_func)
5722
5649
 
5723
5650
  # Was there a defining class identified? If so, is it a sub-class of CommandSet?
5724
5651
  if func_class is not None and issubclass(func_class, CommandSet):
@@ -5729,7 +5656,7 @@ class Cmd(cmd.Cmd):
5729
5656
  # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type?
5730
5657
  # 3. Is there a registered CommandSet that is is the only matching subclass?
5731
5658
 
5732
- func_self: Optional[Union[CommandSet, 'Cmd']]
5659
+ func_self: Optional[Union[CommandSet, Cmd]]
5733
5660
 
5734
5661
  # check if the command's CommandSet is a sub-class of the support function's defining class
5735
5662
  if isinstance(cmd_self, func_class):
@@ -5738,7 +5665,7 @@ class Cmd(cmd.Cmd):
5738
5665
  else:
5739
5666
  # Search all registered CommandSets
5740
5667
  func_self = None
5741
- candidate_sets: List[CommandSet] = []
5668
+ candidate_sets: list[CommandSet] = []
5742
5669
  for installed_cmd_set in self._installed_command_sets:
5743
5670
  if type(installed_cmd_set) == func_class: # noqa: E721
5744
5671
  # Case 2: CommandSet is an exact type match for the function's CommandSet
@@ -5752,5 +5679,4 @@ class Cmd(cmd.Cmd):
5752
5679
  # Case 3: There exists exactly 1 CommandSet that is a sub-class match of the function's CommandSet
5753
5680
  func_self = candidate_sets[0]
5754
5681
  return func_self
5755
- else:
5756
- return self
5682
+ return self