summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py174
1 files changed, 101 insertions, 73 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 7fcb38d6..46b098c5 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -6,9 +6,9 @@ To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you
were using the standard library's cmd, while enjoying the extra features.
Searchable command history (commands: "history")
-Load commands from file, save to file, edit commands in file
+Run commands from file, save to file, edit commands in file
Multi-line commands
-Special-character shortcut commands (beyond cmd's "@" and "!")
+Special-character shortcut commands (beyond cmd's "?" and "!")
Settable environment parameters
Parsing commands with `argparse` argument parsers (flags)
Redirection to file or paste buffer (clipboard) with > or >>
@@ -327,7 +327,7 @@ class Cmd(cmd.Cmd):
Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
"""
- DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
+ DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'run_script', '@@': '_relative_run_script'}
DEFAULT_EDITOR = utils.find_editor()
def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
@@ -343,7 +343,7 @@ class Cmd(cmd.Cmd):
:param stdout: (optional) alternate output file object, if not specified, sys.stdout is used
:param persistent_history_file: (optional) file path to load a persistent cmd2 command history from
:param persistent_history_length: (optional) max number of history items to write to the persistent history file
- :param startup_script: (optional) file path to a a script to load and execute at startup
+ :param startup_script: (optional) file path to a script to execute at startup
:param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell
:param allow_cli_args: (optional) if True, then cmd2 will process command line arguments as either
commands to be run or, if -t is specified, transcript files to run.
@@ -398,7 +398,7 @@ class Cmd(cmd.Cmd):
'timing': 'Report execution times'}
# Commands to exclude from the help menu and tab completion
- self.hidden_commands = ['eof', '_relative_load']
+ self.hidden_commands = ['eof', '_relative_load', '_relative_run_script']
# Commands to exclude from the history command
# initialize history
@@ -429,7 +429,7 @@ class Cmd(cmd.Cmd):
# Built-in commands don't make use of this. It is purely there for user-defined commands and convenience.
self._last_result = None
- # Used load command to store the current script dir as a LIFO queue to support _relative_load command
+ # Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command
self._script_dir = []
# Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt
@@ -460,11 +460,11 @@ class Cmd(cmd.Cmd):
# Commands that will run at the beginning of the command loop
self._startup_commands = []
- # If a startup script is provided, then add it in the queue to load
+ # If a startup script is provided, then execute it in the startup commands
if startup_script is not None:
startup_script = os.path.abspath(os.path.expanduser(startup_script))
if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0:
- self._startup_commands.append("load '{}'".format(startup_script))
+ self._startup_commands.append("run_script '{}'".format(startup_script))
# Transcript files to run instead of interactive command loop
self._transcript_files = None
@@ -3240,15 +3240,15 @@ class Cmd(cmd.Cmd):
return bridge.stop
- pyscript_parser = ACArgumentParser()
- setattr(pyscript_parser.add_argument('script_path', help='path to the script file'),
+ run_pyscript_parser = ACArgumentParser()
+ setattr(run_pyscript_parser.add_argument('script_path', help='path to the script file'),
ACTION_ARG_CHOICES, ('path_complete',))
- setattr(pyscript_parser.add_argument('script_arguments', nargs=argparse.REMAINDER,
- help='arguments to pass to script'),
+ setattr(run_pyscript_parser.add_argument('script_arguments', nargs=argparse.REMAINDER,
+ help='arguments to pass to script'),
ACTION_ARG_CHOICES, ('path_complete',))
- @with_argparser(pyscript_parser)
- def do_pyscript(self, args: argparse.Namespace) -> bool:
+ @with_argparser(run_pyscript_parser)
+ def do_run_pyscript(self, args: argparse.Namespace) -> bool:
"""Run a Python script file inside the console"""
script_path = os.path.expanduser(args.script_path)
py_return = False
@@ -3270,9 +3270,16 @@ class Cmd(cmd.Cmd):
finally:
# Restore command line arguments to original state
sys.argv = orig_args
+ if args.__statement__.command == "pyscript":
+ self.perror("pyscript has been renamed and will be removed in the next release, "
+ "please use run_pyscript instead\n",
+ traceback_war=False, err_color=Fore.LIGHTYELLOW_EX)
return py_return
+ # pyscript is deprecated
+ do_pyscript = do_run_pyscript
+
# Only include the do_ipy() method if IPython is available on the system
if ipython_available: # pragma: no cover
@with_argparser(ACArgumentParser())
@@ -3412,7 +3419,7 @@ class Cmd(cmd.Cmd):
fobj.write('{}\n'.format(command.raw))
try:
self.do_edit(fname)
- return self.do_load(fname)
+ return self.do_run_script(fname)
finally:
os.remove(fname)
elif args.output_file:
@@ -3623,94 +3630,115 @@ class Cmd(cmd.Cmd):
else:
return None
- load_description = ("Run commands in script file that is encoded as either ASCII or UTF-8 text\n"
- "\n"
- "Script should contain one command per line, just like the command would be\n"
- "typed in the console.\n"
- "\n"
- "If the -r/--record_transcript flag is used, this command instead records\n"
- "the output of the script commands to a transcript for testing purposes.\n"
- )
+ run_script_description = ("Run commands in script file that is encoded as either ASCII or UTF-8 text\n"
+ "\n"
+ "Script should contain one command per line, just like the command would be\n"
+ "typed in the console.\n"
+ "\n"
+ "If the -r/--record_transcript flag is used, this command instead records\n"
+ "the output of the script commands to a transcript for testing purposes.\n"
+ )
- load_parser = ACArgumentParser(description=load_description)
- setattr(load_parser.add_argument('-t', '--transcript', help='record the output of the script as a transcript file'),
+ run_script_parser = ACArgumentParser(description=run_script_description)
+ setattr(run_script_parser.add_argument('-t', '--transcript',
+ help='record the output of the script as a transcript file'),
ACTION_ARG_CHOICES, ('path_complete',))
- setattr(load_parser.add_argument('script_path', help="path to the script file"),
+ setattr(run_script_parser.add_argument('script_path', help="path to the script file"),
ACTION_ARG_CHOICES, ('path_complete',))
- @with_argparser(load_parser)
- def do_load(self, args: argparse.Namespace) -> Optional[bool]:
+ @with_argparser(run_script_parser)
+ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]:
"""
Run commands in script file that is encoded as either ASCII or UTF-8 text
:return: True if running of commands should stop
"""
expanded_path = os.path.abspath(os.path.expanduser(args.script_path))
- # Make sure the path exists and we can access it
- if not os.path.exists(expanded_path):
- self.perror("'{}' does not exist or cannot be accessed".format(expanded_path), traceback_war=False)
- return
+ # Wrap everything in a try/finally just to make sure the warning prints at end if `load` was called
+ try:
+ # Make sure the path exists and we can access it
+ if not os.path.exists(expanded_path):
+ self.perror("'{}' does not exist or cannot be accessed".format(expanded_path), traceback_war=False)
+ return
- # Make sure expanded_path points to a file
- if not os.path.isfile(expanded_path):
- self.perror("'{}' is not a file".format(expanded_path), traceback_war=False)
- return
+ # Make sure expanded_path points to a file
+ if not os.path.isfile(expanded_path):
+ self.perror("'{}' is not a file".format(expanded_path), traceback_war=False)
+ return
- # Make sure the file is not empty
- if os.path.getsize(expanded_path) == 0:
- self.perror("'{}' is empty".format(expanded_path), traceback_war=False)
- return
+ # Make sure the file is not empty
+ if os.path.getsize(expanded_path) == 0:
+ self.perror("'{}' is empty".format(expanded_path), traceback_war=False)
+ return
- # Make sure the file is ASCII or UTF-8 encoded text
- if not utils.is_text_file(expanded_path):
- self.perror("'{}' is not an ASCII or UTF-8 encoded text file".format(expanded_path), traceback_war=False)
- return
+ # Make sure the file is ASCII or UTF-8 encoded text
+ if not utils.is_text_file(expanded_path):
+ self.perror("'{}' is not an ASCII or UTF-8 encoded text file".format(expanded_path), traceback_war=False)
+ return
- try:
- # Read all lines of the script
- with open(expanded_path, encoding='utf-8') as target:
- script_commands = target.read().splitlines()
- except OSError as ex: # pragma: no cover
- self.perror("Problem accessing script from '{}': {}".format(expanded_path, ex))
- return
+ try:
+ # Read all lines of the script
+ with open(expanded_path, encoding='utf-8') as target:
+ script_commands = target.read().splitlines()
+ except OSError as ex: # pragma: no cover
+ self.perror("Problem accessing script from '{}': {}".format(expanded_path, ex))
+ return
- orig_script_dir_count = len(self._script_dir)
+ orig_script_dir_count = len(self._script_dir)
- try:
- self._script_dir.append(os.path.dirname(expanded_path))
+ try:
+ self._script_dir.append(os.path.dirname(expanded_path))
- if args.transcript:
- self._generate_transcript(script_commands, os.path.expanduser(args.transcript))
- else:
- return self.runcmds_plus_hooks(script_commands)
+ if args.transcript:
+ self._generate_transcript(script_commands, os.path.expanduser(args.transcript))
+ else:
+ return self.runcmds_plus_hooks(script_commands)
+ finally:
+ with self.sigint_protection:
+ # Check if a script dir was added before an exception occurred
+ if orig_script_dir_count != len(self._script_dir):
+ self._script_dir.pop()
finally:
- with self.sigint_protection:
- # Check if a script dir was added before an exception occurred
- if orig_script_dir_count != len(self._script_dir):
- self._script_dir.pop()
+ if args.__statement__.command == "load":
+ self.perror("load has been renamed and will be removed in the next release, "
+ "please use run_script instead\n",
+ traceback_war=False, err_color=Fore.LIGHTYELLOW_EX)
- relative_load_description = load_description
- relative_load_description += ("\n\n"
- "If this is called from within an already-running script, the filename will be\n"
- "interpreted relative to the already-running script's directory.")
+ # load has been deprecated
+ do_load = do_run_script
- relative_load_epilog = ("Notes:\n"
- " This command is intended to only be used within text file scripts.")
+ relative_run_script_description = run_script_description
+ relative_run_script_description += (
+ "\n\n"
+ "If this is called from within an already-running script, the filename will be\n"
+ "interpreted relative to the already-running script's directory.")
- relative_load_parser = ACArgumentParser(description=relative_load_description, epilog=relative_load_epilog)
- relative_load_parser.add_argument('file_path', help='a file path pointing to a script')
+ relative_run_script_epilog = ("Notes:\n"
+ " This command is intended to only be used within text file scripts.")
- @with_argparser(relative_load_parser)
- def do__relative_load(self, args: argparse.Namespace) -> Optional[bool]:
+ relative_run_script_parser = ACArgumentParser(description=relative_run_script_description,
+ epilog=relative_run_script_epilog)
+ relative_run_script_parser.add_argument('file_path', help='a file path pointing to a script')
+
+ @with_argparser(relative_run_script_parser)
+ def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]:
"""
Run commands in script file that is encoded as either ASCII or UTF-8 text
:return: True if running of commands should stop
"""
+ if args.__statement__.command == "_relative_load":
+ self.perror("_relative_load has been renamed and will be removed in the next release, "
+ "please use _relative_run_script instead\n",
+ traceback_war=False, err_color=Fore.LIGHTYELLOW_EX)
+
file_path = args.file_path
# NOTE: Relative path is an absolute path, it is just relative to the current script directory
relative_path = os.path.join(self._current_script_dir or '', file_path)
- return self.do_load(relative_path)
+ return self.do_run_script(relative_path)
+
+ # _relative_load has been deprecated
+ do__relative_load = do__relative_run_script
def run_transcript_tests(self, transcript_paths: List[str]) -> None:
"""Runs transcript tests for provided file(s).