1 #!/usr/bin/env python 1 #!/usr/bin/env python 2 # SPDX-License-Identifier: GPL-2.0 2 # SPDX-License-Identifier: GPL-2.0 3 # -*- coding: utf-8; mode: python -*- 3 # -*- coding: utf-8; mode: python -*- 4 # pylint: disable=R0903, C0330, R0914, R0912, 4 # pylint: disable=R0903, C0330, R0914, R0912, E0401 5 5 6 u""" 6 u""" 7 maintainers-include 7 maintainers-include 8 ~~~~~~~~~~~~~~~~~~~ 8 ~~~~~~~~~~~~~~~~~~~ 9 9 10 Implementation of the ``maintainers-includ 10 Implementation of the ``maintainers-include`` reST-directive. 11 11 12 :copyright: Copyright (C) 2019 Kees Cook< 12 :copyright: Copyright (C) 2019 Kees Cook <keescook@chromium.org> 13 :license: GPL Version 2, June 1991 see 13 :license: GPL Version 2, June 1991 see linux/COPYING for details. 14 14 15 The ``maintainers-include`` reST-directive 15 The ``maintainers-include`` reST-directive performs extensive parsing 16 specific to the Linux kernel's standard "M 16 specific to the Linux kernel's standard "MAINTAINERS" file, in an 17 effort to avoid needing to heavily mark up 17 effort to avoid needing to heavily mark up the original plain text. 18 """ 18 """ 19 19 20 import sys 20 import sys 21 import re 21 import re 22 import os.path 22 import os.path 23 23 24 from docutils import statemachine 24 from docutils import statemachine 25 from docutils.utils.error_reporting import Err 25 from docutils.utils.error_reporting import ErrorString 26 from docutils.parsers.rst import Directive 26 from docutils.parsers.rst import Directive 27 from docutils.parsers.rst.directives.misc impo 27 from docutils.parsers.rst.directives.misc import Include 28 28 29 __version__ = '1.0' 29 __version__ = '1.0' 30 30 31 def setup(app): 31 def setup(app): 32 app.add_directive("maintainers-include", M 32 app.add_directive("maintainers-include", MaintainersInclude) 33 return dict( 33 return dict( 34 version = __version__, 34 version = __version__, 35 parallel_read_safe = True, 35 parallel_read_safe = True, 36 parallel_write_safe = True 36 parallel_write_safe = True 37 ) 37 ) 38 38 39 class MaintainersInclude(Include): 39 class MaintainersInclude(Include): 40 u"""MaintainersInclude (``maintainers-incl 40 u"""MaintainersInclude (``maintainers-include``) directive""" 41 required_arguments = 0 41 required_arguments = 0 42 42 43 def parse_maintainers(self, path): 43 def parse_maintainers(self, path): 44 """Parse all the MAINTAINERS lines int 44 """Parse all the MAINTAINERS lines into ReST for human-readability""" 45 45 46 result = list() 46 result = list() 47 result.append(".. _maintainers:") 47 result.append(".. _maintainers:") 48 result.append("") 48 result.append("") 49 49 50 # Poor man's state machine. 50 # Poor man's state machine. 51 descriptions = False 51 descriptions = False 52 maintainers = False 52 maintainers = False 53 subsystems = False 53 subsystems = False 54 54 55 # Field letter to field name mapping. 55 # Field letter to field name mapping. 56 field_letter = None 56 field_letter = None 57 fields = dict() 57 fields = dict() 58 58 59 prev = None 59 prev = None 60 field_prev = "" 60 field_prev = "" 61 field_content = "" 61 field_content = "" 62 62 63 for line in open(path): 63 for line in open(path): >> 64 if sys.version_info.major == 2: >> 65 line = unicode(line, 'utf-8') 64 # Have we reached the end of the p 66 # Have we reached the end of the preformatted Descriptions text? 65 if descriptions and line.startswit 67 if descriptions and line.startswith('Maintainers'): 66 descriptions = False 68 descriptions = False 67 # Ensure a blank line followin 69 # Ensure a blank line following the last "|"-prefixed line. 68 result.append("") 70 result.append("") 69 71 70 # Start subsystem processing? This 72 # Start subsystem processing? This is to skip processing the text 71 # between the Maintainers heading 73 # between the Maintainers heading and the first subsystem name. 72 if maintainers and not subsystems: 74 if maintainers and not subsystems: 73 if re.search('^[A-Z0-9]', line 75 if re.search('^[A-Z0-9]', line): 74 subsystems = True 76 subsystems = True 75 77 76 # Drop needless input whitespace. 78 # Drop needless input whitespace. 77 line = line.rstrip() 79 line = line.rstrip() 78 80 79 # Linkify all non-wildcard refs to 81 # Linkify all non-wildcard refs to ReST files in Documentation/. 80 pat = r'(Documentation/([^\s\?\*]* !! 82 pat = '(Documentation/([^\s\?\*]*)\.rst)' 81 m = re.search(pat, line) 83 m = re.search(pat, line) 82 if m: 84 if m: 83 # maintainers.rst is in a subd 85 # maintainers.rst is in a subdirectory, so include "../". 84 line = re.sub(pat, ':doc:`%s < 86 line = re.sub(pat, ':doc:`%s <../%s>`' % (m.group(2), m.group(2)), line) 85 87 86 # Check state machine for output r 88 # Check state machine for output rendering behavior. 87 output = None 89 output = None 88 if descriptions: 90 if descriptions: 89 # Escape the escapes in prefor 91 # Escape the escapes in preformatted text. 90 output = "| %s" % (line.replac 92 output = "| %s" % (line.replace("\\", "\\\\")) 91 # Look for and record field le 93 # Look for and record field letter to field name mappings: 92 # R: Designated *reviewer*: < 94 # R: Designated *reviewer*: FullName <address@domain> 93 m = re.search(r"\s(\S):\s", li !! 95 m = re.search("\s(\S):\s", line) 94 if m: 96 if m: 95 field_letter = m.group(1) 97 field_letter = m.group(1) 96 if field_letter and not field_ 98 if field_letter and not field_letter in fields: 97 m = re.search(r"\*([^\*]+) !! 99 m = re.search("\*([^\*]+)\*", line) 98 if m: 100 if m: 99 fields[field_letter] = 101 fields[field_letter] = m.group(1) 100 elif subsystems: 102 elif subsystems: 101 # Skip empty lines: subsystem 103 # Skip empty lines: subsystem parser adds them as needed. 102 if len(line) == 0: 104 if len(line) == 0: 103 continue 105 continue 104 # Subsystem fields are batched 106 # Subsystem fields are batched into "field_content" 105 if line[1] != ':': 107 if line[1] != ':': 106 # Render a subsystem entry 108 # Render a subsystem entry as: 107 # SUBSYSTEM NAME 109 # SUBSYSTEM NAME 108 # ~~~~~~~~~~~~~~ 110 # ~~~~~~~~~~~~~~ 109 111 110 # Flush pending field cont 112 # Flush pending field content. 111 output = field_content + " 113 output = field_content + "\n\n" 112 field_content = "" 114 field_content = "" 113 115 114 # Collapse whitespace in s 116 # Collapse whitespace in subsystem name. 115 heading = re.sub(r"\s+", " !! 117 heading = re.sub("\s+", " ", line) 116 output = output + "%s\n%s" 118 output = output + "%s\n%s" % (heading, "~" * len(heading)) 117 field_prev = "" 119 field_prev = "" 118 else: 120 else: 119 # Render a subsystem field 121 # Render a subsystem field as: 120 # :Field: entry 122 # :Field: entry 121 # entry... 123 # entry... 122 field, details = line.spli 124 field, details = line.split(':', 1) 123 details = details.strip() 125 details = details.strip() 124 126 125 # Mark paths (and regexes) 127 # Mark paths (and regexes) as literal text for improved 126 # readability and to escap 128 # readability and to escape any escapes. 127 if field in ['F', 'N', 'X' 129 if field in ['F', 'N', 'X', 'K']: 128 # But only if not alre 130 # But only if not already marked :) 129 if not ':doc:' in deta 131 if not ':doc:' in details: 130 details = '``%s``' 132 details = '``%s``' % (details) 131 133 132 # Comma separate email fie 134 # Comma separate email field continuations. 133 if field == field_prev and 135 if field == field_prev and field_prev in ['M', 'R', 'L']: 134 field_content = field_ 136 field_content = field_content + "," 135 137 136 # Do not repeat field name 138 # Do not repeat field names, so that field entries 137 # will be collapsed togeth 139 # will be collapsed together. 138 if field != field_prev: 140 if field != field_prev: 139 output = field_content 141 output = field_content + "\n" 140 field_content = ":%s:" 142 field_content = ":%s:" % (fields.get(field, field)) 141 field_content = field_cont 143 field_content = field_content + "\n\t%s" % (details) 142 field_prev = field 144 field_prev = field 143 else: 145 else: 144 output = line 146 output = line 145 147 146 # Re-split on any added newlines i 148 # Re-split on any added newlines in any above parsing. 147 if output != None: 149 if output != None: 148 for separated in output.split( 150 for separated in output.split('\n'): 149 result.append(separated) 151 result.append(separated) 150 152 151 # Update the state machine when we 153 # Update the state machine when we find heading separators. 152 if line.startswith('----------'): 154 if line.startswith('----------'): 153 if prev.startswith('Descriptio 155 if prev.startswith('Descriptions'): 154 descriptions = True 156 descriptions = True 155 if prev.startswith('Maintainer 157 if prev.startswith('Maintainers'): 156 maintainers = True 158 maintainers = True 157 159 158 # Retain previous line for state m 160 # Retain previous line for state machine transitions. 159 prev = line 161 prev = line 160 162 161 # Flush pending field contents. 163 # Flush pending field contents. 162 if field_content != "": 164 if field_content != "": 163 for separated in field_content.spl 165 for separated in field_content.split('\n'): 164 result.append(separated) 166 result.append(separated) 165 167 166 output = "\n".join(result) 168 output = "\n".join(result) 167 # For debugging the pre-rendered resul 169 # For debugging the pre-rendered results... 168 #print(output, file=open("/tmp/MAINTAI 170 #print(output, file=open("/tmp/MAINTAINERS.rst", "w")) 169 171 170 self.state_machine.insert_input( 172 self.state_machine.insert_input( 171 statemachine.string2lines(output), p 173 statemachine.string2lines(output), path) 172 174 173 def run(self): 175 def run(self): 174 """Include the MAINTAINERS file as par 176 """Include the MAINTAINERS file as part of this reST file.""" 175 if not self.state.document.settings.fi 177 if not self.state.document.settings.file_insertion_enabled: 176 raise self.warning('"%s" directive 178 raise self.warning('"%s" directive disabled.' % self.name) 177 179 178 # Walk up source path directories to f 180 # Walk up source path directories to find Documentation/../ 179 path = self.state_machine.document.att 181 path = self.state_machine.document.attributes['source'] 180 path = os.path.realpath(path) 182 path = os.path.realpath(path) 181 tail = path 183 tail = path 182 while tail != "Documentation" and tail 184 while tail != "Documentation" and tail != "": 183 (path, tail) = os.path.split(path) 185 (path, tail) = os.path.split(path) 184 186 185 # Append "MAINTAINERS" 187 # Append "MAINTAINERS" 186 path = os.path.join(path, "MAINTAINERS 188 path = os.path.join(path, "MAINTAINERS") 187 189 188 try: 190 try: 189 self.state.document.settings.recor 191 self.state.document.settings.record_dependencies.add(path) 190 lines = self.parse_maintainers(pat 192 lines = self.parse_maintainers(path) 191 except IOError as error: 193 except IOError as error: 192 raise self.severe('Problems with " 194 raise self.severe('Problems with "%s" directive path:\n%s.' % 193 (self.name, ErrorString( 195 (self.name, ErrorString(error))) 194 196 195 return [] 197 return []
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.