summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Lin <anselor@gmail.com>2018-04-24 16:17:25 -0400
committerEric Lin <anselor@gmail.com>2018-04-24 16:17:25 -0400
commit0a1c41ce7048b45fc7ef9b0176d988c26861224e (patch)
treedf27473d38566e24dbf6bb5179cf32bb5f966193
parentf11b06374aaf56b755de33a763220140d36eab64 (diff)
downloadcmd2-git-0a1c41ce7048b45fc7ef9b0176d988c26861224e.tar.gz
Initial approach to the pyscript revamp.
Doesn't handle all argparse argument options yet (nargs, append, flag, probably more) For #368
-rwxr-xr-xcmd2/argparse_completer.py8
-rwxr-xr-xcmd2/cmd2.py7
-rw-r--r--cmd2/pyscript_bridge.py107
3 files changed, 119 insertions, 3 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 03f2d965..8b5246e8 100755
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -75,6 +75,7 @@ from .rl_utils import rl_force_redisplay
# define the completion choices for the argument. You may provide a Collection or a Function.
ACTION_ARG_CHOICES = 'arg_choices'
+
class _RangeAction(object):
def __init__(self, nargs: Union[int, str, Tuple[int, int], None]):
self.nargs_min = None
@@ -103,6 +104,7 @@ class _RangeAction(object):
self.nargs_adjusted = nargs
+# noinspection PyShadowingBuiltins,PyShadowingBuiltins
class _StoreRangeAction(argparse._StoreAction, _RangeAction):
def __init__(self,
option_strings,
@@ -131,6 +133,7 @@ class _StoreRangeAction(argparse._StoreAction, _RangeAction):
metavar=metavar)
+# noinspection PyShadowingBuiltins,PyShadowingBuiltins
class _AppendRangeAction(argparse._AppendAction, _RangeAction):
def __init__(self,
option_strings,
@@ -433,7 +436,6 @@ class AutoCompleter(object):
return self.basic_complete(text, line, begidx, endidx, completers.keys())
return []
-
@staticmethod
def _process_action_nargs(action: argparse.Action, arg_state: _ArgumentState) -> None:
if isinstance(action, _RangeAction):
@@ -571,6 +573,7 @@ class AutoCompleter(object):
###############################################################################
+# noinspection PyCompatibility,PyShadowingBuiltins,PyShadowingBuiltins
class ACHelpFormatter(argparse.HelpFormatter):
"""Custom help formatter to configure ordering of help text"""
@@ -631,6 +634,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
# End cmd2 customization
# helper for wrapping lines
+ # noinspection PyMissingOrEmptyDocstring,PyShadowingNames
def get_lines(parts, indent, prefix=None):
lines = []
line = []
@@ -722,6 +726,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
else:
result = default_metavar
+ # noinspection PyMissingOrEmptyDocstring
def format(tuple_size):
if isinstance(result, tuple):
return result
@@ -748,6 +753,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
return text.splitlines()
+# noinspection PyCompatibility
class ACArgumentParser(argparse.ArgumentParser):
"""Custom argparse class to override error method to change default help text."""
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 8d8a5b07..4437426e 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -2877,13 +2877,13 @@ Usage: Usage: unalias [-a] name [name ...]
Non-python commands can be issued with ``cmd("your command")``.
Run python code from external script files with ``run("script.py")``
"""
+ from .pyscript_bridge import PyscriptBridge
if self._in_py:
self.perror("Recursively entering interactive Python consoles is not allowed.", traceback_war=False)
return
self._in_py = True
try:
- self.pystate['self'] = self
arg = arg.strip()
# Support the run command even if called prior to invoking an interactive interpreter
@@ -2906,8 +2906,11 @@ Usage: Usage: unalias [-a] name [name ...]
"""
return self.onecmd_plus_hooks(cmd_plus_args + '\n')
+ bridge = PyscriptBridge(self)
+ self.pystate['self'] = bridge
self.pystate['run'] = run
- self.pystate['cmd'] = onecmd_plus_hooks
+ self.pystate['cmd'] = bridge
+ self.pystate['app'] = bridge
localvars = (self.locals_in_py and self.pystate) or {}
interp = InteractiveConsole(locals=localvars)
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py
new file mode 100644
index 00000000..88e12bfb
--- /dev/null
+++ b/cmd2/pyscript_bridge.py
@@ -0,0 +1,107 @@
+"""Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
+degree of isolation between the two"""
+
+import argparse
+
+class ArgparseFunctor:
+ def __init__(self, cmd2_app, item, parser):
+ self._cmd2_app = cmd2_app
+ self._item = item
+ self._parser = parser
+
+ self._args = {}
+ self.__current_subcommand_parser = parser
+
+ def __getattr__(self, item):
+ # look for sub-command
+ 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
+ return super().__getatttr__(item)
+
+ def __call__(self, *args, **kwargs):
+ next_pos_index = 0
+
+ has_subcommand = False
+ consumed_kw = []
+ for action in self.__current_subcommand_parser._actions:
+ # is this a flag option?
+ if action.option_strings:
+ if action.dest in kwargs:
+ self._args[action.dest] = kwargs[action.dest]
+ consumed_kw.append(action.dest)
+ else:
+ if not isinstance(action, argparse._SubParsersAction):
+ if next_pos_index < len(args):
+ self._args[action.dest] = args[next_pos_index]
+ next_pos_index += 1
+ else:
+ has_subcommand = True
+
+ for kw in kwargs:
+ if kw not in consumed_kw:
+ 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 = getattr(self._cmd2_app, 'do_' + self._item)
+
+ # reconstruct the cmd2 command from the python call
+ cmd_str = ['']
+
+ def traverse_parser(parser):
+ for action in parser._actions:
+ # was something provided for the argument
+ if action.dest in self._args:
+ # was the argument a flag?
+ # TODO: Handle 'narg' and 'append' options
+ if action.option_strings:
+ cmd_str[0] += '"{}" "{}" '.format(action.option_strings[0], self._args[action.dest])
+ else:
+ cmd_str[0] += '"{}" '.format(self._args[action.dest])
+
+ if isinstance(action, argparse._SubParsersAction):
+ traverse_parser(action.choices[self._args[action.dest]])
+ traverse_parser(self._parser)
+
+ func(cmd_str[0])
+ return self._cmd2_app._last_result
+
+
+class PyscriptBridge(object):
+ def __init__(self, cmd2_app):
+ self._cmd2_app = cmd2_app
+ self._last_result = None
+
+ def __getattr__(self, item: str):
+ commands = self._cmd2_app.get_all_commands()
+ if item in commands:
+ func = getattr(self._cmd2_app, 'do_' + item)
+
+ try:
+ parser = getattr(func, 'argparser')
+ except AttributeError:
+ def wrap_func(args=''):
+ func(args)
+ return self._cmd2_app._last_result
+ return wrap_func
+ else:
+ return ArgparseFunctor(self._cmd2_app, item, parser)
+
+ return super().__getattr__(item)
+
+ def __call__(self, args):
+ self._cmd2_app.onecmd_plus_hooks(args + '\n')
+ self._last_result = self._cmd2_app._last_result
+ return self._cmd2_app._last_result