[PATCH 2 of 2] dispatch: provide help for disabled extensions and commands

Brodie Rao dackze at gmail.com
Sun Dec 6 01:12:06 CST 2009


# HG changeset patch
# User Brodie Rao <me+hg at dackz.net>
# Date 1260082485 18000
# Node ID 77ebf6a52e3722b8dbe9f7a466c344c39cf7418d
# Parent  29df34f954641a1e0b348cb8484bc8bb67d7aba3
dispatch: provide help for disabled extensions and commands

Before a command is declared unknown, the cmdtable of each extension in
hgext is searched, starting with hgext.<cmdname>, and then every other
extension. If there's a match, a help message suggesting the appropriate
extension and how to enable them is printed.

Every extension could potentially be imported, but for cases like rebase,
relink, etc. only one extension is imported.

For the case of "hg help disabledext", if the extension is in hgext, the
extension description is read and a similar help suggestion is printed.
No extension import occurs.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1579,10 +1579,14 @@ def help_(ui, name=None, with_version=Fa
     def helpext(name):
         try:
             mod = extensions.find(name)
+            doc = mod.__doc__
         except KeyError:
-            raise error.UnknownCommand(name)
-
-        doc = gettext(mod.__doc__) or _('no help text available')
+            mod = None
+            doc = extensions.disabledext(name)
+            if not doc:
+                raise error.UnknownCommand(name)
+
+        doc = gettext(doc) or _('no help text available')
         if '\n' not in doc:
             head, tail = doc, ""
         else:
@@ -1592,17 +1596,23 @@ def help_(ui, name=None, with_version=Fa
             ui.write(minirst.format(tail, textwidth))
             ui.status('\n\n')
 
-        try:
-            ct = mod.cmdtable
-        except AttributeError:
-            ct = {}
-
-        modcmds = set([c.split('|', 1)[0] for c in ct])
-        helplist(_('list of commands:\n\n'), modcmds.__contains__)
+        if mod:
+            try:
+                ct = mod.cmdtable
+            except AttributeError:
+                ct = {}
+            modcmds = set([c.split('|', 1)[0] for c in ct])
+            helplist(_('list of commands:\n\n'), modcmds.__contains__)
+        else:
+            ui.write(_('use "hg help extensions" for information on enabling '
+                       'extensions\n'))
+
+    def helpextcmd(name):
+        ui.write(help.disabledextcmd(name))
 
     if name and name != 'shortlist':
         i = None
-        for f in (helptopic, helpcmd, helpext):
+        for f in (helptopic, helpcmd, helpext, helpextcmd):
             try:
                 f(name)
                 i = None
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -8,7 +8,7 @@
 from i18n import _
 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
 import util, commands, hg, fancyopts, extensions, hook, error
-import cmdutil, encoding
+import cmdutil, encoding, help
 import ui as _ui
 
 def run():
@@ -92,7 +92,11 @@ def _runcatch(ui, args):
         ui.warn(_("killed!\n"))
     except error.UnknownCommand, inst:
         ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
-        commands.help_(ui, 'shortlist')
+        try:
+            # check if the command is in a disabled extension
+            ui.write(help.disabledextcmd(inst.args[0]))
+        except error.UnknownCommand:
+            commands.help_(ui, 'shortlist')
     except util.Abort, inst:
         ui.warn(_("abort: %s\n") % inst)
     except ImportError, inst:
@@ -217,6 +221,11 @@ class cmdalias(object):
             def fn(ui, *args):
                 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
                             % (self.name, cmd))
+                try:
+                    # check if the command is in a disabled extension
+                    ui.write(help.disabledextcmd(cmd))
+                except error.UnknownCommand:
+                    pass
                 return 1
             self.fn = fn
             self.badalias = True
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -6,7 +6,7 @@
 # GNU General Public License version 2, incorporated herein by reference.
 
 import imp, os
-import util, cmdutil, help
+import util, cmdutil, help, error
 from i18n import _, gettext
 
 _extensions = {}
@@ -131,52 +131,110 @@ def wrapfunction(container, funcname, wr
     setattr(container, funcname, wrap)
     return origfn
 
+def _disabledpaths(init=True):
+    import hgext
+    extpath = os.path.dirname(os.path.abspath(hgext.__file__))
+    try: # might not be a filesystem path
+        files = os.listdir(extpath)
+    except OSError:
+        return {}
+
+    exts = {}
+    for e in files:
+        path = os.path.join(extpath, e)
+        if e.endswith('.py'):
+            name = e.rsplit('.', 1)[0]
+        else:
+            name = e
+            modpath = os.path.join(extpath, e, '__init__.py')
+            if not os.path.exists(modpath):
+                continue
+            elif init:
+                path = modpath
+        if name in exts or name in _order or name == '__init__':
+            continue
+        exts[name] = path
+    return exts
+
+def _disabledhelp(path):
+    try:
+        file = open(path)
+    except IOError:
+        return
+    else:
+        doc = help.moduledoc(file)
+        file.close()
+
+    if doc: # extracting localized synopsis
+        return gettext(doc).splitlines()[0]
+    else:
+        return _('(no help text available)')
+
 def disabled():
     '''find disabled extensions from hgext
     returns a dict of {name: desc}, and the max name length'''
 
-    import hgext
-    extpath = os.path.dirname(os.path.abspath(hgext.__file__))
-
-    try: # might not be a filesystem path
-        files = os.listdir(extpath)
-    except OSError:
+    paths = _disabledpaths()
+    if not paths:
         return None, 0
 
     exts = {}
     maxlength = 0
-    for e in files:
-
-        if e.endswith('.py'):
-            name = e.rsplit('.', 1)[0]
-            path = os.path.join(extpath, e)
-        else:
-            name = e
-            path = os.path.join(extpath, e, '__init__.py')
-            if not os.path.exists(path):
-                continue
-
-        if name in exts or name in _order or name == '__init__':
+    for name, path in paths.iteritems():
+        doc = _disabledhelp(path)
+        if not doc:
             continue
 
-        try:
-            file = open(path)
-        except IOError:
-            continue
-        else:
-            doc = help.moduledoc(file)
-            file.close()
-
-        if doc: # extracting localized synopsis
-            exts[name] = gettext(doc).splitlines()[0]
-        else:
-            exts[name] = _('(no help text available)')
-
+        exts[name] = doc
         if len(name) > maxlength:
             maxlength = len(name)
 
     return exts, maxlength
 
+def disabledext(name):
+    '''find a specific disabled extension from hgext. returns desc'''
+    paths = _disabledpaths()
+    if name in paths:
+        return _disabledhelp(paths[name])
+
+def disabledcmd(cmd):
+    '''import disabled extensions until cmd is found.
+    returns (cmdname, extname, doc)'''
+
+    paths = _disabledpaths(init=False)
+    if not paths:
+        raise error.UnknownCommand(cmd)
+
+    def findcmd(cmd, name, path):
+        mod = loadpath(path, 'hgext.%s' % name)
+        try:
+            aliases, entry = cmdutil.findcmd(cmd,
+                getattr(mod, 'cmdtable', {}), False)
+        except (error.AmbiguousCommand, error.UnknownCommand):
+            return
+        for c in aliases:
+            if c.startswith(cmd):
+                cmd = c
+                break
+        else:
+            cmd = aliases[0]
+        return (cmd, name, mod)
+
+    # first, search for an extension with the same name as the command
+    path = paths.pop(cmd, None)
+    if path:
+        ext = findcmd(cmd, cmd, path)
+        if ext:
+            return ext
+
+    # otherwise, interrogate each extension until there's a match
+    for name, path in paths.iteritems():
+        ext = findcmd(cmd, name, path)
+        if ext:
+            return ext
+
+    raise error.UnknownCommand(cmd)
+
 def enabled():
     '''return a dict of {name: desc} of extensions, and the max name length'''
     exts = {}
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -7,7 +7,7 @@
 
 from i18n import gettext, _
 import sys, os
-import extensions
+import extensions, minirst, util
 
 
 def moduledoc(file):
@@ -60,6 +60,19 @@ def extshelp():
 
     return doc
 
+def disabledextcmd(name):
+    cmd, ext, mod = extensions.disabledcmd(name)
+    doc = gettext(mod.__doc__).splitlines()[0]
+
+    textwidth = util.termwidth() - 2
+    output = minirst.format(listexts(
+        _("\n'%s' is provided by the following extension:") % cmd,
+        {ext: doc}, len(ext)), textwidth)
+    output += '\n\n'
+    output += _('use "hg help extensions" for information on enabling '
+                'extensions\n')
+    return output
+
 def loaddoc(topic):
     """Return a delayed loader for help/topic.txt."""
 
diff --git a/tests/test-extension b/tests/test-extension
--- a/tests/test-extension
+++ b/tests/test-extension
@@ -153,3 +153,13 @@ echo "hgext/mq=" >> $HGRCPATH
 
 echo % show extensions
 hg debugextensions
+
+echo '% disabled extension commands'
+HGRCPATH=
+hg help inserve
+hg qdel
+hg churn
+echo '% disabled extensions'
+hg help churn
+hg help inotify
+exit 0
diff --git a/tests/test-extension.out b/tests/test-extension.out
--- a/tests/test-extension.out
+++ b/tests/test-extension.out
@@ -96,3 +96,28 @@ global options:
 % show extensions
 debugissue811
 mq
+% disabled extension commands
+'inserve' is provided by the following extension:
+
+ inotify   accelerate status report using Linux's inotify service
+
+use "hg help extensions" for information on enabling extensions
+hg: unknown command 'qdel'
+'qdelete' is provided by the following extension:
+
+ mq   manage a stack of patches
+
+use "hg help extensions" for information on enabling extensions
+hg: unknown command 'churn'
+'churn' is provided by the following extension:
+
+ churn   command to display statistics about repository history
+
+use "hg help extensions" for information on enabling extensions
+% disabled extensions
+churn extension - command to display statistics about repository history
+
+use "hg help extensions" for information on enabling extensions
+inotify extension - accelerate status report using Linux's inotify service
+
+use "hg help extensions" for information on enabling extensions


More information about the Mercurial-devel mailing list