Optparse and help formatting?

Tim Chase python.list at tim.thechases.com
Sun Sep 30 14:37:10 CEST 2007


>> I've been learning the ropes of the optparse module and have been
>> having some trouble getting the help to format the way I want.
> 
> A quick perusal of the 'optparse.py' code shows me this:
> 
> ========
> [...]
> class OptionParser([...]):
>     def __init__([...],
>                  formatter=None,
>                  [...]):
>         [...]
>         if formatter is None:
>             formatter = IndentedHelpFormatter()
>         [...]
> ========
> 
> So, the OptionParser init method accepts the help formatter as the
> 'formatter' argument, defaulting to a new instance of
> IndentedHelpFormatter.
> 
> Presumably, it's a matter of subclassing 'optparse.HelpFormatter' and
> overriding the behaviour you want to change, then passing an instance
> of your new class as the 'formatter' argument to the 'OptionParser()'
> invocation.

Ben, thanks for pointing me in the right direction.  Digging in
this code, it looks like textwrap.[fill|wrap] method calls were
what was eating my NL and munging my tabs.

While not a terribly elegant solution, as there's no easy way to
intercept the two bits I want without copying and pasting a large
bit of the format_description and format_option methods just to
change the behavior of one call to textwrap.*, I did manage to
get it working as desired.

In the event that anybody else needs the solution I hacked
together, I've pasted it below which, as Ben suggests, can be
used with

  parser = OptionParser(...
    formatter=IndentedHelpFormatterWithNL()
    )

Hope this helps somebody else too.

-tim



class IndentedHelpFormatterWithNL(IndentedHelpFormatter):
  def format_description(self, description):
    if not description: return ""
    desc_width = self.width - self.current_indent
    indent = " "*self.current_indent
# the above is still the same
    bits = description.split('\n')
    formatted_bits = [
      textwrap.fill(bit,
        desc_width,
        initial_indent=indent,
        subsequent_indent=indent)
      for bit in bits]
    result = "\n".join(formatted_bits) + "\n"
    return result

  def format_option(self, option):
    # The help for each option consists of two parts:
    #   * the opt strings and metavars
    #   eg. ("-x", or "-fFILENAME, --file=FILENAME")
    #   * the user-supplied help string
    #   eg. ("turn on expert mode", "read data from FILENAME")
    #
    # If possible, we write both of these on the same line:
    #   -x    turn on expert mode
    #
    # But if the opt string list is too long, we put the help
    # string on a second line, indented to the same column it would
    # start in if it fit on the first line.
    #   -fFILENAME, --file=FILENAME
    #       read data from FILENAME
    result = []
    opts = self.option_strings[option]
    opt_width = self.help_position - self.current_indent - 2
    if len(opts) > opt_width:
      opts = "%*s%s\n" % (self.current_indent, "", opts)
      indent_first = self.help_position
    else: # start help on same line as opts
      opts = "%*s%-*s  " % (self.current_indent, "", opt_width, opts)
      indent_first = 0
    result.append(opts)
    if option.help:
      help_text = self.expand_default(option)
# Everything is the same up through here
      help_lines = []
      for para in help_text.split("\n"):
        help_lines.extend(textwrap.wrap(para, self.help_width))
# Everything is the same after here
      result.append("%*s%s\n" % (
        indent_first, "", help_lines[0]))
      result.extend(["%*s%s\n" % (self.help_position, "", line)
        for line in help_lines[1:]])
    elif opts[-1] != "\n":
      result.append("\n")
    return "".join(result)






More information about the Python-list mailing list