"Framework for command line interfaces like CVS.  See class CmdFrameWork."


class CommandFrameWork:

	"""Framework class for command line interfaces like CVS.

	The general command line structure is

		command [flags] subcommand [subflags] [argument] ...

	There's a class variable GlobalFlags which specifies the
	global flags options.  Subcommands are defined by defining
	methods named do_<subcommand>.  Flags for the subcommand are
	defined by defining class or instance variables named
	flags_<subcommand>.  If there's no command, method default()
	is called.  The __doc__ strings for the do_ methods are used
	for the usage message, printed after the general usage message
	which is the class variable UsageMessage.  The class variable
	PostUsageMessage is printed after all the do_ methods' __doc__
	strings.  The method's return value can be a suggested exit
	status.  [XXX Need to rewrite this to clarify it.]

	Common usage is to derive a class, instantiate it, and then call its
	run() method; by default this takes its arguments from sys.argv[1:].
	"""

	UsageMessage = \
	  "usage: (name)s [flags] subcommand [subflags] [argument] ..."

	PostUsageMessage = None

	GlobalFlags = ''

	def __init__(self):
		"""Constructor, present for completeness."""
		pass

	def run(self, args = None):
		"""Process flags, subcommand and options, then run it."""
		import getopt, sys
		if args is None: args = sys.argv[1:]
		try:
			opts, args = getopt.getopt(args, self.GlobalFlags)
		except getopt.error, msg:
			return self.usage(msg)
		self.options(opts)
		if not args:
			self.ready()
			return self.default()
		else:
			cmd = args[0]
			mname = 'do_' + cmd
			fname = 'flags_' + cmd
			try:
				method = getattr(self, mname)
			except AttributeError:
				return self.usage("command %s unknown" % `cmd`)
			try:
				flags = getattr(self, fname)
			except AttributeError:
				flags = ''
			try:
				opts, args = getopt.getopt(args[1:], flags)
			except getopt.error, msg:
				return self.usage(
					"subcommand %s: " % cmd + str(msg))
			self.ready()
			return method(opts, args)

	def options(self, opts):
		"""Process the options retrieved by getopt.
		Override this if you have any options."""
		if opts:
			print "-"*40
			print "Options:"
			for o, a in opts:
				print 'option', o, 'value', `a`
			print "-"*40

	def ready(self):
		"""Called just before calling the subcommand."""
		pass

	def usage(self, msg = None):
		"""Print usage message.  Return suitable exit code (2)."""
		if msg: print msg
		print self.UsageMessage % {'name': self.__class__.__name__}
		docstrings = {}
		c = self.__class__
		while 1:
			for name in dir(c):
				if name[:3] == 'do_':
					if docstrings.has_key(name):
						continue
					try:
						doc = getattr(c, name).__doc__
					except:
						doc = None
					if doc:
						docstrings[name] = doc
			if not c.__bases__:
				break
			c = c.__bases__[0]
		if docstrings:
			print "where subcommand can be:"
			names = docstrings.keys()
			names.sort()
			for name in names:
				print docstrings[name]
		if self.PostUsageMessage:
			print self.PostUsageMessage
		return 2

	def default(self):
		"""Default method, called when no subcommand is given.
		You should always override this."""
		print "Nobody expects the Spanish Inquisition!"


def test():
	"""Test script -- called when this module is run as a script."""
	import sys
	class Hello(CommandFrameWork):
		def do_hello(self, opts, args):
			"hello -- print 'hello world', needs no arguments"
			print "Hello, world"
	x = Hello()
	tests = [
		[],
		['hello'],
		['spam'],
		['-x'],
		['hello', '-x'],
		None,
		]
	for t in tests:
		print '-'*10, t, '-'*10
		sts = x.run(t)
		print "Exit status:", `sts`


if __name__ == '__main__':
	test()
