[PATCH v4 02/10] docs: maintainers_include: auto-generate maintainer profile TOC

Mauro Carvalho Chehab
2026-04-27T14:22:35Z

Add a feature to allow auto-generating media entry profiles from the
corresponding field inside MAINTAINERS file(s).

Suggested-by: Dan Williams <djbw@kernel.org>
Closes: https://lore.kernel.org/linux-doc/69dd6299440be_147c801005b@djbw-dev.notmuch/
Acked-by: Dan Williams <djbw@kernel.org>
Reviewed-by: Randy Dunlap <rdunlap@infradead.org>
Tested-by: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Message-ID: <4e9512a3d05942c98361d06d60a118d7c78762b6.1776176108.git.mchehab+huawei@kernel.org>
---
Documentation/sphinx/maintainers_include.py | 93 +++++++++++++++++----
1 file changed, 76 insertions(+), 17 deletions(-)

diff --git a/Documentation/sphinx/maintainers_include.py b/Documentation/sphinx/maintainers_include.py
index 519ad18685b2..1dac83bf1a65 100755
--- a/Documentation/sphinx/maintainers_include.py
+++ b/Documentation/sphinx/maintainers_include.py
@@ -21,6 +21,8 @@ import sys
import re
import os.path

+from textwrap import indent
+
from docutils import statemachine
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives.misc import Include
@@ -30,20 +32,11 @@ def ErrorString(exc): # Shamelessly stolen from docutils

__version__ = '1.0'

-def setup(app):
- app.add_directive("maintainers-include", MaintainersInclude)
- return dict(
- version = __version__,
- parallel_read_safe = True,
- parallel_write_safe = True
- )
+class MaintainersParser:
+ """Parse MAINTAINERS file(s) content"""

-class MaintainersInclude(Include):
- """MaintainersInclude (``maintainers-include``) directive"""
- required_arguments = 0
-
- def parse_maintainers(self, path):
- """Parse all the MAINTAINERS lines into ReST for human-readability"""
+ def __init__(self, base_path, path):
+ self.profiles = list()

result = list()
result.append(".. _maintainers:")
@@ -78,6 +71,12 @@ class MaintainersInclude(Include):
# Drop needless input whitespace.
line = line.rstrip()

+ match = re.match(r"P:\s*(Documentation/\S+)\.rst", line)
+ if match:
+ fname = os.path.relpath(match.group(1), base_path)
+ if fname not in self.profiles:
+ self.profiles.append(fname)
+
# Linkify all non-wildcard refs to ReST files in Documentation/.
pat = r'(Documentation/([^\s\?\*]*)\.rst)'
m = re.search(pat, line)
@@ -165,12 +164,23 @@ class MaintainersInclude(Include):
for separated in field_content.split('\n'):
result.append(separated)

- output = "\n".join(result)
+ self.output = "\n".join(result)
+
+ # Create a TOC class
+
+class MaintainersInclude(Include):
+ """MaintainersInclude (``maintainers-include``) directive"""
+ required_arguments = 0
+
+ def emit(self, base_path, path):
+ """Parse all the MAINTAINERS lines into ReST for human-readability"""
+
+ output = MaintainersParser(base_path, path).output
+
# For debugging the pre-rendered results...
#print(output, file=open("/tmp/MAINTAINERS.rst", "w"))

- self.state_machine.insert_input(
- statemachine.string2lines(output), path)
+ self.state_machine.insert_input(statemachine.string2lines(output), path)

def run(self):
"""Include the MAINTAINERS file as part of this reST file."""
@@ -186,12 +196,61 @@ class MaintainersInclude(Include):

# Append "MAINTAINERS"
path = os.path.join(path, "MAINTAINERS")
+ base_path = os.path.dirname(self.state.document.document.current_source)

try:
self.state.document.settings.record_dependencies.add(path)
- lines = self.parse_maintainers(path)
+ lines = self.emit(base_path, path)
except IOError as error:
raise self.severe('Problems with "%s" directive path:\n%s.' %
(self.name, ErrorString(error)))

return []
+
+class MaintainersProfile(Include):
+ required_arguments = 0
+
+ def emit(self, base_path, path):
+ """Parse all the MAINTAINERS lines looking for profile entries"""
+
+ profiles = MaintainersParser(base_path, path).profiles
+
+ output = ".. toctree::\n"
+ output += " :maxdepth: 2\n\n"
+ output += indent("\n".join(profiles), " ")
+
+ self.state_machine.insert_input(statemachine.string2lines(output), path)
+
+ def run(self):
+ """Include the MAINTAINERS file as part of this reST file."""
+ if not self.state.document.settings.file_insertion_enabled:
+ raise self.warning('"%s" directive disabled.' % self.name)
+
+ # Walk up source path directories to find Documentation/../
+ path = self.state_machine.document.attributes['source']
+ path = os.path.realpath(path)
+ tail = path
+ while tail != "Documentation" and tail != "":
+ (path, tail) = os.path.split(path)
+
+ # Append "MAINTAINERS"
+ path = os.path.join(path, "MAINTAINERS")
+ base_path = os.path.dirname(self.state.document.document.current_source)
+
+ try:
+ self.state.document.settings.record_dependencies.add(path)
+ lines = self.emit(base_path, path)
+ except IOError as error:
+ raise self.severe('Problems with "%s" directive path:\n%s.' %
+ (self.name, ErrorString(error)))
+
+ return []
+
+def setup(app):
+ app.add_directive("maintainers-include", MaintainersInclude)
+ app.add_directive("maintainers-profile-toc", MaintainersProfile)
+ return dict(
+ version = __version__,
+ parallel_read_safe = True,
+ parallel_write_safe = True
+ )
--
2.53.0