summaryrefslogtreecommitdiff
path: root/cmd2/pyscript_bridge.py
diff options
context:
space:
mode:
authorEric Lin <anselor@gmail.com>2018-04-28 16:08:18 -0400
committerEric Lin <anselor@gmail.com>2018-04-28 16:08:18 -0400
commitab7ac493950040565d933ed02deaeb778ef977e3 (patch)
treed6523277fb996b019a6a43714cfbb1ad27106704 /cmd2/pyscript_bridge.py
parent54f9a7a6f691367642ac47913ff5163e1e5155a7 (diff)
downloadcmd2-git-ab7ac493950040565d933ed02deaeb778ef977e3.tar.gz
Added support for translating function positional and keyword arguments into argparse command positional and flag arguments.
Added initial set of tests
Diffstat (limited to 'cmd2/pyscript_bridge.py')
-rw-r--r--cmd2/pyscript_bridge.py64
1 files changed, 55 insertions, 9 deletions
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py
index d48317c3..a9bbc311 100644
--- a/cmd2/pyscript_bridge.py
+++ b/cmd2/pyscript_bridge.py
@@ -1,7 +1,14 @@
-"""Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
-degree of isolation between the two"""
+"""
+Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
+degree of isolation between the two
+
+Copyright 2018 Eric Lin <anselor@gmail.com>
+Released under MIT license, see LICENSE file
+"""
import argparse
+from typing import List, Tuple
+
class ArgparseFunctor:
def __init__(self, cmd2_app, item, parser):
@@ -9,11 +16,14 @@ class ArgparseFunctor:
self._item = item
self._parser = parser
+ # Dictionary mapping command argument name to value
self._args = {}
+ # argparse object for the current command layer
self.__current_subcommand_parser = parser
def __getattr__(self, item):
- # look for sub-command
+ """Search for a subcommand 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:
@@ -22,20 +32,30 @@ class ArgparseFunctor:
self.__current_subcommand_parser = action.choices[item]
self._args[action.dest] = item
return self
+
return super().__getatttr__(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
consumed_kw = []
+
+ # 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]
consumed_kw.append(action.dest)
else:
+ # This is a positional argument, search the positional arguments passed in.
if not isinstance(action, argparse._SubParsersAction):
if next_pos_index < len(args):
self._args[action.dest] = args[next_pos_index]
@@ -43,6 +63,7 @@ class ArgparseFunctor:
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 consumed_kw:
raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(
@@ -60,19 +81,38 @@ class ArgparseFunctor:
# reconstruct the cmd2 command from the python call
cmd_str = ['']
+ def process_flag(action, value):
+ # was the argument a flag?
+ if action.option_strings:
+ cmd_str[0] += '{} '.format(action.option_strings[0])
+
+ if isinstance(value, List) or isinstance(value, Tuple):
+ for item in value:
+ item = str(item).strip()
+ if ' ' in item:
+ item = '"{}"'.format(item)
+ cmd_str[0] += '{} '.format(item)
+ else:
+ value = str(value).strip()
+ if ' ' in value:
+ value = '"{}"'.format(value)
+ cmd_str[0] += '{} '.format(value)
+
def traverse_parser(parser):
for action in parser._actions:
# was something provided for the argument
if action.dest in self._args:
if isinstance(action, argparse._SubParsersAction):
+ cmd_str[0] += '{} '.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_flag(action, values)
+ else:
+ process_flag(action, self._args[action.dest])
else:
- # was the argument a flag?
- if action.option_strings:
- cmd_str[0] += '{} '.format(action.option_strings[0])
-
- # TODO: Handle 'narg' and 'append' options
- cmd_str[0] += '"{}" '.format(self._args[action.dest])
+ process_flag(action, self._args[action.dest])
traverse_parser(self._parser)
@@ -81,23 +121,29 @@ class ArgparseFunctor:
class PyscriptBridge(object):
+ """Preserves the legacy 'cmd' interface for pyscript while also providing a new python API wrapper for
+ application commands."""
def __init__(self, cmd2_app):
self._cmd2_app = cmd2_app
self._last_result = None
def __getattr__(self, item: str):
+ """Check if the attribute is a command. If so, return a callable."""
commands = self._cmd2_app.get_all_commands()
if item in commands:
func = getattr(self._cmd2_app, 'do_' + item)
try:
+ # See if the command uses argparse
parser = getattr(func, 'argparser')
except AttributeError:
+ # Command doesn't, we will accept parameters in the form of a command string
def wrap_func(args=''):
func(args)
return self._cmd2_app._last_result
return wrap_func
else:
+ # Command does use argparse, return an object that can traverse the argparse subcommands and arguments
return ArgparseFunctor(self._cmd2_app, item, parser)
return super().__getattr__(item)