diff options
author | Eric Lin <anselor@gmail.com> | 2018-05-01 18:03:37 -0400 |
---|---|---|
committer | Eric Lin <anselor@gmail.com> | 2018-05-01 18:03:37 -0400 |
commit | 839d957128a4fcb7d6c2b58a4508eb38855e591f (patch) | |
tree | d177140cdcede1d13245c32167a90a370105fd45 | |
parent | 4a36b8c68d24707e85a1030759ec42b1a07421e1 (diff) | |
download | cmd2-git-839d957128a4fcb7d6c2b58a4508eb38855e591f.tar.gz |
Added support for different argument modes and tests to validate.
-rw-r--r-- | cmd2/pyscript_bridge.py | 62 | ||||
-rw-r--r-- | tests/pyscript/bar1.py | 1 | ||||
-rw-r--r-- | tests/pyscript/foo1.py | 1 | ||||
-rw-r--r-- | tests/pyscript/foo2.py | 1 | ||||
-rw-r--r-- | tests/pyscript/foo3.py | 1 | ||||
-rw-r--r-- | tests/test_pyscript.py | 49 |
6 files changed, 96 insertions, 19 deletions
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py index 3eda02f0..2266aeba 100644 --- a/cmd2/pyscript_bridge.py +++ b/cmd2/pyscript_bridge.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable degree of isolation between the two @@ -8,9 +9,13 @@ Released under MIT license, see LICENSE file import argparse from typing import List, Tuple +from .argparse_completer import _RangeAction class ArgparseFunctor: + """ + Encapsulates translating python object traversal + """ def __init__(self, cmd2_app, item, parser): self._cmd2_app = cmd2_app self._item = item @@ -59,10 +64,46 @@ class ArgparseFunctor: # 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): - self._args[action.dest] = args[next_pos_index] - next_pos_index += 1 + # 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 == '?': + 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 @@ -85,6 +126,22 @@ class ArgparseFunctor: cmd_str = [''] def process_flag(action, value): + if isinstance(action, argparse._CountAction): + if isinstance(value, int): + for c in range(value): + cmd_str[0] += '{} '.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. + cmd_str[0] += '{} '.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: cmd_str[0] += '{} '.format(action.option_strings[0]) @@ -114,7 +171,6 @@ class ArgparseFunctor: process_flag(action, values) else: process_flag(action, self._args[action.dest]) - # TODO: StoreTrue/StoreFalse else: process_flag(action, self._args[action.dest]) diff --git a/tests/pyscript/bar1.py b/tests/pyscript/bar1.py new file mode 100644 index 00000000..c6276a87 --- /dev/null +++ b/tests/pyscript/bar1.py @@ -0,0 +1 @@ +app.bar('11', '22') diff --git a/tests/pyscript/foo1.py b/tests/pyscript/foo1.py new file mode 100644 index 00000000..6e345d95 --- /dev/null +++ b/tests/pyscript/foo1.py @@ -0,0 +1 @@ +app.foo('aaa', 'bbb', counter=3, trueval=True, constval=True) diff --git a/tests/pyscript/foo2.py b/tests/pyscript/foo2.py new file mode 100644 index 00000000..d4df7616 --- /dev/null +++ b/tests/pyscript/foo2.py @@ -0,0 +1 @@ +app.foo('11', '22', '33', '44', counter=3, trueval=True, constval=True) diff --git a/tests/pyscript/foo3.py b/tests/pyscript/foo3.py new file mode 100644 index 00000000..db69edaf --- /dev/null +++ b/tests/pyscript/foo3.py @@ -0,0 +1 @@ +app.foo('11', '22', '33', '44', '55', '66', counter=3, trueval=False, constval=False) diff --git a/tests/test_pyscript.py b/tests/test_pyscript.py index 2a264a15..5d685c41 100644 --- a/tests/test_pyscript.py +++ b/tests/test_pyscript.py @@ -71,6 +71,27 @@ class PyscriptExample(Cmd): # 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): + print('foo ' + str(args.__dict__)) + + 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): + print('bar ' + str(args.__dict__)) + @pytest.fixture def ps_app(): @@ -108,6 +129,10 @@ def test_pyscript_help(ps_app, capsys, request, command, pyscript_file): '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, capsys, request, command, pyscript_file): test_dir = os.path.dirname(request.module.__file__) @@ -122,26 +147,18 @@ def test_pyscript_out(ps_app, capsys, request, command, pyscript_file): assert out == expected -@pytest.mark.parametrize('command', [ - 'app.noncommand', - 'app.media.noncommand', +@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_unknown_command(ps_app, capsys, command): +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 'AttributeError' in err - + assert error in err -@pytest.mark.parametrize('command', [ - 'app.media.movies.list(artist="Invalid Keyword")', -]) -def test_pyscript_unknown_kw(ps_app, capsys, command): - run_cmd(ps_app, 'py {}'.format(command)) - _, err = capsys.readouterr() - - assert len(err) > 0 - assert 'Traceback' in err - assert 'TypeError' in err |