summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcmd2.py66
-rw-r--r--docs/argument_processing.rst43
-rwxr-xr-xexamples/arg_print.py39
-rwxr-xr-xexamples/argparse_example.py4
-rwxr-xr-xexamples/example.py4
-rwxr-xr-xexamples/pirate.py23
-rwxr-xr-xexamples/python_scripting.py18
-rw-r--r--tests/test_argparse.py74
8 files changed, 161 insertions, 110 deletions
diff --git a/cmd2.py b/cmd2.py
index 80a85443..ccb1c213 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -258,17 +258,29 @@ def parse_quoted_string(cmdline):
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)
-def with_argument_parser(argparser):
- """A decorator to alter a cmd2 method to populate its ``opts``
+ cmd_wrapper.__doc__ = func.__doc__
+ return cmd_wrapper
+
+
+def with_argparser_and_list(argparser):
+ """A decorator to alter a cmd2 method to populate its ``args``
argument by parsing arguments with the given instance of
- argparse.ArgumentParser.
+ 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)
- opts = argparser.parse_args(lexed_arglist)
- func(instance, lexed_arglist, opts)
+ 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
@@ -286,17 +298,31 @@ def with_argument_parser(argparser):
return arg_decorator
-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)
+def with_argument_parser(argparser):
+ """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):
+ lexed_arglist = parse_quoted_string(cmdline)
+ args = argparser.parse_args(lexed_arglist)
+ func(instance, args)
- cmd_wrapper.__doc__ = func.__doc__
- return cmd_wrapper
+ # 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 options(option_list, arg_desc="arg"):
@@ -1294,7 +1320,7 @@ class Cmd(cmd.Cmd):
show_parser.add_argument('param', nargs='?', help='name of parameter, if not supplied show all parameters')
@with_argument_parser(show_parser)
- def do_show(self, arglist, args):
+ def do_show(self, args):
param = ''
if args.param:
param = args.param.strip().lower()
@@ -1670,7 +1696,7 @@ arg is /regex/ matching regular expression regex"""
show_parser.add_argument('arg', nargs='*', help=_history_arg_help)
@with_argument_parser(show_parser)
- def do_history(self, arglist, args):
+ def do_history(self, args):
# If an argument was supplied, then retrieve partial contents of the history
if args.arg:
# If a character indicating a slice is present, retrieve a slice of the history
@@ -1715,11 +1741,11 @@ arg is /regex/ matching regular expression regex"""
def do_edit(self, arglist):
"""Edit a file or command in a text editor.
- Usage: edit [N]|[file_path]
+Usage: edit [N]|[file_path]
Where:
- N - Number of command (from history), or `*` for all commands in history
+ * 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
+ * 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.
diff --git a/docs/argument_processing.rst b/docs/argument_processing.rst
index 4ab4e12f..b4df68c1 100644
--- a/docs/argument_processing.rst
+++ b/docs/argument_processing.rst
@@ -20,7 +20,8 @@ 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,9 +32,8 @@ Here's what it looks like::
argparser.add_argument('word', nargs='?', help='word to say')
@with_argument_parser(argparser)
- def do_speak(self, arglist, opts)
+ def do_speak(self, opts)
"""Repeats what you tell me to."""
- # arglist contains a list of arguments as parsed by shlex.split()
arg = opts.word
if opts.piglatin:
arg = '%s%say' % (arg[1:], arg[0])
@@ -43,8 +43,6 @@ Here's what it looks like::
for i in range(min(repetitions, self.maxrepeats)):
self.poutput(arg)
-This decorator also changes the value passed to the first argument of the ``do_*`` method. Instead of a string, the method will be passed a list of arguments as parsed by ``shlex.split()``.
-
.. note::
The ``@with_argument_parser`` decorator sets the ``prog`` variable in
@@ -65,7 +63,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, arglist, 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')
@@ -91,7 +89,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, arglist, args=None):
+ def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
@@ -120,7 +118,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')
@@ -150,17 +148,42 @@ The default behavior of ``cmd2`` is to pass the user input directly to your
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/examples/arg_print.py b/examples/arg_print.py
index 00507649..f5ec2a51 100755
--- a/examples/arg_print.py
+++ b/examples/arg_print.py
@@ -9,10 +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
-from optparse import make_option
+import pyparsing
+
+from cmd2 import with_argument_list, with_argument_parser, with_argparser_and_list
class ArgumentAndOptionPrinter(cmd2.Cmd):
@@ -23,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)
@@ -34,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_list(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 b203feef..ae45411c 100755
--- a/examples/argparse_example.py
+++ b/examples/argparse_example.py
@@ -48,7 +48,7 @@ class CmdLineApp(Cmd):
speak_parser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(speak_parser)
- def do_speak(self, arglist, args=None):
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -69,7 +69,7 @@ class CmdLineApp(Cmd):
tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
@with_argument_parser(tag_parser)
- def do_tag(self, arglist, args=None):
+ def do_tag(self, args):
"""create a html tag"""
self.poutput('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
diff --git a/examples/example.py b/examples/example.py
index 7c78edc3..4ba0d29a 100755
--- a/examples/example.py
+++ b/examples/example.py
@@ -45,7 +45,7 @@ class CmdLineApp(Cmd):
speak_parser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(speak_parser)
- def do_speak(self, cmdline, opts=None):
+ def do_speak(self, args):
"""Repeats what you tell me to."""
words = []
for word in args.words:
@@ -67,7 +67,7 @@ class CmdLineApp(Cmd):
mumble_parser.add_argument('words', nargs='+', help='words to say')
@with_argument_parser(mumble_parser)
- 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)):
diff --git a/examples/pirate.py b/examples/pirate.py
index f8c91315..55457e06 100755
--- a/examples/pirate.py
+++ b/examples/pirate.py
@@ -6,8 +6,8 @@ presented as part of her PyCon 2010 talk.
It demonstrates many features of cmd2.
"""
-from cmd2 import Cmd, options
-from optparse import make_option
+import argparse
+from cmd2 import Cmd, with_argument_parser
class Pirate(Cmd):
@@ -73,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 68290a7b..a2892dc8 100755
--- a/examples/python_scripting.py
+++ b/examples/python_scripting.py
@@ -14,11 +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, CmdResult, with_argument_list
-from optparse import make_option
+from cmd2 import Cmd, CmdResult, with_argument_list, with_argparser_and_list
class CmdLineApp(Cmd):
@@ -86,13 +86,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_list(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 +103,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/tests/test_argparse.py b/tests/test_argparse.py
index 0222af30..49e18d86 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
@@ -21,7 +20,7 @@ class ArgparseApp(cmd2.Cmd):
say_parser.add_argument('words', nargs='+', help='words to say')
@cmd2.with_argument_parser(say_parser)
- def do_say(self, arglist, args=None):
+ def do_say(self, args):
"""Repeat what you tell me to."""
words = []
for word in args.words:
@@ -42,43 +41,10 @@ class ArgparseApp(cmd2.Cmd):
tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
@cmd2.with_argument_parser(tag_parser)
- def do_tag(self, arglist, args=None):
+ def do_tag(self, args):
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
self.stdout.write('\n')
- compare_parser = argparse.ArgumentParser()
- compare_parser.add_argument('args', nargs='*')
-
- @cmd2.with_argument_parser(compare_parser)
- def do_compare(self, arglist, args=None):
- cmdline_str = re.sub('\s+', ' ', ' '.join(arglist))
- args_str = re.sub('\s+', ' ', ' '.join(args.args))
- if cmdline_str == args_str:
- self.stdout.write('True')
- else:
- self.stdout.write('False')
-
- argpasre_arglist_parser = argparse.ArgumentParser()
- argpasre_arglist_parser.add_argument('args', nargs='*')
-
- @cmd2.with_argument_parser(argpasre_arglist_parser)
- def do_argparse_arglist(self, arglist, args=None):
- if isinstance(arglist, list):
- self.stdout.write('True')
- else:
- self.stdout.write('False')
-
-
- arglist_and_argparser_parser = argparse.ArgumentParser()
- arglist_and_argparser_parser.add_argument('args', nargs='*')
- @cmd2.with_argument_list
- @cmd2.with_argument_parser(arglist_and_argparser_parser)
- def do_arglistandargparser(self, arglist, args=None):
- if isinstance(arglist, list):
- self.stdout.write(' '.join(arglist))
- else:
- self.stdout.write('False')
-
@cmd2.with_argument_list
def do_arglist(self, arglist):
if isinstance(arglist, list):
@@ -94,6 +60,26 @@ class ArgparseApp(cmd2.Cmd):
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_list(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')
@pytest.fixture
def argparse_app():
@@ -112,6 +98,10 @@ 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_quoted_arguments_multiple(argparse_app):
argparse_app.POSIX = False
argparse_app.STRIP_QUOTES_FOR_NON_POSIX = True
@@ -141,14 +131,6 @@ 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')
- assert out[0] == 'True'
-
-def test_argparse_arglist(argparse_app):
- out = run_cmd(argparse_app, 'argparse_arglist "some arguments" and some more')
- assert out[0] == 'True'
-
def test_arglist(argparse_app):
out = run_cmd(argparse_app, 'arglist "we should" get these')
assert out[0] == 'True'
@@ -156,7 +138,3 @@ def test_arglist(argparse_app):
def test_arglist_decorator_twice(argparse_app):
out = run_cmd(argparse_app, 'arglisttwice "we should" get these')
assert out[0] == 'we should get these'
-
-def test_arglist_and_argparser(argparse_app):
- out = run_cmd(argparse_app, 'arglistandargparser some "quoted words"')
- assert out[0] == 'some quoted words'