Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,5 @@ htmlcov
dmypy.json
dmypy.sock

# cmd2 history file used in main.py
# cmd2 history file used in hello_cmd2.py
cmd2_history.txt

# Virtual environment
venv
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
## 0.9.6 (TBD)
## 0.9.6 (October 13, 2018)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

* Bug Fixes
* Fixed bug introduced in 0.9.5 caused by backing up and restoring `self.prompt` in `pseudo_raw_input`.
As part of this fix, continuation prompts will not be redrawn with `async_update_prompt` or `async_alert`.
* Enhancements
* All platforms now depend on [wcwidth](https://pypi.python.org/pypi/wcwidth) to assist with asynchronous alerts.
* Macros now accept extra arguments when called. These will be tacked onto the resolved command.
* All cmd2 commands run via py now go through onecmd_plus_hooks.

## 0.9.5 (October 11, 2018)
* Bug Fixes
Expand Down
53 changes: 31 additions & 22 deletions cmd2/pyscript_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ def __bool__(self) -> bool:
return not self.stderr


def _exec_cmd(cmd2_app, func: Callable, echo: bool) -> CommandResult:
"""Helper to encapsulate executing a command and capturing the results"""
def _exec_cmd(cmd2_app, command: str, echo: bool) -> CommandResult:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes seem good

"""
Helper to encapsulate executing a command and capturing the results
:param cmd2_app: cmd2 app that will run the command
:param command: command line being run
:param echo: if True, output will be echoed to stdout/stderr while the command runs
:return: result of the command
"""
copy_stdout = StdSim(sys.stdout, echo)
copy_stderr = StdSim(sys.stderr, echo)

Expand All @@ -58,7 +64,8 @@ def _exec_cmd(cmd2_app, func: Callable, echo: bool) -> CommandResult:
cmd2_app.stdout = copy_cmd_stdout
with redirect_stdout(copy_stdout):
with redirect_stderr(copy_stderr):
func()
# Include a newline in case it's a multiline command
cmd2_app.onecmd_plus_hooks(command + '\n')
finally:
cmd2_app.stdout = copy_cmd_stdout.inner_stream

Expand Down Expand Up @@ -199,28 +206,29 @@ def _run(self):
self._command_name))

# reconstruct the cmd2 command from the python call
cmd_str = ['']
command = self._command_name

def process_argument(action, value):
nonlocal command
if isinstance(action, argparse._CountAction):
if isinstance(value, int):
for _ in range(value):
cmd_str[0] += '{} '.format(action.option_strings[0])
command += ' {}'.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])
command += ' {}'.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])
command += ' {}'.format(action.option_strings[0])

is_remainder_arg = action.dest == self._remainder_arg

Expand All @@ -231,33 +239,34 @@ def process_argument(action, value):
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
'to the function.'.format(item))
item = quote_string_if_needed(item)
cmd_str[0] += '{} '.format(item)
command += ' {}'.format(item)

# If this is a flag parameter that can accept a variable number of arguments and we have not
# reached the max number, add a list completion suffix to tell argparse to move to the next
# parameter
if action.option_strings and isinstance(action, _RangeAction) and action.nargs_max is not None and \
action.nargs_max > len(value):
cmd_str[0] += '{0}{0} '.format(self._parser.prefix_chars[0])
command += ' {0}{0}'.format(self._parser.prefix_chars[0])

else:
value = str(value).strip()
if not is_remainder_arg and is_potential_flag(value, self._parser):
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
'to the function.'.format(value))
value = quote_string_if_needed(value)
cmd_str[0] += '{} '.format(value)
command += ' {}'.format(value)

# If this is a flag parameter that can accept a variable number of arguments and we have not
# reached the max number, add a list completion suffix to tell argparse to move to the next
# parameter
if action.option_strings and isinstance(action, _RangeAction) and action.nargs_max is not None and \
action.nargs_max > 1:
cmd_str[0] += '{0}{0} '.format(self._parser.prefix_chars[0])
command += ' {0}{0}'.format(self._parser.prefix_chars[0])

def process_action(action):
nonlocal command
if isinstance(action, argparse._SubParsersAction):
cmd_str[0] += '{} '.format(self._args[action.dest])
command += ' {}'.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):
Expand All @@ -284,8 +293,7 @@ def traverse_parser(parser):
process_action(action)

traverse_parser(self._parser)

return _exec_cmd(self._cmd2_app, functools.partial(func, cmd_str[0]), self._echo)
return _exec_cmd(self._cmd2_app, command, self._echo)


class PyscriptBridge(object):
Expand All @@ -310,7 +318,10 @@ def __getattr__(self, item: str):
else:
# Command doesn't use argparse, we will accept parameters in the form of a command string
def wrap_func(args=''):
return _exec_cmd(self._cmd2_app, functools.partial(func, args), self.cmd_echo)
command = item
if args:
command += ' ' + args
return _exec_cmd(self._cmd2_app, command, self.cmd_echo)

return wrap_func
else:
Expand All @@ -323,17 +334,15 @@ def __dir__(self):
attributes.insert(0, 'cmd_echo')
return attributes

def __call__(self, args: str, echo: Optional[bool]=None) -> CommandResult:
def __call__(self, command: 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
:param command: command line being run
:param echo: if True, output will be echoed to stdout/stderr while the command runs
this temporarily overrides the value of self.cmd_echo
"""
if echo is None:
echo = self.cmd_echo

return _exec_cmd(self._cmd2_app,
functools.partial(self._cmd2_app.onecmd_plus_hooks, args + '\n'),
echo)
return _exec_cmd(self._cmd2_app, command, echo)