Commits

Alain Poirier  committed b902428

Sub-commands can now be defined

  • Participants
  • Parent commits ce5cc1d

Comments (0)

Files changed (6)

File nagare/admin/command.py

 by  ``setuptools``
 """
 
+import os
 import sys
 import optparse
 
 
     @classmethod
     def set_options(cls, parser):
+        """Define the available options for this command
+
+        Out:
+          - ``parser`` -- this ``optparse.OptionParser`` will parse the command line
+        """
         pass
 
     @classmethod
     def run(cls, parser, options, args):
-        pass
+        """Command code
 
-# ---------------------------------------------------------------------------
+        In:
+          - ``parser`` -- this ``optparse.OptionParser`` has parsed the command line
+          - ``options`` -- the options read on the command line
+          - ``args`` -- arguments left on the command line
 
-def usage(commands):
-    """
-    Display the usage of ``nagare-admin``
+        Return:
+          - the Unix command status
+        """
+        return 2
 
-    In:
-      - ``commands`` -- dict of {command name -> command class}
-    """
-    yield '%prog <command>'
-    yield ''
-    yield 'with <command> :'
+    def parse(self, argv, commands):
+        """Parse the command line
 
-    # Display the description of each command
-    l = max(map(len, commands))
-    for name in sorted(commands):
-        yield ' - %s: %s' % (name.ljust(l), commands[name].desc)
+        In:
+          - ``argv`` -- list of the arguments on the command line
+          - ``commands`` -- parent commands of this command
+
+        Return:
+          - the Unix command status
+        """
+        parser = optparse.OptionParser(usage=' '.join(commands))
+        self.set_options(parser)
+
+        return self.run(parser, *parser.parse_args(argv))
+
+    def get_description(self, commands):
+        """Return the description of the command
+
+        In:
+          - ``commands`` -- parent commands of this command
+
+        Return:
+          - A list with a single tuple (full name of the command, description of the command)
+        """
+        return [(' '.join(commands), self.desc)]
+
+
+class Commands(dict):
+    """Command with sub-commands"""
+
+    def add_command(self, names, command):
+        """Define a sub-commands
+
+        In:
+          - ``names`` -- list of names of the sub-command
+          - ``command`` -- the sub-command object
+        """
+        name = names.pop(0)
+        if names:
+            # The sub-commands has sub-commands itself.
+            # Add a new 'Commands()' node if not already registered
+            self.setdefault(name, Commands()).add_command(names, command)
+        else:
+            self[name] = command
+
+    def _usage(self, commands, error):
+        """Usage of this command
+
+        In:
+          - ``commands`` -- parent commands of this command
+          - ``error`` - optional error message
+
+        Return:
+          - yield the usage line per line
+        """
+        yield 'Usage: ' + ' '.join(commands + ('<command>',))
+        yield ''
+        yield 'with <command>:'
+
+        # Return the description of each sub-commands
+        commands = list(self.get_description(()))
+
+        l = max(len(name) for name, _ in commands)
+        for name, desc in sorted(commands):
+            yield ' - %s: %s' % (name.ljust(l), desc)
+
+        if error:
+            yield ''
+            yield error
+
+    def usage(self, commands, error=None):
+        """Print the usage of this command on ``stderr``
+
+        In:
+          - ``commands`` -- parent commands of this command
+          - ``error`` - optional error message
+
+        Return:
+          - **no return**, interpreter exit
+        """
+        print >>sys.stderr, '\n'.join(self._usage(commands, error))
+        sys.exit(2)
+
+    def parse(self, argv, commands):
+        """Parse the command line
+
+        In:
+          - ``argv`` -- list of the arguments on the command line
+          - ``commands`` -- parent commands of this command
+
+        Return:
+          - the Unix command status
+        """
+        if not argv:
+            # No sub-command given on the command line
+            self.usage(commands)
+
+        name = argv.pop(0)
+        if name not in self:
+            # The sub-command given on the command line doesn't exist
+            self.usage(commands, "error: command '%s' not found" % name)
+
+        # Parse the command line for the sub-command given
+        return self[name].parse(argv, commands + (name,))
+
+    def get_description(self, commands):
+        """Return the description of the command
+
+        In:
+          - ``commands`` -- parent commands of this command
+
+        Return:
+          - A list of tuples (full name of the sub-command, description of the sub-command)
+        """
+        for name, command in self.items():
+            for r in command.get_description(commands + (name,)):
+                yield r
 
 # ---------------------------------------------------------------------------
 
     """
 
     # Load all the commands
