1# coding=utf-8 2# 3# Copyright © 2016 Intel Corporation 4# 5# Permission is hereby granted, free of charge, to any person obtaining a 6# copy of this software and associated documentation files (the "Software"), 7# to deal in the Software without restriction, including without limitation 8# the rights to use, copy, modify, merge, publish, distribute, sublicense, 9# and/or sell copies of the Software, and to permit persons to whom the 10# Software is furnished to do so, subject to the following conditions: 11# 12# The above copyright notice and this permission notice (including the next 13# paragraph) shall be included in all copies or substantial portions of the 14# Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22# IN THE SOFTWARE. 23# 24# Authors: 25# Jani Nikula <jani.nikula@intel.com> 26# 27# Please make sure this works on both python2 and python3. 28# 29 30import codecs 31import os 32import subprocess 33import sys 34import re 35import glob 36 37from docutils import nodes, statemachine 38from docutils.statemachine import ViewList 39from docutils.parsers.rst import directives, Directive 40import sphinx 41from sphinx.util.docutils import switch_source_input 42from sphinx.util import logging 43 44__version__ = '1.0' 45 46def cmd_str(cmd): 47 """ 48 Helper function to output a command line that can be used to produce 49 the same records via command line. Helpful to debug troubles at the 50 script. 51 """ 52 53 cmd_line = "" 54 55 for w in cmd: 56 if w == "" or " " in w: 57 esc_cmd = "'" + w + "'" 58 else: 59 esc_cmd = w 60 61 if cmd_line: 62 cmd_line += " " + esc_cmd 63 continue 64 else: 65 cmd_line = esc_cmd 66 67 return cmd_line 68 69class KernelDocDirective(Directive): 70 """Extract kernel-doc comments from the specified file""" 71 required_argument = 1 72 optional_arguments = 4 73 option_spec = { 74 'doc': directives.unchanged_required, 75 'export': directives.unchanged, 76 'internal': directives.unchanged, 77 'identifiers': directives.unchanged, 78 'no-identifiers': directives.unchanged, 79 'functions': directives.unchanged, 80 } 81 has_content = False 82 logger = logging.getLogger('kerneldoc') 83 verbose = 0 84 85 def run(self): 86 env = self.state.document.settings.env 87 cmd = [env.config.kerneldoc_bin, '-rst', '-enable-lineno'] 88 89 filename = env.config.kerneldoc_srctree + '/' + self.arguments[0] 90 export_file_patterns = [] 91 92 verbose = os.environ.get("V") 93 if verbose: 94 try: 95 self.verbose = int(verbose) 96 except ValueError: 97 pass 98 99 # Tell sphinx of the dependency 100 env.note_dependency(os.path.abspath(filename)) 101 102 tab_width = self.options.get('tab-width', self.state.document.settings.tab_width) 103 104 # 'function' is an alias of 'identifiers' 105 if 'functions' in self.options: 106 self.options['identifiers'] = self.options.get('functions') 107 108 # FIXME: make this nicer and more robust against errors 109 if 'export' in self.options: 110 cmd += ['-export'] 111 export_file_patterns = str(self.options.get('export')).split() 112 elif 'internal' in self.options: 113 cmd += ['-internal'] 114 export_file_patterns = str(self.options.get('internal')).split() 115 elif 'doc' in self.options: 116 cmd += ['-function', str(self.options.get('doc'))] 117 elif 'identifiers' in self.options: 118 identifiers = self.options.get('identifiers').split() 119 if identifiers: 120 for i in identifiers: 121 i = i.rstrip("\\").strip() 122 if not i: 123 continue 124 125 cmd += ['-function', i] 126 else: 127 cmd += ['-no-doc-sections'] 128 129 if 'no-identifiers' in self.options: 130 no_identifiers = self.options.get('no-identifiers').split() 131 if no_identifiers: 132 for i in no_identifiers: 133 i = i.rstrip("\\").strip() 134 if not i: 135 continue 136 137 cmd += ['-nosymbol', i] 138 139 for pattern in export_file_patterns: 140 pattern = pattern.rstrip("\\").strip() 141 if not pattern: 142 continue 143 144 for f in glob.glob(env.config.kerneldoc_srctree + '/' + pattern): 145 env.note_dependency(os.path.abspath(f)) 146 cmd += ['-export-file', f] 147 148 cmd += [filename] 149 150 if self.verbose >= 1: 151 print(cmd_str(cmd)) 152 153 try: 154 self.logger.verbose("calling kernel-doc '%s'" % (" ".join(cmd))) 155 156 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 157 out, err = p.communicate() 158 159 out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8') 160 161 if p.returncode != 0: 162 sys.stderr.write(err) 163 164 self.logger.warning("kernel-doc '%s' failed with return code %d" 165 % (" ".join(cmd), p.returncode)) 166 return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))] 167 elif env.config.kerneldoc_verbosity > 0: 168 sys.stderr.write(err) 169 170 lines = statemachine.string2lines(out, tab_width, convert_whitespace=True) 171 result = ViewList() 172 173 lineoffset = 0; 174 line_regex = re.compile(r"^\.\. LINENO ([0-9]+)$") 175 for line in lines: 176 match = line_regex.search(line) 177 if match: 178 # sphinx counts lines from 0 179 lineoffset = int(match.group(1)) - 1 180 # we must eat our comments since the upset the markup 181 else: 182 doc = str(env.srcdir) + "/" + env.docname + ":" + str(self.lineno) 183 result.append(line, doc + ": " + filename, lineoffset) 184 lineoffset += 1 185 186 node = nodes.section() 187 self.do_parse(result, node) 188 189 return node.children 190 191 except Exception as e: # pylint: disable=W0703 192 self.logger.warning("kernel-doc '%s' processing failed with: %s" % 193 (" ".join(cmd), str(e))) 194 return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))] 195 196 def do_parse(self, result, node): 197 with switch_source_input(self.state, result): 198 self.state.nested_parse(result, 0, node, match_titles=1) 199 200def setup(app): 201 app.add_config_value('kerneldoc_bin', None, 'env') 202 app.add_config_value('kerneldoc_srctree', None, 'env') 203 app.add_config_value('kerneldoc_verbosity', 1, 'env') 204 205 app.add_directive('kernel-doc', KernelDocDirective) 206 207 return dict( 208 version = __version__, 209 parallel_read_safe = True, 210 parallel_write_safe = True 211 ) 212