--- 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))
-