mercurial/help.py
changeset 51844 76387080f238
parent 50929 18c8c18993f0
child 51862 607e94e01851
--- a/mercurial/help.py	Wed Sep 11 20:52:51 2024 +0200
+++ b/mercurial/help.py	Thu Sep 05 12:28:12 2024 +0200
@@ -159,6 +159,15 @@
     return rst
 
 
+def ext_help(ui: uimod.ui, ext) -> bytes:
+    doc = pycompat.getdoc(ext)
+    if doc is None:
+        return b""
+    assert doc is not None
+    doc = gettext(doc)
+    return sub_config_item_help(ui, doc)
+
+
 def extshelp(ui: uimod.ui) -> bytes:
     rst = loaddoc(b'extensions')(ui).splitlines(True)
     rst.extend(
@@ -365,6 +374,7 @@
             doc = gettext(fp.read())
         for rewriter in helphooks.get(topic, []):
             doc = rewriter(ui, topic, doc)
+        doc = sub_config_item_help(ui, doc)
         return doc
 
     return loader
@@ -695,6 +705,44 @@
     return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
 
 
+_CONFIG_DOC_RE = re.compile(b'(^ *)?:config-doc:`([^`]+)`', flags=re.MULTILINE)
+
+
+def sub_config_item_help(ui: uimod.ui, doc: bytes) -> bytes:
+    """replace :config-doc:`foo.bar` markup with the item documentation
+
+    This allow grouping config item declaration and help without having to
+    repeat it in the help text file and keep that in sync.
+    """
+    pieces = []
+    last_match_end = 0
+    for match in _CONFIG_DOC_RE.finditer(doc):
+        # finditer is expected to yield result in order
+        start = match.start()
+        assert last_match_end <= match.start()
+        pieces.append(doc[last_match_end:start])
+        item_name = match.group(2)
+        section, key = item_name.split(b'.', 1)
+        section_items = ui._knownconfig.get(section)
+        if section_items is None:
+            item = None
+        else:
+            item = section_items.get(key)
+        if item is None or not item.documentation:
+            item_doc = b'<missing help text for `%s`>' % item_name
+        else:
+            item_doc = gettext(item.documentation)
+            item_doc = sub_config_item_help(ui, item_doc)
+            indent = match.group(1)
+            if indent:  # either None or 0 should be ignored
+                indent = indent
+                item_doc = indent + item_doc.replace(b'\n', b'\n' + indent)
+        pieces.append(item_doc)
+        last_match_end = match.end()
+    pieces.append(doc[last_match_end:])
+    return b''.join(pieces)
+
+
 def _getcategorizedhelpcmds(
     ui: uimod.ui, cmdtable, name: bytes, select: Optional[_SelectFn] = None
 ) -> Tuple[Dict[bytes, List[bytes]], Dict[bytes, bytes], _SynonymTable]:
@@ -822,6 +870,7 @@
                     doc,
                     source,
                 )
+        doc = sub_config_item_help(ui, doc)
         doc = doc.splitlines(True)
         if ui.quiet or not full:
             rst.append(doc[0])
@@ -1042,12 +1091,15 @@
     def helpext(name: bytes, subtopic: Optional[bytes] = None) -> List[bytes]:
         try:
             mod = extensions.find(name)
-            doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available')
+            doc = ext_help(ui, mod)
+            if not doc:
+                doc = _(b'no help text available')
         except KeyError:
             mod = None
             doc = extensions.disabled_help(name)
             if not doc:
                 raise error.UnknownCommand(name)
+            doc = sub_config_item_help(ui, doc)
 
         if b'\n' not in doc:
             head, tail = doc, b""