diff options
-rwxr-xr-x | cmd2.py | 66 | ||||
-rw-r--r-- | docs/argument_processing.rst | 43 | ||||
-rwxr-xr-x | examples/arg_print.py | 39 | ||||
-rwxr-xr-x | examples/argparse_example.py | 4 | ||||
-rwxr-xr-x | examples/example.py | 4 | ||||
-rwxr-xr-x | examples/pirate.py | 23 | ||||
-rwxr-xr-x | examples/python_scripting.py | 18 | ||||
-rw-r--r-- | tests/test_argparse.py | 74 |
8 files changed, 161 insertions, 110 deletions
@@ -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' |