xref: /linux/Documentation/sphinx/kernel_abi.py (revision c67c3fbdd917884e38a366c38717c9f769075c15)
1# -*- coding: utf-8; mode: python -*-
2# coding=utf-8
3# SPDX-License-Identifier: GPL-2.0
4#
5u"""
6    kernel-abi
7    ~~~~~~~~~~
8
9    Implementation of the ``kernel-abi`` reST-directive.
10
11    :copyright:  Copyright (C) 2016  Markus Heiser
12    :copyright:  Copyright (C) 2016-2020  Mauro Carvalho Chehab
13    :maintained-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
14    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
15
16    The ``kernel-abi`` (:py:class:`KernelCmd`) directive calls the
17    scripts/get_abi.py script to parse the Kernel ABI files.
18
19    Overview of directive's argument and options.
20
21    .. code-block:: rst
22
23        .. kernel-abi:: <ABI directory location>
24            :debug:
25
26    The argument ``<ABI directory location>`` is required. It contains the
27    location of the ABI files to be parsed.
28
29    ``debug``
30      Inserts a code-block with the *raw* reST. Sometimes it is helpful to see
31      what reST is generated.
32
33"""
34
35import os
36import re
37import subprocess
38import sys
39
40from docutils import nodes
41from docutils.statemachine import ViewList
42from docutils.parsers.rst import directives, Directive
43from sphinx.util.docutils import switch_source_input
44from sphinx.util import logging
45
46__version__ = "1.0"
47
48
49def setup(app):
50
51    app.add_directive("kernel-abi", KernelCmd)
52    return {
53        "version": __version__,
54        "parallel_read_safe": True,
55        "parallel_write_safe": True
56    }
57
58
59class KernelCmd(Directive):
60    u"""KernelABI (``kernel-abi``) directive"""
61
62    required_arguments = 1
63    optional_arguments = 2
64    has_content = False
65    final_argument_whitespace = True
66    logger = logging.getLogger('kernel_abi')
67
68    option_spec = {
69        "debug"     : directives.flag,
70    }
71
72    def run(self):
73        doc = self.state.document
74        if not doc.settings.file_insertion_enabled:
75            raise self.warning("docutils: file insertion disabled")
76
77        srctree = os.path.abspath(os.environ["srctree"])
78
79        args = [
80            os.path.join(srctree, 'scripts/get_abi.py'),
81            '-D', os.path.join(srctree, 'Documentation', self.arguments[0]),
82            'rest',
83            '--enable-lineno',
84        ]
85
86        lines = subprocess.check_output(args, cwd=os.path.dirname(doc.current_source)).decode('utf-8')
87        nodeList = self.nestedParse(lines, self.arguments[0])
88        return nodeList
89
90    def nestedParse(self, lines, fname):
91        env = self.state.document.settings.env
92        content = ViewList()
93        node = nodes.section()
94
95        if "debug" in self.options:
96            code_block = "\n\n.. code-block:: rst\n    :linenos:\n"
97            for line in lines.split("\n"):
98                code_block += "\n    " + line
99            lines = code_block + "\n\n"
100
101        line_regex = re.compile(r"^\.\. LINENO (\S+)\#([0-9]+)$")
102        ln = 0
103        n = 0
104        f = fname
105
106        for line in lines.split("\n"):
107            n = n + 1
108            match = line_regex.search(line)
109            if match:
110                new_f = match.group(1)
111
112                # Sphinx parser is lazy: it stops parsing contents in the
113                # middle, if it is too big. So, handle it per input file
114                if new_f != f and content:
115                    self.do_parse(content, node)
116                    content = ViewList()
117
118                    # Add the file to Sphinx build dependencies
119                    env.note_dependency(os.path.abspath(f))
120
121                f = new_f
122
123                # sphinx counts lines from 0
124                ln = int(match.group(2)) - 1
125            else:
126                content.append(line, f, ln)
127
128        self.logger.info("%s: parsed %i lines" % (fname, n))
129
130        if content:
131            self.do_parse(content, node)
132
133        return node.children
134
135    def do_parse(self, content, node):
136        with switch_source_input(self.state, content):
137            self.state.nested_parse(content, 0, node, match_titles=1)
138