reorganize/rename the commands and their options stuff, options like --force-html are now split out from main.py into commands/update.py
"""
Command-line based command implementation, handling option/argument parsing.
"""
import concurrent
import inspect, itertools, logging, traceback, optparse
def optify (symbol) :
"""
Turns the given python symbol into the suitable version for use as a command-line argument.
In other words, this replaces '_' with '-'.
"""
return symbol.replace('_', '-')
# wrapper around optparse.make_option
Option = optparse.make_option
class Command (object) :
"""
A Command is simply a function that can be executed from the command line with some options/arguments
"""
def __init__ (self, name, func, doc=None, options=None) :
"""
Create a new Command
name - the name of the command
func - the callable python function
doc - descriptive help text
options - named options as Option objects
"""
self.name = name
self.func = func
self.doc = doc
self.options = options
def parse_args (self, args) :
"""
Pre-parse the given arguments list.
"""
return args
def apply (self, config, gallery, options, *args, **kwargs) :
"""
Construct a CommandContext for execution of this command with the given environment.
the command with the given context.
"""
# apply extra options
for k, v in kwargs.iteritems() :
setattr(options, k, v)
return Environment(self, config, gallery, options, args)
def option_group (self, parser) :
"""
Returns an optparse.OptionGroup for this command's options.
"""
group = optparse.OptionGroup(parser, "Command-specific options")
for opt in self.options :
group.add_option(opt)
return group
def cmdopt_callback (self, option, opt_str, value, parser) :
"""
Used as a optparse.Option callback-action, adds this command's options to the parser, and stores the
selected command.
"""
# check
if hasattr(parser.values, option.dest) :
raise ArgumentError("More than one command option given: %s + %s" % (getattr(parser.values, option.dest), self.name))
if self.options :
# setup command-specific options
parser.add_option_group(self.build_options(parser))
# store selected command
setattr(parser.values, option.dest, self)
class CommandList (object) :
"""
A set of available Commands
"""
def __init__ (self, commands) :
"""
Store with given initial commands
"""
self.list = commands
self.dict = dict((cmd.name, cmd) for cmd in commands)
def lookup (self, name) :
"""
Lookup a command by name
"""
return self.dict[name]
def option_group (self, parser, default) :
"""
Returns an optparse.OptionGroup for these commands, using the given parser.
"""
group = optparse.OptionGroup(parser, "Command Options", "Select what command to execute, may introduce other options")
for command in self.list :
group.add_option('--%s' % optify(command.name), action='callback', callback=command.cmdopt_callback, dest='command', help=command.doc)
# store default
parser.set_defaults(command=default)
return group
class Environment (object) :
"""
The environment that a Command will execute in.
This is bound to a Configuration, a Gallery, options values, argument values and other miscellaneous things. An
environment also provides other services, such as status output, concurrent execution and error handling.
"""
def __init__ (self, command, config, gallery, options, args) :
"""
Create the execution environment
"""
self.command = command
self.config = config
self.gallery = gallery
self.options = options
self.args = args
# conccurency
self.concurrent = concurrent.Manager(thread_count=config.thread_count)
def execute (self) :
"""
Run the command in this context
"""
return self.command.func(self, *self.args)
def run (self) :
"""
Run the command with error handling
"""
try :
# run it
return self.execute()
except KeyboardInterrupt :
self.log_error("Interrupted")
except :
# dump traceback
# XXX: skip all crap up to the actual function
self.handle_error()
# XXX: split off to a .log object
def log_msg (self, level, msg, *args, **kwargs) :
"""
Output a log message with the given level
XXX: unicode
"""
# control level of output
if level < self.config.log_level :
return
# format?
if args or kwargs :
if args and not kwargs :
msg = msg % args
elif kwargs and not args :
msg = msg % kwargs
else :
raise Exception("log_msg called with both args and kwargs")
# output
# XXX: stdout/err?
print msg
def log_debug (self, msg, *args, **kwargs) :
self.log_msg(logging.DEBUG, msg, *args, **kwargs)
def log_info (self, msg, *args, **kwargs) :
self.log_msg(logging.INFO, msg, *args, **kwargs)
def log_warning (self, msg, *args, **kwargs) :
self.log_msg(logging.WARNING, msg, *args, **kwargs)
def log_error (self, msg, *args, **kwargs) :
self.log_msg(logging.ERROR, msg, *args, **kwargs)
def handle_error (self, exc_info=None) :
"""
Do something to handle an error that occured
"""
if exc_info :
traceback.print_execption(*exc_info)
else :
traceback.print_exc()
def command (options=None) :
def _decorator (func) :
"""
A function decorator used to define Commands automatically
"""
# find help string
doc = inspect.getdoc(func)
return Command(func.__name__, func, doc, options)
return _decorator