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 subprocess
37import sys
38
39from os import path
40
41from docutils import nodes, statemachine
42from docutils.statemachine import ViewList
43from docutils.parsers.rst import directives, Directive
44from docutils.utils.error_reporting import ErrorString
45from sphinx.util.docutils import switch_source_input
46
47__version__  = '1.0'
48
49def setup(app):
50
51    app.add_directive("kernel-feat", KernelFeat)
52    return dict(
53        version = __version__
54        , parallel_read_safe = True
55        , parallel_write_safe = True
56    )
57
58class KernelFeat(Directive):
59
60    u"""KernelFeat (``kernel-feat``) directive"""
61
62    required_arguments = 1
63    optional_arguments = 2
64    has_content = False
65    final_argument_whitespace = True
66
67    option_spec = {
68        "debug"     : directives.flag
69    }
70
71    def warn(self, message, **replace):
72        replace["fname"]   = self.state.document.current_source
73        replace["line_no"] = replace.get("line_no", self.lineno)
74        message = ("%(fname)s:%(line_no)s: [kernel-feat WARN] : " + message) % replace
75        self.state.document.settings.env.app.warn(message, prefix="")
76
77    def run(self):
78
79        doc = self.state.document
80        if not doc.settings.file_insertion_enabled:
81            raise self.warning("docutils: file insertion disabled")
82
83        env = doc.settings.env
84        cwd = path.dirname(doc.current_source)
85        cmd = "get_feat.pl rest --dir "
86        cmd += self.arguments[0]
87
88        if len(self.arguments) > 1:
89            cmd += " --arch " + self.arguments[1]
90
91        srctree = path.abspath(os.environ["srctree"])
92
93        fname = cmd
94
95        # extend PATH with $(srctree)/scripts
96        path_env = os.pathsep.join([
97            srctree + os.sep + "scripts",
98            os.environ["PATH"]
99        ])
100        shell_env = os.environ.copy()
101        shell_env["PATH"]    = path_env
102        shell_env["srctree"] = srctree
103
104        lines = self.runCmd(cmd, shell=True, cwd=cwd, env=shell_env)
105        nodeList = self.nestedParse(lines, fname)
106        return nodeList
107
108    def runCmd(self, cmd, **kwargs):
109        u"""Run command ``cmd`` and return it's stdout as unicode."""
110
111        try:
112            proc = subprocess.Popen(
113                cmd
114                , stdout = subprocess.PIPE
115                , stderr = subprocess.PIPE
116                , **kwargs
117            )
118            out, err = proc.communicate()
119
120            out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8')
121
122            if proc.returncode != 0:
123                raise self.severe(
124                    u"command '%s' failed with return code %d"
125                    % (cmd, proc.returncode)
126                )
127        except OSError as exc:
128            raise self.severe(u"problems with '%s' directive: %s."
129                              % (self.name, ErrorString(exc)))
130        return out
131
132    def nestedParse(self, lines, fname):
133        content = ViewList()
134        node    = nodes.section()
135
136        if "debug" in self.options:
137            code_block = "\n\n.. code-block:: rst\n    :linenos:\n"
138            for l in lines.split("\n"):
139                code_block += "\n    " + l
140            lines = code_block + "\n\n"
141
142        for c, l in enumerate(lines.split("\n")):
143            content.append(l, fname, c)
144
145        buf  = self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter
146
147        with switch_source_input(self.state, content):
148            self.state.nested_parse(content, 0, node, match_titles=1)
149
150        return node.children
151