diff options
author | Adrian Likins <alikins@redhat.com> | 2007-10-02 15:36:05 -0400 |
---|---|---|
committer | Adrian Likins <alikins@redhat.com> | 2007-10-02 15:36:05 -0400 |
commit | 6e3807081996f5bd82455612f91da66b5dd5f1fa (patch) | |
tree | dba8c0153e0d55a578d4f12bf947fc58afb5c3a7 | |
parent | c010a077418aeb511cca251f7b2fe0375d00990d (diff) | |
download | func-6e3807081996f5bd82455612f91da66b5dd5f1fa.tar.gz func-6e3807081996f5bd82455612f91da66b5dd5f1fa.tar.xz func-6e3807081996f5bd82455612f91da66b5dd5f1fa.zip |
add command.py from MOAP http://thomas.apestaart.org/moap/trac
convert client.py to use it
Note this changes the current commandline to need a "call" subcommand
to call an rpc method directly.
-rwxr-xr-x | overlord/client.py | 128 | ||||
-rw-r--r-- | overlord/command.py | 270 |
2 files changed, 322 insertions, 76 deletions
diff --git a/overlord/client.py b/overlord/client.py index d6ccdc8..a82990c 100755 --- a/overlord/client.py +++ b/overlord/client.py @@ -24,6 +24,8 @@ from func.commonconfig import CMConfig from func.config import read_config import sslclient +import command + # =================================== # defaults # TO DO: some of this may want to come from config later @@ -60,6 +62,7 @@ class CommandAutomagic(): # =================================== + class Client(): def __init__(self, server_spec, port=DEFAULT_PORT, interactive=False, @@ -217,102 +220,75 @@ class Client(): # =================================================================== -class FuncCommandLine(): - - def __init__(self,myname,args): - """ - Constructor. Takes name of program + arguments. - """ - self.myname = myname - self.args = args - self.verbose = 0 - self.server_spec = None - self.port = DEFAULT_PORT - - # ----------------------------------------------- - - def usage(self): - """ - Returns usage string for command line users. - """ - return FUNC_USAGE % self.myname - - # ----------------------------------------------- - - def run(self): - """ - Engages the command line. - """ - - rc = self.parse_command_line() - if rc != 0: - return rc +class Call(command.Command): + name = "call" + useage = "call nodule method name arg1 arg2..." - return self.run_command() - - # ----------------------------------------------- - - def parse_command_line(self): - """ - Parses the command line and loads up all the variables. - """ + def addOptions(self): + self.parser.add_option("-v","--verbose",dest="verbose",action="store_true") + self.parser.add_option("-p","--port",dest="port",default=DEFAULT_PORT) - # parse options - p = optparse.OptionParser() - p.add_option("-v","--verbose",dest="verbose",action="store_true") - p.add_option("-p","--port",dest="port",default=DEFAULT_PORT) - (options, args) = p.parse_args(self.args) + def handleOptions(self, options): + self.options = options - self.args = args self.verbose = options.verbose - self.port = options.port - # self.help = options.help + self.port = options.port + # I'm not really a fan of the "module methodname" approach + # but we'll keep it for now -akl - # provided for free: - # - #if self.help: - # print self.usage() - # return -411 + def do(self, args): - # process arguments - # a good Klingon program does not have parameters - # it has arguments, and it always wins them. + # I'm not really a fan of the "module methodname" approach + # but we'll keep it for now -akl - if len(args) < 3: - print self.usage() - return -411 + self.server_spec = args[0] + self.module = args[1] + self.method = args[2] + self.method_args = args[3:] - self.server_spec = self.args[0] - self.module = self.args[1] - self.method = self.args[2] - self.method_args = self.args[3:] - - return 0 - - # ----------------------------------------------- - - def run_command(self): - """ - Runs the actual command. - """ client = Client(self.server_spec,port=self.port,interactive=True, - verbose=self.verbose) + verbose=self.verbose) results = client.run(self.module, self.method, self.method_args) # TO DO: add multiplexer support # probably as a higher level module. - + return client.cli_return(results) - -# =================================================================== + +class FuncCommandLine(command.Command): + name = "client" + useage = "func is the commandline interface to a func minion" + + subCommandClasses = [Call] + + def __init__(self): + + command.Command.__init__(self) + + def do(self, args): + pass + + def addOptions(self): + self.parser.add_option('', '--version', action="store_true", + help="show version information") + + def handleOptions(self, options): + if options.version: + #FIXME + print "version is NOT IMPLEMENTED YET" + if __name__ == "__main__": # this is what /usr/bin/func will run myname, argv = sys.argv[0], sys.argv[1:] - cli = FuncCommandLine(myname,argv) - rc = cli.run() + cli = FuncCommandLine() + rc = cli.do() + sys.exit(rc) +# cli = FuncCommandLine(myname,argv) + +# rc = cli.run() sys.exit(rc) diff --git a/overlord/command.py b/overlord/command.py new file mode 100644 index 0000000..d2af494 --- /dev/null +++ b/overlord/command.py @@ -0,0 +1,270 @@ +# -*- Mode: Python; test-case-name: test_command -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# This file is released under the standard PSF license. +# +# from MOAP - https://thomas.apestaart.org/moap/trac +# written by Thomas Vander Stichele (thomas at apestaart dot org) +# + +""" +Command class. +""" + +import optparse +import sys + +class CommandHelpFormatter(optparse.IndentedHelpFormatter): + """ + I format the description as usual, but add an overview of commands + after it if there are any, formatted like the options. + """ + _commands = None + + def addCommand(self, name, description): + if self._commands is None: + self._commands = {} + self._commands[name] = description + + ### override parent method + def format_description(self, description): + # textwrap doesn't allow for a way to preserve double newlines + # to separate paragraphs, so we do it here. + blocks = description.split('\n\n') + rets = [] + + for block in blocks: + rets.append(optparse.IndentedHelpFormatter.format_description(self, + block)) + ret = "\n".join(rets) + if self._commands: + commandDesc = [] + commandDesc.append("commands:") + keys = self._commands.keys() + keys.sort() + length = 0 + for key in keys: + if len(key) > length: + length = len(key) + for name in keys: + format = " %-" + "%d" % length + "s %s" + commandDesc.append(format % (name, self._commands[name])) + ret += "\n" + "\n".join(commandDesc) + "\n" + return ret + +class CommandOptionParser(optparse.OptionParser): + """ + I parse options as usual, but I explicitly allow setting stdout + so that our print_help() method (invoked by default with -h/--help) + defaults to writing there. + """ + _stdout = sys.stdout + + def set_stdout(self, stdout): + self._stdout = stdout + + # we're overriding the built-in file, but we need to since this is + # the signature from the base class + __pychecker__ = 'no-shadowbuiltin' + def print_help(self, file=None): + # we are overriding a parent method so we can't do anything about file + __pychecker__ = 'no-shadowbuiltin' + if file is None: + file = self._stdout + file.write(self.format_help()) + +class Command: + """ + I am a class that handles a command for a program. + Commands can be nested underneath a command for further processing. + + @cvar name: name of the command, lowercase + @cvar aliases: list of alternative lowercase names recognized + @type aliases: list of str + @cvar usage: short one-line usage string; + %command gets expanded to a sub-command or [commands] + as appropriate + @cvar summary: short one-line summary of the command + @cvar description: longer paragraph explaining the command + @cvar subCommands: dict of name -> commands below this command + @type subCommands: dict of str -> L{Command} + """ + name = None + aliases = None + usage = None + summary = None + description = None + parentCommand = None + subCommands = None + subCommandClasses = None + aliasedSubCommands = None + + def __init__(self, parentCommand=None, stdout=sys.stdout, + stderr=sys.stderr): + """ + Create a new command instance, with the given parent. + Allows for redirecting stdout and stderr if needed. + This redirection will be passed on to child commands. + """ + if not self.name: + self.name = str(self.__class__).split('.')[-1].lower() + self.stdout = stdout + self.stderr = stderr + self.parentCommand = parentCommand + + # create subcommands if we have them + self.subCommands = {} + self.aliasedSubCommands = {} + if self.subCommandClasses: + for C in self.subCommandClasses: + c = C(self, stdout=stdout, stderr=stderr) + self.subCommands[c.name] = c + if c.aliases: + for alias in c.aliases: + self.aliasedSubCommands[alias] = c + + # create our formatter and add subcommands if we have them + formatter = CommandHelpFormatter() + if self.subCommands: + for name, command in self.subCommands.items(): + formatter.addCommand(name, command.summary or + command.description) + + # expand %command for the bottom usage + usage = self.usage or self.name + if usage.find("%command") > -1: + usage = usage.split("%command")[0] + '[command]' + usages = [usage, ] + + # FIXME: abstract this into getUsage that takes an optional + # parentCommand on where to stop recursing up + # useful for implementing subshells + + # walk the tree up for our usage + c = self.parentCommand + while c: + usage = c.usage or c.name + if usage.find(" %command") > -1: + usage = usage.split(" %command")[0] + usages.append(usage) + c = c.parentCommand + usages.reverse() + usage = " ".join(usages) + + # create our parser + description = self.description or self.summary + self.parser = CommandOptionParser( + usage=usage, description=description, + formatter=formatter) + self.parser.set_stdout(self.stdout) + self.parser.disable_interspersed_args() + + # allow subclasses to add options + self.addOptions() + + def addOptions(self): + """ + Override me to add options to the parser. + """ + pass + + def do(self, args): + """ + Override me to implement the functionality of the command. + """ + pass + + def parse(self, argv): + """ + Parse the given arguments and act on them. + + @rtype: int + @returns: an exit code + """ + self.options, args = self.parser.parse_args(argv) + + # FIXME: make handleOptions not take options, since we store it + # in self.options now + ret = self.handleOptions(self.options) + if ret: + return ret + + # handle pleas for help + if args and args[0] == 'help': + self.debug('Asked for help, args %r' % args) + + # give help on current command if only 'help' is passed + if len(args) == 1: + self.outputHelp() + return 0 + + # complain if we were asked for help on a subcommand, but we don't + # have any + if not self.subCommands: + self.stderr.write('No subcommands defined.') + self.parser.print_usage(file=self.stderr) + self.stderr.write( + "Use --help to get more information about this command.\n") + return 1 + + # rewrite the args the other way around; + # help doap becomes doap help so it gets deferred to the doap + # command + args = [args[1], args[0]] + + # if we don't have subcommands, defer to our do() method + if not self.subCommands: + ret = self.do(args) + + # if everything's fine, we return 0 + if not ret: + ret = 0 + + return ret + + # if we do have subcommands, defer to them + try: + command = args[0] + except IndexError: + self.parser.print_usage(file=self.stderr) + self.stderr.write( + "Use --help to get a list of commands.\n") + return 1 + + if command in self.subCommands.keys(): + return self.subCommands[command].parse(args[1:]) + + if self.aliasedSubCommands: + if command in self.aliasedSubCommands.keys(): + return self.aliasedSubCommands[command].parse(args[1:]) + + self.stderr.write("Unknown command '%s'.\n" % command) + return 1 + + def outputHelp(self): + """ + Output help information. + """ + self.parser.print_help(file=self.stderr) + + def outputUsage(self): + """ + Output usage information. + Used when the options or arguments were missing or wrong. + """ + self.parser.print_usage(file=self.stderr) + + def handleOptions(self, options): + """ + Handle the parsed options. + """ + pass + + def getRootCommand(self): + """ + Return the top-level command, which is typically the program. + """ + c = self + while c.parentCommand: + c = c.parentCommand + return c |