File Index
Files and directories
Sign in
FILE

kerneldoc.py

File preview with syntax highlighting.

linux-6.19.14/Documentation/sphinx/kerneldoc.py PYTHON
  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__version__  = '1.0'
 51kfiles = None
 52logger = logging.getLogger(__name__)
 53
 54def cmd_str(cmd):
 55    """
 56    Helper function to output a command line that can be used to produce
 57    the same records via command line. Helpful to debug troubles at the
 58    script.
 59    """
 60
 61    cmd_line = ""
 62
 63    for w in cmd:
 64        if w == "" or " " in w:
 65            esc_cmd = "'" + w + "'"
 66        else:
 67            esc_cmd = w
 68
 69        if cmd_line:
 70            cmd_line += " " + esc_cmd
 71            continue
 72        else:
 73            cmd_line = esc_cmd
 74
 75    return cmd_line
 76
 77class KernelDocDirective(Directive):
 78    """Extract kernel-doc comments from the specified file"""
 79    required_argument = 1
 80    optional_arguments = 4
 81    option_spec = {
 82        'doc': directives.unchanged_required,
 83        'export': directives.unchanged,
 84        'internal': directives.unchanged,
 85        'identifiers': directives.unchanged,
 86        'no-identifiers': directives.unchanged,
 87        'functions': directives.unchanged,
 88    }
 89    has_content = False
 90    verbose = 0
 91
 92    parse_args = {}
 93    msg_args = {}
 94
 95    def handle_args(self):
 96
 97        env = self.state.document.settings.env
 98        cmd = [env.config.kerneldoc_bin, '-rst', '-enable-lineno']
 99
