summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md31
-rwxr-xr-xREADME.md11
-rwxr-xr-xcmd2.py441
-rw-r--r--docs/argument_processing.rst61
-rw-r--r--docs/freefeatures.rst11
-rw-r--r--docs/settingchanges.rst1
-rw-r--r--docs/unfreefeatures.rst72
-rwxr-xr-xexamples/arg_print.py38
-rwxr-xr-xexamples/argparse_example.py46
-rwxr-xr-xexamples/example.py33
-rw-r--r--examples/exampleSession.txt1
-rwxr-xr-xexamples/pirate.py22
-rwxr-xr-xexamples/python_scripting.py35
-rw-r--r--examples/transcript_regex.txt1
-rw-r--r--tests/conftest.py31
-rw-r--r--tests/test_argparse.py99
-rw-r--r--tests/test_cmd2.py290
-rw-r--r--tests/test_transcript.py17
-rw-r--r--tests/transcripts/from_cmdloop.txt13
-rw-r--r--tests/transcripts/regex_set.txt1
20 files changed, 595 insertions, 660 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76142feb..c017ff36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,32 @@
* Bug Fixes
* Fixed unit tests on Python 3.7 due to changes in how re.escape() behaves in Python 3.7
* Enhancements
- * Added new **with_argument_parser** decorator for argparse-based argument parsing of command arguments
- * This replaces the old **options** decorator for optparse-based argument parsing
- * The old decorator is still present for now, but should be considered *deprecated* and will eventually be removed
- * See the **Argument Processing** section of the documentation for more information
- * Alternatively, see the **argparse_example.py** example
+ * Three new decorators for **do_*** commands to make argument parsing easier
+ * **with_argument_list** decorator to change argument type from str to List[str]
+ * **do_*** commands get a single argument which is a list of strings, as pre-parsed by shlex.split()
+ * **with_argument_parser** decorator for strict argparse-based argument parsing of command arguments
+ * **do_*** commands get a single argument which is the output of argparse.parse_args()
+ * **with_argparser_and_unknown_args** decorator for argparse-based argument parsing, but allowing unknown args
+ * **do_*** commands get two arguments, the output of argparse.parse_known_args()
+ * See the **Argument Processing** section of the documentation for more information on these decorators
+ * Alternatively, see the **argparse_example.py** and **arg_print.py** examples
+ * The **__relative_load** command is now hidden from the help menu by default
+ * This command is not intended to be called from the command line, only from within scripts
+ * The **set** command now has an additional **-a/--all** option to also display read-only settings
+ * The **history** command can now run, edit, and save prior commands, in addition to the prior behavior of displaying prior commands.
+* Commands Removed
+ * The **cmdenvironment** has been removed and its functionality incorporated into the **-a/--all** argument to **set**
+ * The **show** command has been removed. Its functionality has always existing within **set** and continues to do so
+ * The **save** command has been removed. The capability to save prior commands is now part of the **history** command.
+ * The **run** command has been removed. The capability to run prior commands is now part of the **history** command.
+* Other changes
+ * The **edit** command no longer allows you to edit prior commands. The capability to edit prior commands is now part of the **history** command. The **edit** command still allows you to edit arbitrary files.
+ * the **autorun_on_edit** setting has been removed.
+* Deprecations
+ * The old **options** decorator for optparse-based argument parsing is now *deprecated*
+ * The old decorator is still present for now, but will eventually be removed in a future release
+ * ``cmd2`` no longer includes **optparse.make_option** so if your app needs it you need to import it directly from optparse
+
## 0.7.9 (January 4, 2018)
diff --git a/README.md b/README.md
index 0144f4c3..70fde5a8 100755
--- a/README.md
+++ b/README.md
@@ -70,13 +70,13 @@ Instructions for implementing each feature follow.
- Searchable command history
All commands will automatically be tracked in the session's history, unless the command is listed in Cmd's excludeFromHistory attribute.
- The history is accessed through the `history`, `list`, and `run` commands.
+ The history is accessed through the `history` command.
If you wish to exclude some of your custom commands from the history, append their names
to the list at `Cmd.ExcludeFromHistory`.
- Load commands from file, save to file, edit commands in file
- Type `help load`, `help save`, `help edit` for details.
+ Type `help load`, `help history` for details.
- Multi-line commands
@@ -105,7 +105,7 @@ Instructions for implementing each feature follow.
argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
argparser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(argparser)
- def do_speak(self, cmdline, args=None):
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -175,7 +175,7 @@ class CmdLineApp(Cmd):
argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')
argparser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(argparser)
- def do_speak(self, cmdline, opts=None):
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -196,7 +196,7 @@ class CmdLineApp(Cmd):
argparser.add_argument('-r', '--repeat', type=int, help='how many times to repeat')
argparser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(argparser)
- def do_mumble(self, cmdline, args=None):
+ def do_mumble(self, args):
"""Mumbles what you tell me to."""
repetitions = args.repeat or 1
for i in range(min(repetitions, self.maxrepeats)):
@@ -236,7 +236,6 @@ example/transcript_regex.txt:
# regexes on prompts just make the trailing space obvious
(Cmd) set
abbrev: True
-autorun_on_edit: False
colors: /(True|False)/
continuation_prompt: >/ /
debug: False
diff --git a/cmd2.py b/cmd2.py
index 7540bd00..5d11cb43 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -42,7 +42,6 @@ import tempfile
import traceback
import unittest
from code import InteractiveConsole
-from optparse import make_option
import pyparsing
import pyperclip
@@ -116,8 +115,9 @@ pyparsing.ParserElement.enablePackrat()
pyparsing.ParserElement.setDefaultWhitespaceChars(' \t')
-# The next 3 variables and associated setter functions effect how arguments are parsed for commands using @options.
-# The defaults are "sane" and maximize ease of use for new applications based on cmd2.
+# The next 3 variables and associated setter functions effect how arguments are parsed for decorated commands
+# which use one of the decorators such as @with_argument_list or @with_argument_parser
+# The defaults are sane and maximize ease of use for new applications based on cmd2.
# To maximize backwards compatibility, we recommend setting USE_ARG_LIST to "False"
# Use POSIX or Non-POSIX (Windows) rules for splitting a command-line string into a list of arguments via shlex.split()
@@ -126,12 +126,12 @@ POSIX_SHLEX = False
# Strip outer quotes for convenience if POSIX_SHLEX = False
STRIP_QUOTES_FOR_NON_POSIX = True
-# For option commands, pass a list of argument strings instead of a single argument string to the do_* methods
+# For @options commands, pass a list of argument strings instead of a single argument string to the do_* methods
USE_ARG_LIST = True
def set_posix_shlex(val):
- """ Allows user of cmd2 to choose between POSIX and non-POSIX splitting of args for @options commands.
+ """ Allows user of cmd2 to choose between POSIX and non-POSIX splitting of args for decorated commands.
:param val: bool - True => POSIX, False => Non-POSIX
"""
@@ -142,7 +142,7 @@ def set_posix_shlex(val):
def set_strip_quotes(val):
""" Allows user of cmd2 to choose whether to automatically strip outer-quotes when POSIX_SHLEX is False.
- :param val: bool - True => strip quotes on args and option args for @option commands if POSIX_SHLEX is False.
+ :param val: bool - True => strip quotes on args for decorated commands if POSIX_SHLEX is False.
"""
global STRIP_QUOTES_FOR_NON_POSIX
STRIP_QUOTES_FOR_NON_POSIX = val
@@ -242,23 +242,73 @@ def strip_quotes(arg):
return arg
+def parse_quoted_string(cmdline):
+ """Parse a quoted string into a list of arguments."""
+ if isinstance(cmdline, list):
+ # arguments are already a list, return the list we were passed
+ lexed_arglist = cmdline
+ else:
+ # Use shlex to split the command line into a list of arguments based on shell rules
+ lexed_arglist = shlex.split(cmdline, posix=POSIX_SHLEX)
+ # If not using POSIX shlex, make sure to strip off outer quotes for convenience
+ if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX:
+ temp_arglist = []
+ for arg in lexed_arglist:
+ temp_arglist.append(strip_quotes(arg))
+ lexed_arglist = temp_arglist
+ return lexed_arglist
+
+
+def with_argument_list(func):
+ """A decorator to alter the arguments passed to a do_* cmd2
+ method. Default passes a string of whatever the user typed.
+ With this decorator, the decorated method will receive a list
+ of arguments parsed from user input using shlex.split()."""
+ def cmd_wrapper(self, cmdline):
+ lexed_arglist = parse_quoted_string(cmdline)
+ func(self, lexed_arglist)
+
+ cmd_wrapper.__doc__ = func.__doc__
+ return cmd_wrapper
+
+
+def with_argparser_and_unknown_args(argparser):
+ """A decorator to alter a cmd2 method to populate its ``args``
+ argument by parsing arguments with the given instance of
+ argparse.ArgumentParser, but also returning unknown args as a list.
+ """
+ def arg_decorator(func):
+ def cmd_wrapper(instance, cmdline):
+ lexed_arglist = parse_quoted_string(cmdline)
+ args, unknown = argparser.parse_known_args(lexed_arglist)
+ func(instance, args, unknown)
+
+ # argparser defaults the program name to sys.argv[0]
+ # we want it to be the name of our command
+ argparser.prog = func.__name__[3:]
+
+ # put the help message in the method docstring
+ funcdoc = func.__doc__
+ if funcdoc:
+ funcdoc += '\n'
+ else:
+ # if it's None, make it an empty string
+ funcdoc = ''
+ cmd_wrapper.__doc__ = '{}{}'.format(funcdoc, argparser.format_help())
+ return cmd_wrapper
+ return arg_decorator
+
+
def with_argument_parser(argparser):
- """A decorator to alter a cmd2 method to populate its ``opts``
+ """A decorator to alter a cmd2 method to populate its ``args``
argument by parsing arguments with the given instance of
argparse.ArgumentParser.
"""
def arg_decorator(func):
def cmd_wrapper(instance, cmdline):
- # Use shlex to split the command line into a list of arguments based on shell rules
- lexed_arglist = shlex.split(cmdline, posix=POSIX_SHLEX)
- # If not using POSIX shlex, make sure to strip off outer quotes for convenience
- if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX:
- temp_arglist = []
- for arg in lexed_arglist:
- temp_arglist.append(strip_quotes(arg))
- lexed_arglist = temp_arglist
- opts = argparser.parse_args(lexed_arglist)
- func(instance, cmdline, opts)
+ lexed_arglist = parse_quoted_string(cmdline)
+ args = argparser.parse_args(lexed_arglist)
+ func(instance, args)
# argparser defaults the program name to sys.argv[0]
# we want it to be the name of our command
@@ -498,12 +548,11 @@ class Cmd(cmd.Cmd):
allow_redirection = True # Should output redirection and pipes be allowed
default_to_shell = False # Attempt to run unrecognized commands as shell commands
excludeFromHistory = '''run ru r history histor histo hist his hi h edit edi ed e eof eo eos'''.split()
- exclude_from_help = ['do_eof', 'do_eos'] # Commands to exclude from the help menu
+ exclude_from_help = ['do_eof', 'do_eos', 'do__relative_load'] # Commands to exclude from the help menu
reserved_words = []
# Attributes which ARE dynamically settable at runtime
abbrev = False # Abbreviated commands recognized
- autorun_on_edit = False # Should files automatically run after editing (doesn't apply to commands)
colors = (platform.system() != 'Windows')
continuation_prompt = '> '
debug = False
@@ -525,7 +574,6 @@ class Cmd(cmd.Cmd):
# To make an attribute settable with the "do_set" command, add it to this ...
# This starts out as a dictionary but gets converted to an OrderedDict sorted alphabetically by key
settable = {'abbrev': 'Accept abbreviated commands',
- 'autorun_on_edit': 'Automatically run files after editing',
'colors': 'Colorized output (*nix only)',
'continuation_prompt': 'On 2nd+ line of input',
'debug': 'Show full error stack on error',
@@ -1003,10 +1051,12 @@ class Cmd(cmd.Cmd):
funcname = self._func_named(statement.parsed.command)
if not funcname:
return self.default(statement)
+
try:
func = getattr(self, funcname)
except AttributeError:
return self.default(statement)
+
stop = func(statement)
return stop
@@ -1147,28 +1197,12 @@ class Cmd(cmd.Cmd):
return stop
- # noinspection PyUnusedLocal
- def do_cmdenvironment(self, args):
- """Summary report of interactive parameters."""
- self.poutput("""
- Commands are case-sensitive: {}
- Commands may be terminated with: {}
- Arguments at invocation allowed: {}
- Output redirection and pipes allowed: {}
- Parsing of @options commands:
- Shell lexer mode for command argument splitting: {}
- Strip Quotes after splitting arguments: {}
- Argument type: {}
- \n""".format(not self.case_insensitive, str(self.terminators), self.allow_cli_args, self.allow_redirection,
- "POSIX" if POSIX_SHLEX else "non-POSIX",
- "True" if STRIP_QUOTES_FOR_NON_POSIX and not POSIX_SHLEX else "False",
- "List of argument strings" if USE_ARG_LIST else "string of space-separated arguments"))
-
- def do_help(self, arg):
+ @with_argument_list
+ def do_help(self, arglist):
"""List available commands with "help" or detailed help with "help cmd"."""
- if arg:
+ if arglist:
# Getting help for a specific command
- funcname = self._func_named(arg)
+ funcname = self._func_named(arglist[0])
if funcname:
# No special behavior needed, delegate to cmd base class do_help()
cmd.Cmd.do_help(self, funcname[3:])
@@ -1213,19 +1247,17 @@ class Cmd(cmd.Cmd):
self.print_topics(self.misc_header, list(help_dict.keys()), 15, 80)
self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
- # noinspection PyUnusedLocal
- def do_shortcuts(self, args):
+ def do_shortcuts(self, _):
"""Lists shortcuts (aliases) available."""
result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.shortcuts))
self.poutput("Shortcuts for other commands:\n{}\n".format(result))
- # noinspection PyUnusedLocal
- def do_eof(self, arg):
+ def do_eof(self, _):
"""Called when <Ctrl>-D is pressed."""
# End of script should not exit app, but <Ctrl>-D should.
return self._STOP_AND_EXIT
- def do_quit(self, arg):
+ def do_quit(self, _):
"""Exits this application."""
self._should_quit = True
return self._STOP_AND_EXIT
@@ -1266,17 +1298,30 @@ class Cmd(cmd.Cmd):
len(fulloptions)))
return result
- @options([make_option('-l', '--long', action="store_true", help="describe function of parameter")])
- def do_show(self, arg, opts):
- """Shows value of a parameter."""
- # If arguments are being passed as a list instead of as a string
- if USE_ARG_LIST:
- if arg:
- arg = arg[0]
- else:
- arg = ''
+ def cmdenvironment(self):
+ """Get a summary report of read-only settings which the user cannot modify at runtime.
- param = arg.strip().lower()
+ :return: str - summary report of read-only settings which the user cannot modify at runtime
+ """
+ read_only_settings = """
+ Commands are case-sensitive: {}
+ Commands may be terminated with: {}
+ Arguments at invocation allowed: {}
+ Output redirection and pipes allowed: {}
+ Parsing of @options commands:
+ Shell lexer mode for command argument splitting: {}
+ Strip Quotes after splitting arguments: {}
+ Argument type: {}
+ """.format(not self.case_insensitive, str(self.terminators), self.allow_cli_args, self.allow_redirection,
+ "POSIX" if POSIX_SHLEX else "non-POSIX",
+ "True" if STRIP_QUOTES_FOR_NON_POSIX and not POSIX_SHLEX else "False",
+ "List of argument strings" if USE_ARG_LIST else "string of space-separated arguments")
+ return read_only_settings
+
+ def show(self, args, parameter):
+ param = ''
+ if parameter:
+ param = parameter.strip().lower()
result = {}
maxlen = 0
for p in self.settable:
@@ -1285,21 +1330,31 @@ class Cmd(cmd.Cmd):
maxlen = max(maxlen, len(result[p]))
if result:
for p in sorted(result):
- if opts.long:
+ if args.long:
self.poutput('{} # {}'.format(result[p].ljust(maxlen), self.settable[p]))
else:
self.poutput(result[p])
+
+ # If user has requested to see all settings, also show read-only settings
+ if args.all:
+ self.poutput('\nRead only settings:{}'.format(self.cmdenvironment()))
else:
raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
- def do_set(self, arg):
- """Sets a settable parameter.
+ set_parser = argparse.ArgumentParser(description='show or set value of a parameter')
+ set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
+ set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
+ set_parser.add_argument('settable', nargs='*', help='[param_name] [value]')
+
+ @with_argument_parser(set_parser)
+ def do_set(self, args):
+ """Sets a settable parameter or shows current settings of parameters.
Accepts abbreviated parameter names so long as there is no ambiguity.
Call without arguments for a list of settable parameters with their values.
"""
try:
- statement, param_name, val = arg.parsed.raw.split(None, 2)
+ param_name, val = args.settable
val = val.strip()
param_name = param_name.strip().lower()
if param_name not in self.settable:
@@ -1307,7 +1362,7 @@ class Cmd(cmd.Cmd):
if len(hits) == 1:
param_name = hits[0]
else:
- return self.do_show(param_name)
+ return self.show(args, param_name)
current_val = getattr(self, param_name)
if (val[0] == val[-1]) and val[0] in ("'", '"'):
val = val[1:-1]
@@ -1322,7 +1377,10 @@ class Cmd(cmd.Cmd):
except AttributeError:
pass
except (ValueError, AttributeError):
- self.do_show(arg)
+ param = ''
+ if args.settable:
+ param = args.settable[0]
+ self.show(args, param)
def do_shell(self, command):
"""Execute a command as if at the OS prompt.
@@ -1420,7 +1478,6 @@ class Cmd(cmd.Cmd):
# Enable tab completion of paths for relevant commands
complete_edit = path_complete
complete_load = path_complete
- complete_save = path_complete
# noinspection PyUnusedLocal
@staticmethod
@@ -1589,32 +1646,30 @@ class Cmd(cmd.Cmd):
self._in_py = False
return self._should_quit
- # noinspection PyUnusedLocal
- @options([], arg_desc='<script_path> [script_arguments]')
- def do_pyscript(self, arg, opts=None):
+ @with_argument_list
+ def do_pyscript(self, arglist):
"""\nRuns a python script file inside the console
+ Usage: pyscript <script_path> [script_arguments]
+
Console commands can be executed inside this script with cmd("your command")
However, you cannot run nested "py" or "pyscript" commands from within this script
Paths or arguments that contain spaces must be enclosed in quotes
"""
- if not arg:
+ if not arglist:
self.perror("pyscript command requires at least 1 argument ...", traceback_war=False)
self.do_help('pyscript')
return
- if not USE_ARG_LIST:
- arg = shlex.split(arg, posix=POSIX_SHLEX)
-
# Get the absolute path of the script
- script_path = os.path.expanduser(arg[0])
+ script_path = os.path.expanduser(arglist[0])
# Save current command line arguments
orig_args = sys.argv
# Overwrite sys.argv to allow the script to take command line arguments
sys.argv = [script_path]
- sys.argv.extend(arg[1:])
+ sys.argv.extend(arglist[1:])
# Run the script - use repr formatting to escape things which need to be escaped to prevent issues on Windows
self.do_py("run({!r})".format(script_path))
@@ -1638,27 +1693,31 @@ Paths or arguments that contain spaces must be enclosed in quotes
exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0])
embed(banner1=banner, exit_msg=exit_msg)
- @options([make_option('-s', '--script', action="store_true", help="Script format; no separation lines"),
- ], arg_desc='(limit on which commands to include)')
- def do_history(self, arg, opts):
- """history [arg]: lists past commands issued
-
- | no arg: list all
- | arg is integer: list one history item, by index
- | a..b, a:b, a:, ..b -> list history items by a span of indices (inclusive)
- | arg is string: list all commands matching string search
- | arg is /enclosed in forward-slashes/: regular expression search
- """
- # If arguments are being passed as a list instead of as a string
- if USE_ARG_LIST:
- if arg:
- arg = arg[0]
- else:
- arg = ''
-
- # If an argument was supplied, then retrieve partial contents of the history
- if arg:
- # If a character indicating a slice is present, retrieve a slice of the history
+ history_parser = argparse.ArgumentParser(
+ description='run, edit, and save previously entered commands',
+ formatter_class=argparse.RawTextHelpFormatter,
+ )
+ history_parser_group = history_parser.add_mutually_exclusive_group()
+ history_parser_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
+ history_parser_group.add_argument('-e', '--edit', action='store_true', help='edit and then run selected history items')
+ history_parser_group.add_argument('-o', '--output-file', metavar='FILE', help='output to file')
+ history_parser.add_argument('-s', '--script', action='store_true', help='script format; no separation lines')
+ _history_arg_help = """empty all history items
+a one history item by number
+a..b, a:b, a:, ..b items by indices (inclusive)
+[string] items containing string
+/regex/ items matching regular expression"""
+ history_parser.add_argument('arg', nargs='?', help=_history_arg_help)
+
+ @with_argument_parser(history_parser)
+ def do_history(self, args):
+ # If an argument was supplied, then retrieve partial contents of the
+ # history
+ cowardly_refuse_to_run = False
+ if args.arg:
+ # If a character indicating a slice is present, retrieve
+ # a slice of the history
+ arg = args.arg
if '..' in arg or ':' in arg:
try:
# Get a slice of history
@@ -1670,144 +1729,66 @@ Paths or arguments that contain spaces must be enclosed in quotes
history = self.history.get(arg)
else:
# If no arg given, then retrieve the entire history
+ cowardly_refuse_to_run = True
history = self.history
- # Display the history items retrieved
- for hi in history:
- if opts.script:
- self.poutput(hi)
+ if args.run:
+ if cowardly_refuse_to_run:
+ self.perror("Cowardly refusing to run all previously entered commands.", traceback_war=False)
+ self.perror("If this is what you want to do, specify '1:' as the range of history.", traceback_war=False)
else:
- self.poutput(hi.pr())
-
- def _last_matching(self, arg):
- """Return the last item from the history list that matches arg. Or if arg not provided, return last item.
-
- If not match is found, return None.
+ for runme in history:
+ self.pfeedback(runme)
+ if runme:
+ self.onecmd_plus_hooks(runme)
+ elif args.edit:
+ fd, fname = tempfile.mkstemp(suffix='.txt', text=True)
+ with os.fdopen(fd, 'w') as fobj:
+ for cmd in history:
+ fobj.write('{}\n'.format(cmd))
+ try:
+ os.system('"{}" "{}"'.format(self.editor, fname))
+ self.do_load(fname)
+ except:
+ raise
+ finally:
+ os.remove(fname)
+ elif args.output_file:
+ try:
+ with open(os.path.expanduser(args.output_file), 'w') as fobj:
+ for cmd in history:
+ fobj.write('{}\n'.format(cmd))
+ plural = 's' if len(history) > 1 else ''
+ self.pfeedback('{} command{} saved to {}'.format(len(history), plural, args.output_file))
+ except Exception as e:
+ self.perror('Saving {!r} - {}'.format(args.output_file, e), traceback_war=False)
+ else:
+ # Display the history items retrieved
+ for hi in history:
+ if args.script:
+ self.poutput(hi)
+ else:
+ self.poutput(hi.pr())
- :param arg: str - text to search for in history
- :return: str - last match, last item, or None, depending on arg.
- """
- try:
- if arg:
- return self.history.get(arg)[-1]
- else:
- return self.history[-1]
- except IndexError:
- return None
- @options([], arg_desc="""[N]|[file_path]
- * N - Number of command (from history), or `*` for all commands in history (default: last command)
- * file_path - path to a file to open in editor""")
- def do_edit(self, arg, opts=None):
+ @with_argument_list
+ def do_edit(self, arglist):
"""Edit a file or command in a text editor.
+Usage: edit [file_path]
+ Where:
+ * file_path - path to a file to open in editor
+
The editor used is determined by the ``editor`` settable parameter.
"set editor (program-name)" to change or set the EDITOR environment variable.
-
-The optional arguments are mutually exclusive. Either a command number OR a file name can be supplied.
-If neither is supplied, the most recent command in the history is edited.
-
-Edited commands are always run after the editor is closed.
-
-Edited files are run on close if the ``autorun_on_edit`` settable parameter is True.
"""
if not self.editor:
raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.")
- filename = None
- if arg and arg[0]:
- try:
- # Try to convert argument to an integer
- history_idx = int(arg[0])
- except ValueError:
- # Argument passed is not convertible to an integer, so treat it as a file path
- filename = arg[0]
- history_item = ''
- else:
- # Argument passed IS convertible to an integer, so treat it as a history index
-
- # Save off original index for pringing
- orig_indx = history_idx
-
- # Convert negative index into equivalent positive one
- if history_idx < 0:
- history_idx += len(self.history) + 1
-
- # Make sure the index is actually within the history
- if 1 <= history_idx <= len(self.history):
- history_item = self._last_matching(history_idx)
- else:
- self.perror('index {!r} does not exist within the history'.format(orig_indx), traceback_war=False)
- return
-
+ filename = arglist[0] if arglist else ''
+ if filename:
+ os.system('"{}" "{}"'.format(self.editor, filename))
else:
- try:
- history_item = self.history[-1]
- except IndexError:
- self.perror('edit must be called with argument if history is empty', traceback_war=False)
- return
-
- delete_tempfile = False
- if history_item:
- if filename is None:
- fd, filename = tempfile.mkstemp(suffix='.txt', text=True)
- os.close(fd)
- delete_tempfile = True
-
- f = open(os.path.expanduser(filename), 'w')
- f.write(history_item or '')
- f.close()
-
- os.system('"{}" "{}"'.format(self.editor, filename))
-
- if self.autorun_on_edit or history_item:
- self.do_load(filename)
-
- if delete_tempfile:
- os.remove(filename)
-
- saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums) ^ '*')("idx") +
- pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") +
- pyparsing.stringEnd)
-
- def do_save(self, arg):
- """Saves command(s) from history to file.
-
- Usage: save [N] [file_path]
-
- * N - Number of command (from history), or `*` for all commands in history (default: last command)
- * file_path - location to save script of command(s) to (default: value stored in temporary file)"""
- try:
- args = self.saveparser.parseString(arg)
- except pyparsing.ParseException:
- self.perror('Could not understand save target %s' % arg, traceback_war=False)
- raise SyntaxError(self.do_save.__doc__)
-
- # If a filename was supplied then use that, otherwise use a temp file
- if args.fname:
- fname = args.fname
- else:
- fd, fname = tempfile.mkstemp(suffix='.txt', text=True)
- os.close(fd)
-
- if args.idx == '*':
- saveme = '\n\n'.join(self.history[:])
- elif args.idx:
- saveme = self.history[int(args.idx) - 1]
- else:
- # Wrap in try to deal with case of empty history
- try:
- # Since this save command has already been added to history, need to go one more back for previous
- saveme = self.history[-2]
- except IndexError:
- self.perror('History is empty, nothing to save.', traceback_war=False)
- return
- try:
- f = open(os.path.expanduser(fname), 'w')
- f.write(saveme)
- f.close()
- self.pfeedback('Saved to {}'.format(fname))
- except Exception as e:
- self.perror('Saving {!r} - {}'.format(fname, e), traceback_war=False)
+ os.system('"{}"'.format(self.editor))
@property
def _current_script_dir(self):
@@ -1817,7 +1798,8 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T
else:
return None
- def do__relative_load(self, file_path):
+ @with_argument_list
+ def do__relative_load(self, arglist):
"""Runs commands in script file that is encoded as either ASCII or UTF-8 text.
Usage: _relative_load <file_path>
@@ -1833,11 +1815,11 @@ relative to the already-running script's directory.
NOTE: This command is intended to only be used within text file scripts.
"""
# If arg is None or arg is an empty string this is an error
- if not file_path:
+ if not arglist:
self.perror('_relative_load command requires a file path:', traceback_war=False)
return
- file_path = file_path.strip()
+ file_path = arglist[0].strip()
# 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)
self.do_load(relative_path)
@@ -1847,7 +1829,8 @@ NOTE: This command is intended to only be used within text file scripts.
if self._script_dir:
self._script_dir.pop()
- def do_load(self, file_path):
+ @with_argument_list
+ def do_load(self, arglist):
"""Runs commands in script file that is encoded as either ASCII or UTF-8 text.
Usage: load <file_path>
@@ -1857,11 +1840,12 @@ NOTE: This command is intended to only be used within text file scripts.
Script should contain one command per line, just like command would be typed in console.
"""
# If arg is None or arg is an empty string this is an error
- if not file_path:
+ if not arglist:
self.perror('load command requires a file path:', traceback_war=False)
return
- expanded_path = os.path.abspath(os.path.expanduser(file_path.strip()))
+ file_path = arglist[0].strip()
+ expanded_path = os.path.abspath(os.path.expanduser(file_path))
# Make sure expanded_path points to a file
if not os.path.isfile(expanded_path):
@@ -1892,17 +1876,6 @@ Script should contain one command per line, just like command would be typed in
self._script_dir.append(os.path.dirname(expanded_path))
- def do_run(self, arg):
- """run [arg]: re-runs an earlier command
-
- no arg -> run most recent command
- arg is integer -> run one history item, by index
- arg is string -> run most recent command by string search
- arg is /enclosed in forward-slashes/ -> run most recent by regex"""
- runme = self._last_matching(arg)
- self.pfeedback(runme)
- if runme:
- return self.onecmd_plus_hooks(runme)
@staticmethod
def is_text_file(file_path):
diff --git a/docs/argument_processing.rst b/docs/argument_processing.rst
index f2cd394b..69d0d7ce 100644
--- a/docs/argument_processing.rst
+++ b/docs/argument_processing.rst
@@ -1,3 +1,5 @@
+.. _decorators:
+
===================
Argument Processing
===================
@@ -13,14 +15,15 @@ Argument Processing
These features are all provided by the ``@with_argument_parser`` decorator.
-Using the decorator
-===================
+Using the argument parser decorator
+===================================
For each command in the ``cmd2`` subclass which requires argument parsing,
create an instance of ``argparse.ArgumentParser()`` which can parse the
input appropriately for the command. Then decorate the command method with
the ``@with_argument_parser`` decorator, passing the argument parser as the
-first parameter to the decorator. Add a third variable to the command method, which will contain the results of ``ArgumentParser.parse_args()``.
+first parameter to the decorator. This changes the second argumen to the command method, which will contain the results
+of ``ArgumentParser.parse_args()``.
Here's what it looks like::
@@ -31,7 +34,7 @@ Here's what it looks like::
argparser.add_argument('word', nargs='?', help='word to say')
@with_argument_parser(argparser)
- def do_speak(self, argv, opts)
+ def do_speak(self, opts)
"""Repeats what you tell me to."""
arg = opts.word
if opts.piglatin:
@@ -62,7 +65,7 @@ appended to the docstring for the method of that command. With this code::
argparser.add_argument('tag', nargs=1, help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argument_parser(argparser)
- def do_tag(self, cmdline, args=None):
+ def do_tag(self, args):
"""create a html tag"""
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
@@ -88,7 +91,7 @@ If you would prefer the short description of your command to come after the usag
argparser.add_argument('tag', nargs=1, help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argument_parser(argparser)
- def do_tag(self, cmdline, args=None):
+ def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
@@ -117,7 +120,7 @@ To add additional text to the end of the generated help message, use the ``epilo
argparser.add_argument('tag', nargs=1, help='tag')
argparser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argument_parser(argparser)
- def do_tag(self, cmdline, args=None):
+ def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
@@ -139,6 +142,50 @@ Which yields:
This command can not generate tags with no content, like <br/>
+Receiving an argument list
+==========================
+
+The default behavior of ``cmd2`` is to pass the user input directly to your
+``do_*`` methods as a string. If you don't want to use the full argument parser support outlined above, you can still have ``cmd2`` apply shell parsing rules to the user input and pass you a list of arguments instead of a string. Apply the ``@with_argument_list`` decorator to those methods that should receive an argument list instead of a string::
+
+ class CmdLineApp(cmd2.Cmd):
+ """ Example cmd2 application. """
+
+ def do_say(self, cmdline):
+ # cmdline contains a string
+ pass
+
+ @with_argument_list
+ def do_speak(self, arglist):
+ # arglist contains a list of arguments
+ pass
+
+
+Using the argument parser decorator and also receiving a a list of unknown positional arguments
+===============================================================================================
+If you want all unknown arguments to be passed to your command as a list of strings, then
+decorate the command method with the ``@with_argparser_and_list`` decorator.
+
+Here's what it looks like::
+
+ dir_parser = argparse.ArgumentParser()
+ dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line")
+
+ @with_argparser_and_list(dir_parser)
+ def do_dir(self, args, unknown):
+ """List contents of current directory."""
+ # No arguments for this command
+ if unknown:
+ self.perror("dir does not take any positional arguments:", traceback_war=False)
+ self.do_help('dir')
+ self._last_result = CmdResult('', 'Bad arguments')
+ return
+
+ # Get the contents as a list
+ contents = os.listdir(self.cwd)
+
+ ...
+
Deprecated optparse support
===========================
diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst
index e3787720..ea40c87c 100644
--- a/docs/freefeatures.rst
+++ b/docs/freefeatures.rst
@@ -13,7 +13,7 @@ Script files
============
Text files can serve as scripts for your ``cmd2``-based
-application, with the ``load``, ``_relative_load``, ``save``, and ``edit`` commands.
+application, with the ``load``, ``_relative_load``, ``edit`` and ``history`` commands.
Both ASCII and UTF-8 encoded unicode text files are supported.
@@ -23,10 +23,10 @@ Simply include one command per line, typed exactly as you would inside a ``cmd2`
.. automethod:: cmd2.Cmd.do__relative_load
-.. automethod:: cmd2.Cmd.do_save
-
.. automethod:: cmd2.Cmd.do_edit
+.. automethod:: cmd2.Cmd.do_history
+
Comments
========
@@ -257,12 +257,10 @@ also provide `bash-like history list editing`_.
.. _`bash-like history list editing`: http://www.talug.org/events/20030709/cmdline_history.html
-``cmd2`` makes a third type of history access available, consisting of these commands:
+``cmd2`` makes a third type of history access available with the **history** command:
.. automethod:: cmd2.Cmd.do_history
-.. automethod:: cmd2.Cmd.do_run
-
Quitting the application
========================
@@ -328,7 +326,6 @@ Tab-Completion
- ``edit``
- ``load``
- ``pyscript``
-- ``save``
- ``shell``
``cmd2`` also adds tab-completion of shell commands to the ``shell`` command.
diff --git a/docs/settingchanges.rst b/docs/settingchanges.rst
index 0a24651b..2b9f9a86 100644
--- a/docs/settingchanges.rst
+++ b/docs/settingchanges.rst
@@ -117,7 +117,6 @@ with::
(Cmd) set --long
abbrev: False # Accept abbreviated commands
- autorun_on_edit: False # Automatically run files after editing
colors: True # Colorized output (*nix only)
continuation_prompt: > # On 2nd+ line of input
debug: False # Show full error stack on error
diff --git a/docs/unfreefeatures.rst b/docs/unfreefeatures.rst
index 4557520a..2d497101 100644
--- a/docs/unfreefeatures.rst
+++ b/docs/unfreefeatures.rst
@@ -122,82 +122,26 @@ Commands with flags
All ``do_`` methods are responsible for interpreting
the arguments passed to them. However, ``cmd2`` lets
-a ``do_`` methods accept Unix-style *flags*. It uses optparse_
+a ``do_`` methods accept Unix-style *flags*. It uses argparse_
to parse the flags, and they work the same way as for
that module.
-Flags are defined with the ``options`` decorator,
-which is passed a list of optparse_-style options,
-each created with ``make_option``. The method
-should accept a second argument, ``opts``, in
-addition to ``args``; the flags will be stripped
-from ``args``.
-
-::
-
- @options([make_option('-p', '--piglatin', action="store_true", help="atinLay"),
- make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE"),
- make_option('-r', '--repeat', type="int", help="output [n] times")
- ])
- def do_speak(self, arg, opts=None):
- """Repeats what you tell me to."""
- arg = ''.join(arg)
- if opts.piglatin:
- arg = '%s%say' % (arg[1:].rstrip(), arg[0])
- if opts.shout:
- arg = arg.upper()
- repetitions = opts.repeat or 1
- for i in range(min(repetitions, self.maxrepeats)):
- self.stdout.write(arg)
- self.stdout.write('\n')
-
-::
-
- (Cmd) say goodnight, gracie
- goodnight, gracie
- (Cmd) say -sp goodnight, gracie
- OODNIGHT, GRACIEGAY
- (Cmd) say -r 2 --shout goodnight, gracie
- GOODNIGHT, GRACIE
- GOODNIGHT, GRACIE
-
-``options`` takes an optional additional argument, ``arg_desc``.
-If present, ``arg_desc`` will appear in place of ``arg`` in
-the option's online help.
-
-::
-
- @options([make_option('-t', '--train', action='store_true', help='by train')],
- arg_desc='(from city) (to city)')
- def do_travel(self, arg, opts=None):
- 'Gets you from (from city) to (to city).'
-
-
-::
-
- (Cmd) help travel
- Gets you from (from city) to (to city).
- Usage: travel [options] (from-city) (to-city)
-
- Options:
- -h, --help show this help message and exit
- -t, --train by train
+``cmd2`` defines a few decorators which change the behavior of
+how arguments get parsed for and passed to a ``do_`` method. See the section :ref:`decorators` for more information.
Controlling how arguments are parsed for commands with flags
------------------------------------------------------------
-There are three functions which can globally effect how arguments are parsed for commands with flags:
+There are a couple functions which can globally effect how arguments are parsed for commands with flags:
.. autofunction:: cmd2.set_posix_shlex
.. autofunction:: cmd2.set_strip_quotes
-.. autofunction:: cmd2.set_use_arg_list
-
-.. note::
+.. warning::::
- Since optparse_ has been deprecated since Python 3.2, the ``cmd2`` developers plan to replace optparse_ with
- argparse_ at some point in the future. We will endeavor to keep the API as identical as possible when this
- change occurs.
+ Since optparse_ has been deprecated since Python 3.2, the ``cmd2`` developers have deprecated the old optparse-based
+ ``@options`` decorator. This decorator still exists in the codebase, but it will be removed in a future release.
+ We recommend using one of the new argparse-based decorators.
.. _optparse: https://docs.python.org/3/library/optparse.html
.. _argparse: https://docs.python.org/3/library/argparse.html
diff --git a/examples/arg_print.py b/examples/arg_print.py
index 849cf386..1b18cdf0 100755
--- a/examples/arg_print.py
+++ b/examples/arg_print.py
@@ -9,9 +9,12 @@ and argument parsing is intended to work.
It also serves as an example of how to create command aliases (shortcuts).
"""
-import pyparsing
+import argparse
+
import cmd2
-from cmd2 import options, make_option
+import pyparsing
+
+from cmd2 import with_argument_list, with_argument_parser, with_argparser_and_unknown_args
class ArgumentAndOptionPrinter(cmd2.Cmd):
@@ -22,7 +25,7 @@ class ArgumentAndOptionPrinter(cmd2.Cmd):
# self.commentGrammars = pyparsing.Or([pyparsing.cStyleComment])
# Create command aliases which are shorter
- self.shortcuts.update({'ap': 'aprint', 'op': 'oprint'})
+ self.shortcuts.update({'$': 'aprint', '%': 'oprint'})
# Make sure to call this super class __init__ *after* setting commentGrammars and/or updating shortcuts
cmd2.Cmd.__init__(self)
@@ -33,12 +36,31 @@ class ArgumentAndOptionPrinter(cmd2.Cmd):
"""Print the argument string this basic command is called with."""
print('aprint was called with argument: {!r}'.format(arg))
- @options([make_option('-p', '--piglatin', action="store_true", help="atinLay"),
- make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE"),
- make_option('-r', '--repeat', type="int", help="output [n] times")], arg_desc='positional_arg_string')
- def do_oprint(self, arg, opts=None):
+ @with_argument_list
+ def do_lprint(self, arglist):
+ """Print the argument list this basic command is called with."""
+ print('lprint was called with the following list of arguments: {!r}'.format(arglist))
+
+ oprint_parser = argparse.ArgumentParser()
+ oprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ oprint_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ oprint_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ oprint_parser.add_argument('words', nargs='+', help='words to print')
+
+ @with_argument_parser(oprint_parser)
+ def do_oprint(self, args):
+ """Print the options and argument list this options command was called with."""
+ print('oprint was called with the following\n\toptions: {!r}'.format(args))
+
+ pprint_parser = argparse.ArgumentParser()
+ pprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ pprint_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ pprint_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ @with_argparser_and_unknown_args(pprint_parser)
+ def do_pprint(self, args, unknown):
"""Print the options and argument list this options command was called with."""
- print('oprint was called with the following\n\toptions: {!r}\n\targuments: {!r}'.format(opts, arg))
+ print('oprint was called with the following\n\toptions: {!r}\n\targuments: {}'.format(args, unknown))
+
if __name__ == '__main__':
diff --git a/examples/argparse_example.py b/examples/argparse_example.py
index d784ccf5..ae45411c 100755
--- a/examples/argparse_example.py
+++ b/examples/argparse_example.py
@@ -14,7 +14,8 @@ verifying that the output produced matches the transcript.
import argparse
import sys
-from cmd2 import Cmd, make_option, options, with_argument_parser
+from cmd2 import Cmd, options, with_argument_parser, with_argument_list
+from optparse import make_option
class CmdLineApp(Cmd):
@@ -40,14 +41,14 @@ class CmdLineApp(Cmd):
# Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist
# self.default_to_shell = True
+ speak_parser = argparse.ArgumentParser()
+ speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ speak_parser.add_argument('words', nargs='+', help='words to say')
- argparser = argparse.ArgumentParser()
- argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
- argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
- argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')
- argparser.add_argument('words', nargs='+', help='words to say')
- @with_argument_parser(argparser)
- def do_speak(self, cmdline, args=None):
+ @with_argument_parser(speak_parser)
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -63,16 +64,26 @@ class CmdLineApp(Cmd):
do_say = do_speak # now "say" is a synonym for "speak"
do_orate = do_speak # another synonym, but this one takes multi-line input
+ tag_parser = argparse.ArgumentParser(description='create a html tag')
+ tag_parser.add_argument('tag', nargs=1, help='tag')
+ tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
+
+ @with_argument_parser(tag_parser)
+ def do_tag(self, args):
+ """create a html tag"""
+ self.poutput('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
+
+
+ @with_argument_list
+ def do_tagg(self, arglist):
+ """verion of creating an html tag using arglist instead of argparser"""
+ if len(arglist) >= 2:
+ tag = arglist[0]
+ content = arglist[1:]
+ self.poutput('<{0}>{1}</{0}>'.format(tag, ' '.join(content)))
+ else:
+ self.perror("tagg requires at least 2 arguments")
- argparser = argparse.ArgumentParser(description='create a html tag')
- argparser.add_argument('tag', nargs=1, help='tag')
- argparser.add_argument('content', nargs='+', help='content to surround with tag')
- @with_argument_parser(argparser)
- def do_tag(self, argv, args=None):
- self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
- self.stdout.write('\n')
- # self.stdout.write is better than "print", because Cmd can be
- # initialized with a non-standard output destination
# @options uses the python optparse module which has been deprecated
# since 2011. Use @with_argument_parser instead, which utilizes the
@@ -97,6 +108,7 @@ class CmdLineApp(Cmd):
# self.stdout.write is better than "print", because Cmd can be
# initialized with a non-standard output destination
+
if __name__ == '__main__':
# You can do your custom Argparse parsing here to meet your application's needs
parser = argparse.ArgumentParser(description='Process the arguments however you like.')
diff --git a/examples/example.py b/examples/example.py
index d7d4e21c..4ba0d29a 100755
--- a/examples/example.py
+++ b/examples/example.py
@@ -38,13 +38,14 @@ class CmdLineApp(Cmd):
# Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell
Cmd.__init__(self, use_ipython=False)
- argparser = argparse.ArgumentParser()
- argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
- argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
- argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')
- argparser.add_argument('words', nargs='+', help='words to say')
- @with_argument_parser(argparser)
- def do_speak(self, cmdline, opts=None):
+ speak_parser = argparse.ArgumentParser()
+ speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ speak_parser.add_argument('words', nargs='+', help='words to say')
+
+ @with_argument_parser(speak_parser)
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -61,25 +62,27 @@ class CmdLineApp(Cmd):
do_say = do_speak # now "say" is a synonym for "speak"
do_orate = do_speak # another synonym, but this one takes multi-line input
- argparser = argparse.ArgumentParser()
- argparser.add_argument('-r', '--repeat', type=int, help='how many times to repeat')
- argparser.add_argument('words', nargs='+', help='words to say')
- @with_argument_parser(argparser)
- def do_mumble(self, cmdline, args=None):
+ mumble_parser = argparse.ArgumentParser()
+ mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat')
+ mumble_parser.add_argument('words', nargs='+', help='words to say')
+
+ @with_argument_parser(mumble_parser)
+ def do_mumble(self, args):
"""Mumbles what you tell me to."""
repetitions = args.repeat or 1
for i in range(min(repetitions, self.maxrepeats)):
output = []
- if (random.random() < .33):
+ if random.random() < .33:
output.append(random.choice(self.MUMBLE_FIRST))
for word in args.words:
- if (random.random() < .40):
+ if random.random() < .40:
output.append(random.choice(self.MUMBLES))
output.append(word)
- if (random.random() < .25):
+ if random.random() < .25:
output.append(random.choice(self.MUMBLE_LAST))
self.poutput(' '.join(output))
+
if __name__ == '__main__':
c = CmdLineApp()
c.cmdloop()
diff --git a/examples/exampleSession.txt b/examples/exampleSession.txt
index ef6df857..840bee60 100644
--- a/examples/exampleSession.txt
+++ b/examples/exampleSession.txt
@@ -4,7 +4,6 @@
# regexes on prompts just make the trailing space obvious
(Cmd) set
abbrev: False
-autorun_on_edit: False
colors: /(True|False)/
continuation_prompt: >/ /
debug: False
diff --git a/examples/pirate.py b/examples/pirate.py
index 32d7769e..55457e06 100755
--- a/examples/pirate.py
+++ b/examples/pirate.py
@@ -6,7 +6,8 @@ presented as part of her PyCon 2010 talk.
It demonstrates many features of cmd2.
"""
-from cmd2 import Cmd, options, make_option
+import argparse
+from cmd2 import Cmd, with_argument_parser
class Pirate(Cmd):
@@ -72,17 +73,18 @@ class Pirate(Cmd):
"""Sing a colorful song."""
print(self.colorize(arg, self.songcolor))
- @options([make_option('--ho', type='int', default=2,
- help="How often to chant 'ho'"),
- make_option('-c', '--commas',
- action="store_true",
- help="Intersperse commas")])
- def do_yo(self, arg, opts):
+ yo_parser = argparse.ArgumentParser()
+ yo_parser.add_argument('--ho', type=int, default=2, help="How often to chant 'ho'")
+ yo_parser.add_argument('-c', '--commas', action='store_true', help='Intersperse commas')
+ yo_parser.add_argument('beverage', nargs=1, help='beverage to drink with the chant')
+
+ @with_argument_parser(yo_parser)
+ def do_yo(self, args):
"""Compose a yo-ho-ho type chant with flexible options."""
- chant = ['yo'] + ['ho'] * opts.ho
- separator = ', ' if opts.commas else ' '
+ chant = ['yo'] + ['ho'] * args.ho
+ separator = ', ' if args.commas else ' '
chant = separator.join(chant)
- print('{0} and a bottle of {1}'.format(chant, arg))
+ print('{0} and a bottle of {1}'.format(chant, args.beverage[0]))
if __name__ == '__main__':
diff --git a/examples/python_scripting.py b/examples/python_scripting.py
index ae04fda1..aa62007a 100755
--- a/examples/python_scripting.py
+++ b/examples/python_scripting.py
@@ -14,10 +14,11 @@ command and the "pyscript <script> [arguments]" syntax comes into play.
This application and the "scripts/conditional.py" script serve as an example for one way in which this can be done.
"""
+import argparse
import functools
import os
-from cmd2 import Cmd, options, make_option, CmdResult, set_use_arg_list
+from cmd2 import Cmd, CmdResult, with_argument_list, with_argparser_and_unknown_args
class CmdLineApp(Cmd):
@@ -27,12 +28,8 @@ class CmdLineApp(Cmd):
# Enable the optional ipy command if IPython is installed by setting use_ipython=True
Cmd.__init__(self, use_ipython=True)
self._set_prompt()
- self.autorun_on_edit = False
self.intro = 'Happy 𝛑 Day. Note the full Unicode support: 😇 (Python 3 only) 💩'
- # For option commands, pass a list of argument strings instead of a single argument string to the do_* methods
- set_use_arg_list(True)
-
def _set_prompt(self):
"""Set prompt so it displays the current working directory."""
self.cwd = os.getcwd()
@@ -49,19 +46,21 @@ class CmdLineApp(Cmd):
self._set_prompt()
return stop
- # noinspection PyUnusedLocal
- @options([], arg_desc='<new_dir>')
- def do_cd(self, arg, opts=None):
- """Change directory."""
+ @with_argument_list
+ def do_cd(self, arglist):
+ """Change directory.
+ Usage:
+ cd <new_dir>
+ """
# Expect 1 argument, the directory to change to
- if not arg or len(arg) != 1:
+ if not arglist or len(arglist) != 1:
self.perror("cd requires exactly 1 argument:", traceback_war=False)
self.do_help('cd')
self._last_result = CmdResult('', 'Bad arguments')
return
# Convert relative paths to absolute paths
- path = os.path.abspath(os.path.expanduser(arg[0]))
+ path = os.path.abspath(os.path.expanduser(arglist[0]))
# Make sure the directory exists, is a directory, and we have read access
out = ''
@@ -86,13 +85,15 @@ class CmdLineApp(Cmd):
# Enable directory completion for cd command by freezing an argument to path_complete() with functools.partialmethod
complete_cd = functools.partialmethod(Cmd.path_complete, dir_only=True)
- @options([make_option('-l', '--long', action="store_true", help="display in long format with one item per line")],
- arg_desc='')
- def do_dir(self, arg, opts=None):
+ dir_parser = argparse.ArgumentParser()
+ dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line")
+
+ @with_argparser_and_unknown_args(dir_parser)
+ def do_dir(self, args, unknown):
"""List contents of current directory."""
# No arguments for this command
- if arg:
- self.perror("dir does not take any arguments:", traceback_war=False)
+ if unknown:
+ self.perror("dir does not take any positional arguments:", traceback_war=False)
self.do_help('dir')
self._last_result = CmdResult('', 'Bad arguments')
return
@@ -101,7 +102,7 @@ class CmdLineApp(Cmd):
contents = os.listdir(self.cwd)
fmt = '{} '
- if opts.long:
+ if args.long:
fmt = '{}\n'
for f in contents:
self.stdout.write(fmt.format(f))
diff --git a/examples/transcript_regex.txt b/examples/transcript_regex.txt
index 27b4c639..7d017dee 100644
--- a/examples/transcript_regex.txt
+++ b/examples/transcript_regex.txt
@@ -4,7 +4,6 @@
# regexes on prompts just make the trailing space obvious
(Cmd) set
abbrev: True
-autorun_on_edit: False
colors: /(True|False)/
continuation_prompt: >/ /
debug: False
diff --git a/tests/conftest.py b/tests/conftest.py
index 97116b48..387322b1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,24 +15,29 @@ import cmd2
# Help text for base cmd2.Cmd application
BASE_HELP = """Documented commands (type help <topic>):
========================================
-_relative_load edit history py quit save shell show
-cmdenvironment help load pyscript run set shortcuts
+edit help history load py pyscript quit set shell shortcuts
"""
# Help text for the history command
-HELP_HISTORY = """history [arg]: lists past commands issued
+HELP_HISTORY = """usage: history [-h] [-r | -e | -o FILE] [-s] [arg]
- | no arg: list all
- | arg is integer: list one history item, by index
- | a..b, a:b, a:, ..b -> list history items by a span of indices (inclusive)
- | arg is string: list all commands matching string search
- | arg is /enclosed in forward-slashes/: regular expression search
+run, edit, and save previously entered commands
-Usage: history [options] (limit on which commands to include)
+positional arguments:
+ arg empty all history items
+ a one history item by number
+ a..b, a:b, a:, ..b items by indices (inclusive)
+ [string] items containing string
+ /regex/ items matching regular expression
+
+optional arguments:
+ -h, --help show this help message and exit
+ -r, --run run selected history items
+ -e, --edit edit and then run selected history items
+ -o FILE, --output-file FILE
+ output to file
+ -s, --script script format; no separation lines
-Options:
- -h, --help show this help message and exit
- -s, --script Script format; no separation lines
"""
# Output from the shortcuts command with default built-in shortcuts
@@ -48,7 +53,6 @@ if sys.platform.startswith('win'):
expect_colors = False
# Output from the show command with default settings
SHOW_TXT = """abbrev: False
-autorun_on_edit: False
colors: {}
continuation_prompt: >
debug: False
@@ -67,7 +71,6 @@ else:
color_str = 'False'
SHOW_LONG = """
abbrev: False # Accept abbreviated commands
-autorun_on_edit: False # Automatically run files after editing
colors: {} # Colorized output (*nix only)
continuation_prompt: > # On 2nd+ line of input
debug: False # Show full error stack on error
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 52ce7de8..fea9bebb 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -2,7 +2,6 @@
"""
Cmd2 testing for argument parsing
"""
-import re
import argparse
import pytest
@@ -14,13 +13,14 @@ class ArgparseApp(cmd2.Cmd):
self.maxrepeats = 3
cmd2.Cmd.__init__(self)
- argparser = argparse.ArgumentParser()
- argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
- argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
- argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')
- argparser.add_argument('words', nargs='+', help='words to say')
- @cmd2.with_argument_parser(argparser)
- def do_say(self, cmdline, args=None):
+ say_parser = argparse.ArgumentParser()
+ say_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ say_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ say_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ say_parser.add_argument('words', nargs='+', help='words to say')
+
+ @cmd2.with_argument_parser(say_parser)
+ def do_say(self, args):
"""Repeat what you tell me to."""
words = []
for word in args.words:
@@ -36,31 +36,74 @@ class ArgparseApp(cmd2.Cmd):
self.stdout.write(' '.join(words))
self.stdout.write('\n')
- argparser = argparse.ArgumentParser(description='create a html tag')
- argparser.add_argument('tag', nargs=1, help='tag')
- argparser.add_argument('content', nargs='+', help='content to surround with tag')
- @cmd2.with_argument_parser(argparser)
- def do_tag(self, cmdline, args=None):
+ tag_parser = argparse.ArgumentParser(description='create a html tag')
+ tag_parser.add_argument('tag', nargs=1, help='tag')
+ tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
+
+ @cmd2.with_argument_parser(tag_parser)
+ def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
- argparser = argparse.ArgumentParser()
- argparser.add_argument('args', nargs='*')
- @cmd2.with_argument_parser(argparser)
- def do_compare(self, cmdline, args=None):
- cmdline_str = re.sub('\s+', ' ', cmdline)
- args_str = re.sub('\s+', ' ', ' '.join(args.args))
- if cmdline_str == args_str:
+ @cmd2.with_argument_list
+ def do_arglist(self, arglist):
+ if isinstance(arglist, list):
self.stdout.write('True')
else:
self.stdout.write('False')
+ @cmd2.with_argument_list
+ @cmd2.with_argument_list
+ def do_arglisttwice(self, arglist):
+ if isinstance(arglist, list):
+ self.stdout.write(' '.join(arglist))
+ else:
+ self.stdout.write('False')
+
+ known_parser = argparse.ArgumentParser()
+ known_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ known_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ known_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ @cmd2.with_argparser_and_unknown_args(known_parser)
+ def do_speak(self, args, extra):
+ """Repeat what you tell me to."""
+ words = []
+ for word in extra:
+ if word is None:
+ word = ''
+ if args.piglatin:
+ word = '%s%say' % (word[1:], word[0])
+ if args.shout:
+ word = word.upper()
+ words.append(word)
+ repetitions = args.repeat or 1
+ for i in range(min(repetitions, self.maxrepeats)):
+ self.stdout.write(' '.join(words))
+ self.stdout.write('\n')
+
+ @cmd2.with_argparser_and_unknown_args(known_parser)
+ def do_talk(self, args, extra):
+ words = []
+ for word in extra:
+ if word is None:
+ word = ''
+ if args.piglatin:
+ word = '%s%say' % (word[1:], word[0])
+ if args.shout:
+ word = word.upper()
+ words.append(word)
+ repetitions = args.repeat or 1
+ for i in range(min(repetitions, self.maxrepeats)):
+ self.stdout.write(' '.join(words))
+ self.stdout.write('\n')
+
@pytest.fixture
def argparse_app():
app = ArgparseApp()
app.stdout = StdOut()
return app
+
def test_argparse_basic_command(argparse_app):
out = run_cmd(argparse_app, 'say hello')
assert out == ['hello']
@@ -71,6 +114,14 @@ def test_argparse_quoted_arguments(argparse_app):
out = run_cmd(argparse_app, 'say "hello there"')
assert out == ['hello there']
+def test_argparse_with_list(argparse_app):
+ out = run_cmd(argparse_app, 'speak -s hello world!')
+ assert out == ['HELLO WORLD!']
+
+def test_argparse_with_list_and_empty_doc(argparse_app):
+ out = run_cmd(argparse_app, 'speak -s hello world!')
+ assert out == ['HELLO WORLD!']
+
def test_argparse_quoted_arguments_multiple(argparse_app):
argparse_app.POSIX = False
argparse_app.STRIP_QUOTES_FOR_NON_POSIX = True
@@ -100,8 +151,10 @@ def test_argparse_prog(argparse_app):
progname = out[0].split(' ')[1]
assert progname == 'tag'
-def test_argparse_cmdline(argparse_app):
- out = run_cmd(argparse_app, 'compare this is a test')
+def test_arglist(argparse_app):
+ out = run_cmd(argparse_app, 'arglist "we should" get these')
assert out[0] == 'True'
- \ No newline at end of file
+def test_arglist_decorator_twice(argparse_app):
+ out = run_cmd(argparse_app, 'arglisttwice "we should" get these')
+ assert out[0] == 'we should get these'
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index ebd388b1..30308dd7 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -15,6 +15,7 @@ import pytest
import six
from code import InteractiveConsole
+from optparse import make_option
# Used for sm.input: raw_input() for Python 2 or input() for Python 3
import six.moves as sm
@@ -43,21 +44,18 @@ def test_base_help_history(base_app):
expected = normalize(HELP_HISTORY)
assert out == expected
-def test_base_options_help(base_app, capsys):
- run_cmd(base_app, 'show -h')
+def test_base_argparse_help(base_app, capsys):
+ run_cmd(base_app, 'set -h')
out, err = capsys.readouterr()
- expected = run_cmd(base_app, 'help show')
- # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show'
- assert normalize(str(out)) == expected
+ expected = run_cmd(base_app, 'help set')
+ assert normalize(base_app.do_set.__doc__ + str(err)) == expected
def test_base_invalid_option(base_app, capsys):
- run_cmd(base_app, 'show -z')
+ run_cmd(base_app, 'set -z')
out, err = capsys.readouterr()
- show_help = run_cmd(base_app, 'help show')
- expected = ['no such option: -z']
- expected.extend(show_help)
- # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show'
- assert normalize(str(out)) == expected
+ run_cmd(base_app, 'help set')
+ expected = ['usage: set [-h] [-a] [-l] [settable [settable ...]]', 'set: error: unrecognized arguments: -z']
+ assert normalize(str(err)) == expected
def test_base_shortcuts(base_app):
out = run_cmd(base_app, 'shortcuts')
@@ -68,7 +66,7 @@ def test_base_shortcuts(base_app):
def test_base_show(base_app):
# force editor to be 'vim' so test is repeatable across platforms
base_app.editor = 'vim'
- out = run_cmd(base_app, 'show')
+ out = run_cmd(base_app, 'set')
expected = normalize(SHOW_TXT)
assert out == expected
@@ -76,11 +74,31 @@ def test_base_show(base_app):
def test_base_show_long(base_app):
# force editor to be 'vim' so test is repeatable across platforms
base_app.editor = 'vim'
- out = run_cmd(base_app, 'show -l')
+ out = run_cmd(base_app, 'set -l')
expected = normalize(SHOW_LONG)
assert out == expected
+def test_base_show_readonly(base_app):
+ base_app.editor = 'vim'
+ out = run_cmd(base_app, 'set -a')
+ expected = normalize(SHOW_TXT + '\nRead only settings:' + """
+ Commands are case-sensitive: {}
+ Commands may be terminated with: {}
+ Arguments at invocation allowed: {}
+ Output redirection and pipes allowed: {}
+ Parsing of @options commands:
+ Shell lexer mode for command argument splitting: {}
+ Strip Quotes after splitting arguments: {}
+ Argument type: {}
+
+""".format(not base_app.case_insensitive, base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection,
+ "POSIX" if cmd2.POSIX_SHLEX else "non-POSIX",
+ "True" if cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.POSIX_SHLEX else "False",
+ "List of argument strings" if cmd2.USE_ARG_LIST else "string of space-separated arguments"))
+ assert out == expected
+
+
def test_base_set(base_app):
out = run_cmd(base_app, 'set quiet True')
expected = normalize("""
@@ -89,7 +107,7 @@ now: True
""")
assert out == expected
- out = run_cmd(base_app, 'show quiet')
+ out = run_cmd(base_app, 'set quiet')
assert out == ['quiet: True']
def test_set_not_supported(base_app, capsys):
@@ -109,7 +127,7 @@ now: True
""")
assert out == expected
- out = run_cmd(base_app, 'show quiet')
+ out = run_cmd(base_app, 'set quiet')
assert out == ['quiet: True']
@@ -298,25 +316,49 @@ def test_history_with_span_index_error(base_app):
""")
assert out == expected
+def test_history_output_file(base_app):
+ run_cmd(base_app, 'help')
+ run_cmd(base_app, 'shortcuts')
+ run_cmd(base_app, 'help history')
-def test_base_cmdenvironment(base_app):
- out = run_cmd(base_app, 'cmdenvironment')
- expected = normalize("""
+ fd, fname = tempfile.mkstemp(prefix='', suffix='.txt')
+ os.close(fd)
+ run_cmd(base_app, 'history -o "{}"'.format(fname))
+ expected = normalize('\n'.join(['help', 'shortcuts', 'help history']))
+ with open(fname) as f:
+ content = normalize(f.read())
+ assert content == expected
- Commands are case-sensitive: {}
- Commands may be terminated with: {}
- Arguments at invocation allowed: {}
- Output redirection and pipes allowed: {}
- Parsing of @options commands:
- Shell lexer mode for command argument splitting: {}
- Strip Quotes after splitting arguments: {}
- Argument type: {}
-
-""".format(not base_app.case_insensitive, base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection,
- "POSIX" if cmd2.POSIX_SHLEX else "non-POSIX",
- "True" if cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.POSIX_SHLEX else "False",
- "List of argument strings" if cmd2.USE_ARG_LIST else "string of space-separated arguments"))
- assert out == expected
+def test_history_edit(base_app, monkeypatch):
+ # Set a fake editor just to make sure we have one. We aren't really
+ # going to call it due to the mock
+ base_app.editor = 'fooedit'
+
+ # Mock out the os.system call so we don't actually open an editor
+ m = mock.MagicMock(name='system')
+ monkeypatch.setattr("os.system", m)
+
+ # Run help command just so we have a command in history
+ run_cmd(base_app, 'help')
+ run_cmd(base_app, 'history -e 1')
+
+ # We have an editor, so should expect a system call
+ m.assert_called_once()
+
+def test_history_run_all_commands(base_app):
+ # make sure we refuse to run all commands as a default
+ run_cmd(base_app, 'shortcuts')
+ out = run_cmd(base_app, 'history -r')
+ # this should generate an error, but we don't currently have a way to
+ # capture stderr in these tests. So we assume that if we got nothing on
+ # standard out, that the error occured because if the commaned executed
+ # then we should have a list of shortcuts in our output
+ assert out == []
+
+def test_history_run_one_command(base_app):
+ expected = run_cmd(base_app, 'help')
+ output = run_cmd(base_app, 'history -r 1')
+ assert output == expected
def test_base_load(base_app, request):
test_dir = os.path.dirname(request.module.__file__)
@@ -480,88 +522,6 @@ def test_relative_load_requires_an_argument(base_app, capsys):
assert base_app.cmdqueue == []
-def test_base_save(base_app):
- # TODO: Use a temporary directory for the file
- filename = 'deleteme.txt'
- base_app.feedback_to_output = True
- run_cmd(base_app, 'help')
- run_cmd(base_app, 'help save')
-
- # Test the * form of save which saves all commands from history
- out = run_cmd(base_app, 'save * {}'.format(filename))
- assert out == normalize('Saved to {}\n'.format(filename))
- expected = normalize("""
-help
-
-help save
-
-save * deleteme.txt
-""")
- with open(filename) as f:
- content = normalize(f.read())
- assert content == expected
-
- # Test the N form of save which saves a numbered command from history
- out = run_cmd(base_app, 'save 1 {}'.format(filename))
- assert out == normalize('Saved to {}\n'.format(filename))
- expected = normalize('help')
- with open(filename) as f:
- content = normalize(f.read())
- assert content == expected
-
- # Test the blank form of save which saves the most recent command from history
- out = run_cmd(base_app, 'save {}'.format(filename))
- assert out == normalize('Saved to {}\n'.format(filename))
- expected = normalize('save 1 {}'.format(filename))
- with open(filename) as f:
- content = normalize(f.read())
- assert content == expected
-
- # Delete file that was created
- os.remove(filename)
-
-def test_save_parse_error(base_app, capsys):
- invalid_file = '~!@'
- run_cmd(base_app, 'save {}'.format(invalid_file))
- out, err = capsys.readouterr()
- assert out == ''
- assert err.startswith('ERROR: Could not understand save target {}\n'.format(invalid_file))
-
-def test_save_tempfile(base_app):
- # Just run help to make sure there is something in the history
- base_app.feedback_to_output = True
- run_cmd(base_app, 'help')
- out = run_cmd(base_app, 'save *')
- output = out[0]
- assert output.startswith('Saved to ')
-
- # Delete the tempfile which was created
- temp_file = output.split('Saved to ')[1].strip()
- os.remove(temp_file)
-
-def test_save_invalid_history_index(base_app, capsys):
- run_cmd(base_app, 'save 5')
- out, err = capsys.readouterr()
- assert out == ''
- assert err.startswith("EXCEPTION of type 'IndexError' occurred with message: 'list index out of range'\n")
-
-def test_save_empty_history_and_index(base_app, capsys):
- run_cmd(base_app, 'save')
- out, err = capsys.readouterr()
- assert out == ''
- assert err.startswith("ERROR: History is empty, nothing to save.\n")
-
-def test_save_invalid_path(base_app, capsys):
- # Just run help to make sure there is something in the history
- run_cmd(base_app, 'help')
-
- invalid_path = '/no_such_path/foobar.txt'
- run_cmd(base_app, 'save {}'.format(invalid_path))
- out, err = capsys.readouterr()
- assert out == ''
- assert err.startswith("ERROR: Saving '{}' - ".format(invalid_path))
-
-
def test_output_redirection(base_app):
fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt')
os.close(fd)
@@ -653,12 +613,6 @@ def test_pipe_to_shell(base_app, capsys):
if sys.platform == "win32":
# Windows
command = 'help | sort'
- # Get help menu and pipe it's output to the sort shell command
- # expected = ['', '', '_relative_load edit history py quit save shell show',
- # '========================================',
- # 'cmdenvironment help load pyscript run set shortcuts',
- # 'Documented commands (type help <topic>):']
- # assert out == expected
else:
# Mac and Linux
# Get help on help and pipe it's output to the input of the word count shell command
@@ -820,84 +774,11 @@ def test_edit_blank(base_app, monkeypatch):
m = mock.MagicMock(name='system')
monkeypatch.setattr("os.system", m)
- # Run help command just so we have a command in history
- run_cmd(base_app, 'help')
-
- run_cmd(base_app, 'edit')
-
- # We have an editor, so should expect a system call
- m.assert_called_once()
-
-def test_edit_empty_history(base_app, capsys):
run_cmd(base_app, 'edit')
- out, err = capsys.readouterr()
- assert out == ''
- assert err == 'ERROR: edit must be called with argument if history is empty\n'
-
-def test_edit_valid_positive_number(base_app, monkeypatch):
- # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
- base_app.editor = 'fooedit'
-
- # Mock out the os.system call so we don't actually open an editor
- m = mock.MagicMock(name='system')
- monkeypatch.setattr("os.system", m)
-
- # Run help command just so we have a command in history
- run_cmd(base_app, 'help')
-
- run_cmd(base_app, 'edit 1')
# We have an editor, so should expect a system call
m.assert_called_once()
-def test_edit_valid_negative_number(base_app, monkeypatch):
- # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
- base_app.editor = 'fooedit'
-
- # Mock out the os.system call so we don't actually open an editor
- m = mock.MagicMock(name='system')
- monkeypatch.setattr("os.system", m)
-
- # Run help command just so we have a command in history
- run_cmd(base_app, 'help')
-
- run_cmd(base_app, 'edit "-1"')
-
- # We have an editor, so should expect a system call
- m.assert_called_once()
-
-def test_edit_invalid_positive_number(base_app, monkeypatch):
- # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
- base_app.editor = 'fooedit'
-
- # Mock out the os.system call so we don't actually open an editor
- m = mock.MagicMock(name='system')
- monkeypatch.setattr("os.system", m)
-
- # Run help command just so we have a command in history
- run_cmd(base_app, 'help')
-
- run_cmd(base_app, 'edit 23')
-
- # History index is invalid, so should expect a system call
- m.assert_not_called()
-
-def test_edit_invalid_negative_number(base_app, monkeypatch):
- # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
- base_app.editor = 'fooedit'
-
- # Mock out the os.system call so we don't actually open an editor
- m = mock.MagicMock(name='system')
- monkeypatch.setattr("os.system", m)
-
- # Run help command just so we have a command in history
- run_cmd(base_app, 'help')
-
- run_cmd(base_app, 'edit "-23"')
-
- # History index is invalid, so should expect a system call
- m.assert_not_called()
-
def test_base_py_interactive(base_app):
# Mock out the InteractiveConsole.interact() call so we don't actually wait for a user's response on stdin
@@ -1099,8 +980,7 @@ def test_custom_help_menu(help_app):
expected = normalize("""
Documented commands (type help <topic>):
========================================
-_relative_load edit history py quit save shell show
-cmdenvironment help load pyscript run set shortcuts squat
+edit help history load py pyscript quit set shell shortcuts squat
Undocumented commands:
======================
@@ -1274,7 +1154,7 @@ arg 2: 'bar'
class OptionApp(cmd2.Cmd):
- @cmd2.options([cmd2.make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")])
+ @cmd2.options([make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")])
def do_greet(self, arg, opts=None):
arg = ''.join(arg)
if opts.shout:
@@ -1317,7 +1197,7 @@ class MultilineApp(cmd2.Cmd):
# Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
cmd2.Cmd.__init__(self, *args, **kwargs)
- @cmd2.options([cmd2.make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")])
+ @cmd2.options([make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")])
def do_orate(self, arg, opts=None):
arg = ''.join(arg)
if opts.shout:
@@ -1361,20 +1241,6 @@ def test_clipboard_failure(capsys):
assert 'Cannot redirect to paste buffer; install ``xclip`` and re-run to enable' in err
-def test_run_command_with_empty_arg(base_app):
- command = 'help'
- base_app.feedback_to_output = True
- run_cmd(base_app, command)
- out = run_cmd(base_app, 'run')
- expected = normalize('{}\n\n'.format(command) + BASE_HELP)
- assert out == expected
-
-def test_run_command_with_empty_history(base_app):
- base_app.feedback_to_output = True
- out = run_cmd(base_app, 'run')
- assert out == []
-
-
class CmdResultApp(cmd2.Cmd):
def __init__(self, *args, **kwargs):
# Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
@@ -1490,7 +1356,7 @@ def test_echo(capsys):
# Check the output
assert app.cmdqueue == []
assert app._current_script_dir is None
- assert out.startswith('{}{}\n'.format(app.prompt, command) + 'history [arg]: lists past commands issued')
+ assert out.startswith('{}{}\n'.format(app.prompt, command) + HELP_HISTORY.split()[0])
def test_pseudo_raw_input_tty_rawinput_true():
# use context managers so original functions get put back when we are done
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index e572e919..41322341 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -14,12 +14,10 @@ import mock
import pytest
import six
-# Used for sm.input: raw_input() for Python 2 or input() for Python 3
-import six.moves as sm
-
-from cmd2 import (Cmd, make_option, options, Cmd2TestCase, set_use_arg_list,
+from cmd2 import (Cmd, options, Cmd2TestCase, set_use_arg_list,
set_posix_shlex, set_strip_quotes)
from conftest import run_cmd, StdOut, normalize
+from optparse import make_option
class CmdLineApp(Cmd):
@@ -27,7 +25,7 @@ class CmdLineApp(Cmd):
MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh']
MUMBLE_FIRST = ['so', 'like', 'well']
MUMBLE_LAST = ['right?']
-
+
def __init__(self, *args, **kwargs):
self.abbrev = True
self.multilineCommands = ['orate']
@@ -132,9 +130,8 @@ def test_base_with_transcript(_cmdline_app):
Documented commands (type help <topic>):
========================================
-_relative_load help mumble pyscript save shell speak
-cmdenvironment history orate quit say shortcuts
-edit load py run set show
+edit history mumble py quit set shortcuts
+help load orate pyscript say shell speak
(Cmd) help say
Repeats what you tell me to.
@@ -175,7 +172,7 @@ say -ps --repeat=5 goodnight, Gracie
set maxrepeats 5
-------------------------[6]
say -ps --repeat=5 goodnight, Gracie
-(Cmd) run 4
+(Cmd) history -r 4
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
@@ -332,7 +329,7 @@ def test_transcript(request, capsys, filename, feedback_to_output):
])
def test_parse_transcript_expected(expected, transformed):
app = CmdLineApp()
-
+
class TestMyAppCase(Cmd2TestCase):
cmdapp = app
diff --git a/tests/transcripts/from_cmdloop.txt b/tests/transcripts/from_cmdloop.txt
index 5a12ca03..07bdc30d 100644
--- a/tests/transcripts/from_cmdloop.txt
+++ b/tests/transcripts/from_cmdloop.txt
@@ -5,9 +5,8 @@
Documented commands (type help <topic>):
========================================
-_relative_load help mumble pyscript save shell speak
-cmdenvironment history orate quit say shortcuts
-edit load py run set show/ */
+edit history mumble py quit set shortcuts/ */
+help load orate pyscript say shell speak/ */
(Cmd) help say
Repeats what you tell me to.
@@ -35,7 +34,7 @@ OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
-(Cmd) hi
+(Cmd) history
-------------------------[1]
help
-------------------------[2]
@@ -48,7 +47,7 @@ say -ps --repeat=5 goodnight, Gracie
set maxrepeats 5
-------------------------[6]
say -ps --repeat=5 goodnight, Gracie
-(Cmd) run 4
+(Cmd) history -r 4
say -ps --repeat=5 goodnight, Gracie
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
@@ -56,5 +55,5 @@ OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
OODNIGHT, GRACIEGAY
(Cmd) set prompt "---> "
-prompt - was: (Cmd)/ /
-now: --->/ /
+prompt - was: (Cmd)/ */
+now: --->/ */
diff --git a/tests/transcripts/regex_set.txt b/tests/transcripts/regex_set.txt
index 3a4a234d..6c12b4cb 100644
--- a/tests/transcripts/regex_set.txt
+++ b/tests/transcripts/regex_set.txt
@@ -5,7 +5,6 @@
(Cmd) set
abbrev: True
-autorun_on_edit: False
colors: /(True|False)/
continuation_prompt: >/ /
debug: False