degal/command.py
changeset 144 97505a789003
parent 120 55cb7fc9c8fb
--- 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))
-