3737import re
3838import sys
3939import threading
40+ from collections import namedtuple
4041from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , Type , Union , IO
4142
4243import colorama
@@ -279,6 +280,10 @@ class EmptyStatement(Exception):
279280 pass
280281
281282
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+
282287class Cmd (cmd .Cmd ):
283288 """An easy but powerful framework for writing line-oriented command interpreters.
284289
@@ -521,6 +526,11 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, persistent
521526 # being printed by a command.
522527 self .terminal_lock = threading .RLock ()
523528
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+
524534 # ----- Methods related to presenting output to the user -----
525535
526536 @property
@@ -1562,14 +1572,19 @@ def get_all_commands(self) -> List[str]:
15621572 if name .startswith (COMMAND_FUNC_PREFIX ) and callable (getattr (self , name ))]
15631573
15641574 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 ."""
15661576 commands = self .get_all_commands ()
15671577
15681578 # Remove the hidden commands
15691579 for name in self .hidden_commands :
15701580 if name in commands :
15711581 commands .remove (name )
15721582
1583+ # Remove the disabled commands
1584+ for name in self .disabled_commands :
1585+ if name in commands :
1586+ commands .remove (name )
1587+
15731588 return commands
15741589
15751590 def get_alias_names (self ) -> List [str ]:
@@ -1953,7 +1968,7 @@ def cmd_func_name(self, command: str) -> str:
19531968 def onecmd (self , statement : Union [Statement , str ]) -> bool :
19541969 """ This executes the actual do_* method for a command.
19551970
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.
19571972
19581973 :param statement: intended to be a Statement instance parsed command from the input stream, alternative
19591974 acceptance of a str is present only for backward compatibility with cmd
@@ -1969,8 +1984,9 @@ def onecmd(self, statement: Union[Statement, str]) -> bool:
19691984 else :
19701985 func = self .cmd_func (statement .command )
19711986 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 :
19741990 self .history .append (statement )
19751991
19761992 stop = func (statement )
@@ -3186,13 +3202,15 @@ def do_history(self, args: argparse.Namespace) -> None:
31863202
31873203 # -v must be used alone with no other options
31883204 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 :
31903207 self .poutput ("-v can not be used with any other options" )
31913208 self .poutput (self .history_parser .format_usage ())
31923209 return
31933210
31943211 # -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 ):
31963214 self .poutput ("-s and -x can not be used with -c, -r, -e, -o, or -t" )
31973215 self .poutput (self .history_parser .format_usage ())
31983216 return
@@ -3598,6 +3616,95 @@ def set_window_title(self, title: str) -> None: # pragma: no cover
35983616 else :
35993617 raise RuntimeError ("another thread holds terminal_lock" )
36003618
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+
36013708 def cmdloop (self , intro : Optional [str ] = None ) -> None :
36023709 """This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
36033710
0 commit comments