-    commands = {}
+    commands = Commands()
     for entry_point in pkg_resources.iter_entry_points(entry_point_section):
         try:
-            commands[entry_point.name] = entry_point.load()
+            commands.add_command(entry_point.name.split('.'), entry_point.load()())
         except ImportError:
             print "Warning: the command '%s' can't be imported" % entry_point.name
             raise
 
-    parser = optparse.OptionParser(usage='\n'.join(usage(commands)))
-
-    if (len(sys.argv) == 1) or (sys.argv[1] == '-h') or (sys.argv[1] == '--help'):
-        parser.print_usage(sys.stderr)
-        parser.exit()
-
-    command_name = sys.argv[1]
-    command = commands.get(command_name)
-    if command is None:
-        parser.error("command '%s' not found" % command_name)
-
-    parser.usage = '%%prog %s [options]' % command_name
-
-    argv = command.set_options(parser)  # Let the command register its command line options
-    (options, args) = parser.parse_args((argv if argv is not None else sys.argv)[2:])   # Parse the command line
-
-    return command.run(parser, options, args)  # Run the command
+    return commands.parse(sys.argv[1:], (os.path.basename(sys.argv[0]),))

File nagare/admin/create.py

 
 
 def set_options(optparser):
-    optparser.usage += ' application'
+    optparser.usage += ' <application>'
 
 
 def create_empty_file(filename):

File nagare/admin/create_rules.py

     In:
       - ``optparser`` -- the options parser
     """
-    optparser.usage += ' [applications ...]'
+    optparser.usage += ' [application ...]'
 
     optparser.add_option('-a', '--apache', action='store_const', const=generate_apache_rules, dest='generate', help='generate apache rules', default=generate_apache_rules)
     optparser.add_option('-l', '--lighttpd', action='store_const', const=generate_lighttpd_rules, dest='generate', help='generate lighttpd rules')

File nagare/admin/serve.py

 # ---------------------------------------------------------------------------
 
 def set_options(optparser):
-    optparser.usage += ' application'
+    optparser.usage += ' [application ...]'
 
     optparser.add_option('-c', '--conf', dest='conf', help='configuration file')
     optparser.add_option('--host', action='store', type='string', help='Name of the interface to listen on ("0.0.0.0" to listen on all the interfaces)')
 # ---------------------------------------------------------------------------
 
 class Serve(command.Command):
-    desc = 'Launch an application'
+    desc = 'Launch applications'
 
     set_options = staticmethod(set_options)
     run = staticmethod(run)

File nagare/admin/serve_module.py

 
 
 def set_options(optparser):
-    optparser.usage += ' module_or_file name'
+    optparser.usage += ' <module_or_file> <name>'
 
     optparser.add_option('--host', action='store', type='string', default='127.0.0.1', help='Name of the interface to listen on ("0.0.0.0" to listen on all the interfaces)')
     optparser.add_option('-p', '--port', action='store', type='int', dest='port', default=8080, help='Port to listen on')

File nagare/admin/shell.py

 # -----------------------------------------------------------------------------
 
 def set_shell_options(optparser):
-    optparser.usage += ' <application>'
+    optparser.usage += ' [application ...]'
 
     optparser.add_option('-d', '--debug', action='store_const', const=True, default=False, dest='debug', help='debug mode for the database engine')
     optparser.add_option('--plain', action='store_const', const=False, default=True, dest='ipython', help='launch a plain Python interpreter instead of IPython')