2 # SPDX-License-Identifier: GPL-2.0
3 # -*- coding: utf-8; mode: python -*-
4 # pylint: disable=R0903, C0330, R0914, R0912, E0401
10 Implementation of the ``maintainers-include`` reST-directive.
12 :copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org>
13 :license: GPL Version 2, June 1991 see linux/COPYING for details.
15 The ``maintainers-include`` reST-directive performs extensive parsing
16 specific to the Linux kernel's standard "MAINTAINERS" file, in an
17 effort to avoid needing to heavily mark up the original plain text.
24 from docutils import statemachine
25 from docutils.utils.error_reporting import ErrorString
26 from docutils.parsers.rst import Directive
27 from docutils.parsers.rst.directives.misc import Include
32 app.add_directive("maintainers-include", MaintainersInclude)
34 version = __version__,
35 parallel_read_safe = True,
36 parallel_write_safe = True
39 class MaintainersInclude(Include):
40 u"""MaintainersInclude (``maintainers-include``) directive"""
41 required_arguments = 0
43 def parse_maintainers(self, path):
44 """Parse all the MAINTAINERS lines into ReST for human-readability"""
47 result.append(".. _maintainers:")
50 # Poor man's state machine.
55 # Field letter to field name mapping.
63 for line in open(path):
64 if sys.version_info.major == 2:
65 line = unicode(line, 'utf-8')
66 # Have we reached the end of the preformatted Descriptions text?
67 if descriptions and line.startswith('Maintainers'):
69 # Ensure a blank line following the last "|"-prefixed line.
72 # Start subsystem processing? This is to skip processing the text
73 # between the Maintainers heading and the first subsystem name.
74 if maintainers and not subsystems:
75 if re.search('^[A-Z0-9]', line):
78 # Drop needless input whitespace.
81 # Linkify all non-wildcard refs to ReST files in Documentation/.
82 pat = '(Documentation/([^\s\?\*]*)\.rst)'
83 m = re.search(pat, line)
85 # maintainers.rst is in a subdirectory, so include "../".
86 line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line)
88 # Check state machine for output rendering behavior.
91 # Escape the escapes in preformatted text.
92 output = "| %s" % (line.replace("\\", "\\\\"))
93 # Look for and record field letter to field name mappings:
94 # R: Designated *reviewer*: FullName <address@domain>
95 m = re.search("\s(\S):\s", line)
97 field_letter = m.group(1)
98 if field_letter and not field_letter in fields:
99 m = re.search("\*([^\*]+)\*", line)
101 fields[field_letter] = m.group(1)
103 # Skip empty lines: subsystem parser adds them as needed.
106 # Subsystem fields are batched into "field_content"
108 # Render a subsystem entry as:
112 # Flush pending field content.
113 output = field_content + "\n\n"
116 # Collapse whitespace in subsystem name.
117 heading = re.sub("\s+", " ", line)
118 output = output + "%s\n%s" % (heading, "~" * len(heading))
121 # Render a subsystem field as:
124 field, details = line.split(':', 1)
125 details = details.strip()
127 # Mark paths (and regexes) as literal text for improved
128 # readability and to escape any escapes.
129 if field in ['F', 'N', 'X', 'K']:
130 # But only if not already marked :)
131 if not ':doc:' in details:
132 details = '``%s``' % (details)
134 # Comma separate email field continuations.
135 if field == field_prev and field_prev in ['M', 'R', 'L']:
136 field_content = field_content + ","
138 # Do not repeat field names, so that field entries
139 # will be collapsed together.
140 if field != field_prev:
141 output = field_content + "\n"
142 field_content = ":%s:" % (fields.get(field, field))
143 field_content = field_content + "\n\t%s" % (details)
148 # Re-split on any added newlines in any above parsing.
150 for separated in output.split('\n'):
151 result.append(separated)
153 # Update the state machine when we find heading separators.
154 if line.startswith('----------'):
155 if prev.startswith('Descriptions'):
157 if prev.startswith('Maintainers'):
160 # Retain previous line for state machine transitions.
163 # Flush pending field contents.
164 if field_content != "":
165 for separated in field_content.split('\n'):
166 result.append(separated)
168 output = "\n".join(result)
169 # For debugging the pre-rendered results...
170 #print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
172 self.state_machine.insert_input(
173 statemachine.string2lines(output), path)
176 """Include the MAINTAINERS file as part of this reST file."""
177 if not self.state.document.settings.file_insertion_enabled:
178 raise self.warning('"%s" directive disabled.' % self.name)
180 # Walk up source path directories to find Documentation/../
181 path = self.state_machine.document.attributes['source']
182 path = os.path.realpath(path)
184 while tail != "Documentation" and tail != "":
185 (path, tail) = os.path.split(path)
187 # Append "MAINTAINERS"
188 path = os.path.join(path, "MAINTAINERS")
191 self.state.document.settings.record_dependencies.add(path)
192 lines = self.parse_maintainers(path)
193 except IOError as error:
194 raise self.severe('Problems with "%s" directive path:\n%s.' %
195 (self.name, ErrorString(error)))