stgit.el: Add 'm' as alias for stgit-mark
[stgit] / stgit / main.py
index f59bce6..e324179 100644 (file)
@@ -18,136 +18,43 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os
-from optparse import OptionParser, make_option
-
-from stgit.utils import *
-from stgit import stack, git, gitmergeonefile
-from stgit.version import version
-from stgit.config import config
-from stgit.commands.common import *
-
-# The commands
-import stgit.commands.add
-import stgit.commands.applied
-import stgit.commands.branch
-import stgit.commands.delete
-import stgit.commands.diff
-import stgit.commands.clean
-import stgit.commands.clone
-import stgit.commands.commit
-import stgit.commands.export
-import stgit.commands.files
-import stgit.commands.fold
-import stgit.commands.goto
-import stgit.commands.id
-import stgit.commands.imprt
-import stgit.commands.init
-import stgit.commands.log
-import stgit.commands.mail
-import stgit.commands.new
-import stgit.commands.patches
-import stgit.commands.pick
-import stgit.commands.pop
-import stgit.commands.pull
-import stgit.commands.push
-import stgit.commands.refresh
-import stgit.commands.rename
-import stgit.commands.resolved
-import stgit.commands.rm
-import stgit.commands.series
-import stgit.commands.show
-import stgit.commands.status
-import stgit.commands.top
-import stgit.commands.unapplied
-import stgit.commands.uncommit
+import sys, os, traceback
 
+import stgit.commands
+from stgit.out import *
+from stgit import argparse, run, utils
 
 #
 # The commands map
 #
-commands = {
-    'add':      stgit.commands.add,
-    'applied':  stgit.commands.applied,
-    'branch':   stgit.commands.branch,
-    'delete':   stgit.commands.delete,
-    'diff':     stgit.commands.diff,
-    'clean':    stgit.commands.clean,
-    'clone':    stgit.commands.clone,
-    'commit':   stgit.commands.commit,
-    'export':   stgit.commands.export,
-    'files':    stgit.commands.files,
-    'fold':     stgit.commands.fold,
-    'goto':     stgit.commands.goto,
-    'id':       stgit.commands.id,
-    'import':   stgit.commands.imprt,
-    'init':     stgit.commands.init,
-    'log':      stgit.commands.log,
-    'mail':     stgit.commands.mail,
-    'new':      stgit.commands.new,
-    'patches':  stgit.commands.patches,
-    'pick':     stgit.commands.pick,
-    'pop':      stgit.commands.pop,
-    'pull':     stgit.commands.pull,
-    'push':     stgit.commands.push,
-    'refresh':  stgit.commands.refresh,
-    'rename':   stgit.commands.rename,
-    'resolved': stgit.commands.resolved,
-    'rm':       stgit.commands.rm,
-    'series':   stgit.commands.series,
-    'show':     stgit.commands.show,
-    'status':   stgit.commands.status,
-    'top':      stgit.commands.top,
-    'unapplied':stgit.commands.unapplied,
-    'uncommit': stgit.commands.uncommit,
-    }
-
-# classification: repository, stack, patch, working copy
-repocommands = (
-    'branch',
-    'clone',
-    'id',
-    'pull'
-    )
-stackcommands = (
-    'applied',
-    'clean',
-    'commit',
-    'goto',
-    'init',
-    'pop',
-    'push',
-    'series',
-    'top',
-    'unapplied',
-    'uncommit'
-    )
-patchcommands = (
-    'delete',
-    'export',
-    'files',
-    'fold',
-    'import',
-    'log',
-    'mail',
-    'new',
-    'pick',
-    'refresh',
-    'rename',
-    'show'
-    )
-wccommands = (
-    'add',
-    'diff',
-    'patches',
-    'resolved',
-    'rm',
-    'status'
-    )
+class Commands(dict):
+    """Commands class. It performs on-demand module loading
+    """
+    def canonical_cmd(self, key):
+        """Return the canonical name for a possibly-shortenned
+        command name.
+        """
+        candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
+
+        if not candidates:
+            out.error('Unknown command: %s' % key,
+                      'Try "%s help" for a list of supported commands' % prog)
+            sys.exit(utils.STGIT_GENERAL_ERROR)
+        elif len(candidates) > 1:
+            out.error('Ambiguous command: %s' % key,
+                      'Candidates are: %s' % ', '.join(candidates))
+            sys.exit(utils.STGIT_GENERAL_ERROR)
+
+        return candidates[0]
+        
+    def __getitem__(self, key):
+        cmd_mod = self.get(key) or self.get(self.canonical_cmd(key))
+        return stgit.commands.get_command(cmd_mod)
+
+cmd_list = stgit.commands.get_commands()
+commands = Commands((cmd, mod) for cmd, (mod, kind, help)
+                    in cmd_list.iteritems())
 
-def _print_helpstring(cmd):
-    print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
-    
 def print_help():
     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
     print
@@ -155,112 +62,116 @@ def print_help():
     print '  help        print the detailed command usage'
     print '  version     display version information'
     print '  copyright   display copyright information'
-    # unclassified commands if any
-    cmds = commands.keys()
-    cmds.sort()
-    for cmd in cmds:
-        if not cmd in repocommands and not cmd in stackcommands \
-               and not cmd in patchcommands and not cmd in wccommands:
-            _print_helpstring(cmd)
-    print
-
-    print 'Repository commands:'
-    for cmd in repocommands:
-        _print_helpstring(cmd)
-    print
-    
-    print 'Stack commands:'
-    for cmd in stackcommands:
-        _print_helpstring(cmd)
-    print
-
-    print 'Patch commands:'
-    for cmd in patchcommands:
-        _print_helpstring(cmd)
     print
