Back to home page

OSCL-LXR

 
 

    


0001 #!/usr/bin/env python
0002 # SPDX-License-Identifier: GPL-2.0
0003 # -*- coding: utf-8; mode: python -*-
0004 # pylint: disable=R0903, C0330, R0914, R0912, E0401
0005 
0006 u"""
0007     maintainers-include
0008     ~~~~~~~~~~~~~~~~~~~
0009 
0010     Implementation of the ``maintainers-include`` reST-directive.
0011 
0012     :copyright:  Copyright (C) 2019  Kees Cook <keescook@chromium.org>
0013     :license:    GPL Version 2, June 1991 see linux/COPYING for details.
0014 
0015     The ``maintainers-include`` reST-directive performs extensive parsing
0016     specific to the Linux kernel's standard "MAINTAINERS" file, in an
0017     effort to avoid needing to heavily mark up the original plain text.
0018 """
0019 
0020 import sys
0021 import re
0022 import os.path
0023 
0024 from docutils import statemachine
0025 from docutils.utils.error_reporting import ErrorString
0026 from docutils.parsers.rst import Directive
0027 from docutils.parsers.rst.directives.misc import Include
0028 
0029 __version__  = '1.0'
0030 
0031 def setup(app):
0032     app.add_directive("maintainers-include", MaintainersInclude)
0033     return dict(
0034         version = __version__,
0035         parallel_read_safe = True,
0036         parallel_write_safe = True
0037     )
0038 
0039 class MaintainersInclude(Include):
0040     u"""MaintainersInclude (``maintainers-include``) directive"""
0041     required_arguments = 0
0042 
0043     def parse_maintainers(self, path):
0044         """Parse all the MAINTAINERS lines into ReST for human-readability"""
0045 
0046         result = list()
0047         result.append(".. _maintainers:")
0048         result.append("")
0049 
0050         # Poor man's state machine.
0051         descriptions = False
0052         maintainers = False
0053         subsystems = False
0054 
0055         # Field letter to field name mapping.
0056         field_letter = None
0057         fields = dict()
0058 
0059         prev = None
0060         field_prev = ""
0061         field_content = ""
0062 
0063         for line in open(path):
0064             # Have we reached the end of the preformatted Descriptions text?
0065             if descriptions and line.startswith('Maintainers'):
0066                 descriptions = False
0067                 # Ensure a blank line following the last "|"-prefixed line.
0068                 result.append("")
0069 
0070             # Start subsystem processing? This is to skip processing the text
0071             # between the Maintainers heading and the first subsystem name.
0072             if maintainers and not subsystems:
0073                 if re.search('^[A-Z0-9]', line):
0074                     subsystems = True
0075 
0076             # Drop needless input whitespace.
0077             line = line.rstrip()
0078 
0079             # Linkify all non-wildcard refs to ReST files in Documentation/.
0080             pat = '(Documentation/([^\s\?\*]*)\.rst)'
0081             m = re.search(pat, line)
0082             if m:
0083                 # maintainers.rst is in a subdirectory, so include "../".
0084                 line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line)
0085 
0086             # Check state machine for output rendering behavior.
0087             output = None
0088             if descriptions:
0089                 # Escape the escapes in preformatted text.
0090                 output = "| %s" % (line.replace("\\", "\\\\"))
0091                 # Look for and record field letter to field name mappings:
0092                 #   R: Designated *reviewer*: FullName <address@domain>
0093                 m = re.search("\s(\S):\s", line)
0094                 if m:
0095                     field_letter = m.group(1)
0096                 if field_letter and not field_letter in fields:
0097                     m = re.search("\*([^\*]+)\*", line)
0098                     if m:
0099                         fields[field_letter] = m.group(1)
0100             elif subsystems:
0101                 # Skip empty lines: subsystem parser adds them as needed.
0102                 if len(line) == 0:
0103                     continue
0104                 # Subsystem fields are batched into "field_content"
0105                 if line[1] != ':':
0106                     # Render a subsystem entry as:
0107                     #   SUBSYSTEM NAME
0108                     #   ~~~~~~~~~~~~~~
0109 
0110                     # Flush pending field content.
0111                     output = field_content + "\n\n"
0112                     field_content = ""
0113 
0114                     # Collapse whitespace in subsystem name.
0115                     heading = re.sub("\s+", " ", line)
0116                     output = output + "%s\n%s" % (heading, "~" * len(heading))
0117                     field_prev = ""
0118                 else:
0119                     # Render a subsystem field as:
0120                     #   :Field: entry
0121                     #           entry...
0122                     field, details = line.split(':', 1)
0123                     details = details.strip()
0124 
0125                     # Mark paths (and regexes) as literal text for improved
0126                     # readability and to escape any escapes.
0127                     if field in ['F', 'N', 'X', 'K']:
0128                         # But only if not already marked :)
0129                         if not ':doc:' in details:
0130                             details = '``%s``' % (details)
0131 
0132                     # Comma separate email field continuations.
0133                     if field == field_prev and field_prev in ['M', 'R', 'L']:
0134                         field_content = field_content + ","
0135 
0136                     # Do not repeat field names, so that field entries
0137                     # will be collapsed together.
0138                     if field != field_prev:
0139                         output = field_content + "\n"
0140                         field_content = ":%s:" % (fields.get(field, field))
0141                     field_content = field_content + "\n\t%s" % (details)
0142                     field_prev = field
0143             else:
0144                 output = line
0145 
0146             # Re-split on any added newlines in any above parsing.
0147             if output != None:
0148                 for separated in output.split('\n'):
0149                     result.append(separated)
0150 
0151             # Update the state machine when we find heading separators.
0152             if line.startswith('----------'):
0153                 if prev.startswith('Descriptions'):
0154                     descriptions = True
0155                 if prev.startswith('Maintainers'):
0156                     maintainers = True
0157 
0158             # Retain previous line for state machine transitions.
0159             prev = line
0160 
0161         # Flush pending field contents.
0162         if field_content != "":
0163             for separated in field_content.split('\n'):
0164                 result.append(separated)
0165 
0166         output = "\n".join(result)
0167         # For debugging the pre-rendered results...
0168         #print(output, file=open("/tmp/MAINTAINERS.rst", "w"))
0169 
0170         self.state_machine.insert_input(
0171           statemachine.string2lines(output), path)
0172 
0173     def run(self):
0174         """Include the MAINTAINERS file as part of this reST file."""
0175         if not self.state.document.settings.file_insertion_enabled:
0176             raise self.warning('"%s" directive disabled.' % self.name)
0177 
0178         # Walk up source path directories to find Documentation/../
0179         path = self.state_machine.document.attributes['source']
0180         path = os.path.realpath(path)
0181         tail = path
0182         while tail != "Documentation" and tail != "":
0183             (path, tail) = os.path.split(path)
0184 
0185         # Append "MAINTAINERS"
0186         path = os.path.join(path, "MAINTAINERS")
0187 
0188         try:
0189             self.state.document.settings.record_dependencies.add(path)
0190             lines = self.parse_maintainers(path)
0191         except IOError as error:
0192             raise self.severe('Problems with "%s" directive path:\n%s.' %
0193                       (self.name, ErrorString(error)))
0194 
0195         return []