xref: /linux/Documentation/sphinx/kernel_abi.py (revision aea5e52dce74f679b91c66caad91d587d5504f6c)
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 sys
38
39from docutils import nodes
40from docutils.statemachine import ViewList
41from docutils.parsers.rst import directives, Directive
42from sphinx.util.docutils import switch_source_input
43from sphinx.util import logging
44
45srctree = os.path.abspath(os.environ["srctree"])
46sys.path.insert(0, os.path.join(srctree, "scripts/lib/abi"))
47
48from abi_parser import AbiParser
49
50__version__ = "1.0"
51
52
53def setup(app):
54
55    app.add_directive("kernel-abi", KernelCmd)
56    return {
57        "version": __version__,
58        "parallel_read_safe": True,
59        "parallel_write_safe": True
60    }
61
62
63class KernelCmd(Directive):
64    u"""KernelABI (``kernel-abi``) directive"""
65
66    required_arguments = 1
67    optional_arguments = 2
68    has_content = False
69    final_argument_whitespace = True
70    logger = logging.getLogger('kernel_abi')
71    parser = None
72
73    option_spec = {
74        "debug": directives.flag,
75    }
76
77    def run(self):
78        doc = self.state.document
79        if not doc.settings.file_insertion_enabled:
80            raise self.warning("docutils: file insertion disabled")
81
82        path = os.path.join(srctree, "Documentation", self.arguments[0])
83        self.parser = AbiParser(path, logger=self.logger)
84        self.parser.parse_abi()
85        self.parser.check_issues()
86
87        node = self.nested_parse(None, self.arguments[0])
88        return node
89
90    def nested_parse(self, data, fname):
91        env = self.state.document.settings.env
92        content = ViewList()
93        node = nodes.section()
94
95        if data is not None:
96            # Handles the .rst file
97            for line in data.split("\n"):
98                content.append(line, fname, 0)
99
100            self.do_parse(content, node)
101
102        else:
103            # Handles the ABI parser content, symbol by symbol
104
105            old_f = fname
106            n = 0
107            for msg, f, ln in self.parser.doc():
108                msg_list = msg.split("\n")
109                if "debug" in self.options:
110                    lines = [
111                        "", "",  ".. code-block:: rst",
112                        "    :linenos:", ""
113                    ]
114                    for m in msg_list:
115                        lines.append("    " + m)
116                else:
117                    lines = msg_list
118
119                for line in lines:
120                    # sphinx counts lines from 0
121                    content.append(line, f, ln - 1)
122                    n += 1
123
124                if f != old_f:
125                    # Add the file to Sphinx build dependencies
126                    env.note_dependency(os.path.abspath(f))
127
128                    old_f = f
129
130                # Sphinx doesn't like to parse big messages. So, let's
131                # add content symbol by symbol
132                if content:
133                    self.do_parse(content, node)
134                    content = ViewList()
135
136            self.logger.info("%s: parsed %i lines" % (fname, n))
137
138        return node.children
139
140    def do_parse(self, content, node):
141        with switch_source_input(self.state, content):
142            self.state.nested_parse(content, 0, node, match_titles=1)
143