1# coding=utf-8
2# SPDX-License-Identifier: GPL-2.0
3#
4u"""
5    kernel-feat
6    ~~~~~~~~~~~
7
8    Implementation of the ``kernel-feat`` reST-directive.
9
10    :copyright:  Copyright (C) 2016  Markus Heiser
11    :copyright:  Copyright (C) 2016-2019  Mauro Carvalho Chehab
12    :maintained-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
13    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
14
15    The ``kernel-feat`` (:py:class:`KernelFeat`) directive calls the
16    scripts/get_feat.pl script to parse the Kernel ABI files.
17
18    Overview of directive's argument and options.
19
20    .. code-block:: rst
21
22        .. kernel-feat:: <ABI directory location>
23            :debug:
24
25    The argument ``<ABI directory location>`` is required. It contains the
26    location of the ABI files to be parsed.
27
28    ``debug``
29      Inserts a code-block with the *raw* reST. Sometimes it is helpful to see
30      what reST is generated.
31
32"""
33
34import codecs
35import os
36import re
37import subprocess
38import sys
39
40from os import path
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-feat", KernelFeat)
53    return dict(
54        version = __version__
55        , parallel_read_safe = True
56        , parallel_write_safe = True
57    )
58
59class KernelFeat(Directive):
60
61    u"""KernelFeat (``kernel-feat``) 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    }
71
72    def warn(self, message, **replace):
73        replace["fname"]   = self.state.document.current_source
74        replace["line_no"] = replace.get("line_no", self.lineno)
75        message = ("%(fname)s:%(line_no)s: [kernel-feat WARN] : " + message) % replace
76        self.state.document.settings.env.app.warn(message, prefix="")
77
78    def run(self):
79
80        doc = self.state.document
81        if not doc.settings.file_insertion_enabled:
82            raise self.warning("docutils: file insertion disabled")
83
84        env = doc.settings.env
85        cwd = path.dirname(doc.current_source)
86        cmd = "get_feat.pl rest --enable-fname --dir "
87        cmd += self.arguments[0]
88
89        if len(self.arguments) > 1:
90            cmd += " --arch " + self.arguments[1]
91
92        srctree = path.abspath(os.environ["srctree"])
93
94        fname = cmd
95
96        # extend PATH with $(srctree)/scripts
97        path_env = os.pathsep.join([
98            srctree + os.sep + "scripts",
99            os.environ["PATH"]
100        ])
101        shell_env = os.environ.copy()
102        shell_env["PATH"]    = path_env
103        shell_env["srctree"] = srctree
104
105        lines = self.runCmd(cmd, shell=True, cwd=cwd, env=shell_env)
106
107        line_regex = re.compile("^\.\. FILE (\S+)$")
108
109        out_lines = ""
110
111        for line in lines.split("\n"):
112            match = line_regex.search(line)
113            if match:
114                fname = match.group(1)
115
116                # Add the file to Sphinx build dependencies
117                env.note_dependency(os.path.abspath(fname))
118            else:
119                out_lines += line + "\n"
120
121        nodeList = self.nestedParse(out_lines, fname)
122        return nodeList
123
124    def runCmd(self, cmd, **kwargs):
125        u"""Run command ``cmd`` and return its stdout as unicode."""
126
127        try:
128            proc = subprocess.Popen(
129                cmd
130                , stdout = subprocess.PIPE
131                , stderr = subprocess.PIPE
132                , **kwargs
133            )
134            out, err = proc.communicate()
135
136            out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8')
137
138            if proc.returncode != 0:
139                raise self.severe(
140                    u"command '%s' failed with return code %d"
141                    % (cmd, proc.returncode)
142                )
143        except OSError as exc:
144            raise self.severe(u"problems with '%s' directive: %s."
145                              % (self.name, ErrorString(exc)))
146        return out
147
148    def nestedParse(self, lines, fname):
149        content = ViewList()
150        node    = nodes.section()
151
152        if "debug" in self.options:
153            code_block = "\n\n.. code-block:: rst\n    :linenos:\n"
154            for l in lines.split("\n"):
155                code_block += "\n    " + l
156            lines = code_block + "\n\n"
157
158        for c, l in enumerate(lines.split("\n")):
159            content.append(l, fname, c)
160
161        buf  = self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter
162
163        with switch_source_input(self.state, content):
164            self.state.nested_parse(content, 0, node, match_titles=1)
165
166        return node.children
167