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