diff options
-rw-r--r-- | cmd2/__init__.py | 2 | ||||
-rw-r--r-- | cmd2/cmd2.py | 85 | ||||
-rw-r--r-- | cmd2/command_definition.py | 25 | ||||
-rw-r--r-- | isolated_tests/test_commandset/test_commandset.py | 209 |
4 files changed, 13 insertions, 308 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py index 70a52f70..1fb01b16 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -28,7 +28,7 @@ if cmd2_parser_module is not None: # Get the current value for argparse_custom.DEFAULT_ARGUMENT_PARSER from .argparse_custom import DEFAULT_ARGUMENT_PARSER from .cmd2 import Cmd -from .command_definition import CommandSet, with_default_category, register_command +from .command_definition import CommandSet, with_default_category from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 2215f818..affd395f 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -46,7 +46,7 @@ from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple from . import ansi, constants, plugin, utils from .argparse_custom import DEFAULT_ARGUMENT_PARSER, CompletionItem from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer -from .command_definition import _REGISTERED_COMMANDS, CommandSet, _partial_passthru +from .command_definition import CommandSet, _partial_passthru from .constants import COMMAND_FUNC_PREFIX, COMPLETER_FUNC_PREFIX, HELP_FUNC_PREFIX from .decorators import with_argparser from .exceptions import Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError, SkipPostcommandHooks @@ -90,6 +90,7 @@ except ImportError: # pragma: no cover class _SavedReadlineSettings: """readline settings that are backed up when switching between readline environments""" + def __init__(self): self.completer = None self.delims = '' @@ -98,6 +99,7 @@ class _SavedReadlineSettings: class _SavedCmd2Env: """cmd2 environment settings that are backed up when entering an interactive Python shell""" + def __init__(self): self.readline_settings = _SavedReadlineSettings() self.readline_module = None @@ -397,15 +399,7 @@ class Cmd(cmd.Cmd): self.matches_sorted = False def _autoload_commands(self) -> None: - """ - Load modular command definitions. - :return: None - """ - - # start by loading registered functions as commands - for cmd_name in _REGISTERED_COMMANDS.keys(): - self.install_registered_command(cmd_name) - + """Load modular command definitions.""" # Search for all subclasses of CommandSet, instantiate them if they weren't provided in the constructor all_commandset_defs = CommandSet.__subclasses__() existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] @@ -418,12 +412,11 @@ class Cmd(cmd.Cmd): cmdset = cmdset_type() self.install_command_set(cmdset) - def install_command_set(self, cmdset: CommandSet): + def install_command_set(self, cmdset: CommandSet) -> None: """ Installs a CommandSet, loading all commands defined in the CommandSet :param cmdset: CommandSet to load - :return: None """ existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] if type(cmdset) in existing_commandset_types: @@ -525,64 +518,6 @@ class Cmd(cmd.Cmd): cmdset.on_unregister(self) self._installed_command_sets.remove(cmdset) - def install_registered_command(self, cmd_name: str): - cmd_completer = None - cmd_help = None - - if cmd_name not in _REGISTERED_COMMANDS: - raise KeyError('Command ' + cmd_name + ' has not been registered') - - cmd_func = _REGISTERED_COMMANDS[cmd_name] - - module = inspect.getmodule(cmd_func) - - module_funcs = [mf for mf in inspect.getmembers(module) if inspect.isfunction(mf[1])] - for mf in module_funcs: - if mf[0] == COMPLETER_FUNC_PREFIX + cmd_name: - cmd_completer = mf[1] - elif mf[0] == HELP_FUNC_PREFIX + cmd_name: - cmd_help = mf[1] - if cmd_completer is not None and cmd_help is not None: - break - - self.install_command_function(cmd_name, cmd_func, cmd_completer, cmd_help) - - def install_command_function(self, - cmd_name: str, - cmd_func: Callable, - cmd_completer: Optional[Callable], - cmd_help: Optional[Callable]): - """ - Installs a command by passing in functions for the command, completion, and help - - :param cmd_name: name of the command to install - :param cmd_func: function to handle the command - :param cmd_completer: completion function for the command - :param cmd_help: help generator for the command - :return: None - """ - self.__install_command_function(cmd_name, types.MethodType(cmd_func, self)) - - self._installed_functions.append(cmd_name) - if cmd_completer is not None: - self.__install_completer_function(cmd_name, types.MethodType(cmd_completer, self)) - if cmd_help is not None: - self.__install_help_function(cmd_name, types.MethodType(cmd_help, self)) - - def uninstall_command(self, cmd_name: str): - """ - Uninstall an installed command and any associated completer or help functions - :param cmd_name: Command to uninstall - """ - if cmd_name in self._installed_functions: - delattr(self, COMMAND_FUNC_PREFIX + cmd_name) - - if hasattr(self, COMPLETER_FUNC_PREFIX + cmd_name): - delattr(self, COMPLETER_FUNC_PREFIX + cmd_name) - if hasattr(self, HELP_FUNC_PREFIX + cmd_name): - delattr(self, HELP_FUNC_PREFIX + cmd_name) - self._installed_functions.remove(cmd_name) - def add_settable(self, settable: Settable) -> None: """ Convenience method to add a settable parameter to ``self.settables`` @@ -2156,7 +2091,8 @@ class Cmd(cmd.Cmd): if proc.returncode is not None: subproc_stdin.close() new_stdout.close() - raise RedirectionError('Pipe process exited with code {} before command could run'.format(proc.returncode)) + raise RedirectionError( + 'Pipe process exited with code {} before command could run'.format(proc.returncode)) else: redir_saved_state.redirecting = True cmd_pipe_proc_reader = utils.ProcReader(proc, self.stdout, sys.stderr) @@ -2165,7 +2101,8 @@ class Cmd(cmd.Cmd): elif statement.output: import tempfile if (not statement.output_to) and (not self._can_clip): - raise RedirectionError("Cannot redirect to paste buffer; missing 'pyperclip' and/or pyperclip dependencies") + raise RedirectionError( + "Cannot redirect to paste buffer; missing 'pyperclip' and/or pyperclip dependencies") # Redirecting to a file elif statement.output_to: @@ -2271,7 +2208,6 @@ class Cmd(cmd.Cmd): # 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 and add_to_history: - self.history.append(statement) stop = func(statement) @@ -3358,7 +3294,7 @@ class Cmd(cmd.Cmd): if 'gnureadline' in sys.modules: # Restore what the readline module pointed to if cmd2_env.readline_module is None: - del(sys.modules['readline']) + del (sys.modules['readline']) else: sys.modules['readline'] = cmd2_env.readline_module @@ -3387,6 +3323,7 @@ class Cmd(cmd.Cmd): other arguments. (Defaults to None) :return: True if running of commands should stop """ + def py_quit(): """Function callable from the interactive Python console to exit that environment""" raise EmbeddedConsoleExit diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index d9925969..0645de2a 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -16,11 +16,6 @@ try: # pragma: no cover except ImportError: # pragma: no cover pass -_REGISTERED_COMMANDS = {} # type: Dict[str, Callable] -""" -Registered command tuples. (command, ``do_`` function) -""" - def _partial_passthru(func: Callable, *args, **kwargs) -> functools.partial: """ @@ -52,26 +47,6 @@ def _partial_passthru(func: Callable, *args, **kwargs) -> functools.partial: return passthru_type(func, *args, **kwargs) -def register_command(cmd_func: Callable): - """ - Decorator that allows an arbitrary function to be automatically registered as a command. - If there is a ``help_`` or ``complete_`` function that matches this command, that will also be registered. - - :param cmd_func: Function to register as a cmd2 command - :type cmd_func: Callable[[cmd2.Cmd, Union[Statement, argparse.Namespace]], None] - :return: - """ - assert cmd_func.__name__.startswith(COMMAND_FUNC_PREFIX), 'Command functions must start with `do_`' - - cmd_name = cmd_func.__name__[len(COMMAND_FUNC_PREFIX):] - - if cmd_name not in _REGISTERED_COMMANDS: - _REGISTERED_COMMANDS[cmd_name] = cmd_func - else: - raise KeyError('Command ' + cmd_name + ' is already registered') - return cmd_func - - def with_default_category(category: str): """ Decorator that applies a category to all ``do_*`` command methods in a class that do not already diff --git a/isolated_tests/test_commandset/test_commandset.py b/isolated_tests/test_commandset/test_commandset.py index eedf51de..8de2d3b0 100644 --- a/isolated_tests/test_commandset/test_commandset.py +++ b/isolated_tests/test_commandset/test_commandset.py @@ -14,51 +14,6 @@ from cmd2 import utils from .conftest import complete_tester, normalize, run_cmd -@cmd2.register_command -@cmd2.with_category("AAA") -def do_unbound(cmd: cmd2.Cmd, statement: cmd2.Statement): - """ - This is an example of registering an unbound function - - :param cmd: - :param statement: - :return: - """ - cmd.poutput('Unbound Command: {}'.format(statement.args)) - - -@cmd2.register_command -@cmd2.with_category("AAA") -def do_command_with_support(cmd: cmd2.Cmd, statement: cmd2.Statement): - """ - This is an example of registering an unbound function - - :param cmd: - :param statement: - :return: - """ - cmd.poutput('Command with support functions: {}'.format(statement.args)) - - -def help_command_with_support(cmd: cmd2.Cmd): - cmd.poutput('Help for command_with_support') - - -def complete_command_with_support(cmd: cmd2.Cmd, text: str, line: str, begidx: int, endidx: int) -> List[str]: - """Completion function for do_index_based""" - food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] - sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - - index_dict = \ - { - 1: food_item_strs, # Tab complete food items at index 1 in command line - 2: sport_item_strs, # Tab complete sport items at index 2 in command line - 3: cmd.path_complete, # Tab complete using path_complete function at index 3 in command line - } - - return cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) - - @cmd2.with_default_category('Command Set') class CommandSetA(cmd2.CommandSet): def do_apple(self, cmd: cmd2.Cmd, statement: cmd2.Statement): @@ -126,9 +81,6 @@ def test_autoload_commands(command_sets_app): cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_app._build_command_info() - assert 'AAA' in cmds_cats - assert 'unbound' in cmds_cats['AAA'] - assert 'Alone' in cmds_cats assert 'elderberry' in cmds_cats['Alone'] @@ -150,41 +102,6 @@ def test_custom_construct_commandsets(): def test_load_commands(command_sets_manual): - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - - # start by verifying none of the installable commands are present - assert 'AAA' not in cmds_cats - assert 'Alone' not in cmds_cats - assert 'Command Set' not in cmds_cats - - # install the `unbound` command - command_sets_manual.install_registered_command('unbound') - - # verify that the same registered command can't be installed twice - with pytest.raises(ValueError): - assert command_sets_manual.install_registered_command('unbound') - - # verifies detection of unregistered commands - with pytest.raises(KeyError): - assert command_sets_manual.install_registered_command('nonexistent_command') - - # verifies that a duplicate function name is detected - def do_unbound(cmd: cmd2.Cmd, statement: cmd2.Statement): - """ - This function duplicates an existing command - """ - cmd.poutput('Unbound Command: {}'.format(statement.args)) - - with pytest.raises(KeyError): - assert cmd2.register_command(do_unbound) - - # verify only the `unbound` command was installed - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - - assert 'AAA' in cmds_cats - assert 'unbound' in cmds_cats['AAA'] - assert 'Alone' not in cmds_cats - assert 'Command Set' not in cmds_cats # now install a command set and verify the commands are now present cmd_set = CommandSetA() @@ -192,22 +109,6 @@ def test_load_commands(command_sets_manual): cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' in cmds_cats - assert 'unbound' in cmds_cats['AAA'] - - assert 'Alone' in cmds_cats - assert 'elderberry' in cmds_cats['Alone'] - - assert 'Command Set' in cmds_cats - assert 'cranberry' in cmds_cats['Command Set'] - - # uninstall the `unbound` command and verify only it was uninstalled - command_sets_manual.uninstall_command('unbound') - - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - - assert 'AAA' not in cmds_cats - assert 'Alone' in cmds_cats assert 'elderberry' in cmds_cats['Alone'] @@ -219,17 +120,14 @@ def test_load_commands(command_sets_manual): cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' not in cmds_cats assert 'Alone' not in cmds_cats assert 'Command Set' not in cmds_cats - # reinstall the command set and verifyt is accessible but the `unbound` command isn't + # reinstall the command set and verify it is accessible command_sets_manual.install_command_set(cmd_set) cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' not in cmds_cats - assert 'Alone' in cmds_cats assert 'elderberry' in cmds_cats['Alone'] @@ -237,111 +135,6 @@ def test_load_commands(command_sets_manual): assert 'cranberry' in cmds_cats['Command Set'] -def test_command_functions(command_sets_manual): - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' not in cmds_cats - - out, err = run_cmd(command_sets_manual, 'command_with_support') - assert 'is not a recognized command, alias, or macro' in err[0] - - out, err = run_cmd(command_sets_manual, 'help command_with_support') - assert 'No help on command_with_support' in err[0] - - text = '' - line = 'command_with_support' - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - assert first_match is None - - # A bad command name gets rejected with an exception - with pytest.raises(ValueError): - assert command_sets_manual.install_command_function('>"', - do_command_with_support, - complete_command_with_support, - help_command_with_support) - - # create an alias to verify that it gets removed when the command is created - out, err = run_cmd(command_sets_manual, 'alias create command_with_support run_pyscript') - assert out == normalize("Alias 'command_with_support' created") - - command_sets_manual.install_registered_command('command_with_support') - - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' in cmds_cats - assert 'command_with_support' in cmds_cats['AAA'] - - out, err = run_cmd(command_sets_manual, 'command_with_support') - assert 'Command with support functions' in out[0] - - out, err = run_cmd(command_sets_manual, 'help command_with_support') - assert 'Help for command_with_support' in out[0] - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - assert first_match == 'Ham' - - text = '' - line = 'command_with_support Ham' - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - - assert first_match == 'Basket' - - command_sets_manual.uninstall_command('command_with_support') - - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' not in cmds_cats - - out, err = run_cmd(command_sets_manual, 'command_with_support') - assert 'is not a recognized command, alias, or macro' in err[0] - - out, err = run_cmd(command_sets_manual, 'help command_with_support') - assert 'No help on command_with_support' in err[0] - - text = '' - line = 'command_with_support' - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - assert first_match is None - - # create an alias to verify that it gets removed when the command is created - out, err = run_cmd(command_sets_manual, 'macro create command_with_support run_pyscript') - assert out == normalize("Macro 'command_with_support' created") - - command_sets_manual.install_command_function('command_with_support', - do_command_with_support, - complete_command_with_support, - help_command_with_support) - - cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_manual._build_command_info() - assert 'AAA' in cmds_cats - assert 'command_with_support' in cmds_cats['AAA'] - - out, err = run_cmd(command_sets_manual, 'command_with_support') - assert 'Command with support functions' in out[0] - - out, err = run_cmd(command_sets_manual, 'help command_with_support') - assert 'Help for command_with_support' in out[0] - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - assert first_match == 'Ham' - - text = '' - line = 'command_with_support Ham' - endidx = len(line) - begidx = endidx - len(text) - - first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) - - assert first_match == 'Basket' - - - def test_partial_with_passthru(): def test_func(arg1, arg2): |