How to comply to PEP 257 docstrings when using Python's optparse module?
Asked Answered
M

3

5

According to PEP 257 the docstring of command line script should be its usage message.

The docstring of a script (a stand-alone program) should be usable as its "usage" message, printed when the script is invoked with incorrect or missing arguments (or perhaps with a "-h" option, for "help"). Such a docstring should document the script's function and command line syntax, environment variables, and files. Usage messages can be fairly elaborate (several screens full) and should be sufficient for a new user to use the command properly, as well as a complete quick reference to all options and arguments for the sophisticated user.

So my docstring would look something like this:

<tool name> <copyright info>

Usage: <prog name> [options] [args]

some text explaining the usage...

Options:
  -h, --help  show this help message and exit
   ...

Now I want to use the optparse module. optparse generates the "Options" sections and a "usage" explaining the command line syntax:

from optparse import OptionParser

if __name__ == "__main__":
    parser = OptionParser()
    (options, args) = parser.parse_args() 

So calling the script with the "-h" flag prints:

Usage: script.py [options]

Options:
    -h, --help  show this help message and exit

This can be modified as follows:

parser = OptionParser(usage="Usage: %prog [options] [args]",
                      description="some text explaining the usage...")

which results in

Usage: script.py [options] [args]

some text explaining the usage...

Options:
  -h, --help  show this help message and exit

But how can I use the docstring here? Passing the docstring as the usage message has two problems.

  1. optparse appends "Usage: " to the docstring if it does not start with "Usage: "
  2. The placeholder '%prog' must be used in the docstring

Result

According to the answers it seems that there is no way to reuse the docstring intended by the optparse module. So the remaining option is to parse the docstring manually and construct the OptionParser. (So I'll accept S.Loot's answer)

The "Usage: " part is introduced by the IndentedHelpFormatter which can be replaced with the formatter parameter in OptionParser.__init__().

Mondragon answered 11/8, 2009 at 12:14 Comment(0)
O
4

Choice 1: Copy and paste. Not DRY, but workable.

Choice 2: Parse your own docstring to strip out the description paragraph. It's always paragraph two, so you can split on '\n\n'.

usage, description= __doc__.split('\n\n')[:2]

Since optparse generates usage, you may not want to supply the usage sentence to it. Your version of the usage my be wrong. If you insist on providing a usage string to optparse, I'll leave it as an exercise for the reader to work out how to remove "Usage: " from the front of the usage string produced above.

Oxidation answered 11/8, 2009 at 12:21 Comment(3)
I like the second solution. Not very clean but smart and pragmatic.Chantell
Since blank lines between paragraphs is the RST standard, this saves running a full Docutils parse on doc and gets what is -- by definition -- the expected result.Oxidation
That might be an option for the description. But I still can't reuse the 'usage' part and optparse forces the message to begin with "Usage: ".Mondragon
H
6

I wrote a module docopt to do exactly what you want – write usage-message in docstring and stay DRY. It also allows to avoid writing tedious OptionParser code at all, since docopt is generating parser based on the usage-message.

Check it out: http://github.com/docopt/docopt

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship [<name>] move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored|--drifting]
  naval_fate.py -h | --help
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)
Herring answered 8/4, 2012 at 19:2 Comment(0)
O
4

Choice 1: Copy and paste. Not DRY, but workable.

Choice 2: Parse your own docstring to strip out the description paragraph. It's always paragraph two, so you can split on '\n\n'.

usage, description= __doc__.split('\n\n')[:2]

Since optparse generates usage, you may not want to supply the usage sentence to it. Your version of the usage my be wrong. If you insist on providing a usage string to optparse, I'll leave it as an exercise for the reader to work out how to remove "Usage: " from the front of the usage string produced above.

Oxidation answered 11/8, 2009 at 12:21 Comment(3)
I like the second solution. Not very clean but smart and pragmatic.Chantell
Since blank lines between paragraphs is the RST standard, this saves running a full Docutils parse on doc and gets what is -- by definition -- the expected result.Oxidation
That might be an option for the description. But I still can't reuse the 'usage' part and optparse forces the message to begin with "Usage: ".Mondragon
H
1

I think we have to be reasonable about this PEP's advice -- I would think it's fine to leave the module with __doc__ being the short description that summarizes long usage. But if you're perfectionist:

'''<tool name>

The full description and usage can be generated by optparse module.

Description: ...

'''

...

# Generate usage and options using optparse.
usage, options = ... 

# Modify the docstring on the fly.
docstring = __doc__.split('\n\n')
docstring[1:2] = [__license__, usage, options]
__doc__ = '\n\n'.join(docstring)
Henslowe answered 11/8, 2009 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.