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 codecs
36import os
37import subprocess
38import sys
39import re
40import kernellog
41
42from docutils import nodes, statemachine
43from docutils.statemachine import ViewList
44from docutils.parsers.rst import directives, Directive
45from docutils.utils.error_reporting import ErrorString
46from sphinx.util.docutils import switch_source_input
47
48__version__  = '1.0'
49
50def setup(app):
51
52    app.add_directive("kernel-abi", KernelCmd)
53    return dict(
54        version = __version__
55        , parallel_read_safe = True
56        , parallel_write_safe = True
57    )
58
59class KernelCmd(Directive):
60
61    u"""KernelABI (``kernel-abi``) directive"""
62
63    required_arguments = 1
64    optional_arguments = 2
65    has_content = False
66    final_argument_whitespace = True
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 l in lines.split("\n"):
102                code_block += "\n    " + l
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        kernellog.info(self.state.document.settings.env.app, "%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