diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-02-28 00:27:12 -0500 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-02-28 00:27:12 -0500 |
commit | 4a3c5a02e144a72e26103d02ce38163ba765e796 (patch) | |
tree | 0971ea8be91803409f55c966595d5cca97483132 | |
parent | d3208c84c72bc1f3280c80b9d9854f33631c6b61 (diff) | |
download | cmd2-git-4a3c5a02e144a72e26103d02ce38163ba765e796.tar.gz |
Removed ability to call commands as if they were functions in pyscript (e.g. app.help())
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | cmd2/pyscript_bridge.py | 306 | ||||
-rw-r--r-- | tests/pyscript/bar1.py | 3 | ||||
-rw-r--r-- | tests/pyscript/custom_echo.py | 3 | ||||
-rw-r--r-- | tests/pyscript/foo1.py | 3 | ||||
-rw-r--r-- | tests/pyscript/foo2.py | 3 | ||||
-rw-r--r-- | tests/pyscript/foo3.py | 3 | ||||
-rw-r--r-- | tests/pyscript/foo4.py | 10 | ||||
-rw-r--r-- | tests/pyscript/help.py | 2 | ||||
-rw-r--r-- | tests/pyscript/help_media.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_add1.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_add2.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list1.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list2.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list3.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list4.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list5.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list6.py | 3 | ||||
-rw-r--r-- | tests/pyscript/media_movies_list7.py | 3 | ||||
-rw-r--r-- | tests/pyscript/pyscript_dir.py (renamed from tests/pyscript/pyscript_dir1.py) | 0 | ||||
-rw-r--r-- | tests/pyscript/pyscript_dir2.py | 4 | ||||
-rw-r--r-- | tests/test_pyscript.py | 244 |
22 files changed, 38 insertions, 575 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 148ccda1..cddf1d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Deprecations * Deprecated support for bash completion since this feature had slow performance. Also it relied on ``AutoCompleter`` which has since developed a dependency on ``cmd2`` methods. + * Removed ability to call commands in ``pyscript`` as if they were functions (e.g ``app.help()``) in favor + of only supporting one ``pyscript`` interface. This simplifies future maintenance. * Potentially breaking changes * Made ``cmd2_app`` a positional and required argument of ``AutoCompleter`` since certain functionality now requires that it can't be ``None``. diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py index 6a18fc6a..267088c3 100644 --- a/cmd2/pyscript_bridge.py +++ b/cmd2/pyscript_bridge.py @@ -7,12 +7,10 @@ Copyright 2018 Eric Lin <anselor@gmail.com> Released under MIT license, see LICENSE file """ -import argparse import sys -from typing import List, Optional +from typing import Optional -from .argparse_completer import _RangeAction, is_potential_flag -from .utils import namedtuple_with_defaults, StdSim, quote_string_if_needed +from .utils import namedtuple_with_defaults, StdSim # Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout if sys.version_info < (3, 5): @@ -44,257 +42,6 @@ class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr return not self.stderr -def _exec_cmd(cmd2_app, command: str, echo: bool) -> CommandResult: - """ - Helper to encapsulate executing a command and capturing the results - :param cmd2_app: cmd2 app that will run the command - :param command: command line being run - :param echo: if True, output will be echoed to stdout/stderr while the command runs - :return: result of the command - """ - copy_stdout = StdSim(sys.stdout, echo) - copy_stderr = StdSim(sys.stderr, echo) - - copy_cmd_stdout = StdSim(cmd2_app.stdout, echo) - - cmd2_app._last_result = None - - try: - cmd2_app.stdout = copy_cmd_stdout - with redirect_stdout(copy_stdout): - with redirect_stderr(copy_stderr): - # Include a newline in case it's a multiline command - cmd2_app.onecmd_plus_hooks(command + '\n') - finally: - cmd2_app.stdout = copy_cmd_stdout.inner_stream - - # if stderr is empty, set it to None - stderr = copy_stderr.getvalue() if copy_stderr.getvalue() else None - - outbuf = copy_cmd_stdout.getvalue() if copy_cmd_stdout.getvalue() else copy_stdout.getvalue() - result = CommandResult(stdout=outbuf, stderr=stderr, data=cmd2_app._last_result) - return result - - -class ArgparseFunctor: - """ - Encapsulates translating Python object traversal - """ - def __init__(self, echo: bool, cmd2_app, command_name: str, parser: argparse.ArgumentParser): - self._echo = echo - self._cmd2_app = cmd2_app - self._command_name = command_name - self._parser = parser - - # Dictionary mapping command argument name to value - self._args = {} - # tag the argument that's a remainder type - self._remainder_arg = None - # separately track flag arguments so they will be printed before positionals - self._flag_args = [] - # argparse object for the current command layer - self.__current_subcommand_parser = parser - - def __dir__(self): - """Returns a custom list of attribute names to match the sub-commands""" - commands = [] - for action in self.__current_subcommand_parser._actions: - if not action.option_strings and isinstance(action, argparse._SubParsersAction): - commands.extend(action.choices) - return commands - - def __getattr__(self, item: str): - """Search for a sub-command matching this item and update internal state to track the traversal""" - # look for sub-command under the current command/sub-command layer - for action in self.__current_subcommand_parser._actions: - if not action.option_strings and isinstance(action, argparse._SubParsersAction): - if item in action.choices: - # item matches the a sub-command, save our position in argparse, - # save the sub-command, return self to allow next level of traversal - self.__current_subcommand_parser = action.choices[item] - self._args[action.dest] = item - return self - - raise AttributeError(item) - - def __call__(self, *args, **kwargs): - """ - Process the arguments at this layer of the argparse command tree. If there are more sub-commands, - return self to accept the next sub-command name. If there are no more sub-commands, execute the - sub-command with the given parameters. - """ - next_pos_index = 0 - - has_subcommand = False - - # Iterate through the current sub-command's arguments in order - for action in self.__current_subcommand_parser._actions: - # is this a flag option? - if action.option_strings: - # this is a flag argument, search for the argument by name in the parameters - if action.dest in kwargs: - self._args[action.dest] = kwargs[action.dest] - self._flag_args.append(action.dest) - else: - # This is a positional argument, search the positional arguments passed in. - if not isinstance(action, argparse._SubParsersAction): - if action.dest in kwargs: - # if this positional argument happens to be passed in as a keyword argument - # go ahead and consume the matching keyword argument - self._args[action.dest] = kwargs[action.dest] - elif next_pos_index < len(args): - # Make sure we actually have positional arguments to consume - pos_remain = len(args) - next_pos_index - - # Check if this argument consumes a range of values - if isinstance(action, _RangeAction) and action.nargs_min is not None \ - and action.nargs_max is not None: - # this is a cmd2 ranged action. - - if pos_remain >= action.nargs_min: - # Do we meet the minimum count? - if pos_remain > action.nargs_max: - # Do we exceed the maximum count? - self._args[action.dest] = args[next_pos_index:next_pos_index + action.nargs_max] - next_pos_index += action.nargs_max - else: - self._args[action.dest] = args[next_pos_index:next_pos_index + pos_remain] - next_pos_index += pos_remain - else: - raise ValueError('Expected at least {} values for {}'.format(action.nargs_min, - action.dest)) - elif action.nargs is not None: - if action.nargs == '+': - if pos_remain > 0: - self._args[action.dest] = args[next_pos_index:next_pos_index + pos_remain] - next_pos_index += pos_remain - else: - raise ValueError('Expected at least 1 value for {}'.format(action.dest)) - elif action.nargs == '*': - self._args[action.dest] = args[next_pos_index:next_pos_index + pos_remain] - next_pos_index += pos_remain - elif action.nargs == argparse.REMAINDER: - self._args[action.dest] = args[next_pos_index:next_pos_index + pos_remain] - next_pos_index += pos_remain - self._remainder_arg = action.dest - elif action.nargs == '?': - self._args[action.dest] = args[next_pos_index] - next_pos_index += 1 - else: - self._args[action.dest] = args[next_pos_index] - next_pos_index += 1 - else: - has_subcommand = True - - # Check if there are any extra arguments we don't know how to handle - for kw in kwargs: - if kw not in self._args: - raise TypeError("{}() got an unexpected keyword argument '{}'".format( - self.__current_subcommand_parser.prog, kw)) - - if has_subcommand: - return self - else: - return self._run() - - def _run(self): - # look up command function - func = self._cmd2_app.cmd_func(self._command_name) - if func is None: - raise AttributeError("'{}' object has no command called '{}'".format(self._cmd2_app.__class__.__name__, - self._command_name)) - - # reconstruct the cmd2 command from the python call - command = self._command_name - - def process_argument(action, value): - nonlocal command - if isinstance(action, argparse._CountAction): - if isinstance(value, int): - for _ in range(value): - command += ' {}'.format(action.option_strings[0]) - return - else: - raise TypeError('Expected int for ' + action.dest) - if isinstance(action, argparse._StoreConstAction) or isinstance(action, argparse._AppendConstAction): - if value: - # Nothing else to append to the command string, just the flag is enough. - command += ' {}'.format(action.option_strings[0]) - return - else: - # value is not True so we default to false, which means don't include the flag - return - - # was the argument a flag? - if action.option_strings: - command += ' {}'.format(action.option_strings[0]) - - is_remainder_arg = action.dest == self._remainder_arg - - if isinstance(value, List) or isinstance(value, tuple): - for item in value: - item = str(item).strip() - if not is_remainder_arg and is_potential_flag(item, self._parser): - raise ValueError('{} appears to be a flag and should be supplied as a keyword argument ' - 'to the function.'.format(item)) - item = quote_string_if_needed(item) - command += ' {}'.format(item) - - # If this is a flag parameter that can accept a variable number of arguments and we have not - # reached the max number, add 2 prefix chars (ex: --) to tell argparse to stop processing the - # parameter. This also means the remaining arguments will be treated as positionals by argparse. - if action.option_strings and isinstance(action, _RangeAction) and action.nargs_max is not None and \ - action.nargs_max > len(value): - command += ' {0}{0}'.format(self._parser.prefix_chars[0]) - - else: - value = str(value).strip() - if not is_remainder_arg and is_potential_flag(value, self._parser): - raise ValueError('{} appears to be a flag and should be supplied as a keyword argument ' - 'to the function.'.format(value)) - value = quote_string_if_needed(value) - command += ' {}'.format(value) - - # If this is a flag parameter that can accept a variable number of arguments and we have not - # reached the max number, add 2 prefix chars (ex: --) to tell argparse to stop processing the - # parameter. This also means the remaining arguments will be treated as positionals by argparse. - if action.option_strings and isinstance(action, _RangeAction) and action.nargs_max is not None and \ - action.nargs_max > 1: - command += ' {0}{0}'.format(self._parser.prefix_chars[0]) - - def process_action(action): - nonlocal command - if isinstance(action, argparse._SubParsersAction): - command += ' {}'.format(self._args[action.dest]) - traverse_parser(action.choices[self._args[action.dest]]) - elif isinstance(action, argparse._AppendAction): - if isinstance(self._args[action.dest], list) or isinstance(self._args[action.dest], tuple): - for values in self._args[action.dest]: - process_argument(action, values) - else: - process_argument(action, self._args[action.dest]) - else: - process_argument(action, self._args[action.dest]) - - def traverse_parser(parser): - # first process optional flag arguments - for action in parser._actions: - if action.dest in self._args and action.dest in self._flag_args and action.dest != self._remainder_arg: - process_action(action) - # next process positional arguments - for action in parser._actions: - if action.dest in self._args and action.dest not in self._flag_args and \ - action.dest != self._remainder_arg: - process_action(action) - # Keep remainder argument last - for action in parser._actions: - if action.dest in self._args and action.dest == self._remainder_arg: - process_action(action) - - traverse_parser(self._parser) - return _exec_cmd(self._cmd2_app, command, self._echo) - - class PyscriptBridge(object): """Preserves the legacy 'cmd' interface for pyscript while also providing a new python API wrapper for application commands.""" @@ -303,33 +50,9 @@ class PyscriptBridge(object): self._last_result = None self.cmd_echo = False - def __getattr__(self, item: str): - """ - Provide functionality to call application commands as a method of PyscriptBridge - ex: app.help() - """ - func = self._cmd2_app.cmd_func(item) - - if func: - if hasattr(func, 'argparser'): - # Command uses argparse, return an object that can traverse the argparse subcommands and arguments - return ArgparseFunctor(self.cmd_echo, self._cmd2_app, item, getattr(func, 'argparser')) - else: - # Command doesn't use argparse, we will accept parameters in the form of a command string - def wrap_func(args=''): - command = item - if args: - command += ' ' + args - return _exec_cmd(self._cmd2_app, command, self.cmd_echo) - - return wrap_func - else: - # item does not refer to a command - raise AttributeError("'{}' object has no attribute '{}'".format(self._cmd2_app.pyscript_name, item)) - def __dir__(self): """Return a custom set of attribute names""" - attributes = self._cmd2_app.get_all_commands() + attributes = [] attributes.insert(0, 'cmd_echo') return attributes @@ -344,4 +67,25 @@ class PyscriptBridge(object): if echo is None: echo = self.cmd_echo - return _exec_cmd(self._cmd2_app, command, echo) + copy_stdout = StdSim(sys.stdout, echo) + copy_stderr = StdSim(sys.stderr, echo) + + copy_cmd_stdout = StdSim(self._cmd2_app.stdout, echo) + + self._cmd2_app._last_result = None + + try: + self._cmd2_app.stdout = copy_cmd_stdout + with redirect_stdout(copy_stdout): + with redirect_stderr(copy_stderr): + # Include a newline in case it's a multiline command + self._cmd2_app.onecmd_plus_hooks(command + '\n') + finally: + self._cmd2_app.stdout = copy_cmd_stdout.inner_stream + + # if stderr is empty, set it to None + stderr = copy_stderr.getvalue() if copy_stderr.getvalue() else None + + outbuf = copy_cmd_stdout.getvalue() if copy_cmd_stdout.getvalue() else copy_stdout.getvalue() + result = CommandResult(stdout=outbuf, stderr=stderr, data=self._cmd2_app._last_result) + return result diff --git a/tests/pyscript/bar1.py b/tests/pyscript/bar1.py deleted file mode 100644 index 0f2b1e5e..00000000 --- a/tests/pyscript/bar1.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.bar('11', '22') diff --git a/tests/pyscript/custom_echo.py b/tests/pyscript/custom_echo.py deleted file mode 100644 index 3a79133a..00000000 --- a/tests/pyscript/custom_echo.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -custom.cmd_echo = True -custom.echo('blah!') diff --git a/tests/pyscript/foo1.py b/tests/pyscript/foo1.py deleted file mode 100644 index 443282a5..00000000 --- a/tests/pyscript/foo1.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.foo('aaa', 'bbb', counter=3, trueval=True, constval=True) diff --git a/tests/pyscript/foo2.py b/tests/pyscript/foo2.py deleted file mode 100644 index 9aa37105..00000000 --- a/tests/pyscript/foo2.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.foo('11', '22', '33', '44', counter=3, trueval=True, constval=True) diff --git a/tests/pyscript/foo3.py b/tests/pyscript/foo3.py deleted file mode 100644 index e4384076..00000000 --- a/tests/pyscript/foo3.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.foo('11', '22', '33', '44', '55', '66', counter=3, trueval=False, constval=False) diff --git a/tests/pyscript/foo4.py b/tests/pyscript/foo4.py deleted file mode 100644 index a601ccd8..00000000 --- a/tests/pyscript/foo4.py +++ /dev/null @@ -1,10 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -result = app.foo('aaa', 'bbb', counter=3) -out_text = 'Fail' -if result: - data = result.data - if 'aaa' in data.variable and 'bbb' in data.variable and data.counter == 3: - out_text = 'Success' - -print(out_text) diff --git a/tests/pyscript/help.py b/tests/pyscript/help.py index 3f24246d..933f42bc 100644 --- a/tests/pyscript/help.py +++ b/tests/pyscript/help.py @@ -1,3 +1,3 @@ # flake8: noqa F821 app.cmd_echo = True -app.help() +app('help') diff --git a/tests/pyscript/help_media.py b/tests/pyscript/help_media.py deleted file mode 100644 index 38c4a2f8..00000000 --- a/tests/pyscript/help_media.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.help('media') diff --git a/tests/pyscript/media_movies_add1.py b/tests/pyscript/media_movies_add1.py deleted file mode 100644 index b5045a39..00000000 --- a/tests/pyscript/media_movies_add1.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.add('My Movie', 'PG-13', director=('George Lucas', 'J. J. Abrams')) diff --git a/tests/pyscript/media_movies_add2.py b/tests/pyscript/media_movies_add2.py deleted file mode 100644 index 91dbbc6b..00000000 --- a/tests/pyscript/media_movies_add2.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.add('My Movie', 'PG-13', actor=('Mark Hamill'), director=('George Lucas', 'J. J. Abrams')) diff --git a/tests/pyscript/media_movies_list1.py b/tests/pyscript/media_movies_list1.py deleted file mode 100644 index 505d1f91..00000000 --- a/tests/pyscript/media_movies_list1.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.list() diff --git a/tests/pyscript/media_movies_list2.py b/tests/pyscript/media_movies_list2.py deleted file mode 100644 index 69e0d3c5..00000000 --- a/tests/pyscript/media_movies_list2.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media().movies().list() diff --git a/tests/pyscript/media_movies_list3.py b/tests/pyscript/media_movies_list3.py deleted file mode 100644 index c4f0cc1e..00000000 --- a/tests/pyscript/media_movies_list3.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app('media movies list') diff --git a/tests/pyscript/media_movies_list4.py b/tests/pyscript/media_movies_list4.py deleted file mode 100644 index 29e98fe7..00000000 --- a/tests/pyscript/media_movies_list4.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.list(actor='Mark Hamill') diff --git a/tests/pyscript/media_movies_list5.py b/tests/pyscript/media_movies_list5.py deleted file mode 100644 index 1c249ebf..00000000 --- a/tests/pyscript/media_movies_list5.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.list(actor=('Mark Hamill', 'Carrie Fisher')) diff --git a/tests/pyscript/media_movies_list6.py b/tests/pyscript/media_movies_list6.py deleted file mode 100644 index c16ae6c5..00000000 --- a/tests/pyscript/media_movies_list6.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.list(rating='PG') diff --git a/tests/pyscript/media_movies_list7.py b/tests/pyscript/media_movies_list7.py deleted file mode 100644 index d4ca7dca..00000000 --- a/tests/pyscript/media_movies_list7.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa F821 -app.cmd_echo = True -app.media.movies.list(rating=('PG', 'PG-13')) diff --git a/tests/pyscript/pyscript_dir1.py b/tests/pyscript/pyscript_dir.py index 81814d70..81814d70 100644 --- a/tests/pyscript/pyscript_dir1.py +++ b/tests/pyscript/pyscript_dir.py diff --git a/tests/pyscript/pyscript_dir2.py b/tests/pyscript/pyscript_dir2.py deleted file mode 100644 index ebbbf712..00000000 --- a/tests/pyscript/pyscript_dir2.py +++ /dev/null @@ -1,4 +0,0 @@ -# flake8: noqa F821 -out = dir(app.media) -out.sort() -print(out) diff --git a/tests/test_pyscript.py b/tests/test_pyscript.py index 692a498b..78500185 100644 --- a/tests/test_pyscript.py +++ b/tests/test_pyscript.py @@ -1,255 +1,31 @@ # coding=utf-8 # flake8: noqa E302 """ -Unit/functional testing for argparse completer in cmd2 - -Copyright 2018 Eric Lin <anselor@gmail.com> -Released under MIT license, see LICENSE file +Unit/functional testing for pytest in cmd2 """ import os -import pytest -from cmd2.cmd2 import Cmd, with_argparser -from cmd2 import argparse_completer -from .conftest import run_cmd, normalize -from cmd2.utils import namedtuple_with_defaults, StdSim - - -class PyscriptExample(Cmd): - ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17'] - - def _do_media_movies(self, args) -> None: - if not args.command: - self.do_help('media movies') - else: - self.poutput('media movies ' + str(args.__dict__)) - - def _do_media_shows(self, args) -> None: - if not args.command: - self.do_help('media shows') - - if not args.command: - self.do_help('media shows') - else: - self.poutput('media shows ' + str(args.__dict__)) - - media_parser = argparse_completer.ACArgumentParser(prog='media') - - media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type') - - movies_parser = media_types_subparsers.add_parser('movies') - movies_parser.set_defaults(func=_do_media_movies) - - movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command') - - movies_list_parser = movies_commands_subparsers.add_parser('list') - - movies_list_parser.add_argument('-t', '--title', help='Title Filter') - movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', - choices=ratings_types) - movies_list_parser.add_argument('-d', '--director', help='Director Filter') - movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') - - movies_add_parser = movies_commands_subparsers.add_parser('add') - movies_add_parser.add_argument('title', help='Movie Title') - movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) - movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True) - movies_add_parser.add_argument('actor', help='Actors', nargs='*') - - movies_delete_parser = movies_commands_subparsers.add_parser('delete') - - shows_parser = media_types_subparsers.add_parser('shows') - shows_parser.set_defaults(func=_do_media_shows) - - shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command') - - shows_list_parser = shows_commands_subparsers.add_parser('list') - - @with_argparser(media_parser) - def do_media(self, args): - """Media management command demonstrates multiple layers of sub-commands being handled by AutoCompleter""" - func = getattr(args, 'func', None) - if func is not None: - # Call whatever subcommand function was selected - func(self, args) - else: - # No subcommand was provided, so call help - self.do_help('media') - - foo_parser = argparse_completer.ACArgumentParser(prog='foo') - foo_parser.add_argument('-c', dest='counter', action='count') - foo_parser.add_argument('-t', dest='trueval', action='store_true') - foo_parser.add_argument('-n', dest='constval', action='store_const', const=42) - foo_parser.add_argument('variable', nargs=(2, 3)) - foo_parser.add_argument('optional', nargs='?') - foo_parser.add_argument('zeroormore', nargs='*') - - @with_argparser(foo_parser) - def do_foo(self, args): - self.poutput('foo ' + str(sorted(args.__dict__))) - if self._in_py: - FooResult = namedtuple_with_defaults('FooResult', - ['counter', 'trueval', 'constval', - 'variable', 'optional', 'zeroormore']) - self._last_result = FooResult(**{'counter': args.counter, - 'trueval': args.trueval, - 'constval': args.constval, - 'variable': args.variable, - 'optional': args.optional, - 'zeroormore': args.zeroormore}) - - bar_parser = argparse_completer.ACArgumentParser(prog='bar') - bar_parser.add_argument('first') - bar_parser.add_argument('oneormore', nargs='+') - bar_parser.add_argument('-a', dest='aaa') - - @with_argparser(bar_parser) - def do_bar(self, args): - out = 'bar ' - arg_dict = args.__dict__ - keys = list(arg_dict.keys()) - keys.sort() - out += '{' - for key in keys: - out += "'{}':'{}'".format(key, arg_dict[key]) - self.poutput(out) - - -@pytest.fixture -def ps_app(): - c = PyscriptExample() - c.stdout = StdSim(c.stdout) - return c - - -class PyscriptCustomNameExample(Cmd): - def __init__(self): - super().__init__() - self.pyscript_name = 'custom' - def do_echo(self, out): - self.poutput(out) +from .conftest import run_cmd -@pytest.fixture -def ps_echo(): - c = PyscriptCustomNameExample() - c.stdout = StdSim(c.stdout) - return c - - -@pytest.mark.parametrize('command, pyscript_file', [ - ('help', 'help.py'), - ('help media', 'help_media.py'), -]) -def test_pyscript_help(ps_app, request, command, pyscript_file): +def test_pyscript_help(base_app, request): test_dir = os.path.dirname(request.module.__file__) - python_script = os.path.join(test_dir, 'pyscript', pyscript_file) - expected = run_cmd(ps_app, command) + python_script = os.path.join(test_dir, 'pyscript', 'help.py') + expected = run_cmd(base_app, 'help') assert len(expected) > 0 assert len(expected[0]) > 0 - out = run_cmd(ps_app, 'pyscript {}'.format(python_script)) + out = run_cmd(base_app, 'pyscript {}'.format(python_script)) assert len(out) > 0 assert out == expected -@pytest.mark.parametrize('command, pyscript_file', [ - ('media movies list', 'media_movies_list1.py'), - ('media movies list', 'media_movies_list2.py'), - ('media movies list', 'media_movies_list3.py'), - ('media movies list -a "Mark Hamill"', 'media_movies_list4.py'), - ('media movies list -a "Mark Hamill" -a "Carrie Fisher"', 'media_movies_list5.py'), - ('media movies list -r PG', 'media_movies_list6.py'), - ('media movies list -r PG PG-13', 'media_movies_list7.py'), - ('media movies add "My Movie" PG-13 --director "George Lucas" "J. J. Abrams"', - 'media_movies_add1.py'), - ('media movies add "My Movie" PG-13 --director "George Lucas" "J. J. Abrams" "Mark Hamill"', - 'media_movies_add2.py'), - ('foo aaa bbb -ccc -t -n', 'foo1.py'), - ('foo 11 22 33 44 -ccc -t -n', 'foo2.py'), - ('foo 11 22 33 44 55 66 -ccc', 'foo3.py'), - ('bar 11 22', 'bar1.py'), -]) -def test_pyscript_out(ps_app, request, command, pyscript_file): +def test_pyscript_dir(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) - python_script = os.path.join(test_dir, 'pyscript', pyscript_file) - expected = run_cmd(ps_app, command) - assert expected - - out = run_cmd(ps_app, 'pyscript {}'.format(python_script)) - assert out - assert out == expected - - -@pytest.mark.parametrize('command, error', [ - ('app.noncommand', 'AttributeError'), - ('app.media.noncommand', 'AttributeError'), - ('app.media.movies.list(artist="Invalid Keyword")', 'TypeError'), - ('app.foo(counter="a")', 'TypeError'), - ('app.foo("aaa")', 'ValueError'), -]) -def test_pyscript_errors(ps_app, capsys, command, error): - run_cmd(ps_app, 'py {}'.format(command)) - _, err = capsys.readouterr() - - assert len(err) > 0 - assert 'Traceback' in err - assert error in err - - -@pytest.mark.parametrize('pyscript_file, exp_out', [ - ('foo4.py', 'Success'), -]) -def test_pyscript_results(ps_app, capsys, request, pyscript_file, exp_out): - test_dir = os.path.dirname(request.module.__file__) - python_script = os.path.join(test_dir, 'pyscript', pyscript_file) - - run_cmd(ps_app, 'pyscript {}'.format(python_script)) - expected, _ = capsys.readouterr() - assert len(expected) > 0 - assert exp_out in expected - + python_script = os.path.join(test_dir, 'pyscript', 'pyscript_dir.py') -@pytest.mark.parametrize('expected, pyscript_file', [ - ("['_relative_load', 'alias', 'bar', 'cmd_echo', 'edit', 'eof', 'eos', 'foo', 'help', 'history', 'load', 'macro', 'media', 'py', 'pyscript', 'quit', 'set', 'shell', 'shortcuts']", - 'pyscript_dir1.py'), - ("['movies', 'shows']", 'pyscript_dir2.py') -]) -def test_pyscript_dir(ps_app, capsys, request, expected, pyscript_file): - test_dir = os.path.dirname(request.module.__file__) - python_script = os.path.join(test_dir, 'pyscript', pyscript_file) - - run_cmd(ps_app, 'pyscript {}'.format(python_script)) + run_cmd(base_app, 'pyscript {}'.format(python_script)) out, _ = capsys.readouterr() out = out.strip() assert len(out) > 0 - assert out == expected - - -def test_pyscript_custom_name(ps_echo, request): - message = 'blah!' - - test_dir = os.path.dirname(request.module.__file__) - python_script = os.path.join(test_dir, 'pyscript', 'custom_echo.py') - - out = run_cmd(ps_echo, 'pyscript {}'.format(python_script)) - assert out - assert message == out[0] - - -def test_pyscript_argparse_checks(ps_app, capsys): - # Test command that has nargs.REMAINDER and make sure all tokens are accepted - # Include a flag in the REMAINDER section to show that they are processed as literals in that section - run_cmd(ps_app, 'py app.alias.create("my_alias", "alias_command", "command_arg1", "-h")') - out = run_cmd(ps_app, 'alias list my_alias') - assert out == normalize('alias create my_alias alias_command command_arg1 -h') - - # Specify flag outside of keyword argument - run_cmd(ps_app, 'py app.help("-h")') - _, err = capsys.readouterr() - assert '-h appears to be a flag' in err - - # Specify list with flag outside of keyword argument - run_cmd(ps_app, 'py app.help(["--help"])') - _, err = capsys.readouterr() - assert '--help appears to be a flag' in err + assert out == "['cmd_echo']" |