14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"Framework for command line interfaces like CVS.  See class CmdFrameWork."
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass CommandFrameWork:
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Framework class for command line interfaces like CVS.
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    The general command line structure is
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            command [flags] subcommand [subflags] [argument] ...
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    There's a class variable GlobalFlags which specifies the
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    global flags options.  Subcommands are defined by defining
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    methods named do_<subcommand>.  Flags for the subcommand are
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    defined by defining class or instance variables named
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    flags_<subcommand>.  If there's no command, method default()
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    is called.  The __doc__ strings for the do_ methods are used
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for the usage message, printed after the general usage message
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    which is the class variable UsageMessage.  The class variable
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    PostUsageMessage is printed after all the do_ methods' __doc__
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    strings.  The method's return value can be a suggested exit
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    status.  [XXX Need to rewrite this to clarify it.]
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Common usage is to derive a class, instantiate it, and then call its
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    run() method; by default this takes its arguments from sys.argv[1:].
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    UsageMessage = \
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm      "usage: (name)s [flags] subcommand [subflags] [argument] ..."
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    PostUsageMessage = None
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    GlobalFlags = ''
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self):
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Constructor, present for completeness."""
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        pass
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def run(self, args = None):
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Process flags, subcommand and options, then run it."""
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        import getopt, sys
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if args is None: args = sys.argv[1:]
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            opts, args = getopt.getopt(args, self.GlobalFlags)
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except getopt.error, msg:
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self.usage(msg)
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.options(opts)
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not args:
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.ready()
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self.default()
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            cmd = args[0]
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            mname = 'do_' + cmd
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            fname = 'flags_' + cmd
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                method = getattr(self, mname)
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except AttributeError:
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return self.usage("command %r unknown" % (cmd,))
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                flags = getattr(self, fname)
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except AttributeError:
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                flags = ''
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                opts, args = getopt.getopt(args[1:], flags)
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except getopt.error, msg:
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return self.usage(
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        "subcommand %s: " % cmd + str(msg))
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.ready()
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return method(opts, args)
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def options(self, opts):
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Process the options retrieved by getopt.
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Override this if you have any options."""
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if opts:
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "-"*40
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "Options:"
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            for o, a in opts:
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print 'option', o, 'value', repr(a)
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "-"*40
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def ready(self):
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Called just before calling the subcommand."""
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        pass
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def usage(self, msg = None):
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Print usage message.  Return suitable exit code (2)."""
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if msg: print msg
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print self.UsageMessage % {'name': self.__class__.__name__}
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        docstrings = {}
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        c = self.__class__
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while 1:
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            for name in dir(c):
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                if name[:3] == 'do_':
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    if docstrings.has_key(name):
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        continue
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    try:
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        doc = getattr(c, name).__doc__
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    except:
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        doc = None
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    if doc:
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        docstrings[name] = doc
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not c.__bases__:
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            c = c.__bases__[0]
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if docstrings:
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "where subcommand can be:"
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            names = docstrings.keys()
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            names.sort()
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            for name in names:
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print docstrings[name]
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.PostUsageMessage:
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print self.PostUsageMessage
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return 2
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def default(self):
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Default method, called when no subcommand is given.
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        You should always override this."""
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print "Nobody expects the Spanish Inquisition!"
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef test():
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Test script -- called when this module is run as a script."""
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import sys
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    class Hello(CommandFrameWork):
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def do_hello(self, opts, args):
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            "hello -- print 'hello world', needs no arguments"
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "Hello, world"
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x = Hello()
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    tests = [
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            [],
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ['hello'],
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ['spam'],
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ['-x'],
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ['hello', '-x'],
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            None,
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ]
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for t in tests:
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print '-'*10, t, '-'*10
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sts = x.run(t)
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print "Exit status:", repr(sts)
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmif __name__ == '__main__':
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    test()
145