Re: How can I roll my own --help output?

Greg Ward <[email protected]> Fri, 23 Jul 2004 21:13:14 -0400
Newsgroups gmane.comp.python.optik.user
Message-ID <[email protected]>
[nemir nemiria on July 20]
> I wanted to write a littel thing that will report CPU usage.  It needed
> to take parameters, and it needs to behave like similar things that have
> been written before it.

OK, good start.

> What this means is that in response to a -h or --help parameter I need
> add the version message first, to put in a little copyright message,
> print the basic syntax, then print the full help for the options,

Hmmm... this isn't a perfect fit to what Optik provides out of the box.
Should be achievable with a little hackery though.

> , and finally follow
> it up with bugs (if relevent) and a contact blurb.

Err, umm, that's not so straightforward.  Optik only has one slot (the
"description") for you to throw in free-form text.  Might be doable with
a bit more work though.

> I figured out with a google search that I could turn off the bonus help
> function by specifying add_help_option=0 in the function brackets, but I
> don't know where this is documented...  is it add_version_option=0 for
> the version thing?  What other things can go there?

Yeah, that stuff's a bit underdocumented.  Sorry about that.  The good
news is that the source code is all there and, in my not-even-remotely-
humble opinion, quite clear and readable.  Start with the OptionParser
constructor (lib/option_parser.py if you're using Optik, or somewhere in
optparse.py if using Python 2.3 and optparse) and go from there.

First of all, the usage thing should be trivial:

  usage = """\
Usage: check_cpu -w limit -c limit [-t timeout]
       check_cpu (-h|--help)
       check_cpu (-V|--version)
"""
  [...]
  parser = OptionParser(usage)

With that done, Optik will always print your usage message whenever the
user screws up.  And if you detect user error after parse_args()
returns, you can just call

  parser.error("you screwed up")

(for an error message of your choosing) and Optik will do the Right
Thing.

Next, to enable the standard --version option, you need to pass a
'version' value to the OptionParser constructor.  This is clearly
documented near the end of the basic tutorial doc; example:

  parser = OptionParser(usage, version="%prog 1.0")

If you want -V as an alias for --version, you'll need to subclass
OptionParser and override _add_version_option():

  class MyOptionParser (OptionParser):
      [...]
      def _add_version_option(self):
          self.add_option("-V", "--version",
                          action="version",
                          help="show program's version number and exit")
      
As a bonus, this lets you supply a different help text for this option
if you want.

Some random comments on the code you posted:

  parser.add_option("-w", "--warning", action="store", type="int",
                    dest="warn", default=-1)
  parser.add_option("-c", "--critical", action="store", type="int",
                    dest="crit", default=-2)
  [...]
  if -3 > critical or 101 < critical:
          print "Critical value is a percentage and must be between 0 and 100\n" + use
          sys.exit(3)

  if -2 > warning or 101 < warning:
          print "Warning value is a percentage and must be between 0 and 100\n" + use
          sys.exit(3)

Several problems here.  First is that your code doesn't fit in 80
columns.  ;-)  More importantly, you're neglecting the incredibly useful
and all-powerful value None.  Worse, the code is incorrect: someone
could supply "--critical -1" and pass your test, despite the assertion
that they must pass a value >= 0. Finally, you're not taking advantage
of Optik's error-reporting mechanism, OptionParser.error().  Here's how
I'd define those two options:

  parser.add_option("-w", "--warning", type="int")
  parser.add_option("-c", "--critical", type="int")

("store" is the default action, and None is the default default value.
And the default destination is appropriate here too.)

And here's how I'd check the user's values:

  if options.critical is not None and not 0 <= options.critical <= 100:
      parser.error("-c/--critical value must be a percentage between "
                   "0 and 100")
  if options.warning is not None and not 0 <= options.warning <= 100:
      parser.error("-w/--warning value must be a percentage between "
                   "0 and 100")

Much cleaner and simpler, and correct to boot.

Now, as for your real question, about formatting the usage and help
messages.  I don't think callbacks are the right way to do this.
Rather, you'll need to subclass OptionParser and override selected
bits.  Eg. if you want more verbose usage:

  usage = [...same as above...]
  intro = "This tool comes with ABSOLUTELY NO WARRANTY ..."
  preamble = "This tool will check the percent..."
  parser = OptionParser(usage, ...)
  [...]
  class MyOptionParser (OptionParser):
      [...]
      def print_usage(self, file=None):
          OptionParser.print_usage(self, file)
          print >>file, intro
          print >>file, preamble

...or something like that.

If you want to throw in extra text at the end of the full help output,
you can probably get away with just overriding print_help():

  class MyOptionParser (OptionParser):
      [...]
      def print_help(self, file=None):
          OptionParser.print_help(self, file)
          print [...more stuff...]

You probably don't want to go mucking around in the HelpFormatter code,
which is only really necessary if you want to fiddle with how option
help is formatted.  Not really newbie territory.

Oh, finally, you can pass a description keyword arg to OptionParser --
it gets included in the full help output, but not in the usage.  Play
around with it and see what you come up with.  I'm pretty sure you can
do what you want just by subclassing OptionParser -- no need for icky
callbacks.  The benefit of that is that you can reuse your subclass in
your next script, and bang! all the custom behaviour lives on, for
free.

Enjoy --

        Greg
-- 
Greg Ward <[email protected]>                         http://www.gerg.ca/
Don't hate yourself in the morning -- sleep till noon.


-------------------------------------------------------
This SF.Net email is sponsored by BEA Weblogic Workshop
FREE Java Enterprise J2EE developer tools!
Get your free copy of BEA WebLogic Workshop 8.1 today.
http://ads.osdn.com/?ad_id=4721&alloc_id=10040&op=click