100        filename = env.config.kerneldoc_srctree + '/' + self.arguments[0]
101
102        # Arguments used by KernelFiles.parse() function
103        self.parse_args = {
104            "file_list": [filename],
105            "export_file": []
106        }
107
108        # Arguments used by KernelFiles.msg() function
109        self.msg_args = {
110            "enable_lineno": True,
111            "export": False,
112            "internal": False,
113            "symbol": [],
114            "nosymbol": [],
115            "no_doc_sections": False
116        }
117
118        export_file_patterns = []
119
120        verbose = os.environ.get("V")
121        if verbose:
122            try:
123                self.verbose = int(verbose)
124            except ValueError:
125                pass
126
127        # Tell sphinx of the dependency
128        env.note_dependency(os.path.abspath(filename))
129
130        self.tab_width = self.options.get('tab-width',
131                                          self.state.document.settings.tab_width)
132
133        # 'function' is an alias of 'identifiers'
134        if 'functions' in self.options:
135            self.options['identifiers'] = self.options.get('functions')
136
137        # FIXME: make this nicer and more robust against errors
138        if 'export' in self.options:
139            cmd += ['-export']
140            self.msg_args["export"] = True
141            export_file_patterns = str(self.options.get('export')).split()
142        elif 'internal' in self.options:
143            cmd += ['-internal']
144            self.msg_args["internal"] = True
145            export_file_patterns = str(self.options.get('internal')).split()
146        elif 'doc' in self.options:
147            func = str(self.options.get('doc'))
148            cmd += ['-function', func]
149            self.msg_args["symbol"].append(func)
150        elif 'identifiers' in self.options:
151            identifiers = self.options.get('identifiers').split()
152            if identifiers:
153                for i in identifiers:
154                    i = i.rstrip("\\").strip()
155                    if not i:
156                        continue
157
158                    cmd += ['-function', i]
159                    self.msg_args["symbol"].append(i)
160            else:
161                cmd += ['-no-doc-sections']
162                self.msg_args["no_doc_sections"] = True
163
164        if 'no-identifiers' in self.options:
165            no_identifiers = self.options.get('no-identifiers').split()
166            if no_identifiers:
167                for i in no_identifiers:
168                    i = i.rstrip("\\").strip()
169                    if not i:
170                        continue
171
172                    cmd += ['-nosymbol', i]
173                    self.msg_args["nosymbol"].append(i)
174
175        for pattern in export_file_patterns:
176            pattern = pattern.rstrip("\\").strip()
177            if not pattern:
178                continue
179
180            for f in glob.glob(env.config.kerneldoc_srctree + '/' + pattern):
181                env.note_dependency(os.path.abspath(f))
182                cmd += ['-export-file', f]
183                self.parse_args["export_file"].append(f)
184
185            # Export file is needed by both parse and msg, as kernel-doc
186            # cache exports.
187            self.msg_args["export_file"] = self.parse_args["export_file"]
188
189        cmd += [filename]
190
191        return cmd
192
193    def run_cmd(self, cmd):
194        """
195        Execute an external kernel-doc command.
196        """
197
198        env = self.state.document.settings.env
199        node = nodes.section()
200
201        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
202        out, err = p.communicate()
203
204        out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8')
205
206        if p.returncode != 0:
207            sys.stderr.write(err)
208
209            logger.warning("kernel-doc '%s' failed with return code %d"
210                                % (" ".join(cmd), p.returncode))
211            return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))]
212        elif env.config.kerneldoc_verbosity > 0:
213            sys.stderr.write(err)
214
215        filenames = self.parse_args["file_list"]
216        for filename in filenames:
217            self.parse_msg(filename, node, out, cmd)
218
219        return node.children
220
221    def parse_msg(self, filename, node, out, cmd):
222        """
223        Handles a kernel-doc output for a given file
224        """
225
226        env = self.state.document.settings.env
227
228        lines = statemachine.string2lines(out, self.tab_width,
229                                            convert_whitespace=True)
230        result = ViewList()
231
232        lineoffset = 0;
233        line_regex = re.compile(r"^\.\. LINENO ([0-9]+)$")
234        for line in lines:
235            match = line_regex.search(line)
236            if match:
237                # sphinx counts lines from 0
238                lineoffset = int(match.group(1)) - 1
239                # we must eat our comments since the upset the markup
240            else:
241                doc = str(env.srcdir) + "/" + env.docname + ":" + str(self.lineno)
242                result.append(line, doc + ": " + filename, lineoffset)
243                lineoffset += 1
244
245        self.do_parse(result, node)
246
247    def run_kdoc(self, cmd, kfiles):
248        """
249        Execute kernel-doc classes directly instead of running as a separate
250        command.
251        """
252
253        env = self.state.document.settings.env
254
255        node = nodes.section()
256
257        kfiles.parse(**self.parse_args)
258        filenames = self.parse_args["file_list"]
259
260        for filename, out in kfiles.msg(**self.msg_args, filenames=filenames):
261            self.parse_msg(filename, node, out, cmd)
262
263        return node.children
264
265    def run(self):
266        global kfiles
267
268        cmd = self.handle_args()
269        if self.verbose >= 1:
270            logger.info(cmd_str(cmd))
271
272        try:
273            if kfiles:
274                return self.run_kdoc(cmd, kfiles)
275            else:
276                return self.run_cmd(cmd)
277
278        except Exception as e:  # pylint: disable=W0703
279            logger.warning("kernel-doc '%s' processing failed with: %s" %
280                           (cmd_str(cmd), pformat(e)))
281            return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))]
282
283    def do_parse(self, result, node):
284        with switch_source_input(self.state, result):
285            self.state.nested_parse(result, 0, node, match_titles=1)
286
287def setup_kfiles(app):
288    global kfiles
289
290    kerneldoc_bin = app.env.config.kerneldoc_bin
291
292    if kerneldoc_bin and kerneldoc_bin.endswith("kernel-doc.py"):
293        print("Using Python kernel-doc")
294        out_style = RestFormat()
295        kfiles = KernelFiles(out_style=out_style, logger=logger)
296    else:
297        print(f"Using {kerneldoc_bin}")
298
299
300def setup(app):
301    app.add_config_value('kerneldoc_bin', None, 'env')
302    app.add_config_value('kerneldoc_srctree', None, 'env')
303    app.add_config_value('kerneldoc_verbosity', 1, 'env')
304
305    app.add_directive('kernel-doc', KernelDocDirective)
306
307    app.connect('builder-inited', setup_kfiles)
308
309    return dict(
310        version = __version__,
311        parallel_read_safe = True,
312        parallel_write_safe = True
313    )
~/ Read-only file preview.