-
-    print 'Working-copy commands:'
-    for cmd in wccommands:
-        _print_helpstring(cmd)
+    stgit.commands.pretty_command_list(cmd_list, sys.stdout)
 
 #
 # The main function (command dispatcher)
 #
-def main():
+def _main():
     """The main function
     """
+    global prog
+
     prog = os.path.basename(sys.argv[0])
 
     if len(sys.argv) < 2:
-        print >> sys.stderr, 'Unknown command'
+        print >> sys.stderr, 'usage: %s <command>' % prog
         print >> sys.stderr, \
               '  Try "%s --help" for a list of supported commands' % prog
-        sys.exit(1)
+        sys.exit(utils.STGIT_GENERAL_ERROR)
 
     cmd = sys.argv[1]
 
     if cmd in ['-h', '--help']:
-        if len(sys.argv) == 3 and sys.argv[2] in commands:
-            cmd = sys.argv[2]
+        if len(sys.argv) >= 3:
+            cmd = commands.canonical_cmd(sys.argv[2])
             sys.argv[2] = '--help'
         else:
             print_help()
-            sys.exit(0)
+            sys.exit(utils.STGIT_SUCCESS)
     if cmd == 'help':
         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
-            cmd = sys.argv[2]
+            cmd = commands.canonical_cmd(sys.argv[2])
             if not cmd in commands:
-                print >> sys.stderr, '%s help: "%s" command unknown' \
-                      % (prog, cmd)
-                sys.exit(1)
+                out.error('%s help: "%s" command unknown' % (prog, cmd))
+                sys.exit(utils.STGIT_GENERAL_ERROR)
 
             sys.argv[0] += ' %s' % cmd
             command = commands[cmd]
-            parser = OptionParser(usage = command.usage,
-                                  option_list = command.options)
-            parser.print_help()
+            parser = argparse.make_option_parser(command)
+            from pydoc import pager
+            pager(parser.format_help())
         else:
-            print 'usage: %s help <command>' % prog
-
-        sys.exit(0)
+            print_help()
+        sys.exit(utils.STGIT_SUCCESS)
     if cmd in ['-v', '--version', 'version']:
+        from stgit.version import version
         print 'Stacked GIT %s' % version
         os.system('git --version')
         print 'Python version %s' % sys.version
-        sys.exit(0)
+        sys.exit(utils.STGIT_SUCCESS)
     if cmd in ['copyright']:
         print __copyright__
-        sys.exit(0)
-    if not cmd in commands:
-        print >> sys.stderr, 'Unknown command: %s' % cmd
-        print >> sys.stderr, '  Try "%s help" for a list of supported ' \
-              'commands' % prog
-        sys.exit(1)
+        sys.exit(utils.STGIT_SUCCESS)
 
     # re-build the command line arguments
+    cmd = commands.canonical_cmd(cmd)
     sys.argv[0] += ' %s' % cmd
     del(sys.argv[1])
 
     command = commands[cmd]
-    usage = command.usage.split('\n')[0].strip()
-    parser = OptionParser(usage = usage, option_list = command.options)
+    parser = argparse.make_option_parser(command)
     options, args = parser.parse_args()
+    directory = command.directory
+
+    # These modules are only used from this point onwards and do not
+    # need to be imported earlier
+    from stgit.exception import StgException
+    from stgit.config import config_setup
+    from ConfigParser import ParsingError, NoSectionError
+    from stgit.stack import Series
+
+    try:
+        debug_level = int(os.environ.get('STGIT_DEBUG_LEVEL', 0))
+    except ValueError:
+        out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
+        sys.exit(utils.STGIT_GENERAL_ERROR)
+
     try:
-        # 'clone' doesn't expect an already initialised GIT tree. A Series
-        # object will be created after the GIT tree is cloned
-        if cmd != 'clone':
+        directory.setup()
+        config_setup()
+
+        # Some commands don't (always) need an initialized series.
+        if directory.needs_current_series:
             if hasattr(options, 'branch') and options.branch:
-                command.crt_series = stack.Series(options.branch)
+                command.crt_series = Series(options.branch)
             else:
-                command.crt_series = stack.Series()
-            stgit.commands.common.crt_series = command.crt_series
-
-        command.func(parser, options, args)
-    except (IOError, CmdException, stack.StackException, git.GitException,
-            gitmergeonefile.GitMergeException), err:
-        print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
-        sys.exit(2)
+                command.crt_series = Series()
+
+        ret = command.func(parser, options, args)
+    except (StgException, IOError, ParsingError, NoSectionError), err:
+        directory.write_log(cmd)
+        out.error(str(err), title = '%s %s' % (prog, cmd))
+        if debug_level > 0:
+            traceback.print_exc()
+        sys.exit(utils.STGIT_COMMAND_ERROR)
+    except SystemExit:
+        # Triggered by the option parser when it finds bad commandline
+        # parameters.
+        sys.exit(utils.STGIT_COMMAND_ERROR)
     except KeyboardInterrupt:
-        sys.exit(1)
+        sys.exit(utils.STGIT_GENERAL_ERROR)
+    except:
+        out.error('Unhandled exception:')
+        traceback.print_exc()
+        sys.exit(utils.STGIT_BUG_ERROR)
+
+    directory.write_log(cmd)
+    sys.exit(ret or utils.STGIT_SUCCESS)
 
-    sys.exit(0)
+def main():
+    try:
+        _main()
+    finally:
+        run.finish_logging()