summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Lin <anselor@gmail.com>2018-05-01 18:03:37 -0400
committerEric Lin <anselor@gmail.com>2018-05-01 18:03:37 -0400
commit839d957128a4fcb7d6c2b58a4508eb38855e591f (patch)
treed177140cdcede1d13245c32167a90a370105fd45
parent4a36b8c68d24707e85a1030759ec42b1a07421e1 (diff)
downloadcmd2-git-839d957128a4fcb7d6c2b58a4508eb38855e591f.tar.gz
Added support for different argument modes and tests to validate.
-rw-r--r--cmd2/pyscript_bridge.py62
-rw-r--r--tests/pyscript/bar1.py1
-rw-r--r--tests/pyscript/foo1.py1
-rw-r--r--tests/pyscript/foo2.py1
-rw-r--r--tests/pyscript/foo3.py1
-rw-r--r--tests/test_pyscript.py49
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