162306a36Sopenharmony_ci#!/usr/bin/env python 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 362306a36Sopenharmony_ci# -*- coding: utf-8; mode: python -*- 462306a36Sopenharmony_ci# pylint: disable=R0903, C0330, R0914, R0912, E0401 562306a36Sopenharmony_ci 662306a36Sopenharmony_ciu""" 762306a36Sopenharmony_ci maintainers-include 862306a36Sopenharmony_ci ~~~~~~~~~~~~~~~~~~~ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci Implementation of the ``maintainers-include`` reST-directive. 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci :copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org> 1362306a36Sopenharmony_ci :license: GPL Version 2, June 1991 see linux/COPYING for details. 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci The ``maintainers-include`` reST-directive performs extensive parsing 1662306a36Sopenharmony_ci specific to the Linux kernel's standard "MAINTAINERS" file, in an 1762306a36Sopenharmony_ci effort to avoid needing to heavily mark up the original plain text. 1862306a36Sopenharmony_ci""" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciimport sys 2162306a36Sopenharmony_ciimport re 2262306a36Sopenharmony_ciimport os.path 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cifrom docutils import statemachine 2562306a36Sopenharmony_cifrom docutils.utils.error_reporting import ErrorString 2662306a36Sopenharmony_cifrom docutils.parsers.rst import Directive 2762306a36Sopenharmony_cifrom docutils.parsers.rst.directives.misc import Include 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci__version__ = '1.0' 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cidef setup(app): 3262306a36Sopenharmony_ci app.add_directive("maintainers-include", MaintainersInclude) 3362306a36Sopenharmony_ci return dict( 3462306a36Sopenharmony_ci version = __version__, 3562306a36Sopenharmony_ci parallel_read_safe = True, 3662306a36Sopenharmony_ci parallel_write_safe = True 3762306a36Sopenharmony_ci ) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ciclass MaintainersInclude(Include): 4062306a36Sopenharmony_ci u"""MaintainersInclude (``maintainers-include``) directive""" 4162306a36Sopenharmony_ci required_arguments = 0 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci def parse_maintainers(self, path): 4462306a36Sopenharmony_ci """Parse all the MAINTAINERS lines into ReST for human-readability""" 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci result = list() 4762306a36Sopenharmony_ci result.append(".. _maintainers:") 4862306a36Sopenharmony_ci result.append("") 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci # Poor man's state machine. 5162306a36Sopenharmony_ci descriptions = False 5262306a36Sopenharmony_ci maintainers = False 5362306a36Sopenharmony_ci subsystems = False 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci # Field letter to field name mapping. 5662306a36Sopenharmony_ci field_letter = None 5762306a36Sopenharmony_ci fields = dict() 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci prev = None 6062306a36Sopenharmony_ci field_prev = "" 6162306a36Sopenharmony_ci field_content = "" 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci for line in open(path): 6462306a36Sopenharmony_ci # Have we reached the end of the preformatted Descriptions text? 6562306a36Sopenharmony_ci if descriptions and line.startswith('Maintainers'): 6662306a36Sopenharmony_ci descriptions = False 6762306a36Sopenharmony_ci # Ensure a blank line following the last "|"-prefixed line. 6862306a36Sopenharmony_ci result.append("") 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci # Start subsystem processing? This is to skip processing the text 7162306a36Sopenharmony_ci # between the Maintainers heading and the first subsystem name. 7262306a36Sopenharmony_ci if maintainers and not subsystems: 7362306a36Sopenharmony_ci if re.search('^[A-Z0-9]', line): 7462306a36Sopenharmony_ci subsystems = True 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci # Drop needless input whitespace. 7762306a36Sopenharmony_ci line = line.rstrip() 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci # Linkify all non-wildcard refs to ReST files in Documentation/. 8062306a36Sopenharmony_ci pat = r'(Documentation/([^\s\?\*]*)\.rst)' 8162306a36Sopenharmony_ci m = re.search(pat, line) 8262306a36Sopenharmony_ci if m: 8362306a36Sopenharmony_ci # maintainers.rst is in a subdirectory, so include "../". 8462306a36Sopenharmony_ci line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line) 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci # Check state machine for output rendering behavior. 8762306a36Sopenharmony_ci output = None 8862306a36Sopenharmony_ci if descriptions: 8962306a36Sopenharmony_ci # Escape the escapes in preformatted text. 9062306a36Sopenharmony_ci output = "| %s" % (line.replace("\\", "\\\\")) 9162306a36Sopenharmony_ci # Look for and record field letter to field name mappings: 9262306a36Sopenharmony_ci # R: Designated *reviewer*: FullName <address@domain> 9362306a36Sopenharmony_ci m = re.search(r"\s(\S):\s", line) 9462306a36Sopenharmony_ci if m: 9562306a36Sopenharmony_ci field_letter = m.group(1) 9662306a36Sopenharmony_ci if field_letter and not field_letter in fields: 9762306a36Sopenharmony_ci m = re.search(r"\*([^\*]+)\*", line) 9862306a36Sopenharmony_ci if m: 9962306a36Sopenharmony_ci fields[field_letter] = m.group(1) 10062306a36Sopenharmony_ci elif subsystems: 10162306a36Sopenharmony_ci # Skip empty lines: subsystem parser adds them as needed. 10262306a36Sopenharmony_ci if len(line) == 0: 10362306a36Sopenharmony_ci continue 10462306a36Sopenharmony_ci # Subsystem fields are batched into "field_content" 10562306a36Sopenharmony_ci if line[1] != ':': 10662306a36Sopenharmony_ci # Render a subsystem entry as: 10762306a36Sopenharmony_ci # SUBSYSTEM NAME 10862306a36Sopenharmony_ci # ~~~~~~~~~~~~~~ 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci # Flush pending field content. 11162306a36Sopenharmony_ci output = field_content + "\n\n" 11262306a36Sopenharmony_ci field_content = "" 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci # Collapse whitespace in subsystem name. 11562306a36Sopenharmony_ci heading = re.sub(r"\s+", " ", line) 11662306a36Sopenharmony_ci output = output + "%s\n%s" % (heading, "~" * len(heading)) 11762306a36Sopenharmony_ci field_prev = "" 11862306a36Sopenharmony_ci else: 11962306a36Sopenharmony_ci # Render a subsystem field as: 12062306a36Sopenharmony_ci # :Field: entry 12162306a36Sopenharmony_ci # entry... 12262306a36Sopenharmony_ci field, details = line.split(':', 1) 12362306a36Sopenharmony_ci details = details.strip() 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci # Mark paths (and regexes) as literal text for improved 12662306a36Sopenharmony_ci # readability and to escape any escapes. 12762306a36Sopenharmony_ci if field in ['F', 'N', 'X', 'K']: 12862306a36Sopenharmony_ci # But only if not already marked :) 12962306a36Sopenharmony_ci if not ':doc:' in details: 13062306a36Sopenharmony_ci details = '``%s``' % (details) 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci # Comma separate email field continuations. 13362306a36Sopenharmony_ci if field == field_prev and field_prev in ['M', 'R', 'L']: 13462306a36Sopenharmony_ci field_content = field_content + "," 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci # Do not repeat field names, so that field entries 13762306a36Sopenharmony_ci # will be collapsed together. 13862306a36Sopenharmony_ci if field != field_prev: 13962306a36Sopenharmony_ci output = field_content + "\n" 14062306a36Sopenharmony_ci field_content = ":%s:" % (fields.get(field, field)) 14162306a36Sopenharmony_ci field_content = field_content + "\n\t%s" % (details) 14262306a36Sopenharmony_ci field_prev = field 14362306a36Sopenharmony_ci else: 14462306a36Sopenharmony_ci output = line 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci # Re-split on any added newlines in any above parsing. 14762306a36Sopenharmony_ci if output != None: 14862306a36Sopenharmony_ci for separated in output.split('\n'): 14962306a36Sopenharmony_ci result.append(separated) 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci # Update the state machine when we find heading separators. 15262306a36Sopenharmony_ci if line.startswith('----------'): 15362306a36Sopenharmony_ci if prev.startswith('Descriptions'): 15462306a36Sopenharmony_ci descriptions = True 15562306a36Sopenharmony_ci if prev.startswith('Maintainers'): 15662306a36Sopenharmony_ci maintainers = True 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci # Retain previous line for state machine transitions. 15962306a36Sopenharmony_ci prev = line 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci # Flush pending field contents. 16262306a36Sopenharmony_ci if field_content != "": 16362306a36Sopenharmony_ci for separated in field_content.split('\n'): 16462306a36Sopenharmony_ci result.append(separated) 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci output = "\n".join(result) 16762306a36Sopenharmony_ci # For debugging the pre-rendered results... 16862306a36Sopenharmony_ci #print(output, file=open("/tmp/MAINTAINERS.rst", "w")) 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci self.state_machine.insert_input( 17162306a36Sopenharmony_ci statemachine.string2lines(output), path) 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci def run(self): 17462306a36Sopenharmony_ci """Include the MAINTAINERS file as part of this reST file.""" 17562306a36Sopenharmony_ci if not self.state.document.settings.file_insertion_enabled: 17662306a36Sopenharmony_ci raise self.warning('"%s" directive disabled.' % self.name) 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci # Walk up source path directories to find Documentation/../ 17962306a36Sopenharmony_ci path = self.state_machine.document.attributes['source'] 18062306a36Sopenharmony_ci path = os.path.realpath(path) 18162306a36Sopenharmony_ci tail = path 18262306a36Sopenharmony_ci while tail != "Documentation" and tail != "": 18362306a36Sopenharmony_ci (path, tail) = os.path.split(path) 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci # Append "MAINTAINERS" 18662306a36Sopenharmony_ci path = os.path.join(path, "MAINTAINERS") 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci try: 18962306a36Sopenharmony_ci self.state.document.settings.record_dependencies.add(path) 19062306a36Sopenharmony_ci lines = self.parse_maintainers(path) 19162306a36Sopenharmony_ci except IOError as error: 19262306a36Sopenharmony_ci raise self.severe('Problems with "%s" directive path:\n%s.' % 19362306a36Sopenharmony_ci (self.name, ErrorString(error))) 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci return [] 196