Merge tag 'v5.11-rc1' into spi-5.11
[linux-2.6-microblaze.git] / Documentation / sphinx / maintainers_include.py
1 #!/usr/bin/env python
2 # SPDX-License-Identifier: GPL-2.0
3 # -*- coding: utf-8; mode: python -*-
4 # pylint: disable=R0903, C0330, R0914, R0912, E0401
5
6 u"""
7     maintainers-include
8     ~~~~~~~~~~~~~~~~~~~
9
10     Implementation of the ``maintainers-include`` reST-directive.
11
12     :copyright:  Copyright (C) 2019  Kees Cook <keescook@chromium.org>
13     :license:    GPL Version 2, June 1991 see linux/COPYING for details.
14
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.
18 """
19
20 import sys
21 import re
22 import os.path
23
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
28
29 __version__  = '1.0'
30
31 def setup(app):
32     app.add_directive("maintainers-include", MaintainersInclude)
33     return dict(
34         version = __version__,
35         parallel_read_safe = True,
36         parallel_write_safe = True
37     )
38
39 class MaintainersInclude(Include):
40     u"""MaintainersInclude (``maintainers-include``) directive"""
41     required_arguments = 0
42
43     def parse_maintainers(self, path):
44         """Parse all the MAINTAINERS lines into ReST for human-readability"""
45
46         result = list()
47         result.append(".. _maintainers:")
48         result.append("")
49
50         # Poor man's state machine.
51         descriptions = False
52         maintainers = False
53         subsystems = False
54
55         # Field letter to field name mapping.
56         field_letter = None
57         fields = dict()
58
59         prev = None
60         field_prev = ""
61         field_content = ""
62
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'):
68                 descriptions = False
69                 # Ensure a blank line following the last "|"-prefixed line.
70                 result.append("")
71
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):
76                     subsystems = True
77
78             # Drop needless input whitespace.
79             line = line.rstrip()
80
81             # Linkify all non-wildcard refs to ReST files in Documentation/.
82             pat = '(Documentation/([^\s\?\*]*)\.rst)'
83             m = re.search(pat, line)
84             if m:
85                 # maintainers.rst is in a subdirectory, so include "../".
86                 line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line)
87
88             # Check state machine for output rendering behavior.
89             output = None
90             if descriptions:
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)
96                 if m:
97                     field_letter = m.group(1)
98                 if field_letter and not field_letter in fields:
99                     m = re.search("\*([^\*]+)\*", line)
100                     if m:
101                         fields[field_letter] = m.group(1)
102             elif subsystems:
103                 # Skip empty lines: subsystem parser adds them as needed.
104                 if len(line) == 0:
105                     continue
106                 # Subsystem fields are batched into "field_content"
107                 if line[1] != ':':
108                     # Render a subsystem entry as:
109                     #   SUBSYSTEM NAME
110                     #   ~~~~~~~~~~~~~~
111
112                     # Flush pending field content.
113                     output = field_content + "\n\n"
114                     field_content = ""
115
116                     # Collapse whitespace in subsystem name.
117                     heading = re.sub("\s+", " ", line)
118                     output = output + "%s\n%s" % (heading, "~" * len(heading))
119                     field_prev = ""
120                 else:
121                     # Render a subsystem field as:
122                     #   :Field: entry
123                     #           entry...
124                     field, details = line.split(':', 1)
125                     details = details.strip()
126
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)
133
134                     # Comma separate email field continuations.
135                     if field == field_prev and field_prev in ['M', 'R', 'L']:
136                         field_content = field_content + ","
137
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)
144                     field_prev = field
145             else:
146                 output = line
147
148             # Re-split on any added newlines in any above parsing.
149             if output != None:
150                 for separated in output.split('\n'):
151                     result.append(separated)
152
153             # Update the state machine when we find heading separators.
154             if line.startswith('----------'):
155                 if prev.startswith('Descriptions'):
156                     descriptions = True
157                 if prev.startswith('Maintainers'):
158                     maintainers = True
159
160             # Retain previous line for state machine transitions.
161             prev = line
162
163         # Flush pending field contents.
164         if field_content != "":
165             for separated in field_content.split('\n'):
166                 result.append(separated)
167
168         output = "\n".join(result)
169         # For debugging the pre-rendered results...
170         #print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
171
172         self.state_machine.insert_input(
173           statemachine.string2lines(output), path)
174
175     def run(self):
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)
179
180         # Walk up source path directories to find Documentation/../
181         path = self.state_machine.document.attributes['source']
182         path = os.path.realpath(path)
183         tail = path
184         while tail != "Documentation" and tail != "":
185             (path, tail) = os.path.split(path)
186
187         # Append "MAINTAINERS"
188         path = os.path.join(path, "MAINTAINERS")
189
190         try:
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)))
196
197         return []