xref: /linux/Documentation/sphinx/kernel_abi.py (revision 484e9aa6efaf96a7a5b5fe3216f24973166fbfe3)
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.pl 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        "rst"       : directives.unchanged
71    }
72
73    def run(self):
74        doc = self.state.document
75        if not doc.settings.file_insertion_enabled:
76            raise self.warning("docutils: file insertion disabled")
77
78        srctree = os.path.abspath(os.environ["srctree"])
79
80        args = [
81            os.path.join(srctree, 'scripts/get_abi.pl'),
82            'rest',
83            '--enable-lineno',
84            '--dir', os.path.join(srctree, 'Documentation', self.arguments[0]),
85        ]
86
87        if 'rst' in self.options:
88            args.append('--rst-source')
89
90        lines = subprocess.check_output(args, cwd=os.path.dirname(doc.current_source)).decode('utf-8')
91        nodeList = self.nestedParse(lines, self.arguments[0])
92        return nodeList
93
94    def nestedParse(self, lines, fname):
95        env = self.state.document.settings.env
96        content = ViewList()
97        node = nodes.section()
98
99        if "debug" in self.options:
100            code_block = "\n\n.. code-block:: rst\n    :linenos:\n"
101            for line in lines.split("\n"):
102                code_block += "\n    " + line
103            lines = code_block + "\n\n"
104
105        line_regex = re.compile(r"^\.\. LINENO (\S+)\#([0-9]+)$")
106        ln = 0
107        n = 0
108        f = fname
109
110        for line in lines.split("\n"):
111            n = n + 1
112            match = line_regex.search(line)
113            if match:
114                new_f = match.group(1)
115
116                # Sphinx parser is lazy: it stops parsing contents in the
117                # middle, if it is too big. So, handle it per input file
118                if new_f != f and content:
119                    self.do_parse(content, node)
120                    content = ViewList()
121
122                    # Add the file to Sphinx build dependencies
123                    env.note_dependency(os.path.abspath(f))
124
125                f = new_f
126
127                # sphinx counts lines from 0
128                ln = int(match.group(2)) - 1
129            else:
130                content.append(line, f, ln)
131
132        self.logger.info("%s: parsed %i lines" % (fname, n))
133
134        if content:
135            self.do_parse(content, node)
136
137        return node.children
138
139    def do_parse(self, content, node):
140        with switch_source_input(self.state, content):
141            self.state.nested_parse(content, 0, node, match_titles=1)
142