1# -*- coding: utf-8; mode: python -*-
2# coding=utf-8
3# SPDX-License-Identifier: GPL-2.0
4#
5"""
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
52logger = logging.getLogger('kernel_abi')
53path = os.path.join(srctree, "Documentation/ABI")
54
55_kernel_abi = None
56
57def get_kernel_abi():
58    """
59    Initialize kernel_abi global var, if not initialized yet.
60
61    This is needed to avoid warnings during Sphinx module initialization.
62    """
63    global _kernel_abi
64
65    if not _kernel_abi:
66        # Parse ABI symbols only once
67        _kernel_abi = AbiParser(path, logger=logger)
68        _kernel_abi.parse_abi()
69        _kernel_abi.check_issues()
70
71    return _kernel_abi
72
73def setup(app):
74
75    app.add_directive("kernel-abi", KernelCmd)
76    return {
77        "version": __version__,
78        "parallel_read_safe": True,
79        "parallel_write_safe": True
80    }
81
82
83class KernelCmd(Directive):
84    """KernelABI (``kernel-abi``) directive"""
85
86    required_arguments = 1
87    optional_arguments = 3
88    has_content = False
89    final_argument_whitespace = True
90    parser = None
91
92    option_spec = {
93        "debug": directives.flag,
94        "no-symbols": directives.flag,
95        "no-files":  directives.flag,
96    }
97
98    def run(self):
99        kernel_abi = get_kernel_abi()
100
101        doc = self.state.document
102        if not doc.settings.file_insertion_enabled:
103            raise self.warning("docutils: file insertion disabled")
104
105        env = self.state.document.settings.env
106        content = ViewList()
107        node = nodes.section()
108
109        abi_type = self.arguments[0]
110
111        if "no-symbols" in self.options:
112            show_symbols = False
113        else:
114            show_symbols = True
115
116        if "no-files" in self.options:
117            show_file = False
118        else:
119            show_file = True
120
121        tab_width = self.options.get('tab-width',
122                                     self.state.document.settings.tab_width)
123
124        old_f = None
125        n = 0
126        n_sym = 0
127        for msg, f, ln in kernel_abi.doc(show_file=show_file,
128                                            show_symbols=show_symbols,
129                                            filter_path=abi_type):
130            n_sym += 1
131            msg_list = statemachine.string2lines(msg, tab_width,
132                                                 convert_whitespace=True)
133            if "debug" in self.options:
134                lines = [
135                    "", "",  ".. code-block:: rst",
136                    "    :linenos:", ""
137                ]
138                for m in msg_list:
139                    lines.append("    " + m)
140            else:
141                lines = msg_list
142
143            for line in lines:
144                # sphinx counts lines from 0
145                content.append(line, f, ln - 1)
146                n += 1
147
148            if f != old_f:
149                # Add the file to Sphinx build dependencies
150                env.note_dependency(os.path.abspath(f))
151
152                old_f = f
153
154            # Sphinx doesn't like to parse big messages. So, let's
155            # add content symbol by symbol
156            if content:
157                self.do_parse(content, node)
158                content = ViewList()
159
160        if show_symbols and not show_file:
161            logger.verbose("%s ABI: %i symbols (%i ReST lines)" % (abi_type, n_sym, n))
162        elif not show_symbols and show_file:
163            logger.verbose("%s ABI: %i files (%i ReST lines)" % (abi_type, n_sym, n))
164        else:
165            logger.verbose("%s ABI: %i data (%i ReST lines)" % (abi_type, n_sym, n))
166
167        return node.children
168
169    def do_parse(self, content, node):
170        with switch_source_input(self.state, content):
171            self.state.nested_parse(content, 0, node, match_titles=1)
172