diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-10-03 10:56:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-03 10:56:40 -0400 |
commit | b99c0941f2738202b40c76ef89e6ab418ff45c8c (patch) | |
tree | cb003358da16c536bd2746a816f29b54d62f6648 /cmd2 | |
parent | fadb8d3d005f36d9945a4680f799bbdf730da67b (diff) | |
parent | 7e2497d1a97f8bfc3ac969e488b5f9263f26c878 (diff) | |
download | cmd2-git-b99c0941f2738202b40c76ef89e6ab418ff45c8c.tar.gz |
Merge pull request #560 from python-cmd2/py_enhancements
Py enhancements
Diffstat (limited to 'cmd2')
-rw-r--r-- | cmd2/cmd2.py | 52 | ||||
-rw-r--r-- | cmd2/pyscript_bridge.py | 43 |
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) |