summaryrefslogtreecommitdiff
path: root/docs/argument_processing.rst
blob: 279dbe47a50df3f1996b9a9178c529dcfb5c24d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
===================
Argument Processing
===================

cmd2 currently includes code which makes it easier to add arguments to the
commands in your cmd2 subclass. This support utilizes the optparse library,
which has been deprecated since Python 2.7 (released on July 3rd 2010) and
Python 3.2 (released on February 20th, 2011). Optparse is still included in the
python standard library, but the documentation recommends using argparse
instead. It's time to modernize cmd2 to utilize argparse.

I don't believe there is a way to change cmd2 to use argparse instead of
optparse without requiring subclasses of cmd2 to make some change. The long
recomended way to use optparse in cmd2 includes the use of the
optparse.make_option, which must be changed in order to use argparse.

There are two potential ways to use argument parsing with cmd2: to parse
options given at the shell prompt when invoked, and to parse options given at
the cmd2 prompt prior to executing a command.

optparse example
================

Here's an example of the current optparse support:

      opts = [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")]

      @options(opts, arg_desc='(text to say)')
      def do_speak(self, arg, opts=None):
        """Repeats what you tell me to."""
        arg = ''.join(arg)
        if opts.piglatin:
            arg = '%s%say' % (arg[1:], arg[0])
        if opts.shout:
            arg = arg.upper()
        repetitions = opts.repeat or 1
        for i in range(min(repetitions, self.maxrepeats)):
            self.poutput(arg)

The current optparse decorator performs the following key functions for you:

1.  Use `shlex` to split the arguments entered by the user.
2.  Parse the arguments using the given optparse options.
3.  Replace the `__doc__` string of the decorated function (i.e. do_speak) with
the help string generated by optparse.
4.  Call the decorated function (i.e. do_speak) passing an additional parameter
which contains the parsed options.

Here are several options for replacing this functionality with argparse.


No cmd2 support
===============

The easiest option would be to just remove the cmd2 specific support for
argument parsing. The above example would then look something like this:

      argparser = argparse.ArgumentParser(
          prog='speak',
          description='Repeats what you tell me to'
      )
      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('word', nargs='?', help='word to say')

      def do_speak(self, argv)
        """Repeats what you tell me to."""
        opts = argparser.parse_args(shlex.split(argv, posix=POSIX_SHLEX))
        arg = opts.word
        if opts.piglatin:
            arg = '%s%say' % (arg[1:], arg[0])
        if opts.shout:
            arg = arg.upper()
        repetitions = opts.repeat or 1
        for i in range(min(repetitions, self.maxrepeats)):
            self.poutput(arg)

Using shlex in this example is technically not necessary because the `do_speak`
command only expects a single word argument. It is included here to show what
would be required to replicate the current optparse based functionality.


A single argparse specific decorator
====================================

In this approach, we would create one new decorator, perhaps called
`with_argument_parser`. This single decorator would take as it's argument a fully
defined `argparse.ArgumentParser`. This decorator would shelx the user input,
apply the ArgumentParser, and pass the resulting object to the decorated method, like so:

      argparser = argparse.ArgumentParser(
          prog='speak',
          description='Repeats what you tell me to'
      )
      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('word', nargs='?', help='word to say')

      @with_argument_parser(argparser)
      def do_speak(self, argv, opts)
        """Repeats what you tell me to."""
        arg = opts.word
        if opts.piglatin:
            arg = '%s%say' % (arg[1:], arg[0])
        if opts.shout:
            arg = arg.upper()
        repetitions = opts.repeat or 1
        for i in range(min(repetitions, self.maxrepeats)):
            self.poutput(arg)

Compared to the no argparse support in cmd2 approach, this replaces a line of
code with a nested function with a decorator without a nested function.


A whole bunch of argparse specific decorators
=============================================

This approach would turn out something like the climax library
(https://github.com/miguelgrinberg/climax), which includes a decorator for each method available
on the `ArgumentParser()` object. Our `do_speak` command would look like this:

      @command()
      @argument('-p', '--piglatin', action='store_true', help='atinLay')
      @argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
      @argument('r', '--repeat', type='int', help='output [n] times')
      @add_argument('word', nargs='?', help='word to say')
      def do_speak(self, argv, piglatin, shout, repeat, word)
        """Repeats what you tell me to."""
        arg = word
        if piglatin:
            arg = '%s%say' % (arg[1:], arg[0])
        if shout:
            arg = arg.upper()
        repetitions = repeat or 1
        for i in range(min(repetitions, self.maxrepeats)):
            self.poutput(arg)