cmd2 2.5.6__py3-none-any.whl → 2.5.8__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/ansi.py CHANGED
@@ -1042,9 +1042,6 @@ def style(
1042
1042
  # Default styles for printing strings of various types.
1043
1043
  # These can be altered to suit an application's needs and only need to be a
1044
1044
  # function with the following structure: func(str) -> str
1045
- style_output = functools.partial(style)
1046
- """Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text for normal output"""
1047
-
1048
1045
  style_success = functools.partial(style, fg=Fg.GREEN)
1049
1046
  """Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""
1050
1047
 
cmd2/cmd2.py CHANGED
@@ -218,6 +218,81 @@ else:
218
218
  ClassArgParseBuilder = classmethod
219
219
 
220
220
 
221
+ class _CommandParsers:
222
+ """
223
+ Create and store all command method argument parsers for a given Cmd instance.
224
+
225
+ Parser creation and retrieval are accomplished through the get() method.
226
+ """
227
+
228
+ def __init__(self, cmd: 'Cmd') -> None:
229
+ self._cmd = cmd
230
+
231
+ # Keyed by the fully qualified method names. This is more reliable than
232
+ # the methods themselves, since wrapping a method will change its address.
233
+ self._parsers: Dict[str, argparse.ArgumentParser] = {}
234
+
235
+ @staticmethod
236
+ def _fully_qualified_name(command_method: CommandFunc) -> str:
237
+ """Return the fully qualified name of a method or None if a method wasn't passed in."""
238
+ try:
239
+ return f"{command_method.__module__}.{command_method.__qualname__}"
240
+ except AttributeError:
241
+ return ""
242
+
243
+ def __contains__(self, command_method: CommandFunc) -> bool:
244
+ """
245
+ Return whether a given method's parser is in self.
246
+
247
+ If the parser does not yet exist, it will be created if applicable.
248
+ This is basically for checking if a method is argarse-based.
249
+ """
250
+ parser = self.get(command_method)
251
+ return bool(parser)
252
+
253
+ 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.
256
+
257
+ If the parser does not yet exist, it will be created.
258
+ """
259
+ full_method_name = self._fully_qualified_name(command_method)
260
+ if not full_method_name:
261
+ return None
262
+
263
+ if full_method_name not in self._parsers:
264
+ if not command_method.__name__.startswith(COMMAND_FUNC_PREFIX):
265
+ return None
266
+ command = command_method.__name__[len(COMMAND_FUNC_PREFIX) :]
267
+
268
+ parser_builder = getattr(command_method, constants.CMD_ATTR_ARGPARSER, None)
269
+ parent = self._cmd.find_commandset_for_command(command) or self._cmd
270
+ parser = self._cmd._build_parser(parent, parser_builder)
271
+ if parser is None:
272
+ return None
273
+
274
+ # argparser defaults the program name to sys.argv[0], but we want it to be the name of our command
275
+ from .decorators import (
276
+ _set_parser_prog,
277
+ )
278
+
279
+ _set_parser_prog(parser, command)
280
+
281
+ # If the description has not been set, then use the method docstring if one exists
282
+ if parser.description is None and hasattr(command_method, '__wrapped__') and command_method.__wrapped__.__doc__:
283
+ parser.description = strip_doc_annotations(command_method.__wrapped__.__doc__)
284
+
285
+ self._parsers[full_method_name] = parser
286
+
287
+ return self._parsers.get(full_method_name)
288
+
289
+ def remove(self, command_method: CommandFunc) -> None:
290
+ """Remove a given method's parser if it exists."""
291
+ full_method_name = self._fully_qualified_name(command_method)
292
+ if full_method_name in self._parsers:
293
+ del self._parsers[full_method_name]
294
+
295
+
221
296
  class Cmd(cmd.Cmd):
222
297
  """An easy but powerful framework for writing line-oriented command interpreters.
223
298
 
@@ -537,11 +612,7 @@ class Cmd(cmd.Cmd):
537
612
  self.matches_sorted = False
538
613
 
539
614
  # Command parsers for this Cmd instance.
540
- self._command_parsers: Dict[str, argparse.ArgumentParser] = {}
541
-
542
- # Locates the command parser template or factory and creates an instance-specific parser
543
- for command in self.get_all_commands():
544
- self._register_command_parser(command, self.cmd_func(command)) # type: ignore[arg-type]
615
+ self._command_parsers = _CommandParsers(self)
545
616
 
546
617
  # Add functions decorated to be subcommands
547
618
  self._register_subcommands(self)
@@ -657,11 +728,11 @@ class Cmd(cmd.Cmd):
657
728
 
658
729
  installed_attributes = []
659
730
  try:
660
- for method_name, method in methods:
661
- command = method_name[len(COMMAND_FUNC_PREFIX) :]
731
+ for cmd_func_name, command_method in methods:
732
+ command = cmd_func_name[len(COMMAND_FUNC_PREFIX) :]
662
733
 
663
- self._install_command_function(command, method, type(cmdset).__name__)
664
- installed_attributes.append(method_name)
734
+ self._install_command_function(cmd_func_name, command_method, type(cmdset).__name__)
735
+ installed_attributes.append(cmd_func_name)
665
736
 
666
737
  completer_func_name = COMPLETER_FUNC_PREFIX + command
667
738
  cmd_completer = getattr(cmdset, completer_func_name, None)
@@ -677,8 +748,8 @@ class Cmd(cmd.Cmd):
677
748
 
678
749
  self._cmd_to_command_sets[command] = cmdset
679
750
 
680
- if default_category and not hasattr(method, constants.CMD_ATTR_HELP_CATEGORY):
681
- utils.categorize(method, default_category)
751
+ if default_category and not hasattr(command_method, constants.CMD_ATTR_HELP_CATEGORY):
752
+ utils.categorize(command_method, default_category)
682
753
 
683
754
  self._installed_command_sets.add(cmdset)
684
755
 
@@ -718,33 +789,31 @@ class Cmd(cmd.Cmd):
718
789
  parser = copy.deepcopy(parser_builder)
719
790
  return parser
720
791
 
721
- def _register_command_parser(self, command: str, command_method: Callable[..., Any]) -> None:
722
- if command not in self._command_parsers:
723
- parser_builder = getattr(command_method, constants.CMD_ATTR_ARGPARSER, None)
724
- parent = self.find_commandset_for_command(command) or self
725
- parser = self._build_parser(parent, parser_builder)
726
- if parser is None:
727
- return
728
-
729
- # argparser defaults the program name to sys.argv[0], but we want it to be the name of our command
730
- from .decorators import (
731
- _set_parser_prog,
732
- )
792
+ 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.
733
795
 
734
- _set_parser_prog(parser, command)
796
+ :param command_func_name: name of command function to add
797
+ This points to the command method and may differ from the method's
798
+ name if it's being used as a synonym. (e.g. do_exit = do_quit)
799
+ :param command_method: the actual command method which runs when the command function is called
800
+ :param context: optional info to provide in error message. (e.g. class this function belongs to)
801
+ :raises CommandSetRegistrationError: if the command function fails to install
802
+ """
735
803
 
736
- # If the description has not been set, then use the method docstring if one exists
737
- if parser.description is None and hasattr(command_method, '__wrapped__') and command_method.__wrapped__.__doc__:
738
- parser.description = strip_doc_annotations(command_method.__wrapped__.__doc__)
804
+ # command_func_name must begin with COMMAND_FUNC_PREFIX to be identified as a command by cmd2.
805
+ if not command_func_name.startswith(COMMAND_FUNC_PREFIX):
806
+ raise CommandSetRegistrationError(f"{command_func_name} does not begin with '{COMMAND_FUNC_PREFIX}'")
739
807
 
740
- self._command_parsers[command] = parser
808
+ # command_method must start with COMMAND_FUNC_PREFIX for use in self._command_parsers.
809
+ if not command_method.__name__.startswith(COMMAND_FUNC_PREFIX):
810
+ raise CommandSetRegistrationError(f"{command_method.__name__} does not begin with '{COMMAND_FUNC_PREFIX}'")
741
811
 
742
- def _install_command_function(self, command: str, command_wrapper: Callable[..., Any], context: str = '') -> None:
743
- cmd_func_name = COMMAND_FUNC_PREFIX + command
812
+ command = command_func_name[len(COMMAND_FUNC_PREFIX) :]
744
813
 
745
814
  # Make sure command function doesn't share name with existing attribute
746
- if hasattr(self, cmd_func_name):
747
- raise CommandSetRegistrationError(f'Attribute already exists: {cmd_func_name} ({context})')
815
+ if hasattr(self, command_func_name):
816
+ raise CommandSetRegistrationError(f'Attribute already exists: {command_func_name} ({context})')
748
817
 
749
818
  # Check if command has an invalid name
750
819
  valid, errmsg = self.statement_parser.is_valid_command(command)
@@ -761,9 +830,7 @@ class Cmd(cmd.Cmd):
761
830
  self.pwarning(f"Deleting macro '{command}' because it shares its name with a new command")
762
831
  del self.macros[command]
763
832
 
764
- self._register_command_parser(command, command_wrapper)
765
-
766
- setattr(self, cmd_func_name, command_wrapper)
833
+ setattr(self, command_func_name, command_method)
767
834
 
768
835
  def _install_completer_function(self, cmd_name: str, cmd_completer: CompleterFunc) -> None:
769
836
  completer_func_name = COMPLETER_FUNC_PREFIX + cmd_name
@@ -790,62 +857,66 @@ class Cmd(cmd.Cmd):
790
857
  cmdset.on_unregister()
791
858
  self._unregister_subcommands(cmdset)
792
859
 
793
- methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers(
860
+ methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
794
861
  cmdset,
795
862
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
796
863
  and hasattr(meth, '__name__')
797
864
  and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
798
865
  )
799
866
 
800
- for method in methods:
801
- cmd_name = method[0][len(COMMAND_FUNC_PREFIX) :]
867
+ for cmd_func_name, command_method in methods:
868
+ command = cmd_func_name[len(COMMAND_FUNC_PREFIX) :]
802
869
 
803
870
  # Enable the command before uninstalling it to make sure we remove both
804
871
  # the real functions and the ones used by the DisabledCommand object.
805
- if cmd_name in self.disabled_commands:
806
- self.enable_command(cmd_name)
872
+ if command in self.disabled_commands:
873
+ self.enable_command(command)
874
+
875
+ if command in self._cmd_to_command_sets:
876
+ del self._cmd_to_command_sets[command]
807
877
 
808
- if cmd_name in self._cmd_to_command_sets:
809
- del self._cmd_to_command_sets[cmd_name]
878
+ # Only remove the parser if this is the actual
879
+ # command since command synonyms don't own it.
880
+ if cmd_func_name == command_method.__name__:
881
+ self._command_parsers.remove(command_method)
810
882
 
811
- delattr(self, COMMAND_FUNC_PREFIX + cmd_name)
812
- if cmd_name in self._command_parsers:
813
- del self._command_parsers[cmd_name]
883
+ if hasattr(self, COMPLETER_FUNC_PREFIX + command):
884
+ delattr(self, COMPLETER_FUNC_PREFIX + command)
885
+ if hasattr(self, HELP_FUNC_PREFIX + command):
886
+ delattr(self, HELP_FUNC_PREFIX + command)
814
887
 
815
- if hasattr(self, COMPLETER_FUNC_PREFIX + cmd_name):
816
- delattr(self, COMPLETER_FUNC_PREFIX + cmd_name)
817
- if hasattr(self, HELP_FUNC_PREFIX + cmd_name):
818
- delattr(self, HELP_FUNC_PREFIX + cmd_name)
888
+ delattr(self, cmd_func_name)
819
889
 
820
890
  cmdset.on_unregistered()
821
891
  self._installed_command_sets.remove(cmdset)
822
892
 
823
893
  def _check_uninstallable(self, cmdset: CommandSet) -> None:
824
- methods: List[Tuple[str, Callable[[Any], Any]]] = inspect.getmembers(
894
+ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None:
895
+ for action in parser._actions:
896
+ if isinstance(action, argparse._SubParsersAction):
897
+ for subparser in action.choices.values():
898
+ attached_cmdset = getattr(subparser, constants.PARSER_ATTR_COMMANDSET, None)
899
+ if attached_cmdset is not None and attached_cmdset is not cmdset:
900
+ raise CommandSetRegistrationError(
901
+ 'Cannot uninstall CommandSet when another CommandSet depends on it'
902
+ )
903
+ check_parser_uninstallable(subparser)
904
+ break
905
+
906
+ methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers(
825
907
  cmdset,
826
908
  predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
827
909
  and hasattr(meth, '__name__')
828
910
  and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
829
911
  )
830
912
 
831
- for method in methods:
832
- command_name = method[0][len(COMMAND_FUNC_PREFIX) :]
833
- command_parser = self._command_parsers.get(command_name, None)
834
-
835
- def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None:
836
- for action in parser._actions:
837
- if isinstance(action, argparse._SubParsersAction):
838
- for subparser in action.choices.values():
839
- attached_cmdset = getattr(subparser, constants.PARSER_ATTR_COMMANDSET, None)
840
- if attached_cmdset is not None and attached_cmdset is not cmdset:
841
- raise CommandSetRegistrationError(
842
- 'Cannot uninstall CommandSet when another CommandSet depends on it'
843
- )
844
- check_parser_uninstallable(subparser)
845
- break
846
-
847
- if command_parser is not None:
848
- check_parser_uninstallable(command_parser)
913
+ for cmd_func_name, command_method in methods:
914
+ # We only need to check if it's safe to remove the parser if this
915
+ # is the actual command since command synonyms don't own it.
916
+ if cmd_func_name == command_method.__name__:
917
+ command_parser = self._command_parsers.get(command_method)
918
+ if command_parser is not None:
919
+ check_parser_uninstallable(command_parser)
849
920
 
850
921
  def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
851
922
  """
@@ -889,7 +960,7 @@ class Cmd(cmd.Cmd):
889
960
  raise CommandSetRegistrationError(
890
961
  f"Could not find command '{command_name}' needed by subcommand: {str(method)}"
891
962
  )
892
- command_parser = self._command_parsers.get(command_name, None)
963
+ command_parser = self._command_parsers.get(command_func)
893
964
  if command_parser is None:
894
965
  raise CommandSetRegistrationError(
895
966
  f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}"
@@ -991,7 +1062,7 @@ class Cmd(cmd.Cmd):
991
1062
  raise CommandSetRegistrationError(
992
1063
  f"Could not find command '{command_name}' needed by subcommand: {str(method)}"
993
1064
  )
994
- command_parser = self._command_parsers.get(command_name, None)
1065
+ command_parser = self._command_parsers.get(command_func)
995
1066
  if command_parser is None: # pragma: no cover
996
1067
  # This really shouldn't be possible since _register_subcommands would prevent this from happening
997
1068
  # but keeping in case it does for some strange reason
@@ -1141,123 +1212,67 @@ class Cmd(cmd.Cmd):
1141
1212
 
1142
1213
  def print_to(
1143
1214
  self,
1144
- dest: Union[TextIO, IO[str]],
1215
+ dest: IO[str],
1145
1216
  msg: Any,
1146
1217
  *,
1147
1218
  end: str = '\n',
1148
1219
  style: Optional[Callable[[str], str]] = None,
1149
- paged: bool = False,
1150
- chop: bool = False,
1151
1220
  ) -> None:
1221
+ """
1222
+ Print message to a given file object.
1223
+
1224
+ :param dest: the file object being written to
1225
+ :param msg: object to print
1226
+ :param end: string appended after the end of the message, default a newline
1227
+ :param style: optional style function to format msg with (e.g. ansi.style_success)
1228
+ """
1152
1229
  final_msg = style(msg) if style is not None else msg
1153
- if paged:
1154
- self.ppaged(final_msg, end=end, chop=chop, dest=dest)
1155
- else:
1156
- try:
1157
- ansi.style_aware_write(dest, f'{final_msg}{end}')
1158
- except BrokenPipeError:
1159
- # This occurs if a command's output is being piped to another
1160
- # process and that process closes before the command is
1161
- # finished. If you would like your application to print a
1162
- # warning message, then set the broken_pipe_warning attribute
1163
- # to the message you want printed.
1164
- if self.broken_pipe_warning:
1165
- sys.stderr.write(self.broken_pipe_warning)
1230
+ try:
1231
+ ansi.style_aware_write(dest, f'{final_msg}{end}')
1232
+ except BrokenPipeError:
1233
+ # This occurs if a command's output is being piped to another
1234
+ # process and that process closes before the command is
1235
+ # finished. If you would like your application to print a
1236
+ # warning message, then set the broken_pipe_warning attribute
1237
+ # to the message you want printed.
1238
+ if self.broken_pipe_warning:
1239
+ sys.stderr.write(self.broken_pipe_warning)
1166
1240
 
1167
- def poutput(
1168
- self,
1169
- msg: Any = '',
1170
- *,
1171
- end: str = '\n',
1172
- apply_style: bool = True,
1173
- paged: bool = False,
1174
- chop: bool = False,
1175
- ) -> None:
1241
+ def poutput(self, msg: Any = '', *, end: str = '\n') -> None:
1176
1242
  """Print message to self.stdout and appends a newline by default
1177
1243
 
1178
- Also handles BrokenPipeError exceptions for when a command's output has
1179
- been piped to another process and that process terminates before the
1180
- cmd2 command is finished executing.
1181
-
1182
1244
  :param msg: object to print
1183
1245
  :param end: string appended after the end of the message, default a newline
1184
- :param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
1185
- where the message text already has the desired style. Defaults to True.
1186
- :param paged: If True, pass the output through the configured pager.
1187
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1188
1246
  """
1189
- self.print_to(self.stdout, msg, end=end, style=ansi.style_output if apply_style else None, paged=paged, chop=chop)
1247
+ self.print_to(self.stdout, msg, end=end)
1190
1248
 
1191
- def perror(
1192
- self,
1193
- msg: Any = '',
1194
- *,
1195
- end: str = '\n',
1196
- apply_style: bool = True,
1197
- paged: bool = False,
1198
- chop: bool = False,
1199
- ) -> None:
1249
+ def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1200
1250
  """Print message to sys.stderr
1201
1251
 
1202
1252
  :param msg: object to print
1203
1253
  :param end: string appended after the end of the message, default a newline
1204
1254
  :param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
1205
1255
  where the message text already has the desired style. Defaults to True.
1206
- :param paged: If True, pass the output through the configured pager.
1207
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1208
1256
  """
1209
- self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None, paged=paged, chop=chop)
1257
+ self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None)
1210
1258
 
1211
- def psuccess(
1212
- self,
1213
- msg: Any = '',
1214
- *,
1215
- end: str = '\n',
1216
- paged: bool = False,
1217
- chop: bool = False,
1218
- ) -> None:
1219
- """Writes to stdout applying ansi.style_success by default
1259
+ def psuccess(self, msg: Any = '', *, end: str = '\n') -> None:
1260
+ """Wraps poutput, but applies ansi.style_success by default
1220
1261
 
1221
1262
  :param msg: object to print
1222
1263
  :param end: string appended after the end of the message, default a newline
1223
- :param paged: If True, pass the output through the configured pager.
1224
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1225
1264
  """
1226
- self.print_to(self.stdout, msg, end=end, style=ansi.style_success, paged=paged, chop=chop)
1265
+ msg = ansi.style_success(msg)
1266
+ self.poutput(msg, end=end)
1227
1267
 
1228
- def pwarning(
1229
- self,
1230
- msg: Any = '',
1231
- *,
1232
- end: str = '\n',
1233
- paged: bool = False,
1234
- chop: bool = False,
1235
- ) -> None:
1268
+ def pwarning(self, msg: Any = '', *, end: str = '\n') -> None:
1236
1269
  """Wraps perror, but applies ansi.style_warning by default
1237
1270
 
1238
1271
  :param msg: object to print
1239
1272
  :param end: string appended after the end of the message, default a newline
1240
- :param paged: If True, pass the output through the configured pager.
1241
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1242
- """
1243
- self.print_to(sys.stderr, msg, end=end, style=ansi.style_warning, paged=paged, chop=chop)
1244
-
1245
- def pfailure(
1246
- self,
1247
- msg: Any = '',
1248
- *,
1249
- end: str = '\n',
1250
- paged: bool = False,
1251
- chop: bool = False,
1252
- ) -> None:
1253
- """Writes to stderr applying ansi.style_error by default
1254
-
1255
- :param msg: object to print
1256
- :param end: string appended after the end of the message, default a newline
1257
- :param paged: If True, pass the output through the configured pager.
1258
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1259
1273
  """
1260
- self.print_to(sys.stderr, msg, end=end, style=ansi.style_error, paged=paged, chop=chop)
1274
+ msg = ansi.style_warning(msg)
1275
+ self.perror(msg, end=end, apply_style=False)
1261
1276
 
1262
1277
  def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
1263
1278
  """Print Exception message to sys.stderr. If debug is true, print exception traceback if one exists.
@@ -1286,36 +1301,20 @@ class Cmd(cmd.Cmd):
1286
1301
 
1287
1302
  self.perror(final_msg, end=end, apply_style=False)
1288
1303
 
1289
- def pfeedback(
1290
- self,
1291
- msg: Any,
1292
- *,
1293
- end: str = '\n',
1294
- apply_style: bool = True,
1295
- paged: bool = False,
1296
- chop: bool = False,
1297
- ) -> None:
1304
+ def pfeedback(self, msg: Any, *, end: str = '\n') -> None:
1298
1305
  """For printing nonessential feedback. Can be silenced with `quiet`.
1299
1306
  Inclusion in redirected output is controlled by `feedback_to_output`.
1300
1307
 
1301
1308
  :param msg: object to print
1302
1309
  :param end: string appended after the end of the message, default a newline
1303
- :param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
1304
- where the message text already has the desired style. Defaults to True.
1305
- :param paged: If True, pass the output through the configured pager.
1306
- :param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1307
1310
  """
1308
1311
  if not self.quiet:
1309
- self.print_to(
1310
- self.stdout if self.feedback_to_output else sys.stderr,
1311
- msg,
1312
- end=end,
1313
- style=ansi.style_output if apply_style else None,
1314
- paged=paged,
1315
- chop=chop,
1316
- )
1312
+ if self.feedback_to_output:
1313
+ self.poutput(msg, end=end)
1314
+ else:
1315
+ self.perror(msg, end=end, apply_style=False)
1317
1316
 
1318
- def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optional[Union[TextIO, IO[str]]] = None) -> None:
1317
+ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None:
1319
1318
  """Print output using a pager if it would go off screen and stdout isn't currently being redirected.
1320
1319
 
1321
1320
  Never uses a pager inside a script (Python or text) or when output is being redirected or piped or when
@@ -1328,53 +1327,44 @@ class Cmd(cmd.Cmd):
1328
1327
  - chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
1329
1328
  False -> causes lines longer than the screen width to wrap to the next line
1330
1329
  - wrapping is ideal when you want to keep users from having to use horizontal scrolling
1331
- :param dest: Optionally specify the destination stream to write to. If unspecified, defaults to self.stdout
1332
1330
 
1333
1331
  WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
1334
1332
  """
1335
- # msg can be any type, so convert to string before checking if it's blank
1336
- msg_str = str(msg)
1337
- dest = self.stdout if dest is None else dest
1338
-
1339
- # Consider None to be no data to print
1340
- if msg is None or msg_str == '':
1341
- return
1333
+ # Attempt to detect if we are not running within a fully functional terminal.
1334
+ # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
1335
+ functional_terminal = False
1342
1336
 
1343
- try:
1344
- import subprocess
1345
-
1346
- # Attempt to detect if we are not running within a fully functional terminal.
1347
- # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
1348
- functional_terminal = False
1337
+ if self.stdin.isatty() and self.stdout.isatty():
1338
+ if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
1339
+ functional_terminal = True
1349
1340
 
1350
- if self.stdin.isatty() and self.stdout.isatty():
1351
- if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
1352
- functional_terminal = True
1341
+ # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python).
1342
+ # Also only attempt to use a pager if actually running in a real fully functional terminal.
1343
+ if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
1344
+ final_msg = f"{msg}{end}"
1345
+ if ansi.allow_style == ansi.AllowStyle.NEVER:
1346
+ final_msg = ansi.strip_style(final_msg)
1353
1347
 
1354
- # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
1355
- # Also only attempt to use a pager if actually running in a real fully functional terminal
1356
- if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
1357
- if ansi.allow_style == ansi.AllowStyle.NEVER:
1358
- msg_str = ansi.strip_style(msg_str)
1359
- msg_str += end
1360
-
1361
- pager = self.pager
1362
- if chop:
1363
- pager = self.pager_chop
1348
+ pager = self.pager
1349
+ if chop:
1350
+ pager = self.pager_chop
1364
1351
 
1352
+ try:
1365
1353
  # Prevent KeyboardInterrupts while in the pager. The pager application will
1366
1354
  # still receive the SIGINT since it is in the same process group as us.
1367
1355
  with self.sigint_protection:
1368
- pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
1369
- pipe_proc.communicate(msg_str.encode('utf-8', 'replace'))
1370
- else:
1371
- ansi.style_aware_write(dest, f'{msg_str}{end}')
1372
- except BrokenPipeError:
1373
- # This occurs if a command's output is being piped to another process and that process closes before the
1374
- # command is finished. If you would like your application to print a warning message, then set the
1375
- # broken_pipe_warning attribute to the message you want printed.`
1376
- if self.broken_pipe_warning:
1377
- sys.stderr.write(self.broken_pipe_warning)
1356
+ import subprocess
1357
+
1358
+ pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout)
1359
+ pipe_proc.communicate(final_msg.encode('utf-8', 'replace'))
1360
+ except BrokenPipeError:
1361
+ # This occurs if a command's output is being piped to another process and that process closes before the
1362
+ # command is finished. If you would like your application to print a warning message, then set the
1363
+ # broken_pipe_warning attribute to the message you want printed.`
1364
+ if self.broken_pipe_warning:
1365
+ sys.stderr.write(self.broken_pipe_warning)
1366
+ else:
1367
+ self.poutput(msg, end=end)
1378
1368
 
1379
1369
  # ----- Methods related to tab completion -----
1380
1370
 
@@ -2098,12 +2088,12 @@ class Cmd(cmd.Cmd):
2098
2088
  else:
2099
2089
  # There's no completer function, next see if the command uses argparse
2100
2090
  func = self.cmd_func(command)
2101
- argparser = self._command_parsers.get(command, None)
2091
+ argparser = None if func is None else self._command_parsers.get(func)
2102
2092
 
2103
2093
  if func is not None and argparser is not None:
2104
2094
  # Get arguments for complete()
2105
2095
  preserve_quotes = getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES)
2106
- cmd_set = self._cmd_to_command_sets[command] if command in self._cmd_to_command_sets else None
2096
+ cmd_set = self.find_commandset_for_command(command)
2107
2097
 
2108
2098
  # Create the argparse completer
2109
2099
  completer_type = self._determine_ap_completer_type(argparser)
@@ -3044,19 +3034,9 @@ class Cmd(cmd.Cmd):
3044
3034
 
3045
3035
  helpfunc now contains a reference to the ``do_help`` method
3046
3036
  """
3047
- func_name = self._cmd_func_name(command)
3048
- if func_name:
3049
- return cast(Optional[CommandFunc], getattr(self, func_name))
3050
- return None
3051
-
3052
- def _cmd_func_name(self, command: str) -> str:
3053
- """Get the method name associated with a given command.
3054
-
3055
- :param command: command to look up method name which implements it
3056
- :return: method name which implements the given command
3057
- """
3058
- target = constants.COMMAND_FUNC_PREFIX + command
3059
- return target if callable(getattr(self, target, None)) else ''
3037
+ func_name = constants.COMMAND_FUNC_PREFIX + command
3038
+ func = getattr(self, func_name, None)
3039
+ return cast(CommandFunc, func) if callable(func) else None
3060
3040
 
3061
3041
  def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool:
3062
3042
  """This executes the actual do_* method for a command.
@@ -3404,7 +3384,7 @@ class Cmd(cmd.Cmd):
3404
3384
  alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string."
3405
3385
  alias_epilog = "See also:\n" " macro"
3406
3386
  alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog)
3407
- alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND', required=True)
3387
+ alias_parser.add_subparsers(metavar='SUBCOMMAND', required=True)
3408
3388
 
3409
3389
  # Preserve quotes since we are passing strings to other commands
3410
3390
  @with_argparser(alias_parser, preserve_quotes=True)
@@ -3572,7 +3552,7 @@ class Cmd(cmd.Cmd):
3572
3552
  macro_description = "Manage macros\n" "\n" "A macro is similar to an alias, but it can contain argument placeholders."
3573
3553
  macro_epilog = "See also:\n" " alias"
3574
3554
  macro_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog)
3575
- macro_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND', required=True)
3555
+ macro_parser.add_subparsers(metavar='SUBCOMMAND', required=True)
3576
3556
 
3577
3557
  # Preserve quotes since we are passing strings to other commands
3578
3558
  @with_argparser(macro_parser, preserve_quotes=True)
@@ -3820,9 +3800,7 @@ class Cmd(cmd.Cmd):
3820
3800
  return []
3821
3801
 
3822
3802
  # Check if this command uses argparse
3823
- func = self.cmd_func(command)
3824
- argparser = self._command_parsers.get(command, None)
3825
- if func is None or argparser is None:
3803
+ if (func := self.cmd_func(command)) is None or (argparser := self._command_parsers.get(func)) is None:
3826
3804
  return []
3827
3805
 
3828
3806
  completer = argparse_completer.DEFAULT_AP_COMPLETER(argparser, self)
@@ -3857,7 +3835,7 @@ class Cmd(cmd.Cmd):
3857
3835
  # Getting help for a specific command
3858
3836
  func = self.cmd_func(args.command)
3859
3837
  help_func = getattr(self, constants.HELP_FUNC_PREFIX + args.command, None)
3860
- argparser = self._command_parsers.get(args.command, None)
3838
+ argparser = None if func is None else self._command_parsers.get(func)
3861
3839
 
3862
3840
  # If the command function uses argparse, then use argparse's help
3863
3841
  if func is not None and argparser is not None:
@@ -3979,28 +3957,29 @@ class Cmd(cmd.Cmd):
3979
3957
  def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]:
3980
3958
  # Get a sorted list of help topics
3981
3959
  help_topics = sorted(self.get_help_topics(), key=self.default_sort_key)
3960
+
3982
3961
  # Get a sorted list of visible command names
3983
3962
  visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key)
3984
3963
  cmds_doc: List[str] = []
3985
3964
  cmds_undoc: List[str] = []
3986
3965
  cmds_cats: Dict[str, List[str]] = {}
3987
3966
  for command in visible_commands:
3988
- func = self.cmd_func(command)
3967
+ func = cast(CommandFunc, self.cmd_func(command))
3989
3968
  has_help_func = False
3969
+ has_parser = func in self._command_parsers
3990
3970
 
3991
3971
  if command in help_topics:
3992
3972
  # Prevent the command from showing as both a command and help topic in the output
3993
3973
  help_topics.remove(command)
3994
3974
 
3995
3975
  # Non-argparse commands can have help_functions for their documentation
3996
- if command not in self._command_parsers:
3997
- has_help_func = True
3976
+ has_help_func = not has_parser
3998
3977
 
3999
3978
  if hasattr(func, constants.CMD_ATTR_HELP_CATEGORY):
4000
3979
  category: str = getattr(func, constants.CMD_ATTR_HELP_CATEGORY)
4001
3980
  cmds_cats.setdefault(category, [])
4002
3981
  cmds_cats[category].append(command)
4003
- elif func.__doc__ or has_help_func:
3982
+ elif func.__doc__ or has_help_func or has_parser:
4004
3983
  cmds_doc.append(command)
4005
3984
  else:
4006
3985
  cmds_undoc.append(command)
@@ -4035,11 +4014,17 @@ class Cmd(cmd.Cmd):
4035
4014
  # Try to get the documentation string for each command
4036
4015
  topics = self.get_help_topics()
4037
4016
  for command in cmds:
4038
- cmd_func = self.cmd_func(command)
4017
+ if (cmd_func := self.cmd_func(command)) is None:
4018
+ continue
4019
+
4039
4020
  doc: Optional[str]
4040
4021
 
4022
+ # If this is an argparse command, use its description.
4023
+ if (cmd_parser := self._command_parsers.get(cmd_func)) is not None:
4024
+ doc = cmd_parser.description
4025
+
4041
4026
  # Non-argparse commands can have help_functions for their documentation
4042
- if command not in self._command_parsers and command in topics:
4027
+ elif command in topics:
4043
4028
  help_func = getattr(self, constants.HELP_FUNC_PREFIX + command)
4044
4029
  result = io.StringIO()
4045
4030
 
@@ -5436,12 +5421,13 @@ class Cmd(cmd.Cmd):
5436
5421
  if command not in self.disabled_commands:
5437
5422
  return
5438
5423
 
5424
+ cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
5439
5425
  help_func_name = constants.HELP_FUNC_PREFIX + command
5440
5426
  completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
5441
5427
 
5442
5428
  # Restore the command function to its original value
5443
5429
  dc = self.disabled_commands[command]
5444
- setattr(self, self._cmd_func_name(command), dc.command_function)
5430
+ setattr(self, cmd_func_name, dc.command_function)
5445
5431
 
5446
5432
  # Restore the help function to its original value
5447
5433
  if dc.help_function is None:
@@ -5489,6 +5475,7 @@ class Cmd(cmd.Cmd):
5489
5475
  if command_function is None:
5490
5476
  raise AttributeError(f"'{command}' does not refer to a command")
5491
5477
 
5478
+ cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
5492
5479
  help_func_name = constants.HELP_FUNC_PREFIX + command
5493
5480
  completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
5494
5481
 
@@ -5503,7 +5490,7 @@ class Cmd(cmd.Cmd):
5503
5490
  new_func = functools.partial(
5504
5491
  self._report_disabled_command_usage, message_to_print=message_to_print.replace(constants.COMMAND_NAME, command)
5505
5492
  )
5506
- setattr(self, self._cmd_func_name(command), new_func)
5493
+ setattr(self, cmd_func_name, new_func)
5507
5494
  setattr(self, help_func_name, new_func)
5508
5495
 
5509
5496
  # Set the completer to a function that returns a blank list
cmd2/decorators.py CHANGED
@@ -346,7 +346,9 @@ def with_argparser(
346
346
  statement, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(
347
347
  command_name, statement_arg, preserve_quotes
348
348
  )
349
- arg_parser = cmd2_app._command_parsers.get(command_name, None)
349
+
350
+ # Pass cmd_wrapper instead of func, since it contains the parser info.
351
+ arg_parser = cmd2_app._command_parsers.get(cmd_wrapper)
350
352
  if arg_parser is None:
351
353
  # This shouldn't be possible to reach
352
354
  raise ValueError(f'No argument parser found for {command_name}') # pragma: no cover
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cmd2
3
- Version: 2.5.6
3
+ Version: 2.5.8
4
4
  Summary: cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
5
5
  Author: cmd2 Contributors
6
6
  License: The MIT License (MIT)
@@ -43,43 +43,43 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
43
  Requires-Python: >=3.8
44
44
  Description-Content-Type: text/markdown
45
45
  License-File: LICENSE
46
+ Requires-Dist: gnureadline; platform_system == "Darwin"
46
47
  Requires-Dist: pyperclip
48
+ Requires-Dist: pyreadline3; platform_system == "Windows"
47
49
  Requires-Dist: wcwidth
48
- Requires-Dist: gnureadline ; platform_system == "Darwin"
49
- Requires-Dist: pyreadline3 ; platform_system == "Windows"
50
50
  Provides-Extra: build
51
- Requires-Dist: build ; extra == 'build'
52
- Requires-Dist: setuptools ; extra == 'build'
53
- Requires-Dist: setuptools-scm ; extra == 'build'
51
+ Requires-Dist: build; extra == "build"
52
+ Requires-Dist: setuptools; extra == "build"
53
+ Requires-Dist: setuptools-scm; extra == "build"
54
54
  Provides-Extra: dev
55
- Requires-Dist: codecov ; extra == 'dev'
56
- Requires-Dist: doc8 ; extra == 'dev'
57
- Requires-Dist: invoke ; extra == 'dev'
58
- Requires-Dist: mypy ; extra == 'dev'
59
- Requires-Dist: pytest ; extra == 'dev'
60
- Requires-Dist: pytest-cov ; extra == 'dev'
61
- Requires-Dist: pytest-mock ; extra == 'dev'
62
- Requires-Dist: sphinx ; extra == 'dev'
63
- Requires-Dist: sphinx-autobuild ; extra == 'dev'
64
- Requires-Dist: sphinx-rtd-theme ; extra == 'dev'
65
- Requires-Dist: ruff ; extra == 'dev'
66
- Requires-Dist: twine ; extra == 'dev'
55
+ Requires-Dist: codecov; extra == "dev"
56
+ Requires-Dist: doc8; extra == "dev"
57
+ Requires-Dist: invoke; extra == "dev"
58
+ Requires-Dist: mypy; extra == "dev"
59
+ Requires-Dist: pytest; extra == "dev"
60
+ Requires-Dist: pytest-cov; extra == "dev"
61
+ Requires-Dist: pytest-mock; extra == "dev"
62
+ Requires-Dist: sphinx; extra == "dev"
63
+ Requires-Dist: sphinx-autobuild; extra == "dev"
64
+ Requires-Dist: sphinx-rtd-theme; extra == "dev"
65
+ Requires-Dist: ruff; extra == "dev"
66
+ Requires-Dist: twine; extra == "dev"
67
67
  Provides-Extra: docs
68
- Requires-Dist: setuptools ; extra == 'docs'
69
- Requires-Dist: setuptools-scm ; extra == 'docs'
70
- Requires-Dist: sphinx ; extra == 'docs'
71
- Requires-Dist: sphinx-autobuild ; extra == 'docs'
72
- Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
68
+ Requires-Dist: setuptools; extra == "docs"
69
+ Requires-Dist: setuptools_scm; extra == "docs"
70
+ Requires-Dist: sphinx; extra == "docs"
71
+ Requires-Dist: sphinx-autobuild; extra == "docs"
72
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
73
73
  Provides-Extra: test
74
- Requires-Dist: codecov ; extra == 'test'
75
- Requires-Dist: coverage ; extra == 'test'
76
- Requires-Dist: pytest ; extra == 'test'
77
- Requires-Dist: pytest-cov ; extra == 'test'
78
- Requires-Dist: pytest-mock ; extra == 'test'
74
+ Requires-Dist: codecov; extra == "test"
75
+ Requires-Dist: coverage; extra == "test"
76
+ Requires-Dist: pytest; extra == "test"
77
+ Requires-Dist: pytest-cov; extra == "test"
78
+ Requires-Dist: pytest-mock; extra == "test"
79
79
  Provides-Extra: validate
80
- Requires-Dist: mypy ; extra == 'validate'
81
- Requires-Dist: ruff ; extra == 'validate'
82
- Requires-Dist: types-setuptools ; extra == 'validate'
80
+ Requires-Dist: mypy; extra == "validate"
81
+ Requires-Dist: ruff; extra == "validate"
82
+ Requires-Dist: types-setuptools; extra == "validate"
83
83
 
84
84
  <h1 align="center">cmd2 : immersive interactive command line applications</h1>
85
85
 
@@ -89,7 +89,6 @@ Requires-Dist: types-setuptools ; extra == 'validate'
89
89
  [![Documentation Status](https://readthedocs.org/projects/cmd2/badge/?version=latest)](http://cmd2.readthedocs.io/en/latest/?badge=latest)
90
90
  <a href="https://discord.gg/RpVG6tk"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg" alt="Chat"></a>
91
91
 
92
-
93
92
  <p align="center">
94
93
  <a href="#the-developers-toolbox">Developer's Toolbox</a> •
95
94
  <a href="#philosophy">Philosophy</a> •
@@ -104,17 +103,15 @@ Requires-Dist: types-setuptools ; extra == 'validate'
104
103
 
105
104
  cmd2 is a tool for building interactive command line applications in Python. Its goal is to make it
106
105
  quick and easy for developers to build feature-rich and user-friendly interactive command line
107
- applications. It provides a simple API which is an extension of Python's built-in
108
- [cmd](https://docs.python.org/3/library/cmd.html) module. cmd2 provides a wealth of features on top
106
+ applications. It provides a simple API which is an extension of Python's built-in
107
+ [cmd](https://docs.python.org/3/library/cmd.html) module. cmd2 provides a wealth of features on top
109
108
  of cmd to make your life easier and eliminates much of the boilerplate code which would be necessary
110
109
  when using cmd.
111
110
 
112
- The developers toolbox
113
- ----------------------
111
+ ## The developers toolbox
114
112
 
115
113
  ![system schema](https://raw.githubusercontent.com/python-cmd2/cmd2/master/.github/images/graph.drawio.png)
116
114
 
117
-
118
115
  When creating solutions developers have no shortage of tools to create rich and smart user interfaces.
119
116
  System administrators have long been duct taping together brittle workflows based on a menagerie of simple command line tools created by strangers on github and the guy down the hall.
120
117
  Unfortunately, when CLIs become significantly complex the ease of command discoverability tends to fade quickly.
@@ -125,15 +122,13 @@ The price we pay for beautifully colored displays is complexity required to aggr
125
122
  The `cmd2` framework provides a great mixture of both worlds. Application designers can easily create complex applications and rely on the cmd2 library to offer effortless user facing help and extensive tab completion.
126
123
  When users become comfortable with functionality, cmd2 turns into a feature rich library enabling a smooth transition to full automation. If designed with enough forethought, a well implemented cmd2 application can serve as a boutique workflow tool. `cmd2` pulls off this flexibility based on two pillars of philosophy:
127
124
 
128
- * Tab Completion
129
- * Automation Transition
125
+ - Tab Completion
126
+ - Automation Transition
130
127
 
131
- Philosophy
132
- -------------
128
+ ## Philosophy
133
129
 
134
130
  <a href="https://imgflip.com/i/63h03x"><img src="https://i.imgflip.com/63h03x.jpg" title="made at imgflip.com" width="70%" height="%70"/></a>
135
131
 
136
-
137
132
  Deep extensive tab completion and help text generation based on the argparse library create the first pillar of 'ease of command discovery'. The following is a list of features in this category.
138
133
 
139
134
  - Great tab completion of commands, subcommands, file system paths, and shell commands.
@@ -152,9 +147,8 @@ cmd2 creates the second pillar of 'ease of transition to automation' through ali
152
147
  - Powerful and flexible built-in Python scripting of your application using the `run_pyscript` command
153
148
  - Transcripts for use with built-in regression can be automatically generated from `history -t` or `run_script -t`
154
149
 
150
+ ## Installation
155
151
 
156
- Installation
157
- ------------
158
152
  On all operating systems, the latest stable version of `cmd2` can be installed using pip:
159
153
 
160
154
  ```bash
@@ -167,31 +161,26 @@ For information on other installation options, see
167
161
  [Installation Instructions](https://cmd2.readthedocs.io/en/latest/overview/installation.html) in the cmd2
168
162
  documentation.
169
163
 
164
+ ## Documentation
170
165
 
171
- Documentation
172
- -------------
173
166
  The latest documentation for cmd2 can be read online here: https://cmd2.readthedocs.io/en/latest/
174
167
 
175
168
  It is available in HTML, PDF, and ePub formats.
176
169
 
177
-
178
170
  The best way to learn the cmd2 api is to delve into the example applications located in source under examples.
179
171
 
180
- Tutorials
181
- ---------
182
-
183
- * PyOhio 2019 presentation:
184
- * [video](https://www.youtube.com/watch?v=pebeWrTqIIw)
185
- * [slides](https://github.com/python-cmd2/talks/blob/master/PyOhio_2019/cmd2-PyOhio_2019.pdf)
186
- * [example code](https://github.com/python-cmd2/talks/tree/master/PyOhio_2019/examples)
187
- * [Cookiecutter](https://github.com/cookiecutter/cookiecutter) Templates from community
188
- * Basic cookiecutter template for cmd2 application : https://github.com/jayrod/cookiecutter-python-cmd2
189
- * Advanced cookiecutter template with external plugin support : https://github.com/jayrod/cookiecutter-python-cmd2-ext-plug
190
- * [Example Applications](https://github.com/jayrod/cmd2-example-apps)
172
+ ## Tutorials
191
173
 
174
+ - PyOhio 2019 presentation:
175
+ - [video](https://www.youtube.com/watch?v=pebeWrTqIIw)
176
+ - [slides](https://github.com/python-cmd2/talks/blob/master/PyOhio_2019/cmd2-PyOhio_2019.pdf)
177
+ - [example code](https://github.com/python-cmd2/talks/tree/master/PyOhio_2019/examples)
178
+ - [Cookiecutter](https://github.com/cookiecutter/cookiecutter) Templates from community
179
+ - Basic cookiecutter template for cmd2 application : https://github.com/jayrod/cookiecutter-python-cmd2
180
+ - Advanced cookiecutter template with external plugin support : https://github.com/jayrod/cookiecutter-python-cmd2-ext-plug
181
+ - [Example Applications](https://github.com/jayrod/cmd2-example-apps)
192
182
 
193
- Hello World
194
- -----------
183
+ ## Hello World
195
184
 
196
185
  ```python
197
186
  #!/usr/bin/env python
@@ -212,36 +201,48 @@ if __name__ == '__main__':
212
201
 
213
202
  ```
214
203
 
215
-
216
- Found a bug?
217
- ------------
218
-
219
- If you think you've found a bug, please first read through the open [Issues](https://github.com/python-cmd2/cmd2/issues). If you're confident it's a new bug, go ahead and create a new GitHub issue. Be sure to include as much information as possible so we can reproduce the bug. At a minimum, please state the following:
220
-
221
- * ``cmd2`` version
222
- * Python version
223
- * OS name and version
224
- * What you did to cause the bug to occur
225
- * Include any traceback or error message associated with the bug
226
-
227
-
228
- Projects using cmd2
229
- -------------------------------
230
-
231
- | Application Name | Description | |
232
- |-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|---|
233
- | [Jok3r](http://www.jok3r-framework.com) | Network & Web Pentest Automation Framework | |
234
- | [CephFS Shell](https://github.com/ceph/ceph) | [Ceph](https://ceph.com/) is a distributed object, block, and file storage platform | |
235
- | [psiTurk](https://psiturk.org) | An open platform for science on Amazon Mechanical Turk | |
236
- | [Poseidon](https://github.com/CyberReboot/poseidon) | Leverages software-defined networks (SDNs) to acquire and then feed network traffic to a number of machine learning techniques. | |
237
- | [Unipacker](https://github.com/unipacker/unipacker) | Automatic and platform-independent unpacker for Windows binaries based on emulation | |
238
- | [tomcatmanager](https://github.com/tomcatmanager/tomcatmanager) | A command line tool and python library for managing a tomcat server | |
239
- | [Expliot](https://gitlab.com/expliot_framework/expliot) | Internet of Things (IoT) exploitation framework | |
240
- | [mptcpanalyzer]() | Tool to help analyze mptcp pcaps | |
241
- | [clanvas](https://github.com/marklalor/clanvas) | Command-line client for Canvas by Instructure | |
242
-
204
+ ## Found a bug?
205
+
206
+ If you think you've found a bug, please first read through the open [Issues](https://github.com/python-cmd2/cmd2/issues). If you're confident it's a new bug, go ahead and create a new GitHub issue. Be sure to include as much information as possible so we can reproduce the bug. At a minimum, please state the following:
207
+
208
+ - `cmd2` version
209
+ - Python version
210
+ - OS name and version
211
+ - What you did to cause the bug to occur
212
+ - Include any traceback or error message associated with the bug
213
+
214
+ ## Projects using cmd2
215
+
216
+ | Application Name | Description | Organization or Author |
217
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
218
+ | [CephFS Shell](https://github.com/ceph/ceph) | The Ceph File System, or CephFS, is a POSIX-compliant file system built on top of Ceph’s distributed object store | [ceph](https://ceph.com/) |
219
+ | [garak](https://github.com/NVIDIA/garak) | LLM vulnerability scanner that checks if an LLM can be made to fail in a way we don't want | [NVIDIA](https://github.com/NVIDIA) |
220
+ | [medusa](https://github.com/Ch0pin/medusa) | Binary instrumentation framework that that automates processes for the dynamic analysis of Android and iOS Applications | [Ch0pin](https://github.com/Ch0pin) |
221
+ | [InternalBlue](https://github.com/seemoo-lab/internalblue) | Bluetooth experimentation framework for Broadcom and Cypress chips | [Secure Mobile Networking Lab](https://github.com/seemoo-lab) |
222
+ | [SCCMHunter](https://github.com/garrettfoster13/sccmhunter) | A post-ex tool built to streamline identifying, profiling, and attacking SCCM related assets in an Active Directory domain | [Garret Foster](https://github.com/garrettfoster13) |
223
+ | [Unipacker](https://github.com/unipacker/unipacker) | Automatic and platform-independent unpacker for Windows binaries based on emulation | [unipacker](https://github.com/unipacker) |
224
+ | [Frankenstein](https://github.com/seemoo-lab/frankenstein) | Broadcom and Cypress firmware emulation for fuzzing and further full-stack debugging | [Secure Mobile Networking Lab](https://github.com/seemoo-lab) |
225
+ | [Poseidon](https://github.com/faucetsdn/poseidon) | Leverages software-defined networks (SDNs) to acquire and then feed network traffic to a number of machine learning techniques. | [Faucet SDN](https://github.com/faucetsdn) |
226
+ | [DFTimewolf](https://github.com/log2timeline/dftimewolf) | A framework for orchestrating forensic collection, processing and data export | [log2timeline](https://github.com/log2timeline) |
227
+ | [GAP SDK](https://github.com/GreenWaves-Technologies/gap_sdk) | SDK for Greenwaves Technologies' GAP8 IoT Application Processor | [GreenWaves Technologies](https://github.com/GreenWaves-Technologies) |
228
+ | [REW Sploit](https://github.com/REW-sploit/REW-sploit) | Emulate and Dissect Metasploit Framework (MSF) and other attacks | [REW-sploit](https://github.com/REW-sploit) |
229
+ | [tomcatmanager](https://github.com/tomcatmanager/tomcatmanager) | A command line tool and python library for managing a tomcat server | [tomcatmanager](https://github.com/tomcatmanager) |
230
+ | [Falcon Toolkit](https://github.com/CrowdStrike/Falcon-Toolkit) | Unleash the power of the CrowdStrike Falcon Platform at the CLI | [CrowdStrike](https://github.com/CrowdStrike) |
231
+ | [EXPLIoT](https://gitlab.com/expliot_framework/expliot) | Internet of Things Security Testing and Exploitation framework | [expliot_framework](https://gitlab.com/expliot_framework/) |
243
232
 
244
233
  Possibly defunct but still good examples
245
234
 
246
- * [JSShell](https://github.com/Den1al/JSShell)
247
- * [FLASHMINGO](https://github.com/fireeye/flashmingo)
235
+ | Application Name | Description | Organization or Author |
236
+ | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
237
+ | [Katana](https://github.com/JohnHammond/katana) | Automatic CTF Challenge Solver | [John Hammond](https://github.com/JohnHammond) |
238
+ | [SatanSword](https://github.com/Lucifer1993/SatanSword) (in Chinese) | Comprehensive Penetration Framework for Red Teaming | [Lucifer1993](https://github.com/Lucifer1993) |
239
+ | [Jok3r](http://www.jok3r-framework.com) | Network & Web Pentest Automation Framework | [Koutto](https://github.com/koutto) |
240
+ | [Counterfit](https://github.com/Azure/counterfit) | a CLI that provides a generic automation layer for assessing the security of ML models | [Microsoft Azure](https://github.com/Azure) |
241
+ | [Overlord](https://github.com/qsecure-labs/overlord) | Red Teaming Infrastructure Automation | [QSecure Labs](https://github.com/qsecure-labs) |
242
+ | [Automated Reconnaissance Pipeline](https://github.com/epi052/recon-pipeline) | An automated target reconnaissance pipeline | [epi052](https://github.com/epi052) |
243
+ | [JSShell](https://github.com/Den1al/JSShell) | An interactive multi-user web JavaScript (JS) shell | [Den1al](https://github.com/Den1al) |
244
+ | [RedShell](https://github.com/Verizon/redshell) | An interactive command prompt for red teaming and pentesting | [Verizon](https://github.com/Verizon) |
245
+ | [FLASHMINGO](https://github.com/mandiant/flashmingo) | Automatic analysis of SWF files based on some heuristics. Extensible via plugins. | [Mandiant](https://github.com/mandiant) |
246
+ | [psiTurk](https://github.com/NYUCCL/psiTurk) | An open platform for science on Amazon Mechanical Turk | [NYU Computation and Cognition Lab](https://github.com/NYUCCL) |
247
+
248
+ Note: If you have created an application based on `cmd2` that you would like us to mention here, please get in touch.
@@ -1,12 +1,12 @@
1
1
  cmd2/__init__.py,sha256=C6IHJ_uHgkqejuRwA10IK0-OzPxHKyZbPn82nMUwn-A,2602
2
- cmd2/ansi.py,sha256=BasPj_Wla0cEyjpe_zu-cn24CgVdSXK3sfxknLM1WcY,32203
2
+ cmd2/ansi.py,sha256=DD1yVk4EnbKSmGM9Tj8jxWv23UZBVjbhdf0oOVhhl_c,32054
3
3
  cmd2/argparse_completer.py,sha256=6z0zmODqM7vhQhI0c6JUvCmR2V4qfdWQEyTZeNIea8c,36505
4
4
  cmd2/argparse_custom.py,sha256=bbAYRr79ZSJWGchd7trmRQDHWAsSJaW0dTQMp65Mrv4,57806
5
5
  cmd2/clipboard.py,sha256=7EISono76d7djj17hbvR9cCTB7jVGSBkUjgVQiMyUjE,549
6
- cmd2/cmd2.py,sha256=_u8AqoPQ_3kydZOT_PJOb46qRqtHGKoUwN_g9mSwFfU,261510
6
+ cmd2/cmd2.py,sha256=--45LzbUB5vLrrHYPV_pSQK3H9ePAacb4lBlXBQfB1A,261355
7
7
  cmd2/command_definition.py,sha256=OscFEk-O2N20fb8_fhkczqclaCxOwYyxNTnXWXOlngg,7655
8
8
  cmd2/constants.py,sha256=DioxETv-_HzcMUnjuPyz7Cqw4YEt_MTrzOMotiHj-Jo,1981
9
- cmd2/decorators.py,sha256=tNVvNyOR6uZW_bkU1ti13bBDNKbN5V-AJtbTns_aSOQ,19743
9
+ cmd2/decorators.py,sha256=XXQKqei5CVM6k5zt8CdBmIDan-QwuN3YgJiaIsabbuA,19820
10
10
  cmd2/exceptions.py,sha256=DwX7rQmon4eRyX1ZK4yne4lObdPO1j5Agg3Bav6pXwU,3600
11
11
  cmd2/history.py,sha256=GkJ3pZQNGNoXa9pYLul_iaqLeK132V5nLZZG6MsIEus,15000
12
12
  cmd2/parsing.py,sha256=zWG_iDjUU6TWVNwMeY-yn6AIa2jx12Q9ey4DA9DGZFc,28272
@@ -17,8 +17,8 @@ cmd2/rl_utils.py,sha256=HVPdNjuYyU67-XerPUJArmzhohzI1ABrZhU9cLc-EIs,11538
17
17
  cmd2/table_creator.py,sha256=qoxt9s3MxQkfSkp4cIPFMlVzC7kRjUU55p0ow7pFQus,47544
18
18
  cmd2/transcript.py,sha256=I0sD38RbG79Qz9GXIlfh1pHSNmWBTY2SLZAKEdSLWI4,9182
19
19
  cmd2/utils.py,sha256=4eBvbG6yqe3wg9wwi2jdng2iy5gghueNQjuF2afSyuI,49385
20
- cmd2-2.5.6.dist-info/LICENSE,sha256=QXrW0Z0merk9mncyUkn-sgRxhT8_o1dL5HEaBNH47Q4,1099
21
- cmd2-2.5.6.dist-info/METADATA,sha256=8_-ALhxdH1tG6ZonYNcVZJ5wdpMCZ5sTj4Ka2axdLDM,13308
22
- cmd2-2.5.6.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
23
- cmd2-2.5.6.dist-info/top_level.txt,sha256=gJbOJmyrARwLhm5diXAtzlNQdxbDZ8iRJ8HJi65_5hg,5
24
- cmd2-2.5.6.dist-info/RECORD,,
20
+ cmd2-2.5.8.dist-info/LICENSE,sha256=QXrW0Z0merk9mncyUkn-sgRxhT8_o1dL5HEaBNH47Q4,1099
21
+ cmd2-2.5.8.dist-info/METADATA,sha256=-y-uEodZc06B7Akuvlb5oRWVfaTmuUqjwJZ4kGuvR_I,18098
22
+ cmd2-2.5.8.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
23
+ cmd2-2.5.8.dist-info/top_level.txt,sha256=gJbOJmyrARwLhm5diXAtzlNQdxbDZ8iRJ8HJi65_5hg,5
24
+ cmd2-2.5.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes