help: adding support for command categories
authorrdamazio@google.com
Sat, 13 Oct 2018 05:03:50 -0700
changeset 40291 170926caf44c
parent 40290 091f9b8a1051
child 40292 9c6473d2038b
help: adding support for command categories Differential Revision: https://phab.mercurial-scm.org/D5065
doc/gendoc.py
mercurial/help.py
mercurial/registrar.py
--- a/doc/gendoc.py	Sun Oct 14 13:35:47 2018 +0200
+++ b/doc/gendoc.py	Sat Oct 13 05:03:50 2018 -0700
@@ -149,7 +149,8 @@
     helpprinter(ui, helptable + extrahelptable, None, include=[topic])
 
 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
-    for names, sec, doc in helptable:
+    for h in helptable:
+        names, sec, doc = h[0:3]
         if exclude and names[0] in exclude:
             continue
         if include and names[0] not in include:
--- a/mercurial/help.py	Sun Oct 14 13:35:47 2018 +0200
+++ b/mercurial/help.py	Sat Oct 13 05:03:50 2018 -0700
@@ -25,6 +25,7 @@
     fileset,
     minirst,
     pycompat,
+    registrar,
     revset,
     templatefilters,
     templatefuncs,
@@ -47,6 +48,20 @@
     _("(EXPERIMENTAL)"),
 }
 
+# The order in which command categories will be displayed.
+# Extensions with custom categories should insert them into this list
+# after/before the appropriate item, rather than replacing the list or
+# assuming absolute positions.
+CATEGORY_ORDER = [
+    registrar.command.CATEGORY_NONE,
+]
+
+# Human-readable category names. These are translated.
+# Extensions with custom categories should add their names here.
+CATEGORY_NAMES = {
+    registrar.command.CATEGORY_NONE: 'Uncategorized commands',
+}
+
 def listexts(header, exts, indent=1, showdeprecated=False):
     '''return a text listing of the given extensions'''
     rst = []
@@ -419,39 +434,39 @@
 
         return rst
 
-
     def helplist(select=None, **opts):
-        # list of commands
-        if name == "shortlist":
-            header = _('basic commands:\n\n')
-        elif name == "debug":
-            header = _('debug commands (internal and unsupported):\n\n')
-        else:
-            header = _('list of commands:\n\n')
-
+        # Category -> list of commands
+        cats = {}
+        # Command -> short description
         h = {}
-        cmds = {}
+        # Command -> string showing synonyms
+        syns = {}
         for c, e in commands.table.iteritems():
             fs = cmdutil.parsealiases(c)
             f = fs[0]
+            syns[f] = ', '.join(fs)
+            func = e[0]
             p = ''
             if c.startswith("^"):
                 p = '^'
             if select and not select(p + f):
                 continue
             if (not select and name != 'shortlist' and
-                e[0].__module__ != commands.__name__):
+                func.__module__ != commands.__name__):
                 continue
             if name == "shortlist" and not p:
                 continue
-            doc = pycompat.getdoc(e[0])
+            doc = pycompat.getdoc(func)
             if filtercmd(ui, f, name, doc):
                 continue
             doc = gettext(doc)
             if not doc:
                 doc = _("(no help text available)")
             h[f] = doc.splitlines()[0].rstrip()
-            cmds[f] = '|'.join(fs)
+
+            cat = getattr(func, 'helpcategory', None) or (
+                registrar.command.CATEGORY_NONE)
+            cats.setdefault(cat, []).append(f)
 
         rst = []
         if not h:
@@ -459,15 +474,42 @@
                 rst.append(_('no commands defined\n'))
             return rst
 
+        # Output top header.
         if not ui.quiet:
-            rst.append(header)
-        fns = sorted(h)
-        for f in fns:
-            if ui.verbose:
-                commacmds = cmds[f].replace("|",", ")
-                rst.append(" :%s: %s\n" % (commacmds, h[f]))
+            if name == "shortlist":
+                rst.append(_('basic commands:\n\n'))
+            elif name == "debug":
+                rst.append(_('debug commands (internal and unsupported):\n\n'))
             else:
-                rst.append(' :%s: %s\n' % (f, h[f]))
+                rst.append(_('list of commands:\n'))
+
+        def appendcmds(cmds):
+            cmds = sorted(cmds)
+            for c in cmds:
+                if ui.verbose:
+                    rst.append(" :%s: %s\n" % (syns[c], h[c]))
+                else:
+                    rst.append(' :%s: %s\n' % (c, h[c]))
+
+        if name in ('shortlist', 'debug'):
+            # List without categories.
+            appendcmds(h)
+        else:
+            # Check that all categories have an order.
+            missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
+            if missing_order:
+                ui.develwarn('help categories missing from CATEGORY_ORDER: %s' %
+                             missing_order)
+
+            # List per category.
+            for cat in CATEGORY_ORDER:
+                catfns = cats.get(cat, [])
+                if catfns:
+                    if len(cats) > 1:
+                        catname = gettext(CATEGORY_NAMES[cat])
+                        rst.append("\n%s:\n" % catname)
+                    rst.append("\n")
+                    appendcmds(catfns)
 
         ex = opts.get
         anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
@@ -499,7 +541,7 @@
             elif name and not full:
                 rst.append(_("\n(use 'hg help %s' to show the full help "
                              "text)\n") % name)
-            elif name and cmds and name in cmds.keys():
+            elif name and syns and name in syns.keys():
                 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
                              "aliases and global options)\n") % name)
             else:
--- a/mercurial/registrar.py	Sun Oct 14 13:35:47 2018 +0200
+++ b/mercurial/registrar.py	Sat Oct 13 05:03:50 2018 -0700
@@ -146,6 +146,10 @@
     to prevent the command from running if the requested intent could not be
     fulfilled.
 
+    If `helpcategory` is set (usually to one of the constants in the help
+    module), the command will be displayed under that category in the help's
+    list of commands.
+
     The following intents are defined:
 
     readonly
@@ -164,14 +168,17 @@
     descriptions and examples.
     """
 
+    # Command categories for grouping them in help output.
+    CATEGORY_NONE = 'none'
+
     def _doregister(self, func, name, options=(), synopsis=None,
                     norepo=False, optionalrepo=False, inferrepo=False,
-                    intents=None):
-
+                    intents=None, helpcategory=None):
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
         func.intents = intents or set()
+        func.helpcategory = helpcategory
         if synopsis:
             self._table[name] = func, list(options), synopsis
         else: