cmd2 2.5.6__py3-none-any.whl → 2.5.7__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 +195 -133
- cmd2/decorators.py +3 -1
- {cmd2-2.5.6.dist-info → cmd2-2.5.7.dist-info}/METADATA +31 -31
- {cmd2-2.5.6.dist-info → cmd2-2.5.7.dist-info}/RECORD +7 -7
- {cmd2-2.5.6.dist-info → cmd2-2.5.7.dist-info}/WHEEL +1 -1
- {cmd2-2.5.6.dist-info → cmd2-2.5.7.dist-info}/LICENSE +0 -0
- {cmd2-2.5.6.dist-info → cmd2-2.5.7.dist-info}/top_level.txt +0 -0
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
|
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
|
661
|
-
command =
|
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(
|
664
|
-
installed_attributes.append(
|
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(
|
681
|
-
utils.categorize(
|
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
|
722
|
-
|
723
|
-
|
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
|
-
|
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
|
-
|
737
|
-
|
738
|
-
|
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
|
-
|
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
|
-
|
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,
|
747
|
-
raise CommandSetRegistrationError(f'Attribute already exists: {
|
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
|
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[
|
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
|
801
|
-
|
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
|
806
|
-
self.enable_command(
|
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
|
809
|
-
|
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
|
-
|
812
|
-
|
813
|
-
|
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
|
-
|
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
|
-
|
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
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
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(
|
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(
|
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
|
@@ -1332,49 +1403,43 @@ class Cmd(cmd.Cmd):
|
|
1332
1403
|
|
1333
1404
|
WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
|
1334
1405
|
"""
|
1335
|
-
# msg can be any type, so convert to string before checking if it's blank
|
1336
|
-
msg_str = str(msg)
|
1337
1406
|
dest = self.stdout if dest is None else dest
|
1338
1407
|
|
1339
|
-
#
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
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
|
1408
|
+
# Attempt to detect if we are not running within a fully functional terminal.
|
1409
|
+
# Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
|
1410
|
+
functional_terminal = False
|
1349
1411
|
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1412
|
+
if self.stdin.isatty() and dest.isatty():
|
1413
|
+
if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
|
1414
|
+
functional_terminal = True
|
1353
1415
|
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1416
|
+
# Don't attempt to use a pager that can block if redirecting or running a script (either text or Python).
|
1417
|
+
# Also only attempt to use a pager if actually running in a real fully functional terminal.
|
1418
|
+
if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
|
1419
|
+
final_msg = f"{msg}{end}"
|
1420
|
+
if ansi.allow_style == ansi.AllowStyle.NEVER:
|
1421
|
+
final_msg = ansi.strip_style(final_msg)
|
1360
1422
|
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1423
|
+
pager = self.pager
|
1424
|
+
if chop:
|
1425
|
+
pager = self.pager_chop
|
1364
1426
|
|
1427
|
+
try:
|
1365
1428
|
# Prevent KeyboardInterrupts while in the pager. The pager application will
|
1366
1429
|
# still receive the SIGINT since it is in the same process group as us.
|
1367
1430
|
with self.sigint_protection:
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1431
|
+
import subprocess
|
1432
|
+
|
1433
|
+
pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=dest)
|
1434
|
+
pipe_proc.communicate(final_msg.encode('utf-8', 'replace'))
|
1435
|
+
except BrokenPipeError:
|
1436
|
+
# This occurs if a command's output is being piped to another process and that process closes before the
|
1437
|
+
# command is finished. If you would like your application to print a warning message, then set the
|
1438
|
+
# broken_pipe_warning attribute to the message you want printed.`
|
1439
|
+
if self.broken_pipe_warning:
|
1440
|
+
sys.stderr.write(self.broken_pipe_warning)
|
1441
|
+
else:
|
1442
|
+
self.print_to(dest, msg, end=end, paged=False)
|
1378
1443
|
|
1379
1444
|
# ----- Methods related to tab completion -----
|
1380
1445
|
|
@@ -2098,12 +2163,12 @@ class Cmd(cmd.Cmd):
|
|
2098
2163
|
else:
|
2099
2164
|
# There's no completer function, next see if the command uses argparse
|
2100
2165
|
func = self.cmd_func(command)
|
2101
|
-
argparser = self._command_parsers.get(
|
2166
|
+
argparser = None if func is None else self._command_parsers.get(func)
|
2102
2167
|
|
2103
2168
|
if func is not None and argparser is not None:
|
2104
2169
|
# Get arguments for complete()
|
2105
2170
|
preserve_quotes = getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES)
|
2106
|
-
cmd_set = self.
|
2171
|
+
cmd_set = self.find_commandset_for_command(command)
|
2107
2172
|
|
2108
2173
|
# Create the argparse completer
|
2109
2174
|
completer_type = self._determine_ap_completer_type(argparser)
|
@@ -3044,19 +3109,9 @@ class Cmd(cmd.Cmd):
|
|
3044
3109
|
|
3045
3110
|
helpfunc now contains a reference to the ``do_help`` method
|
3046
3111
|
"""
|
3047
|
-
func_name =
|
3048
|
-
|
3049
|
-
|
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 ''
|
3112
|
+
func_name = constants.COMMAND_FUNC_PREFIX + command
|
3113
|
+
func = getattr(self, func_name, None)
|
3114
|
+
return cast(CommandFunc, func) if callable(func) else None
|
3060
3115
|
|
3061
3116
|
def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool:
|
3062
3117
|
"""This executes the actual do_* method for a command.
|
@@ -3404,7 +3459,7 @@ class Cmd(cmd.Cmd):
|
|
3404
3459
|
alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string."
|
3405
3460
|
alias_epilog = "See also:\n" " macro"
|
3406
3461
|
alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog)
|
3407
|
-
alias_parser.add_subparsers(
|
3462
|
+
alias_parser.add_subparsers(metavar='SUBCOMMAND', required=True)
|
3408
3463
|
|
3409
3464
|
# Preserve quotes since we are passing strings to other commands
|
3410
3465
|
@with_argparser(alias_parser, preserve_quotes=True)
|
@@ -3572,7 +3627,7 @@ class Cmd(cmd.Cmd):
|
|
3572
3627
|
macro_description = "Manage macros\n" "\n" "A macro is similar to an alias, but it can contain argument placeholders."
|
3573
3628
|
macro_epilog = "See also:\n" " alias"
|
3574
3629
|
macro_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog)
|
3575
|
-
macro_parser.add_subparsers(
|
3630
|
+
macro_parser.add_subparsers(metavar='SUBCOMMAND', required=True)
|
3576
3631
|
|
3577
3632
|
# Preserve quotes since we are passing strings to other commands
|
3578
3633
|
@with_argparser(macro_parser, preserve_quotes=True)
|
@@ -3820,9 +3875,7 @@ class Cmd(cmd.Cmd):
|
|
3820
3875
|
return []
|
3821
3876
|
|
3822
3877
|
# Check if this command uses argparse
|
3823
|
-
func
|
3824
|
-
argparser = self._command_parsers.get(command, None)
|
3825
|
-
if func is None or argparser is None:
|
3878
|
+
if (func := self.cmd_func(command)) is None or (argparser := self._command_parsers.get(func)) is None:
|
3826
3879
|
return []
|
3827
3880
|
|
3828
3881
|
completer = argparse_completer.DEFAULT_AP_COMPLETER(argparser, self)
|
@@ -3857,7 +3910,7 @@ class Cmd(cmd.Cmd):
|
|
3857
3910
|
# Getting help for a specific command
|
3858
3911
|
func = self.cmd_func(args.command)
|
3859
3912
|
help_func = getattr(self, constants.HELP_FUNC_PREFIX + args.command, None)
|
3860
|
-
argparser = self._command_parsers.get(
|
3913
|
+
argparser = None if func is None else self._command_parsers.get(func)
|
3861
3914
|
|
3862
3915
|
# If the command function uses argparse, then use argparse's help
|
3863
3916
|
if func is not None and argparser is not None:
|
@@ -3979,28 +4032,29 @@ class Cmd(cmd.Cmd):
|
|
3979
4032
|
def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]:
|
3980
4033
|
# Get a sorted list of help topics
|
3981
4034
|
help_topics = sorted(self.get_help_topics(), key=self.default_sort_key)
|
4035
|
+
|
3982
4036
|
# Get a sorted list of visible command names
|
3983
4037
|
visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key)
|
3984
4038
|
cmds_doc: List[str] = []
|
3985
4039
|
cmds_undoc: List[str] = []
|
3986
4040
|
cmds_cats: Dict[str, List[str]] = {}
|
3987
4041
|
for command in visible_commands:
|
3988
|
-
func = self.cmd_func(command)
|
4042
|
+
func = cast(CommandFunc, self.cmd_func(command))
|
3989
4043
|
has_help_func = False
|
4044
|
+
has_parser = func in self._command_parsers
|
3990
4045
|
|
3991
4046
|
if command in help_topics:
|
3992
4047
|
# Prevent the command from showing as both a command and help topic in the output
|
3993
4048
|
help_topics.remove(command)
|
3994
4049
|
|
3995
4050
|
# Non-argparse commands can have help_functions for their documentation
|
3996
|
-
|
3997
|
-
has_help_func = True
|
4051
|
+
has_help_func = not has_parser
|
3998
4052
|
|
3999
4053
|
if hasattr(func, constants.CMD_ATTR_HELP_CATEGORY):
|
4000
4054
|
category: str = getattr(func, constants.CMD_ATTR_HELP_CATEGORY)
|
4001
4055
|
cmds_cats.setdefault(category, [])
|
4002
4056
|
cmds_cats[category].append(command)
|
4003
|
-
elif func.__doc__ or has_help_func:
|
4057
|
+
elif func.__doc__ or has_help_func or has_parser:
|
4004
4058
|
cmds_doc.append(command)
|
4005
4059
|
else:
|
4006
4060
|
cmds_undoc.append(command)
|
@@ -4035,11 +4089,17 @@ class Cmd(cmd.Cmd):
|
|
4035
4089
|
# Try to get the documentation string for each command
|
4036
4090
|
topics = self.get_help_topics()
|
4037
4091
|
for command in cmds:
|
4038
|
-
cmd_func
|
4092
|
+
if (cmd_func := self.cmd_func(command)) is None:
|
4093
|
+
continue
|
4094
|
+
|
4039
4095
|
doc: Optional[str]
|
4040
4096
|
|
4097
|
+
# If this is an argparse command, use its description.
|
4098
|
+
if (cmd_parser := self._command_parsers.get(cmd_func)) is not None:
|
4099
|
+
doc = cmd_parser.description
|
4100
|
+
|
4041
4101
|
# Non-argparse commands can have help_functions for their documentation
|
4042
|
-
|
4102
|
+
elif command in topics:
|
4043
4103
|
help_func = getattr(self, constants.HELP_FUNC_PREFIX + command)
|
4044
4104
|
result = io.StringIO()
|
4045
4105
|
|
@@ -5436,12 +5496,13 @@ class Cmd(cmd.Cmd):
|
|
5436
5496
|
if command not in self.disabled_commands:
|
5437
5497
|
return
|
5438
5498
|
|
5499
|
+
cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
|
5439
5500
|
help_func_name = constants.HELP_FUNC_PREFIX + command
|
5440
5501
|
completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
|
5441
5502
|
|
5442
5503
|
# Restore the command function to its original value
|
5443
5504
|
dc = self.disabled_commands[command]
|
5444
|
-
setattr(self,
|
5505
|
+
setattr(self, cmd_func_name, dc.command_function)
|
5445
5506
|
|
5446
5507
|
# Restore the help function to its original value
|
5447
5508
|
if dc.help_function is None:
|
@@ -5489,6 +5550,7 @@ class Cmd(cmd.Cmd):
|
|
5489
5550
|
if command_function is None:
|
5490
5551
|
raise AttributeError(f"'{command}' does not refer to a command")
|
5491
5552
|
|
5553
|
+
cmd_func_name = constants.COMMAND_FUNC_PREFIX + command
|
5492
5554
|
help_func_name = constants.HELP_FUNC_PREFIX + command
|
5493
5555
|
completer_func_name = constants.COMPLETER_FUNC_PREFIX + command
|
5494
5556
|
|
@@ -5503,7 +5565,7 @@ class Cmd(cmd.Cmd):
|
|
5503
5565
|
new_func = functools.partial(
|
5504
5566
|
self._report_disabled_command_usage, message_to_print=message_to_print.replace(constants.COMMAND_NAME, command)
|
5505
5567
|
)
|
5506
|
-
setattr(self,
|
5568
|
+
setattr(self, cmd_func_name, new_func)
|
5507
5569
|
setattr(self, help_func_name, new_func)
|
5508
5570
|
|
5509
5571
|
# 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
|
-
|
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.
|
3
|
+
Version: 2.5.7
|
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
|
52
|
-
Requires-Dist: setuptools
|
53
|
-
Requires-Dist: setuptools-scm
|
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
|
56
|
-
Requires-Dist: doc8
|
57
|
-
Requires-Dist: invoke
|
58
|
-
Requires-Dist: mypy
|
59
|
-
Requires-Dist: pytest
|
60
|
-
Requires-Dist: pytest-cov
|
61
|
-
Requires-Dist: pytest-mock
|
62
|
-
Requires-Dist: sphinx
|
63
|
-
Requires-Dist: sphinx-autobuild
|
64
|
-
Requires-Dist: sphinx-rtd-theme
|
65
|
-
Requires-Dist: ruff
|
66
|
-
Requires-Dist: twine
|
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
|
69
|
-
Requires-Dist:
|
70
|
-
Requires-Dist: sphinx
|
71
|
-
Requires-Dist: sphinx-autobuild
|
72
|
-
Requires-Dist: sphinx-rtd-theme
|
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
|
75
|
-
Requires-Dist: coverage
|
76
|
-
Requires-Dist: pytest
|
77
|
-
Requires-Dist: pytest-cov
|
78
|
-
Requires-Dist: pytest-mock
|
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
|
81
|
-
Requires-Dist: ruff
|
82
|
-
Requires-Dist: types-setuptools
|
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
|
|
@@ -3,10 +3,10 @@ cmd2/ansi.py,sha256=BasPj_Wla0cEyjpe_zu-cn24CgVdSXK3sfxknLM1WcY,32203
|
|
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=
|
6
|
+
cmd2/cmd2.py,sha256=5hIjeGoWE-56qZxXpFNL5KNQBDFNA7337RZBuV2o2_k,264393
|
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=
|
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.
|
21
|
-
cmd2-2.5.
|
22
|
-
cmd2-2.5.
|
23
|
-
cmd2-2.5.
|
24
|
-
cmd2-2.5.
|
20
|
+
cmd2-2.5.7.dist-info/LICENSE,sha256=QXrW0Z0merk9mncyUkn-sgRxhT8_o1dL5HEaBNH47Q4,1099
|
21
|
+
cmd2-2.5.7.dist-info/METADATA,sha256=KYPEREhxzAnfwxBhwbGC-ury6RiBU4izCwJa4F___uM,13278
|
22
|
+
cmd2-2.5.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
23
|
+
cmd2-2.5.7.dist-info/top_level.txt,sha256=gJbOJmyrARwLhm5diXAtzlNQdxbDZ8iRJ8HJi65_5hg,5
|
24
|
+
cmd2-2.5.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|