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 # Have we reached the end of the preformatted Descriptions text?
65 if descriptions
and line
.startswith('Maintainers'):
67 # Ensure a blank line following the last "|"-prefixed line.
70 # Start subsystem processing? This is to skip processing the text
71 # between the Maintainers heading and the first subsystem name.
72 if maintainers
and not subsystems
:
73 if re
.search('^[A-Z0-9]', line
):
76 # Drop needless input whitespace.
79 # Linkify all non-wildcard refs to ReST files in Documentation/.
80 pat
= r
'(Documentation/([^\s\?\*]*)\.rst)'
81 m
= re
.search(pat
, line
)
83 # maintainers.rst is in a subdirectory, so include "../".
84 line
= re
.sub(pat
, ':doc:`%s <../%s>`' % (m
.group(2), m
.group(2)), line
)
86 # Check state machine for output rendering behavior.
89 # Escape the escapes in preformatted text.
90 output
= "| %s" % (line
.replace("\\", "\\\\"))
91 # Look for and record field letter to field name mappings:
92 # R: Designated *reviewer*: FullName <address@domain>
93 m
= re
.search(r
"\s(\S):\s", line
)
95 field_letter
= m
.group(1)
96 if field_letter
and not field_letter
in fields
:
97 m
= re
.search(r
"\*([^\*]+)\*", line
)
99 fields
[field_letter
] = m
.group(1)
101 # Skip empty lines: subsystem parser adds them as needed.
104 # Subsystem fields are batched into "field_content"
106 # Render a subsystem entry as:
110 # Flush pending field content.
111 output
= field_content
+ "\n\n"
114 # Collapse whitespace in subsystem name.
115 heading
= re
.sub(r
"\s+", " ", line
)
116 output
= output
+ "%s\n%s" % (heading
, "~" * len(heading
))
119 # Render a subsystem field as:
122 field
, details
= line
.split(':', 1)
123 details
= details
.strip()
125 # Mark paths (and regexes) as literal text for improved
126 # readability and to escape any escapes.
127 if field
in ['F', 'N', 'X', 'K']:
128 # But only if not already marked :)
129 if not ':doc:' in details
:
130 details
= '``%s``' % (details
)
132 # Comma separate email field continuations.
133 if field
== field_prev
and field_prev
in ['M', 'R', 'L']:
134 field_content
= field_content
+ ","
136 # Do not repeat field names, so that field entries
137 # will be collapsed together.
138 if field
!= field_prev
:
139 output
= field_content
+ "\n"
140 field_content
= ":%s:" % (fields
.get(field
, field
))
141 field_content
= field_content
+ "\n\t%s" % (details
)
146 # Re-split on any added newlines in any above parsing.
148 for separated
in output
.split('\n'):
149 result
.append(separated
)
151 # Update the state machine when we find heading separators.
152 if line
.startswith('----------'):
153 if prev
.startswith('Descriptions'):
155 if prev
.startswith('Maintainers'):
158 # Retain previous line for state machine transitions.
161 # Flush pending field contents.
162 if field_content
!= "":
163 for separated
in field_content
.split('\n'):
164 result
.append(separated
)
166 output
= "\n".join(result
)
167 # For debugging the pre-rendered results...
168 #print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
170 self
.state_machine
.insert_input(
171 statemachine
.string2lines(output
), path
)
174 """Include the MAINTAINERS file as part of this reST file."""
175 if not self
.state
.document
.settings
.file_insertion_enabled
:
176 raise self
.warning('"%s" directive disabled.' % self
.name
)
178 # Walk up source path directories to find Documentation/../
179 path
= self
.state_machine
.document
.attributes
['source']
180 path
= os
.path
.realpath(path
)
182 while tail
!= "Documentation" and tail
!= "":
183 (path
, tail
) = os
.path
.split(path
)
185 # Append "MAINTAINERS"
186 path
= os
.path
.join(path
, "MAINTAINERS")
189 self
.state
.document
.settings
.record_dependencies
.add(path
)
190 lines
= self
.parse_maintainers(path
)
191 except IOError as error
:
192 raise self
.severe('Problems with "%s" directive path:\n%s.' %
193 (self
.name
, ErrorString(error
)))