summaryrefslogtreecommitdiff
path: root/cmd2
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-10-03 10:56:40 -0400
committerGitHub <noreply@github.com>2018-10-03 10:56:40 -0400
commitb99c0941f2738202b40c76ef89e6ab418ff45c8c (patch)
treecb003358da16c536bd2746a816f29b54d62f6648 /cmd2
parentfadb8d3d005f36d9945a4680f799bbdf730da67b (diff)
parent7e2497d1a97f8bfc3ac969e488b5f9263f26c878 (diff)
downloadcmd2-git-b99c0941f2738202b40c76ef89e6ab418ff45c8c.tar.gz
Merge pull request #560 from python-cmd2/py_enhancements
Py enhancements
Diffstat (limited to 'cmd2')
-rw-r--r--cmd2/cmd2.py52
-rw-r--r--cmd2/pyscript_bridge.py43
2 files changed, 53 insertions, 42 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index cc4c4bbc..6859de5b 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -2058,15 +2058,14 @@ class Cmd(cmd.Cmd):
:param statement: Statement object with parsed input
"""
- arg = statement.raw
if self.default_to_shell:
- result = os.system(arg)
+ result = os.system(statement.command_and_args)
# If os.system() succeeded, then don't print warning about unknown command
if not result:
return
# Print out a message stating this is an unknown command
- self.poutput('*** Unknown syntax: {}\n'.format(arg))
+ self.poutput('*** Unknown syntax: {}\n'.format(statement.command_and_args))
def pseudo_raw_input(self, prompt: str) -> str:
"""Began life as a copy of cmd's cmdloop; like raw_input but
@@ -2208,10 +2207,9 @@ class Cmd(cmd.Cmd):
# ----- Alias subcommand functions -----
def alias_create(self, args: argparse.Namespace):
- """ Creates or overwrites an alias """
+ """Create or overwrites an alias"""
# Validate the alias name
- args.name = utils.strip_quotes(args.name)
valid, errmsg = self.statement_parser.is_valid_command(args.name)
if not valid:
self.perror("Invalid alias name: {}".format(errmsg), traceback_war=False)
@@ -2234,17 +2232,14 @@ class Cmd(cmd.Cmd):
self.poutput("Alias '{}' {}".format(args.name, result))
def alias_delete(self, args: argparse.Namespace):
- """ Deletes aliases """
+ """Delete aliases"""
if args.all:
self.aliases.clear()
self.poutput("All aliases deleted")
elif not args.name:
self.do_help('alias delete')
else:
- # Get rid of duplicates and strip quotes since the argparse decorator for do_alias() preserves them
- aliases_to_delete = [utils.strip_quotes(cur_name) for cur_name in utils.remove_duplicates(args.name)]
-
- for cur_name in aliases_to_delete:
+ for cur_name in utils.remove_duplicates(args.name):
if cur_name in self.aliases:
del self.aliases[cur_name]
self.poutput("Alias '{}' deleted".format(cur_name))
@@ -2252,12 +2247,9 @@ class Cmd(cmd.Cmd):
self.perror("Alias '{}' does not exist".format(cur_name), traceback_war=False)
def alias_list(self, args: argparse.Namespace):
- """ Lists some or all aliases """
+ """List some or all aliases"""
if args.name:
- # Get rid of duplicates and strip quotes since the argparse decorator for do_alias() preserves them
- names_to_view = [utils.strip_quotes(cur_name) for cur_name in utils.remove_duplicates(args.name)]
-
- for cur_name in names_to_view:
+ for cur_name in utils.remove_duplicates(args.name):
if cur_name in self.aliases:
self.poutput("alias create {} {}".format(cur_name, self.aliases[cur_name]))
else:
@@ -2343,10 +2335,9 @@ class Cmd(cmd.Cmd):
# ----- Macro subcommand functions -----
def macro_create(self, args: argparse.Namespace):
- """ Creates or overwrites a macro """
+ """Create or overwrites a macro"""
# Validate the macro name
- args.name = utils.strip_quotes(args.name)
valid, errmsg = self.statement_parser.is_valid_command(args.name)
if not valid:
self.perror("Invalid macro name: {}".format(errmsg), traceback_war=False)
@@ -2419,17 +2410,14 @@ class Cmd(cmd.Cmd):
self.poutput("Macro '{}' {}".format(args.name, result))
def macro_delete(self, args: argparse.Namespace):
- """ Deletes macros """
+ """Delete macros"""
if args.all:
self.macros.clear()
self.poutput("All macros deleted")
elif not args.name:
self.do_help('macro delete')
else:
- # Get rid of duplicates and strip quotes since the argparse decorator for do_macro() preserves them
- macros_to_delete = [utils.strip_quotes(cur_name) for cur_name in utils.remove_duplicates(args.name)]
-
- for cur_name in macros_to_delete:
+ for cur_name in utils.remove_duplicates(args.name):
if cur_name in self.macros:
del self.macros[cur_name]
self.poutput("Macro '{}' deleted".format(cur_name))
@@ -2437,12 +2425,9 @@ class Cmd(cmd.Cmd):
self.perror("Macro '{}' does not exist".format(cur_name), traceback_war=False)
def macro_list(self, args: argparse.Namespace):
- """ Lists some or all macros """
+ """List some or all macros"""
if args.name:
- # Get rid of duplicates and strip quotes since the argparse decorator for do_macro() preserves them
- names_to_view = [utils.strip_quotes(cur_name) for cur_name in utils.remove_duplicates(args.name)]
-
- for cur_name in names_to_view:
+ for cur_name in utils.remove_duplicates(args.name):
if cur_name in self.macros:
self.poutput("macro create {} {}".format(cur_name, self.macros[cur_name].value))
else:
@@ -2949,7 +2934,16 @@ class Cmd(cmd.Cmd):
sys.displayhook = sys.__displayhook__
sys.excepthook = sys.__excepthook__
- py_parser = ACArgumentParser()
+ py_description = ("Invoke Python command or shell\n"
+ "\n"
+ "Note that, when invoking a command directly from the command line, this shell\n"
+ "has limited ability to parse Python statements into tokens. In particular,\n"
+ "there may be problems with whitespace and quotes depending on their placement.\n"
+ "\n"
+ "If you see strange parsing behavior, it's best to just open the Python shell by\n"
+ "providing no arguments to py and run more complex statements there.")
+
+ py_parser = ACArgumentParser(description=py_description)
py_parser.add_argument('command', help="command to run", nargs='?')
py_parser.add_argument('remainder', help="remainder of command", nargs=argparse.REMAINDER)
@@ -3000,6 +2994,8 @@ class Cmd(cmd.Cmd):
if args.remainder:
full_command += ' ' + ' '.join(args.remainder)
+ # If running at the CLI, print the output of the command
+ bridge.cmd_echo = True
interp.runcode(full_command)
# If there are no args, then we will open an interactive Python console
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py
index f03b530f..a70a7ae6 100644
--- a/cmd2/pyscript_bridge.py
+++ b/cmd2/pyscript_bridge.py
@@ -10,7 +10,7 @@ Released under MIT license, see LICENSE file
import argparse
import functools
import sys
-from typing import List, Callable
+from typing import List, Callable, Optional
from .argparse_completer import _RangeAction
from .utils import namedtuple_with_defaults, StdSim
@@ -38,7 +38,7 @@ class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr
return not self.stderr and self.data is not None
-def _exec_cmd(cmd2_app, func: Callable, echo: bool):
+def _exec_cmd(cmd2_app, func: Callable, echo: bool) -> CommandResult:
"""Helper to encapsulate executing a command and capturing the results"""
copy_stdout = StdSim(sys.stdout, echo)
copy_stderr = StdSim(sys.stderr, echo)
@@ -65,7 +65,7 @@ def _exec_cmd(cmd2_app, func: Callable, echo: bool):
class ArgparseFunctor:
"""
- Encapsulates translating python object traversal
+ Encapsulates translating Python object traversal
"""
def __init__(self, echo: bool, cmd2_app, command_name: str, parser: argparse.ArgumentParser):
self._echo = echo
@@ -169,7 +169,7 @@ class ArgparseFunctor:
# Check if there are any extra arguments we don't know how to handle
for kw in kwargs:
if kw not in self._args: # consumed_kw:
- raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(
+ raise TypeError("{}() got an unexpected keyword argument '{}'".format(
self.__current_subcommand_parser.prog, kw))
if has_subcommand:
@@ -181,7 +181,7 @@ class ArgparseFunctor:
# look up command function
func = self._cmd2_app.cmd_func(self._command_name)
if func is None:
- raise AttributeError("{!r} object has no command called {!r}".format(self._cmd2_app.__class__.__name__,
+ raise AttributeError("'{}' object has no command called '{}'".format(self._cmd2_app.__class__.__name__,
self._command_name))
# reconstruct the cmd2 command from the python call
@@ -250,7 +250,10 @@ class PyscriptBridge(object):
self.cmd_echo = False
def __getattr__(self, item: str):
- """Check if the attribute is a command. If so, return a callable."""
+ """
+ Provide functionality to call application commands as a method of PyscriptBridge
+ ex: app.help()
+ """
func = self._cmd2_app.cmd_func(item)
if func:
@@ -264,14 +267,26 @@ class PyscriptBridge(object):
return wrap_func
else:
- return super().__getattr__(item)
+ # 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 to match the available commands"""
- commands = list(self._cmd2_app.get_all_commands())
- commands.insert(0, 'cmd_echo')
- return commands
+ """Return a custom set of attribute names"""
+ attributes = self._cmd2_app.get_all_commands()
+ attributes.insert(0, 'cmd_echo')
+ return attributes
+
+ def __call__(self, args: str, echo: Optional[bool]=None) -> CommandResult:
+ """
+ Provide functionality to call application commands by calling PyscriptBridge
+ ex: app('help')
+ :param args: The string being passed to the command
+ :param echo: If True, output will be echoed while the command runs
+ This temporarily overrides the value of self.cmd_echo
+ """
+ if echo is None:
+ echo = self.cmd_echo
- def __call__(self, args: str):
- return _exec_cmd(self._cmd2_app, functools.partial(self._cmd2_app.onecmd_plus_hooks, args + '\n'),
- self.cmd_echo)
+ return _exec_cmd(self._cmd2_app,
+ functools.partial(self._cmd2_app.onecmd_plus_hooks, args + '\n'),
+ echo)