summaryrefslogtreecommitdiff
path: root/README.rst
blob: 2fe156396d3d5bf751a95ec31dd82323a5db949f (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
argparse extensions
===================

This package provides extensions to argparse on two levels:

- basic argparse extensions: default subparser, subparser aliases in
  2.X
- additional actions that can be specified for add_argument
- smart formatter that allows combination of defaults help formatting
  **and** raw desciptions
- wrapper for argparse using decorators

Extensions to basic argparse
----------------------------

Insert the following to be able to specify `aliases
<https://docs.python.org/3/library/argparse.html#sub-commands>`_ in
subparser definitions in 2.6 and 2.7::

  from __future__ import print_function

  import sys
  from ruamel.std.argparse import ArgumentParser, SubParsersAction

  parser = ArgumentParser()
  if sys.version_info < (3,):  # add aliases support
      parser.register('action', 'parsers', SubParsersAction)
  subparsers = parser.add_subparsers()
  checkout = subparsers.add_parser('checkout', aliases=['co'])
  checkout.add_argument('foo')
  args = parser.parse_args(['co', 'bar'])
  print(args)

.. example code aliases.py

Resulting in::

  Namespace(foo='bar')


.. example output aliases.py

Additional actions
------------------

CountAction
+++++++++++

Count up and down::

  from __future__ import print_function

  from ruamel.std.argparse import CountAction
  import argparse

  parser = argparse.ArgumentParser()
  parser.add_argument('--verbose', '-v', action=CountAction, const=1, nargs=0)
  parser.add_argument('--quiet', '-q', action=CountAction, dest='verbose',
                      const=-1, nargs=0)

  print(parser.parse_args("--verbose -v -q".split()))

.. example code countaction.py

results in::

  Namespace(verbose=1)


.. example output countaction.py


SplitAppend
+++++++++++

Append after splitting on "``,``". Running::

  from __future__ import print_function

  from ruamel.std.argparse import SplitAppendAction
  import argparse

  parser = argparse.ArgumentParser()
  parser.add_argument('-d', action=SplitAppendAction)

  print(parser.parse_args("-d ab -d cd -d kl -d mn".split()))
  print(parser.parse_args("-d ab,cd,kl,mn".split()))
  print(parser.parse_args("-d ab,cd -d kl,mn".split()))

.. example code splitaction.py

results in::

  Namespace(d=['ab', 'cd', 'kl', 'mn'])
  Namespace(d=['ab', 'cd', 'kl', 'mn'])
  Namespace(d=['ab', 'cd', 'kl', 'mn'])


.. example output splitaction.py

CheckSingleStoreAction
++++++++++++++++++++++

Complain if the same option is called  multiple times::

  from __future__ import print_function

  from ruamel.std.argparse import CheckSingleStoreAction
  import argparse

  parser = argparse.ArgumentParser()
  parser.add_argument('--check', '-c', action=CheckSingleStoreAction, const=1,
                      nargs=0)

  print(parser.parse_args("--check -c".split()))

.. example code checksingleaction.py

results in::

  WARNING: previous optional argument "-c []" overwritten by "-c []"
  Namespace(check=[])


.. example output checksingleaction.py

Smart formatting
----------------

You can only specify one formatter in standard argparse, so you cannot
both have pre-formatted description. using
RawDescriptionHelpFormatter,as well as default arguments with
ArgumentDefaultsHelpFormatter.

The ``SmartFormatter`` is a subclass of ``argparse.HelpFormatter`` and
has the normal formatter as default. Help text can be marked at the
beginning for variations in formatting:

- ``"R|.."`` format raw, i.e. don't wrap and fill out, observer newline
- ``"*|.."`` format a password help, never echo password defaults
- ``"D|.."`` add defaults to **all** entries (that is why having ``*|``
  is important)

The version string is formatted using _split_lines and preserves any
line breaks in the version string.

::

  from __future__ import print_function

  from ruamel.std.argparse import SmartFormatter
  import argparse


  def exit(self, *args, **kw):
      pass

  argparse.ArgumentParser.exit = exit

  # the 'D|....' in the second pass triggers generating defaults for all entries,
  # while being smart about which one already have a %(default)s

  for index, log_s in enumerate(['log to file', 'D|log to file']):
      parser = argparse.ArgumentParser(formatter_class=SmartFormatter)

      parser.add_argument('--log', default='abc.log', help=log_s)
      parser.add_argument('--username',
                          help='username to login with (default: %(default)s)')
      parser.add_argument('--password', help='*|password to use for login')
      parser.add_argument('--recursive', '-r', action='store_true',
                          help="R|recurse into subdirectories \nto find files")
      parser.set_defaults(username='anthon', password="test123")

      if index > 0:
          print('--------------------------------------\n')
      parser.parse_args(["--help"])

.. example code smartformatter.py

results in::

  usage: smartformatter.py [-h] [--log LOG] [--username USERNAME]
                           [--password PASSWORD] [--recursive]

  optional arguments:
    -h, --help           show this help message and exit
    --log LOG            log to file
    --username USERNAME  username to login with (default: anthon)
    --password PASSWORD  password to use for login
    --recursive, -r      recurse into subdirectories
                         to find files
  --------------------------------------

  usage: smartformatter.py [-h] [--log LOG] [--username USERNAME]
                           [--password PASSWORD] [--recursive]

  optional arguments:
    -h, --help           show this help message and exit
    --log LOG            log to file (default: abc.log)
    --username USERNAME  username to login with (default: anthon)
    --password PASSWORD  password to use for login (default: *******)
    --recursive, -r      recurse into subdirectories
                         to find files (default: False)


.. example output smartformatter.py


Wrapping argparse
-----------------

When using argparse with subparser, each of which have their own
function ( using ``.set_defaults(func=function``) that can be called,
there is a lot of repetitive code.

An alternative is provided by the ``ProgramBase`` class that should be
subclassed and the ``sub_parser``, ``option`` and ``version``
decorators that can be applied to methods of that subclass.

A typical use case is::

  from __future__ import print_function

  import sys
  import os

  from ruamel.std.argparse import ProgramBase, option, sub_parser, version, \
      SmartFormatter


  class TestCmd(ProgramBase):
      def __init__(self):
          super(TestCmd, self).__init__(
              formatter_class=SmartFormatter
          )

      # you can put these on __init__, but subclassing TestCmd
      # will cause that to break
      @option('--quiet', '-q', help='suppress verbosity', action='store_true',
              global_option=True)
      @version('version: 1.2.3')
      def _pb_init(self):
          # special name for which attribs are included in help
          pass

      def run(self):
          if self._args.func:
              return self._args.func()

      def parse_args(self, *args):
          self._parse_args(*args)

      @sub_parser(help='specific help for readit')
      @option('--name', default='abc')
      def readit(self):
          print('calling readit')

      @sub_parser('writeit', help='help for writeit')
      @option('--target')
      def other_name(self):
          print('calling writeit')


  n = TestCmd()
  n.parse_args(['--help'])
  n.run()

.. example code testcmd.py

and output::

  usage: testcmd.py [-h] [--quiet] [--version] {readit,writeit} ...

  positional arguments:
    {readit,writeit}
      readit          specific help for readit
      writeit         help for writeit

  optional arguments:
    -h, --help        show this help message and exit
    --quiet, -q       suppress verbosity
    --version         show program's version number and exit


.. example output testcmd.py



The method name is by default the name of the sub_parser. This can be
overriden by providing a non-keyword argument to ``sub_parser``. The
keyword arguments are passed to the  ``add_parser`` method.

The ``option`` functions as ``add_argument``. If ``option`` is put on
a method that is not a sub_parser, such an option will be a global
option. These have to be specified before any sub_parser argument when
invoking the script. Often it is handy to specify such an option with
an ``global_option=True`` keyword argument. This makes sure that
option is added to all the sub_parsers as well. This allows you to
invoke both ``prog --quiet writeit`` and ``prog writeit --quiet``).
You can assing these options to ``__init__``, but when sub classing
``TestCmd`` this will lead to problems. It is therefore better to pu
them on the special handled method ``_pb_init`` if subclassing might
happen.

Care should be taken that all attributes on ``TestCmd`` are accessed
during scanning for sub parsers. In particular any property method
will be accessedi and its code executed.

Default command
---------------

In case you want to have specific sub_parser be invoked as the default, you
can use::

  self._parse_args(default_sub_parser='show')

to have the following invocations on the commandline of a program called
``pass`` be the same::

    pass
    pass show

Help on all subcommands
-----------------------

If you provide a True value to the optional help_all parameter for
``self._parse_args()``::

  self._parse_args(help_all=True)

then the commandline is checked for the option ``--help-all`` and the global
help is printed, follow by the help for each sub parsers, separated by a dashed
line.

Testing
-------

Testing is done using the `tox <https://pypi.python.org/pypi/tox>`_, which
uses `virtualenv <https://pypi.python.org/pypi/virtualenv>`_ and
`pytest <http://pytest.org/latest/>`_.