summaryrefslogtreecommitdiffstats
path: root/func/overlord/command.py
diff options
context:
space:
mode:
authorDevan Goodwin <dgoodwin@dangerouslyinc.com>2007-10-02 21:42:47 -0300
committerJames Bowes <jbowes@redhat.com>2007-10-02 21:33:49 -0400
commit1ce955ec36f775d8fde2cb9d7943178e8b9d60da (patch)
tree4c69d218fde87091d4e5d1f3138a435b9164dbf4 /func/overlord/command.py
parent3c13a4f30f247f4aa75c02c65e6bb6e575e30d01 (diff)
downloadthird_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.tar.gz
third_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.tar.xz
third_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.zip
Moved code under the func namespace.
Previously we had overlord, minion, modules, and func all at the root of the source tree. After install these would all be shuffled below func. Relocated them in the source tree to reflect this.
Diffstat (limited to 'func/overlord/command.py')
-rw-r--r--func/overlord/command.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/func/overlord/command.py b/func/overlord/command.py
new file mode 100644
index 0000000..812ad8d
--- /dev/null
+++ b/func/overlord/command.py
@@ -0,0 +1,275 @@
+# -*- 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
+
+from func.config import read_config, CONFIG_FILE
+from func.commonconfig import CMConfig
+
+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
+
+ self.config = read_config(CONFIG_FILE, CMConfig)
+
+ # 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