1# coding=utf-8 2# SPDX-License-Identifier: MIT 3# 4# Copyright © 2016 Intel Corporation 5# 6# Permission is hereby granted, free of charge, to any person obtaining a 7# copy of this software and associated documentation files (the "Software"), 8# to deal in the Software without restriction, including without limitation 9# the rights to use, copy, modify, merge, publish, distribute, sublicense, 10# and/or sell copies of the Software, and to permit persons to whom the 11# Software is furnished to do so, subject to the following conditions: 12# 13# The above copyright notice and this permission notice (including the next 14# paragraph) shall be included in all copies or substantial portions of the 15# Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23# IN THE SOFTWARE. 24# 25# Authors: 26# Jani Nikula <jani.nikula@intel.com> 27# 28 29import codecs 30import os 31import subprocess 32import sys 33import re 34import glob 35 36from docutils import nodes, statemachine 37from docutils.statemachine import ViewList 38from docutils.parsers.rst import directives, Directive 39import sphinx 40from sphinx.util.docutils import switch_source_input 41from sphinx.util import logging 42from pprint import pformat 43 44srctree = os.path.abspath(os.environ["srctree"]) 45sys.path.insert(0, os.path.join(srctree, "tools/lib/python")) 46 47from kdoc.kdoc_files import KernelFiles 48from kdoc.kdoc_output import RestFormat 49 50# Used when verbose is active to show how to reproduce kernel-doc 51# issues via command line 52kerneldoc_bin = "tools/docs/kernel-doc" 53 54__version__ = '1.0' 55kfiles = None 56logger = logging.getLogger(__name__) 57 58def cmd_str(cmd): 59 """ 60 Helper function to output a command line that can be used to produce 61 the same records via command line. Helpful to debug troubles at the 62 script. 63 """ 64 65 cmd_line = "" 66 67 for w in cmd: 68 if w == "" or " " in w: 69 esc_cmd = "'" + w + "'" 70 else: 71 esc_cmd = w 72 73 if cmd_line: 74 cmd_line += " " + esc_cmd 75 continue 76 else: 77 cmd_line = esc_cmd 78 79 return cmd_line 80 81class KernelDocDirective(Directive): 82 """Extract kernel-doc comments from the specified file""" 83 required_argument = 1 84 optional_arguments = 4 85 option_spec = { 86 'doc': directives.unchanged_required, 87 'export': directives.unchanged, 88 'internal': directives.unchanged, 89 'identifiers': directives.unchanged, 90 'no-identifiers': directives.unchanged, 91 'functions': directives.unchanged, 92 } 93 has_content = False 94 verbose = 0 95 96 parse_args = {} 97 msg_args = {} 98 99 def handle_args(self): 100 101 env = self.state.document.settings.env 102 cmd = [kerneldoc_bin, '-rst', '-enable-lineno'] 103 104 filename = env.config.kerneldoc_srctree + '/' + self.arguments[0] 105 106 # Arguments used by KernelFiles.parse() function 107 self.parse_args = { 108 "file_list": [filename], 109 "export_file": [] 110 } 111 112 # Arguments used by KernelFiles.msg() function 113 self.msg_args = { 114 "enable_lineno": True, 115 "export": False, 116 "internal": False, 117 "symbol": [], 118 "nosymbol": [], 119 "no_doc_sections": False 120 } 121 122 export_file_patterns = [] 123 124 verbose = os.environ.get("V") 125 if verbose: 126 try: 127 self.verbose = int(verbose) 128 except ValueError: 129 pass 130 131 # Tell sphinx of the dependency 132 env.note_dependency(os.path.abspath(filename)) 133 134 self.tab_width = self.options.get('tab-width', 135 self.state.document.settings.tab_width) 136 137 # 'function' is an alias of 'identifiers' 138 if 'functions' in self.options: 139 self.options['identifiers'] = self.options.get('functions') 140 141 # FIXME: make this nicer and more robust against errors 142 if 'export' in self.options: 143 cmd += ['-export'] 144 self.msg_args["export"] = True 145 export_file_patterns = str(self.options.get('export')).split() 146 elif 'internal' in self.options: 147 cmd += ['-internal'] 148 self.msg_args["internal"] = True 149 export_file_patterns = str(self.options.get('internal')).split() 150 elif 'doc' in self.options: 151 func = str(self.options.get('doc')) 152 cmd += ['-function', func] 153 self.msg_args["symbol"].append(func) 154 elif 'identifiers' in self.options: 155 identifiers = self.options.get('identifiers').split() 156 if identifiers: 157 for i in identifiers: 158 i = i.rstrip("\\").strip() 159 if not i: 160 continue 161 162 cmd += ['-function', i] 163 self.msg_args["symbol"].append(i) 164 else: 165 cmd += ['-no-doc-sections'] 166 self.msg_args["no_doc_sections"] = True 167 168 if 'no-identifiers' in self.options: 169 no_identifiers = self.options.get('no-identifiers').split() 170 if no_identifiers: 171 for i in no_identifiers: 172 i = i.rstrip("\\").strip() 173 if not i: 174 continue 175 176 cmd += ['-nosymbol', i] 177 self.msg_args["nosymbol"].append(i) 178 179 for pattern in export_file_patterns: 180 pattern = pattern.rstrip("\\").strip() 181 if not pattern: 182 continue 183 184 for f in glob.glob(env.config.kerneldoc_srctree + '/' + pattern): 185 env.note_dependency(os.path.abspath(f)) 186 cmd += ['-export-file', f] 187 self.parse_args["export_file"].append(f) 188 189 # Export file is needed by both parse and msg, as kernel-doc 190 # cache exports. 191 self.msg_args["export_file"] = self.parse_args["export_file"] 192 193 cmd += [filename] 194 195 return cmd 196 197 def parse_msg(self, filename, node, out): 198 """ 199 Handles a kernel-doc output for a given file 200 """ 201 202 env = self.state.document.settings.env 203 204 lines = statemachine.string2lines(out, self.tab_width, 205 convert_whitespace=True) 206 result = ViewList() 207 208 lineoffset = 0; 209 line_regex = re.compile(r"^\.\. LINENO ([0-9]+)$") 210 for line in lines: 211 match = line_regex.search(line) 212 if match: 213 # sphinx counts lines from 0 214 lineoffset = int(match.group(1)) - 1 215 # we must eat our comments since the upset the markup 216 else: 217 doc = str(env.srcdir) + "/" + env.docname + ":" + str(self.lineno) 218 result.append(line, doc + ": " + filename, lineoffset) 219 lineoffset += 1 220 221 self.do_parse(result, node) 222 223 def run_kdoc(self, kfiles): 224 """ 225 Execute kernel-doc classes directly instead of running as a separate 226 command. 227 """ 228 229 env = self.state.document.settings.env 230 231 node = nodes.section() 232 233 kfiles.parse(**self.parse_args) 234 filenames = self.parse_args["file_list"] 235 236 for filename, out in kfiles.msg(**self.msg_args, filenames=filenames): 237 self.parse_msg(filename, node, out) 238 239 return node.children 240 241 def run(self): 242 cmd = self.handle_args() 243 if self.verbose >= 1: 244 logger.info(cmd_str(cmd)) 245 246 try: 247 return self.run_kdoc(kfiles) 248 except Exception as e: # pylint: disable=W0703 249 logger.warning("kernel-doc '%s' processing failed with: %s" % 250 (cmd_str(cmd), pformat(e))) 251 return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))] 252 253 def do_parse(self, result, node): 254 with switch_source_input(self.state, result): 255 self.state.nested_parse(result, 0, node, match_titles=1) 256 257def setup_kfiles(app): 258 global kfiles 259 out_style = RestFormat() 260 kfiles = KernelFiles(out_style=out_style, logger=logger) 261 262 263def setup(app): 264 app.add_config_value('kerneldoc_srctree', None, 'env') 265 app.add_config_value('kerneldoc_verbosity', 1, 'env') 266 267 app.add_directive('kernel-doc', KernelDocDirective) 268 269 app.connect('builder-inited', setup_kfiles) 270 271 return dict( 272 version = __version__, 273 parallel_read_safe = True, 274 parallel_write_safe = True 275 ) 276