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