Skip to content

Commit dc6590f

Browse files
committed
Fixed issue where calling exit() or quit() from a pyscript would close the whole console
1 parent 7d93134 commit dc6590f

File tree

3 files changed

+144
-140
lines changed

3 files changed

+144
-140
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
## 0.9.8 (TBD, 2019)
1+
## 0.9.8 (February 06, 2019)
22
* Bug Fixes
33
* Fixed issue with echoing strings in StdSim. Because they were being sent to a binary buffer, line buffering
44
was being ignored.
5+
* Enhancements
6+
* Made quit() and exit() functions available to scripts run with pyscript. This allows those scripts to exit
7+
back to the console's prompt instead of exiting the whole application.
58

69
## 0.9.7 (January 08, 2019)
710
* Bug Fixes

cmd2/cmd2.py

Lines changed: 138 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,160 +2959,159 @@ def do_py(self, args: argparse.Namespace) -> bool:
29592959
return False
29602960
self._in_py = True
29612961

2962-
# noinspection PyBroadException
2963-
try:
2964-
# Support the run command even if called prior to invoking an interactive interpreter
2965-
def run(filename: str):
2966-
"""Run a Python script file in the interactive console.
2967-
2968-
:param filename: filename of *.py script file to run
2969-
"""
2970-
expanded_filename = os.path.expanduser(filename)
2962+
# Support the run command even if called prior to invoking an interactive interpreter
2963+
def py_run(filename: str):
2964+
"""Run a Python script file in the interactive console.
2965+
:param filename: filename of *.py script file to run
2966+
"""
2967+
expanded_filename = os.path.expanduser(filename)
29712968

2972-
# cmd_echo defaults to False for scripts. The user can always toggle this value in their script.
2973-
bridge.cmd_echo = False
2969+
# cmd_echo defaults to False for scripts. The user can always toggle this value in their script.
2970+
bridge.cmd_echo = False
29742971

2975-
try:
2976-
with open(expanded_filename) as f:
2977-
interp.runcode(f.read())
2978-
except OSError as ex:
2979-
error_msg = "Error opening script file '{}': {}".format(expanded_filename, ex)
2980-
self.perror(error_msg, traceback_war=False)
2972+
try:
2973+
with open(expanded_filename) as f:
2974+
interp.runcode(f.read())
2975+
except OSError as ex:
2976+
error_msg = "Error opening script file '{}': {}".format(expanded_filename, ex)
2977+
self.perror(error_msg, traceback_war=False)
2978+
2979+
def py_quit():
2980+
"""Function callable from the interactive Python console to exit that environment"""
2981+
raise EmbeddedConsoleExit
2982+
2983+
# Set up Python environment
2984+
bridge = PyscriptBridge(self)
2985+
self.pystate[self.pyscript_name] = bridge
2986+
self.pystate['run'] = py_run
2987+
self.pystate['quit'] = py_quit
2988+
self.pystate['exit'] = py_quit
2989+
2990+
if self.locals_in_py:
2991+
self.pystate['self'] = self
2992+
elif 'self' in self.pystate:
2993+
del self.pystate['self']
2994+
2995+
localvars = self.pystate
2996+
from code import InteractiveConsole
2997+
interp = InteractiveConsole(locals=localvars)
2998+
interp.runcode('import sys, os;sys.path.insert(0, os.getcwd())')
2999+
3000+
# Check if the user is running a Python statement on the command line
3001+
if args.command:
3002+
full_command = args.command
3003+
if args.remainder:
3004+
full_command += ' ' + ' '.join(args.remainder)
3005+
3006+
# Set cmd_echo to True so PyscriptBridge statements like: py app('help')
3007+
# run at the command line will print their output.
3008+
bridge.cmd_echo = True
3009+
3010+
# noinspection PyBroadException
3011+
try:
3012+
interp.runcode(full_command)
3013+
except BaseException:
3014+
pass
29813015

2982-
bridge = PyscriptBridge(self)
2983-
self.pystate['run'] = run
2984-
self.pystate[self.pyscript_name] = bridge
3016+
# If there are no args, then we will open an interactive Python console
3017+
else:
3018+
# Set up readline for Python console
3019+
if rl_type != RlType.NONE:
3020+
# Save cmd2 history
3021+
saved_cmd2_history = []
3022+
for i in range(1, readline.get_current_history_length() + 1):
3023+
saved_cmd2_history.append(readline.get_history_item(i))
29853024

2986-
if self.locals_in_py:
2987-
self.pystate['self'] = self
2988-
elif 'self' in self.pystate:
2989-
del self.pystate['self']
2990-
2991-
localvars = self.pystate
2992-
from code import InteractiveConsole
2993-
interp = InteractiveConsole(locals=localvars)
2994-
interp.runcode('import sys, os;sys.path.insert(0, os.getcwd())')
2995-
2996-
# Check if the user is running a Python statement on the command line
2997-
if args.command:
2998-
full_command = args.command
2999-
if args.remainder:
3000-
full_command += ' ' + ' '.join(args.remainder)
3001-
3002-
# Set cmd_echo to True so PyscriptBridge statements like: py app('help')
3003-
# run at the command line will print their output.
3004-
bridge.cmd_echo = True
3005-
interp.runcode(full_command)
3025+
readline.clear_history()
30063026

