summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py119
1 files changed, 113 insertions, 6 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 0d1d0b51..1767cc67 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -37,6 +37,7 @@ import os
import re
import sys
import threading
+from collections import namedtuple
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union, IO
import colorama
@@ -279,6 +280,10 @@ class EmptyStatement(Exception):
pass
+# Contains data about a disabled command which is used to restore its original functions when the command is enabled
+DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function'])
+
+
class Cmd(cmd.Cmd):
"""An easy but powerful framework for writing line-oriented command interpreters.
@@ -521,6 +526,11 @@ class Cmd(cmd.Cmd):
# being printed by a command.
self.terminal_lock = threading.RLock()
+ # Commands that have been disabled from use. This is to support commands that are only available
+ # during specific states of the application. This dictionary's keys are the command names and its
+ # values are DisabledCommand objects.
+ self.disabled_commands = dict()
+
# ----- Methods related to presenting output to the user -----
@property
@@ -1562,7 +1572,7 @@ class Cmd(cmd.Cmd):
if name.startswith(COMMAND_FUNC_PREFIX) and callable(getattr(self, name))]
def get_visible_commands(self) -> List[str]:
- """Returns a list of commands that have not been hidden."""
+ """Returns a list of commands that have not been hidden or disabled."""
commands = self.get_all_commands()
# Remove the hidden commands
@@ -1570,6 +1580,11 @@ class Cmd(cmd.Cmd):
if name in commands:
commands.remove(name)
+ # Remove the disabled commands
+ for name in self.disabled_commands:
+ if name in commands:
+ commands.remove(name)
+
return commands
def get_alias_names(self) -> List[str]:
@@ -1953,7 +1968,7 @@ class Cmd(cmd.Cmd):
def onecmd(self, statement: Union[Statement, str]) -> bool:
""" This executes the actual do_* method for a command.
- If the command provided doesn't exist, then it executes _default() instead.
+ If the command provided doesn't exist, then it executes default() instead.
:param statement: intended to be a Statement instance parsed command from the input stream, alternative
acceptance of a str is present only for backward compatibility with cmd
@@ -1969,8 +1984,9 @@ class Cmd(cmd.Cmd):
else:
func = self.cmd_func(statement.command)
if func:
- # Since we have a valid command store it in the history
- if statement.command not in self.exclude_from_history:
+ # Check to see if this command should be stored in history
+ if statement.command not in self.exclude_from_history \
+ and statement.command not in self.disabled_commands:
self.history.append(statement)
stop = func(statement)
@@ -3186,13 +3202,15 @@ class Cmd(cmd.Cmd):
# -v must be used alone with no other options
if args.verbose:
- if args.clear or args.edit or args.output_file or args.run or args.transcript or args.expanded or args.script:
+ if args.clear or args.edit or args.output_file or args.run or args.transcript \
+ or args.expanded or args.script:
self.poutput("-v can not be used with any other options")
self.poutput(self.history_parser.format_usage())
return
# -s and -x can only be used if none of these options are present: [-c -r -e -o -t]
- if (args.script or args.expanded) and (args.clear or args.edit or args.output_file or args.run or args.transcript):
+ if (args.script or args.expanded) \
+ and (args.clear or args.edit or args.output_file or args.run or args.transcript):
self.poutput("-s and -x can not be used with -c, -r, -e, -o, or -t")
self.poutput(self.history_parser.format_usage())
return
@@ -3598,6 +3616,95 @@ class Cmd(cmd.Cmd):
else:
raise RuntimeError("another thread holds terminal_lock")
+ def enable_command(self, command: str) -> None:
+ """
+ Enable a command by restoring its functions
+ :param command: the command being enabled
+ """
+ # If the commands is already enabled, then return
+ if command not in self.disabled_commands:
+ return
+
+ help_func_name = HELP_FUNC_PREFIX + command
+
+ # Restore the command and help functions to their original values
+ dc = self.disabled_commands[command]
+ setattr(self, self.cmd_func_name(command), dc.command_function)
+
+ if dc.help_function is None:
+ delattr(self, help_func_name)
+ else:
+ setattr(self, help_func_name, dc.help_function)
+
+ # Remove the disabled command entry
+ del self.disabled_commands[command]
+
+ def enable_category(self, category: str) -> None:
+ """
+ Enable an entire category of commands
+ :param category: the category to enable
+ """
+ for cmd_name in list(self.disabled_commands):
+ dc = self.disabled_commands[cmd_name]
+ cmd_category = getattr(dc.command_function, HELP_CATEGORY, None)
+ if cmd_category is not None and cmd_category == category:
+ self.enable_command(cmd_name)
+
+ def disable_command(self, command: str, message_to_print: str) -> None:
+ """
+ Disable a command and overwrite its functions
+ :param command: the command being disabled
+ :param message_to_print: what to print when this command is run or help is called on it while disabled
+ """
+ import functools
+
+ # If the commands is already disabled, then return
+ if command in self.disabled_commands:
+ return
+
+ # Make sure this is an actual command
+ command_function = self.cmd_func(command)
+ if command_function is None:
+ raise AttributeError("{} does not refer to a command".format(command))
+
+ help_func_name = HELP_FUNC_PREFIX + command
+
+ # Add the disabled command record
+ self.disabled_commands[command] = DisabledCommand(command_function=command_function,
+ help_function=getattr(self, help_func_name, None))
+
+ # Overwrite the command and help functions to print the message
+ new_func = functools.partial(self._report_disabled_command_usage, message_to_print=message_to_print)
+ setattr(self, self.cmd_func_name(command), new_func)
+ setattr(self, help_func_name, new_func)
+
+ def disable_category(self, category: str, message_to_print: str) -> None:
+ """
+ Disable an entire category of commands
+ :param category: the category to disable
+ :param message_to_print: what to print when anything in this category is run or help is called on it
+ while disabled
+ """
+ all_commands = self.get_all_commands()
+
+ for cmd_name in all_commands:
+ func = self.cmd_func(cmd_name)
+ cmd_category = getattr(func, HELP_CATEGORY, None)
+
+ # If this command is in the category, then disable it
+ if cmd_category is not None and cmd_category == category:
+ self.disable_command(cmd_name, message_to_print)
+
+ # noinspection PyUnusedLocal
+ def _report_disabled_command_usage(self, *args, message_to_print: str, **kwargs) -> None:
+ """
+ Report when a disabled command has been run or had help called on it
+ :param args: not used
+ :param message_to_print: the message reporting that the command is disabled
+ :param kwargs: not used
+ """
+ self.poutput(message_to_print)
+
def cmdloop(self, intro: Optional[str] = None) -> None:
"""This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.