summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xoverlord/client.py128
-rw-r--r--overlord/command.py270
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