python OptionParser extended to allow multiple commands

OptionParser is designed for a fixed set of options. But if the first argument is a command that can affect which options are available, then OptionParser can still be used with some clever coding.

#!/usr/bin/env python
# $Id: options.py 17 2013-11-07 03:43:22Z andrewfn $
"""Demo of using OptionParser where the first argument is a command that can take variable options.
The help system is totally automated and the framework is very easy to use."""
ver = 1.0 #put version number here
use = {}
#put the commands in here, with the integer giving the number of extra arguments
use['command1'] = [0, '          (simple command)']
use['command2'] = [1, 'arg1      (command with a single argument)']
use['command3'] = [2,"""arg1 arg2 (command with two arguments
                      and a very very long description on more than one line.)"""]
use['help']  = [0, "[options]  \nCommands:"]
copyright = "Copyright (c) Andrew Fountain 2014, freely distributed under GPL v3"

#The next section does not need to be changed
for c in sorted(use.iterkeys()):
  if c != 'help':
    use['help'][1] += "\n  " + c + ' ' + use[c][1]
use['help'][1] += "\nFor more info try %prog  --help"

from optparse import OptionParser
import sys

if len(sys.argv) < 2:
  cmdguess = 'help'
else:
  cmdguess = sys.argv[1] #have a guess at command, assuming it is first argument. If not, then little is lost
  if cmdguess not in use: cmdguess = 'help'

parser = OptionParser(description=__doc__, version="%prog version "+str(ver), epilog="v"+str(ver)+' '+copyright)
if cmdguess == 'help': parser.set_usage('usage: %prog ' + use[cmdguess][1])
else: parser.set_usage('usage: %prog ' + cmdguess + ' ' + use[cmdguess][1])
parser.add_option("-v", "--verbose", action="store_true", dest="verbose")
parser.add_option("-q", "--quiet", action="store_true", dest="quiet")
parser.add_option("-t", "--test", action="store_true", dest="test", help="make the right noises but don't actually do anything")

#individual commands go in here:

if cmdguess in ['command1', 'help']:
  parser.add_option("-x", "--xarg", dest="xopt", default='xopt', help="special option for command1, default=%default")
if cmdguess in ['command2', 'help']:
  parser.add_option("-y", "--yarg", dest="yopt", default='yopt', help="special option for command2")
if cmdguess in ['command3', 'help']:
  parser.add_option("-z", "--zarg", dest="zopt", default='zopt', help="special option for command3")
(options,args) = parser.parse_args()
if len(args) < 1:
  parser.error("command missing")

#if options.verbose: print __doc__ #sometimes useful to include this

def command1():
  if options.verbose: print 'doing ' + command + ' with ' + options.xopt

def command2():
  if options.verbose: print 'doing ' + command + ' with ' + args[1] + " and " + options.yopt

def command3():
  if options.verbose: print 'doing ' + command + ' with ' + args[1] + ", " + args[2] + " and " + options.zopt

#finally dispatch the command
def help():
  parser.print_help()

command = args[0]
if (command in use) and (command != 'help'):
  parser.set_usage('%prog ' + command + ' ' + use[command][1])
if args[0] not in use:
  parser.error('"' + args[0] + '" is not a valid command.')
elif ((len(args)-1) != use[command][0]):
  parser.error(command+' takes ' + str(use[command][0]) + ' arguments and ' + str(len(args)-1) + ' supplied')
exec(command + "()")