[PATCH 7/9] docs: maintainers_include: store maintainers entries on a dict

Mauro Carvalho Chehab
2026-05-04T15:51:29Z

Instead of creating just a big output data, store entries inside
a dictionary. Doing that simplifies the parser a little bit
and make the code clearer.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
Documentation/sphinx/maintainers_include.py | 82 +++++++++------------
1 file changed, 35 insertions(+), 47 deletions(-)

diff --git a/Documentation/sphinx/maintainers_include.py b/Documentation/sphinx/maintainers_include.py
index 189f22e5fae4..50359b125db0 100755
--- a/Documentation/sphinx/maintainers_include.py
+++ b/Documentation/sphinx/maintainers_include.py
@@ -49,10 +49,7 @@ class MaintainersParser:

# Field letter to field name mapping.
self.field_letter = None
- self.fields = {}

- self.field_prev = ""
- self.field_content = ""
self.subsystem_name = None

self.app_dir = app_dir
@@ -65,7 +62,9 @@ class MaintainersParser:
#
self.profile_toc = set()
self.profile_entries = {}
- self.output = ".. _maintainers:\n\n"
+ self.header = ".. _maintainers:\n\n"
+ self.maint_entries = {}
+ self.fields = {}

prev = None
with open(path, "r", encoding="utf-8") as fp:
@@ -77,11 +76,11 @@ class MaintainersParser:
self.subsystems = True
self.parse_subsystems(line)
else:
- self.output += line
+ self.header += line
elif self.subsystems:
self.parse_subsystems(line)
else:
- self.output += line
+ self.header += line

# Update the state machine when we find heading separators.
if line.startswith("----------"):
@@ -93,13 +92,6 @@ class MaintainersParser:
# Retain previous line for state machine transitions.
prev = line

- # Flush pending field contents.
- if self.field_content:
- self.output += self.field_content + "\n\n"
-
- self.output = self.output.rstrip()
-
-
def linkify(self, text):
"""Linkify all non-wildcard refs to ReST files in Documentation/"""

@@ -132,12 +124,12 @@ class MaintainersParser:
# Have we reached the end of the preformatted Descriptions text?
if line.startswith("Maintainers"):
self.descriptions = False
- self.output += "\n" + line
+ self.header += "\n" + line
return

# Escape the escapes in preformatted text.
line = self.linkify(line).replace("\\", "\\\\").replace("**", "\\**")
- self.output += "| " + line
+ self.header += "| " + line

# Look for and record field letter to field name mappings:
# R: Designated *reviewer*: FullName <address@domain>
@@ -160,24 +152,8 @@ class MaintainersParser:
if not line:
return

- # Subsystem fields are batched into "field_content"
if line[1] != ':':
- line = self.linkify(line)
-
- # Render a subsystem entry as:
- # SUBSYSTEM NAME
- # ~~~~~~~~~~~~~~
- # Flush pending field content.
- self.output += self.field_content + "\n\n"
- self.field_content = ""
-
- self.subsystem_name = line.title()
-
- # Collapse whitespace in subsystem name.
- heading = re.sub(r"\s+", " ", line)
- self.output += "%s\n%s" % (heading, "~" * len(heading)) + "\n"
- self.field_prev = ""
-
+ self.subsystem_name = re.sub(r"\s+", " ", self.linkify(line))
return

# Render a subsystem field as:
@@ -192,20 +168,22 @@ class MaintainersParser:
if field == "P":
match = self.re_doc.match(details)
if match:
- name = "".join(match.groups())
- entry = os.path.relpath(self.base_dir + name, self.app_dir)
+ fname = match.group(1)
+ ename = "/" + match.group(2)

- full_name = os.path.join(self.base_dir, name)
+ entry = os.path.relpath(self.base_dir + ename, self.app_dir)
+ full_name = os.path.join(self.base_dir, fname)
path = os.path.relpath(full_name, self.app_dir)
#
# When SPHINXDIRS is used, it will try to reference files
# outside srctree, causing warnings. To avoid that, point
# to the latest official documentation
#
+
if path.startswith("../"):
- entry = KERNELDOC_URL + "/" + match.group(2) + ".html"
+ entry = KERNELDOC_URL + fname + ".html"
else:
- entry = "/" + entry
+ entry = ename

if "*" in entry:
for e in glob(entry):
@@ -222,23 +200,23 @@ class MaintainersParser:

details = self.linkify(details)

+ #
# Mark paths (and regexes) as literal text for improved
# readability and to escape any escapes.
+ #
if field in ['F', 'N', 'X', 'K']:
# But only if not already marked :)
if not ':doc:' in details:
details = '``%s``' % (details)

- # Comma separate email field continuations.
- if field == self.field_prev and self.field_prev in ['M', 'R', 'L']:
- self.field_content = self.field_content + ","
+ if self.subsystem_name not in self.maint_entries:
+ self.maint_entries[self.subsystem_name] = {}
+
+ if field not in self.maint_entries[self.subsystem_name]:
+ self.maint_entries[self.subsystem_name][field] = []
+
+ self.maint_entries[self.subsystem_name][field].append(details)

- # Do not repeat field names, so that field entries
- # will be collapsed together.
- if field != self.field_prev:
- self.output += self.field_content + "\n\n"
- self.field_content = ":%s:" % (self.fields.get(field, field))
- self.field_content = self.field_content + "\n\t%s" % (details)
self.field_prev = field

@@ -250,7 +228,15 @@ class MaintainersInclude(Include):
def emit(self):
"""Parse all the MAINTAINERS lines into ReST for human-readability"""
path = maint_parser.path
- output = maint_parser.output
+ output = maint_parser.header
+
+ for name, fields in maint_parser.maint_entries.items():
+ output += "\n" + name + "\n"
+ output += "~" * len(name) + "\n"
+
+ for field, lines in fields.items():
+ field_name = maint_parser.fields.get(field, field)
+ output += f":{field_name}:\n\t" + ",\n\t".join(lines) + "\n\n"

# For debugging the pre-rendered results...
print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
@@ -286,6 +272,8 @@ class MaintainersProfile(Include):
#
output = ""
for profile, entry in sorted(maint_parser.profile_entries.items()):
+ profile = profile.title()
+
if entry.startswith("http"):
output += f"- `{profile} <{entry}>`_\n"
else:
--
2.54.0