3007-
# If there are no args, then we will open an interactive Python console
3008-
else:
3009-
# noinspection PyShadowingBuiltins
3010-
def quit():
3011-
"""Function callable from the interactive Python console to exit that environment"""
3012-
raise EmbeddedConsoleExit
3027+
# Restore py's history
3028+
for item in self.py_history:
3029+
readline.add_history(item)
3030+
3031+
if self.use_rawinput and self.completekey:
3032+
# Set up tab completion for the Python console
3033+
# rlcompleter relies on the default settings of the Python readline module
3034+
if rl_type == RlType.GNU:
3035+
old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
3036+
rl_basic_quote_characters.value = orig_rl_basic_quotes
3037+
3038+
if 'gnureadline' in sys.modules:
3039+
# rlcompleter imports readline by name, so it won't use gnureadline
3040+
# Force rlcompleter to use gnureadline instead so it has our settings and history
3041+
saved_readline = None
3042+
if 'readline' in sys.modules:
3043+
saved_readline = sys.modules['readline']
3044+
3045+
sys.modules['readline'] = sys.modules['gnureadline']
3046+
3047+
old_delims = readline.get_completer_delims()
3048+
readline.set_completer_delims(orig_rl_delims)
3049+
3050+
# rlcompleter will not need cmd2's custom display function
3051+
# This will be restored by cmd2 the next time complete() is called
3052+
if rl_type == RlType.GNU:
3053+
readline.set_completion_display_matches_hook(None)
3054+
elif rl_type == RlType.PYREADLINE:
3055+
readline.rl.mode._display_completions = self._display_matches_pyreadline
3056+
3057+
# Save off the current completer and set a new one in the Python console
3058+
# Make sure it tab completes from its locals() dictionary
3059+
old_completer = readline.get_completer()
3060+
interp.runcode("from rlcompleter import Completer")
3061+
interp.runcode("import readline")
3062+
interp.runcode("readline.set_completer(Completer(locals()).complete)")
3063+
3064+
# Set up sys module for the Python console
3065+
self._reset_py_display()
3066+
keepstate = Statekeeper(sys, ('stdin', 'stdout'))
3067+
sys.stdout = self.stdout
3068+
sys.stdin = self.stdin
3069+
3070+
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
3071+
instructions = ('End with `Ctrl-D` (Unix) / `Ctrl-Z` (Windows), `quit()`, `exit()`.\n'
3072+
'Non-Python commands can be issued with: {}("your command")\n'
3073+
'Run Python code from external script files with: run("script.py")'
3074+
.format(self.pyscript_name))
3075+
3076+
# noinspection PyBroadException
3077+
try:
3078+
interp.interact(banner="Python {} on {}\n{}\n\n{}\n".
3079+
format(sys.version, sys.platform, cprt, instructions))
3080+
except BaseException:
3081+
pass
30133082

3014-
self.pystate['quit'] = quit
3015-
self.pystate['exit'] = quit
3083+
finally:
3084+
keepstate.restore()
30163085

3017-
# Set up readline for Python console
3086+
# Set up readline for cmd2
30183087
if rl_type != RlType.NONE:
3019-
# Save cmd2 history
3020-
saved_cmd2_history = []
3088+
# Save py's history
3089+
self.py_history.clear()
30213090
for i in range(1, readline.get_current_history_length() + 1):
3022-
saved_cmd2_history.append(readline.get_history_item(i))
3091+
self.py_history.append(readline.get_history_item(i))
30233092

30243093
readline.clear_history()
30253094

3026-
# Restore py's history
3027-
for item in self.py_history:
3095+
# Restore cmd2's history
3096+
for item in saved_cmd2_history:
30283097
readline.add_history(item)
30293098

30303099
if self.use_rawinput and self.completekey:
3031-
# Set up tab completion for the Python console
3032-
# rlcompleter relies on the default settings of the Python readline module
3033-
if rl_type == RlType.GNU:
3034-
old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
3035-
rl_basic_quote_characters.value = orig_rl_basic_quotes
3036-
3037-
if 'gnureadline' in sys.modules:
3038-
# rlcompleter imports readline by name, so it won't use gnureadline
3039-
# Force rlcompleter to use gnureadline instead so it has our settings and history
3040-
saved_readline = None
3041-
if 'readline' in sys.modules:
3042-
saved_readline = sys.modules['readline']
3043-
3044-
sys.modules['readline'] = sys.modules['gnureadline']
3045-
3046-
old_delims = readline.get_completer_delims()
3047-
readline.set_completer_delims(orig_rl_delims)
3100+
# Restore cmd2's tab completion settings
3101+
readline.set_completer(old_completer)
3102+
readline.set_completer_delims(old_delims)
30483103

