aa20485528
In order to have the MAINTAINERS file visible in the rendered ReST output, this makes some small changes to the existing MAINTAINERS file to allow for better machine processing, and adds a new Sphinx directive "maintainers-include" to perform the rendering. Features include: - Per-subsystem reference links: subsystem maintainer entries can be trivially linked to both internally and external. For example: https://www.kernel.org/doc/html/latest/process/maintainers.html#secure-computing - Internally referenced .rst files are linked so they can be followed when browsing the resulting rendering. This allows, for example, the future addition of maintainer profiles to be automatically linked. - Field name expansion: instead of the short fields (e.g. "M", "F", "K"), use the indicated inline "full names" for the fields (which are marked with "*"s in MAINTAINERS) so that a rendered subsystem entry is more human readable. Email lists are additionally comma-separated. For example: SECURE COMPUTING Mail: Kees Cook <keescook@chromium.org> Reviewer: Andy Lutomirski <luto@amacapital.net>, Will Drewry <wad@chromium.org> SCM: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git seccomp Status: Supported Files: kernel/seccomp.c include/uapi/linux/seccomp.h include/linux/seccomp.h tools/testing/selftests/seccomp/* tools/testing/selftests/kselftest_harness.h userspace-api/seccomp_filter Content regex: \bsecure_computing \bTIF_SECCOMP\b Signed-off-by: Kees Cook <keescook@chromium.org> Signed-off-by: Jonathan Corbet <corbet@lwn.net>
198 lines
7.4 KiB
Python
Executable File
198 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
# -*- coding: utf-8; mode: python -*-
|
|
# pylint: disable=R0903, C0330, R0914, R0912, E0401
|
|
|
|
u"""
|
|
maintainers-include
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
Implementation of the ``maintainers-include`` reST-directive.
|
|
|
|
:copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org>
|
|
:license: GPL Version 2, June 1991 see linux/COPYING for details.
|
|
|
|
The ``maintainers-include`` reST-directive performs extensive parsing
|
|
specific to the Linux kernel's standard "MAINTAINERS" file, in an
|
|
effort to avoid needing to heavily mark up the original plain text.
|
|
"""
|
|
|
|
import sys
|
|
import re
|
|
import os.path
|
|
|
|
from docutils import statemachine
|
|
from docutils.utils.error_reporting import ErrorString
|
|
from docutils.parsers.rst import Directive
|
|
from docutils.parsers.rst.directives.misc import Include
|
|
|
|
__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 MaintainersInclude(Include):
|
|
u"""MaintainersInclude (``maintainers-include``) directive"""
|
|
required_arguments = 0
|
|
|
|
def parse_maintainers(self, path):
|
|
"""Parse all the MAINTAINERS lines into ReST for human-readability"""
|
|
|
|
result = list()
|
|
result.append(".. _maintainers:")
|
|
result.append("")
|
|
|
|
# Poor man's state machine.
|
|
descriptions = False
|
|
maintainers = False
|
|
subsystems = False
|
|
|
|
# Field letter to field name mapping.
|
|
field_letter = None
|
|
fields = dict()
|
|
|
|
prev = None
|
|
field_prev = ""
|
|
field_content = ""
|
|
|
|
for line in open(path):
|
|
if sys.version_info.major == 2:
|
|
line = unicode(line, 'utf-8')
|
|
# Have we reached the end of the preformatted Descriptions text?
|
|
if descriptions and line.startswith('Maintainers'):
|
|
descriptions = False
|
|
# Ensure a blank line following the last "|"-prefixed line.
|
|
result.append("")
|
|
|
|
# Start subsystem processing? This is to skip processing the text
|
|
# between the Maintainers heading and the first subsystem name.
|
|
if maintainers and not subsystems:
|
|
if re.search('^[A-Z0-9]', line):
|
|
subsystems = True
|
|
|
|
# Drop needless input whitespace.
|
|
line = line.rstrip()
|
|
|
|
# Linkify all non-wildcard refs to ReST files in Documentation/.
|
|
pat = '(Documentation/([^\s\?\*]*)\.rst)'
|
|
m = re.search(pat, line)
|
|
if m:
|
|
# maintainers.rst is in a subdirectory, so include "../".
|
|
line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line)
|
|
|
|
# Check state machine for output rendering behavior.
|
|
output = None
|
|
if descriptions:
|
|
# Escape the escapes in preformatted text.
|
|
output = "| %s" % (line.replace("\\", "\\\\"))
|
|
# Look for and record field letter to field name mappings:
|
|
# R: Designated *reviewer*: FullName <address@domain>
|
|
m = re.search("\s(\S):\s", line)
|
|
if m:
|
|
field_letter = m.group(1)
|
|
if field_letter and not field_letter in fields:
|
|
m = re.search("\*([^\*]+)\*", line)
|
|
if m:
|
|
fields[field_letter] = m.group(1)
|
|
elif subsystems:
|
|
# Skip empty lines: subsystem parser adds them as needed.
|
|
if len(line) == 0:
|
|
continue
|
|
# Subsystem fields are batched into "field_content"
|
|
if line[1] != ':':
|
|
# Render a subsystem entry as:
|
|
# SUBSYSTEM NAME
|
|
# ~~~~~~~~~~~~~~
|
|
|
|
# Flush pending field content.
|
|
output = field_content + "\n\n"
|
|
field_content = ""
|
|
|
|
# Collapse whitespace in subsystem name.
|
|
heading = re.sub("\s+", " ", line)
|
|
output = output + "%s\n%s" % (heading, "~" * len(heading))
|
|
field_prev = ""
|
|
else:
|
|
# Render a subsystem field as:
|
|
# :Field: entry
|
|
# entry...
|
|
field, details = line.split(':', 1)
|
|
details = details.strip()
|
|
|
|
# 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 == field_prev and field_prev in ['M', 'R', 'L']:
|
|
field_content = field_content + ","
|
|
|
|
# Do not repeat field names, so that field entries
|
|
# will be collapsed together.
|
|
if field != field_prev:
|
|
output = field_content + "\n"
|
|
field_content = ":%s:" % (fields.get(field, field))
|
|
field_content = field_content + "\n\t%s" % (details)
|
|
field_prev = field
|
|
else:
|
|
output = line
|
|
|
|
# Re-split on any added newlines in any above parsing.
|
|
if output != None:
|
|
for separated in output.split('\n'):
|
|
result.append(separated)
|
|
|
|
# Update the state machine when we find heading separators.
|
|
if line.startswith('----------'):
|
|
if prev.startswith('Descriptions'):
|
|
descriptions = True
|
|
if prev.startswith('Maintainers'):
|
|
maintainers = True
|
|
|
|
# Retain previous line for state machine transitions.
|
|
prev = line
|
|
|
|
# Flush pending field contents.
|
|
if field_content != "":
|
|
for separated in field_content.split('\n'):
|
|
result.append(separated)
|
|
|
|
output = "\n".join(result)
|
|
# For debugging the pre-rendered results...
|
|
#print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
|
|
|
|
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")
|
|
|
|
try:
|
|
self.state.document.settings.record_dependencies.add(path)
|
|
lines = self.parse_maintainers(path)
|
|
except IOError as error:
|
|
raise self.severe('Problems with "%s" directive path:\n%s.' %
|
|
(self.name, ErrorString(error)))
|
|
|
|
return []
|