diff -r a6e53a20fccb -r 97505a789003 degal/command.py --- a/degal/command.py Wed Jul 01 20:57:03 2009 +0300 +++ b/degal/command.py Thu Jul 02 21:59:01 2009 +0300 @@ -1,14 +1,94 @@ """ - Command implementations + Command-line based command implementation, handling option/argument parsing. """ -import inspect, logging, traceback, concurrent +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 list of available Commands - - XXX: not yet used + A set of available Commands """ def __init__ (self, commands) : @@ -26,39 +106,30 @@ return self.dict[name] -class Command (object) : + 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) : """ - A Command is simply a function that can be executed from the command line with some options/arguments + 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, name, func, doc=None) : - """ - Create a new Command - - name - the name of the command - func - the callable python function - doc - descriptive help text - """ - - self.name = name - self.func = func - self.doc = doc - - def setup (self, config, gallery) : - """ - Run the command with the given context - """ - - return CommandContext(self, config, gallery) - -class CommandContext (object) : - """ - A CommandContext is the context that a Command executes in - - It is bound to a Configuration and a Gallery. - """ - - def __init__ (self, command, config, gallery) : + def __init__ (self, command, config, gallery, options, args) : """ Create the execution environment """ @@ -66,25 +137,27 @@ 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, *args, **kwargs) : + def execute (self) : """ Run the command in this context """ - return self.command.func(self, *args, **kwargs) + return self.command.func(self, *self.args) - def run (self, *args, **kwargs) : + def run (self) : """ Run the command with error handling """ try : # run it - return self.execute(*args, **kwargs) + return self.execute() except KeyboardInterrupt : self.log_error("Interrupted") @@ -93,7 +166,8 @@ # 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 @@ -143,10 +217,16 @@ else : traceback.print_exc() -def command (func) : - """ - A function decorator used to define Commands automatically - """ +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 - return Command(func.__name__, func, inspect.getdoc(func)) -