3049-
# rlcompleter will not need cmd2's custom display function
3050-
# This will be restored by cmd2 the next time complete() is called
30513104
if rl_type == RlType.GNU:
3052-
readline.set_completion_display_matches_hook(None)
3053-
elif rl_type == RlType.PYREADLINE:
3054-
readline.rl.mode._display_completions = self._display_matches_pyreadline
3055-
3056-
# Save off the current completer and set a new one in the Python console
3057-
# Make sure it tab completes from its locals() dictionary
3058-
old_completer = readline.get_completer()
3059-
interp.runcode("from rlcompleter import Completer")
3060-
interp.runcode("import readline")
3061-
interp.runcode("readline.set_completer(Completer(locals()).complete)")
3062-
3063-
# Set up sys module for the Python console
3064-
self._reset_py_display()
3065-
keepstate = Statekeeper(sys, ('stdin', 'stdout'))
3066-
sys.stdout = self.stdout
3067-
sys.stdin = self.stdin
3068-
3069-
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
3070-
instructions = ('End with `Ctrl-D` (Unix) / `Ctrl-Z` (Windows), `quit()`, `exit()`.\n'
3071-
'Non-Python commands can be issued with: {}("your command")\n'
3072-
'Run Python code from external script files with: run("script.py")'
3073-
.format(self.pyscript_name))
3074-
3075-
try:
3076-
interp.interact(banner="Python {} on {}\n{}\n\n{}\n".
3077-
format(sys.version, sys.platform, cprt, instructions))
3078-
except EmbeddedConsoleExit:
3079-
pass
3080-
3081-
finally:
3082-
keepstate.restore()
3083-
3084-
# Set up readline for cmd2
3085-
if rl_type != RlType.NONE:
3086-
# Save py's history
3087-
self.py_history.clear()
3088-
for i in range(1, readline.get_current_history_length() + 1):
3089-
self.py_history.append(readline.get_history_item(i))
3105+
rl_basic_quote_characters.value = old_basic_quotes
30903106

3091-
readline.clear_history()
3092-
3093-
# Restore cmd2's history
3094-
for item in saved_cmd2_history:
3095-
readline.add_history(item)
3096-
3097-
if self.use_rawinput and self.completekey:
3098-
# Restore cmd2's tab completion settings
3099-
readline.set_completer(old_completer)
3100-
readline.set_completer_delims(old_delims)
3101-
3102-
if rl_type == RlType.GNU:
3103-
rl_basic_quote_characters.value = old_basic_quotes
3104-
3105-
if 'gnureadline' in sys.modules:
3106-
# Restore what the readline module pointed to
3107-
if saved_readline is None:
3108-
del(sys.modules['readline'])
3109-
else:
3110-
sys.modules['readline'] = saved_readline
3107+
if 'gnureadline' in sys.modules:
3108+
# Restore what the readline module pointed to
3109+
if saved_readline is None:
3110+
del(sys.modules['readline'])
3111+
else:
3112+
sys.modules['readline'] = saved_readline
31113113

3112-
except Exception:
3113-
pass
3114-
finally:
3115-
self._in_py = False
3114+
self._in_py = False
31163115
return self._should_quit
31173116

31183117
pyscript_parser = ACArgumentParser()
@@ -3123,7 +3122,7 @@ def quit():
31233122
ACTION_ARG_CHOICES, ('path_complete',))
31243123

31253124
@with_argparser(pyscript_parser)
3126-
def do_pyscript(self, args: argparse.Namespace) -> None:
3125+
def do_pyscript(self, args: argparse.Namespace) -> bool:
31273126
"""Run a Python script file inside the console"""
31283127
script_path = os.path.expanduser(args.script_path)
31293128

@@ -3134,11 +3133,13 @@ def do_pyscript(self, args: argparse.Namespace) -> None:
31343133
sys.argv = [script_path] + args.script_arguments
31353134

31363135
# Run the script - use repr formatting to escape things which need to be escaped to prevent issues on Windows
3137-
self.do_py("run({!r})".format(script_path))
3136+
py_return = self.do_py("run({!r})".format(script_path))
31383137

31393138
# Restore command line arguments to original state
31403139
sys.argv = orig_args
31413140

3141+
return py_return
3142+
31423143
# Only include the do_ipy() method if IPython is available on the system
31433144
if ipython_available: # pragma: no cover
31443145
@with_argparser(ACArgumentParser())

examples/scripts/conditional.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
./python_scripting.py
88
pyscript scripts/conditional.py directory_path
99
10-
Note: The "cmd" function is defined within the cmd2 embedded Python environment and in there "self" is your cmd2
11-
application instance.
10+
Note: The "app" function is defined within the cmd2 embedded Python environment and in there "self" is your cmd2
11+
application instance. Note: self only exists in this environment if locals_in_py is True.
1212
"""
1313
import os
1414
import sys

0 commit comments

Comments
 (0)