xref: /linux/Documentation/sphinx/kernel_abi.py (revision 2a21d80dfb4135b4766d8ff3231a3ea1c19bcc83)
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, statemachine
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 = statemachine.string2lines(msg, tab_width,
109                                                     convert_whitespace=True)
110                if "debug" in self.options:
111                    lines = [
112                        "", "",  ".. code-block:: rst",
113                        "    :linenos:", ""
114                    ]
115                    for m in msg_list:
116                        lines.append("    " + m)
117                else:
118                    lines = msg_list
119
120                for line in lines:
121                    # sphinx counts lines from 0
122                    content.append(line, f, ln - 1)
123                    n += 1
124
125                if f != old_f:
126                    # Add the file to Sphinx build dependencies
127                    env.note_dependency(os.path.abspath(f))
128
129                    old_f = f
130
131                # Sphinx doesn't like to parse big messages. So, let's
132                # add content symbol by symbol
133                if content:
134                    self.do_parse(content, node)
135                    content = ViewList()
136
137            self.logger.info("%s: parsed %i lines" % (fname, n))
138
139        return node.children
140
141    def do_parse(self, content, node):
142        with switch_source_input(self.state, content):
143            self.state.nested_parse(content, 0, node, match_titles=1)
144