37
37
import re
38
38
import sys
39
39
import threading
40
+ from collections import namedtuple
40
41
from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , Type , Union , IO
41
42
42
43
import colorama
@@ -279,6 +280,10 @@ class EmptyStatement(Exception):
279
280
pass
280
281
281
282
283
+ # Contains data about a disabled command which is used to restore its original functions when the command is enabled
284
+ DisabledCommand = namedtuple ('DisabledCommand' , ['command_function' , 'help_function' ])
285
+
286
+
282
287
class Cmd (cmd .Cmd ):
283
288
"""An easy but powerful framework for writing line-oriented command interpreters.
284
289
@@ -521,6 +526,11 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
521
526
# being printed by a command.
522
527
self .terminal_lock = threading .RLock ()
523
528
529
+ # Commands that have been disabled from use. This is to support commands that are only available
530
+ # during specific states of the application. This dictionary's keys are the command names and its
531
+ # values are DisabledCommand objects.
532
+ self .disabled_commands = dict ()
533
+
524
534
# ----- Methods related to presenting output to the user -----
525
535
526
536
@property
@@ -1562,14 +1572,19 @@ def get_all_commands(self) -> List[str]:
1562
1572
if name .startswith (COMMAND_FUNC_PREFIX ) and callable (getattr (self , name ))]
1563
1573
1564
1574
def get_visible_commands (self ) -> List [str ]:
1565
- """Returns a list of commands that have not been hidden."""
1575
+ """Returns a list of commands that have not been hidden or disabled ."""
1566
1576
commands = self .get_all_commands ()
1567
1577
1568
1578
# Remove the hidden commands
1569
1579
for name in self .hidden_commands :
1570
1580
if name in commands :
1571
1581
commands .remove (name )
1572
1582
1583
+ # Remove the disabled commands
1584
+ for name in self .disabled_commands :
1585
+ if name in commands :
1586
+ commands .remove (name )
1587
+
1573
1588
return commands
1574
1589
1575
1590
def get_alias_names (self ) -> List [str ]:
@@ -1953,7 +1968,7 @@ def cmd_func_name(self, command: str) -> str:
1953
1968
def onecmd (self , statement : Union [Statement , str ]) -> bool :
1954
1969
""" This executes the actual do_* method for a command.
1955
1970
1956
- If the command provided doesn't exist, then it executes _default () instead.
1971
+ If the command provided doesn't exist, then it executes default () instead.
1957
1972
1958
1973
:param statement: intended to be a Statement instance parsed command from the input stream, alternative
1959
1974
acceptance of a str is present only for backward compatibility with cmd
@@ -1969,8 +1984,9 @@ def onecmd(self, statement: Union[Statement, str]) -> bool:
1969
1984
else :
1970
1985
func = self .cmd_func (statement .command )
1971
1986
if func :
1972
- # Since we have a valid command store it in the history
1973
- if statement .command not in self .exclude_from_history :
1987
+ # Check to see if this command should be stored in history
1988
+ if statement .command not in self .exclude_from_history \
1989
+ and statement .command not in self .disabled_commands :
1974
1990
self .history .append (statement )
1975
1991
1976
1992
stop = func (statement )
@@ -3186,13 +3202,15 @@ def do_history(self, args: argparse.Namespace) -> None:
3186
3202
3187
3203
# -v must be used alone with no other options
3188
3204
if args .verbose :
3189
- if args .clear or args .edit or args .output_file or args .run or args .transcript or args .expanded or args .script :
3205
+ if args .clear or args .edit or args .output_file or args .run or args .transcript \
3206
+ or args .expanded or args .script :
3190
3207
self .poutput ("-v can not be used with any other options" )
3191
3208
self .poutput (self .history_parser .format_usage ())
3192
3209
return
3193
3210
3194
3211
# -s and -x can only be used if none of these options are present: [-c -r -e -o -t]
3195
- if (args .script or args .expanded ) and (args .clear or args .edit or args .output_file or args .run or args .transcript ):
3212
+ if (args .script or args .expanded ) \
3213
+ and (args .clear or args .edit or args .output_file or args .run or args .transcript ):
3196
3214
self .poutput ("-s and -x can not be used with -c, -r, -e, -o, or -t" )
3197
3215
self .poutput (self .history_parser .format_usage ())
3198
3216
return
@@ -3598,6 +3616,95 @@ def set_window_title(self, title: str) -> None: # pragma: no cover
3598
3616
else :
3599
3617
raise RuntimeError ("another thread holds terminal_lock" )
3600
3618
3619
+ def enable_command (self , command : str ) -> None :
3620
+ """
3621
+ Enable a command by restoring its functions
3622
+ :param command: the command being enabled
3623
+ """
3624
+ # If the commands is already enabled, then return
3625
+ if command not in self .disabled_commands :
3626
+ return
3627
+
3628
+ help_func_name = HELP_FUNC_PREFIX + command
3629
+
3630
+ # Restore the command and help functions to their original values
3631
+ dc = self .disabled_commands [command ]
3632
+ setattr (self , self .cmd_func_name (command ), dc .command_function )
3633
+
3634
+ if dc .help_function is None :
3635
+ delattr (self , help_func_name )
3636
+ else :
3637
+ setattr (self , help_func_name , dc .help_function )
3638
+
3639
+ # Remove the disabled command entry
3640
+ del self .disabled_commands [command ]
3641
+
3642
+ def enable_category (self , category : str ) -> None :
3643
+ """
3644
+ Enable an entire category of commands
3645
+ :param category: the category to enable
3646
+ """
3647
+ for cmd_name in list (self .disabled_commands ):
3648
+ dc = self .disabled_commands [cmd_name ]
3649
+ cmd_category = getattr (dc .command_function , HELP_CATEGORY , None )
3650
+ if cmd_category is not None and cmd_category == category :
3651
+ self .enable_command (cmd_name )
3652
+
3653
+ def disable_command (self , command : str , message_to_print : str ) -> None :
3654
+ """
3655
+ Disable a command and overwrite its functions
3656
+ :param command: the command being disabled
3657
+ :param message_to_print: what to print when this command is run or help is called on it while disabled
3658
+ """
3659
+ import functools
3660
+
3661
+ # If the commands is already disabled, then return
3662
+ if command in self .disabled_commands :
3663
+ return
3664
+
3665
+ # Make sure this is an actual command
3666
+ command_function = self .cmd_func (command )
3667
+ if command_function is None :
3668
+ raise AttributeError ("{} does not refer to a command" .format (command ))
3669
+
3670
+ help_func_name = HELP_FUNC_PREFIX + command
3671
+
3672
+ # Add the disabled command record
3673
+ self .disabled_commands [command ] = DisabledCommand (command_function = command_function ,
3674
+ help_function = getattr (self , help_func_name , None ))
3675
+
3676
+ # Overwrite the command and help functions to print the message
3677
+ new_func = functools .partial (self ._report_disabled_command_usage , message_to_print = message_to_print )
3678
+ setattr (self , self .cmd_func_name (command ), new_func )
3679
+ setattr (self , help_func_name , new_func )
3680
+
3681
+ def disable_category (self , category : str , message_to_print : str ) -> None :
3682
+ """
3683
+ Disable an entire category of commands
3684
+ :param category: the category to disable
3685
+ :param message_to_print: what to print when anything in this category is run or help is called on it
3686
+ while disabled
3687
+ """
3688
+ all_commands = self .get_all_commands ()
3689
+
3690
+ for cmd_name in all_commands :
3691
+ func = self .cmd_func (cmd_name )
3692
+ cmd_category = getattr (func , HELP_CATEGORY , None )
3693
+
3694
+ # If this command is in the category, then disable it
3695
+ if cmd_category is not None and cmd_category == category :
3696
+ self .disable_command (cmd_name , message_to_print )
3697
+
3698
+ # noinspection PyUnusedLocal
3699
+ def _report_disabled_command_usage (self , * args , message_to_print : str , ** kwargs ) -> None :
3700
+ """
3701
+ Report when a disabled command has been run or had help called on it
3702
+ :param args: not used
3703
+ :param message_to_print: the message reporting that the command is disabled
3704
+ :param kwargs: not used
3705
+ """
3706
+ self .poutput (message_to_print )
3707
+
3601
3708
def cmdloop (self , intro : Optional [str ] = None ) -> None :
3602
3709
"""This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
3603
3710
0 commit comments