xref: /OK3568_Linux_fs/kernel/Documentation/sphinx/kfigure.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# -*- coding: utf-8; mode: python -*-
2*4882a593Smuzhiyun# pylint: disable=C0103, R0903, R0912, R0915
3*4882a593Smuzhiyunu"""
4*4882a593Smuzhiyun    scalable figure and image handling
5*4882a593Smuzhiyun    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun    Sphinx extension which implements scalable image handling.
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun    :copyright:  Copyright (C) 2016  Markus Heiser
10*4882a593Smuzhiyun    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun    The build for image formats depend on image's source format and output's
13*4882a593Smuzhiyun    destination format. This extension implement methods to simplify image
14*4882a593Smuzhiyun    handling from the author's POV. Directives like ``kernel-figure`` implement
15*4882a593Smuzhiyun    methods *to* always get the best output-format even if some tools are not
16*4882a593Smuzhiyun    installed. For more details take a look at ``convert_image(...)`` which is
17*4882a593Smuzhiyun    the core of all conversions.
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun    * ``.. kernel-image``: for image handling / a ``.. image::`` replacement
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun    * ``.. kernel-figure``: for figure handling / a ``.. figure::`` replacement
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun    * ``.. kernel-render``: for render markup / a concept to embed *render*
24*4882a593Smuzhiyun      markups (or languages). Supported markups (see ``RENDER_MARKUP_EXT``)
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun      - ``DOT``: render embedded Graphviz's **DOC**
27*4882a593Smuzhiyun      - ``SVG``: render embedded Scalable Vector Graphics (**SVG**)
28*4882a593Smuzhiyun      - ... *developable*
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun    Used tools:
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun    * ``dot(1)``: Graphviz (https://www.graphviz.org). If Graphviz is not
33*4882a593Smuzhiyun      available, the DOT language is inserted as literal-block.
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun    * SVG to PDF: To generate PDF, you need at least one of this tools:
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun      - ``convert(1)``: ImageMagick (https://www.imagemagick.org)
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun    List of customizations:
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun    * generate PDF from SVG / used by PDF (LaTeX) builder
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun    * generate SVG (html-builder) and PDF (latex-builder) from DOT files.
44*4882a593Smuzhiyun      DOT: see https://www.graphviz.org/content/dot-language
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun    """
47*4882a593Smuzhiyun
48*4882a593Smuzhiyunimport os
49*4882a593Smuzhiyunfrom os import path
50*4882a593Smuzhiyunimport subprocess
51*4882a593Smuzhiyunfrom hashlib import sha1
52*4882a593Smuzhiyunimport sys
53*4882a593Smuzhiyun
54*4882a593Smuzhiyunfrom docutils import nodes
55*4882a593Smuzhiyunfrom docutils.statemachine import ViewList
56*4882a593Smuzhiyunfrom docutils.parsers.rst import directives
57*4882a593Smuzhiyunfrom docutils.parsers.rst.directives import images
58*4882a593Smuzhiyunimport sphinx
59*4882a593Smuzhiyun
60*4882a593Smuzhiyunfrom sphinx.util.nodes import clean_astext
61*4882a593Smuzhiyunfrom six import iteritems
62*4882a593Smuzhiyun
63*4882a593Smuzhiyunimport kernellog
64*4882a593Smuzhiyun
65*4882a593SmuzhiyunPY3 = sys.version_info[0] == 3
66*4882a593Smuzhiyun
67*4882a593Smuzhiyunif PY3:
68*4882a593Smuzhiyun    _unicode = str
69*4882a593Smuzhiyunelse:
70*4882a593Smuzhiyun    _unicode = unicode
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun# Get Sphinx version
73*4882a593Smuzhiyunmajor, minor, patch = sphinx.version_info[:3]
74*4882a593Smuzhiyunif major == 1 and minor > 3:
75*4882a593Smuzhiyun    # patches.Figure only landed in Sphinx 1.4
76*4882a593Smuzhiyun    from sphinx.directives.patches import Figure  # pylint: disable=C0413
77*4882a593Smuzhiyunelse:
78*4882a593Smuzhiyun    Figure = images.Figure
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun__version__  = '1.0.0'
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun# simple helper
83*4882a593Smuzhiyun# -------------
84*4882a593Smuzhiyun
85*4882a593Smuzhiyundef which(cmd):
86*4882a593Smuzhiyun    """Searches the ``cmd`` in the ``PATH`` environment.
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    This *which* searches the PATH for executable ``cmd`` . First match is
89*4882a593Smuzhiyun    returned, if nothing is found, ``None` is returned.
90*4882a593Smuzhiyun    """
91*4882a593Smuzhiyun    envpath = os.environ.get('PATH', None) or os.defpath
92*4882a593Smuzhiyun    for folder in envpath.split(os.pathsep):
93*4882a593Smuzhiyun        fname = folder + os.sep + cmd
94*4882a593Smuzhiyun        if path.isfile(fname):
95*4882a593Smuzhiyun            return fname
96*4882a593Smuzhiyun
97*4882a593Smuzhiyundef mkdir(folder, mode=0o775):
98*4882a593Smuzhiyun    if not path.isdir(folder):
99*4882a593Smuzhiyun        os.makedirs(folder, mode)
100*4882a593Smuzhiyun
101*4882a593Smuzhiyundef file2literal(fname):
102*4882a593Smuzhiyun    with open(fname, "r") as src:
103*4882a593Smuzhiyun        data = src.read()
104*4882a593Smuzhiyun        node = nodes.literal_block(data, data)
105*4882a593Smuzhiyun    return node
106*4882a593Smuzhiyun
107*4882a593Smuzhiyundef isNewer(path1, path2):
108*4882a593Smuzhiyun    """Returns True if ``path1`` is newer than ``path2``
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun    If ``path1`` exists and is newer than ``path2`` the function returns
111*4882a593Smuzhiyun    ``True`` is returned otherwise ``False``
112*4882a593Smuzhiyun    """
113*4882a593Smuzhiyun    return (path.exists(path1)
114*4882a593Smuzhiyun            and os.stat(path1).st_ctime > os.stat(path2).st_ctime)
115*4882a593Smuzhiyun
116*4882a593Smuzhiyundef pass_handle(self, node):           # pylint: disable=W0613
117*4882a593Smuzhiyun    pass
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun# setup conversion tools and sphinx extension
120*4882a593Smuzhiyun# -------------------------------------------
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun# Graphviz's dot(1) support
123*4882a593Smuzhiyundot_cmd = None
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun# ImageMagick' convert(1) support
126*4882a593Smuzhiyunconvert_cmd = None
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun
129*4882a593Smuzhiyundef setup(app):
130*4882a593Smuzhiyun    # check toolchain first
131*4882a593Smuzhiyun    app.connect('builder-inited', setupTools)
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun    # image handling
134*4882a593Smuzhiyun    app.add_directive("kernel-image",  KernelImage)
135*4882a593Smuzhiyun    app.add_node(kernel_image,
136*4882a593Smuzhiyun                 html    = (visit_kernel_image, pass_handle),
137*4882a593Smuzhiyun                 latex   = (visit_kernel_image, pass_handle),
138*4882a593Smuzhiyun                 texinfo = (visit_kernel_image, pass_handle),
139*4882a593Smuzhiyun                 text    = (visit_kernel_image, pass_handle),
140*4882a593Smuzhiyun                 man     = (visit_kernel_image, pass_handle), )
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun    # figure handling
143*4882a593Smuzhiyun    app.add_directive("kernel-figure", KernelFigure)
144*4882a593Smuzhiyun    app.add_node(kernel_figure,
145*4882a593Smuzhiyun                 html    = (visit_kernel_figure, pass_handle),
146*4882a593Smuzhiyun                 latex   = (visit_kernel_figure, pass_handle),
147*4882a593Smuzhiyun                 texinfo = (visit_kernel_figure, pass_handle),
148*4882a593Smuzhiyun                 text    = (visit_kernel_figure, pass_handle),
149*4882a593Smuzhiyun                 man     = (visit_kernel_figure, pass_handle), )
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun    # render handling
152*4882a593Smuzhiyun    app.add_directive('kernel-render', KernelRender)
153*4882a593Smuzhiyun    app.add_node(kernel_render,
154*4882a593Smuzhiyun                 html    = (visit_kernel_render, pass_handle),
155*4882a593Smuzhiyun                 latex   = (visit_kernel_render, pass_handle),
156*4882a593Smuzhiyun                 texinfo = (visit_kernel_render, pass_handle),
157*4882a593Smuzhiyun                 text    = (visit_kernel_render, pass_handle),
158*4882a593Smuzhiyun                 man     = (visit_kernel_render, pass_handle), )
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun    app.connect('doctree-read', add_kernel_figure_to_std_domain)
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun    return dict(
163*4882a593Smuzhiyun        version = __version__,
164*4882a593Smuzhiyun        parallel_read_safe = True,
165*4882a593Smuzhiyun        parallel_write_safe = True
166*4882a593Smuzhiyun    )
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun
169*4882a593Smuzhiyundef setupTools(app):
170*4882a593Smuzhiyun    u"""
171*4882a593Smuzhiyun    Check available build tools and log some *verbose* messages.
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun    This function is called once, when the builder is initiated.
174*4882a593Smuzhiyun    """
175*4882a593Smuzhiyun    global dot_cmd, convert_cmd   # pylint: disable=W0603
176*4882a593Smuzhiyun    kernellog.verbose(app, "kfigure: check installed tools ...")
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun    dot_cmd = which('dot')
179*4882a593Smuzhiyun    convert_cmd = which('convert')
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun    if dot_cmd:
182*4882a593Smuzhiyun        kernellog.verbose(app, "use dot(1) from: " + dot_cmd)
183*4882a593Smuzhiyun    else:
184*4882a593Smuzhiyun        kernellog.warn(app, "dot(1) not found, for better output quality install "
185*4882a593Smuzhiyun                       "graphviz from https://www.graphviz.org")
186*4882a593Smuzhiyun    if convert_cmd:
187*4882a593Smuzhiyun        kernellog.verbose(app, "use convert(1) from: " + convert_cmd)
188*4882a593Smuzhiyun    else:
189*4882a593Smuzhiyun        kernellog.warn(app,
190*4882a593Smuzhiyun            "convert(1) not found, for SVG to PDF conversion install "
191*4882a593Smuzhiyun            "ImageMagick (https://www.imagemagick.org)")
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun# integrate conversion tools
195*4882a593Smuzhiyun# --------------------------
196*4882a593Smuzhiyun
197*4882a593SmuzhiyunRENDER_MARKUP_EXT = {
198*4882a593Smuzhiyun    # The '.ext' must be handled by convert_image(..) function's *in_ext* input.
199*4882a593Smuzhiyun    # <name> : <.ext>
200*4882a593Smuzhiyun    'DOT' : '.dot',
201*4882a593Smuzhiyun    'SVG' : '.svg'
202*4882a593Smuzhiyun}
203*4882a593Smuzhiyun
204*4882a593Smuzhiyundef convert_image(img_node, translator, src_fname=None):
205*4882a593Smuzhiyun    """Convert a image node for the builder.
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun    Different builder prefer different image formats, e.g. *latex* builder
208*4882a593Smuzhiyun    prefer PDF while *html* builder prefer SVG format for images.
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun    This function handles output image formats in dependence of source the
211*4882a593Smuzhiyun    format (of the image) and the translator's output format.
212*4882a593Smuzhiyun    """
213*4882a593Smuzhiyun    app = translator.builder.app
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun    fname, in_ext = path.splitext(path.basename(img_node['uri']))
216*4882a593Smuzhiyun    if src_fname is None:
217*4882a593Smuzhiyun        src_fname = path.join(translator.builder.srcdir, img_node['uri'])
218*4882a593Smuzhiyun        if not path.exists(src_fname):
219*4882a593Smuzhiyun            src_fname = path.join(translator.builder.outdir, img_node['uri'])
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun    dst_fname = None
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun    # in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun    kernellog.verbose(app, 'assert best format for: ' + img_node['uri'])
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun    if in_ext == '.dot':
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun        if not dot_cmd:
230*4882a593Smuzhiyun            kernellog.verbose(app,
231*4882a593Smuzhiyun                              "dot from graphviz not available / include DOT raw.")
232*4882a593Smuzhiyun            img_node.replace_self(file2literal(src_fname))
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun        elif translator.builder.format == 'latex':
235*4882a593Smuzhiyun            dst_fname = path.join(translator.builder.outdir, fname + '.pdf')
236*4882a593Smuzhiyun            img_node['uri'] = fname + '.pdf'
237*4882a593Smuzhiyun            img_node['candidates'] = {'*': fname + '.pdf'}
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun        elif translator.builder.format == 'html':
241*4882a593Smuzhiyun            dst_fname = path.join(
242*4882a593Smuzhiyun                translator.builder.outdir,
243*4882a593Smuzhiyun                translator.builder.imagedir,
244*4882a593Smuzhiyun                fname + '.svg')
245*4882a593Smuzhiyun            img_node['uri'] = path.join(
246*4882a593Smuzhiyun                translator.builder.imgpath, fname + '.svg')
247*4882a593Smuzhiyun            img_node['candidates'] = {
248*4882a593Smuzhiyun                '*': path.join(translator.builder.imgpath, fname + '.svg')}
249*4882a593Smuzhiyun
250*4882a593Smuzhiyun        else:
251*4882a593Smuzhiyun            # all other builder formats will include DOT as raw
252*4882a593Smuzhiyun            img_node.replace_self(file2literal(src_fname))
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun    elif in_ext == '.svg':
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun        if translator.builder.format == 'latex':
257*4882a593Smuzhiyun            if convert_cmd is None:
258*4882a593Smuzhiyun                kernellog.verbose(app,
259*4882a593Smuzhiyun                                  "no SVG to PDF conversion available / include SVG raw.")
260*4882a593Smuzhiyun                img_node.replace_self(file2literal(src_fname))
261*4882a593Smuzhiyun            else:
262*4882a593Smuzhiyun                dst_fname = path.join(translator.builder.outdir, fname + '.pdf')
263*4882a593Smuzhiyun                img_node['uri'] = fname + '.pdf'
264*4882a593Smuzhiyun                img_node['candidates'] = {'*': fname + '.pdf'}
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun    if dst_fname:
267*4882a593Smuzhiyun        # the builder needs not to copy one more time, so pop it if exists.
268*4882a593Smuzhiyun        translator.builder.images.pop(img_node['uri'], None)
269*4882a593Smuzhiyun        _name = dst_fname[len(translator.builder.outdir) + 1:]
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun        if isNewer(dst_fname, src_fname):
272*4882a593Smuzhiyun            kernellog.verbose(app,
273*4882a593Smuzhiyun                              "convert: {out}/%s already exists and is newer" % _name)
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun        else:
276*4882a593Smuzhiyun            ok = False
277*4882a593Smuzhiyun            mkdir(path.dirname(dst_fname))
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun            if in_ext == '.dot':
280*4882a593Smuzhiyun                kernellog.verbose(app, 'convert DOT to: {out}/' + _name)
281*4882a593Smuzhiyun                ok = dot2format(app, src_fname, dst_fname)
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun            elif in_ext == '.svg':
284*4882a593Smuzhiyun                kernellog.verbose(app, 'convert SVG to: {out}/' + _name)
285*4882a593Smuzhiyun                ok = svg2pdf(app, src_fname, dst_fname)
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun            if not ok:
288*4882a593Smuzhiyun                img_node.replace_self(file2literal(src_fname))
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun
291*4882a593Smuzhiyundef dot2format(app, dot_fname, out_fname):
292*4882a593Smuzhiyun    """Converts DOT file to ``out_fname`` using ``dot(1)``.
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun    * ``dot_fname`` pathname of the input DOT file, including extension ``.dot``
295*4882a593Smuzhiyun    * ``out_fname`` pathname of the output file, including format extension
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun    The *format extension* depends on the ``dot`` command (see ``man dot``
298*4882a593Smuzhiyun    option ``-Txxx``). Normally you will use one of the following extensions:
299*4882a593Smuzhiyun
300*4882a593Smuzhiyun    - ``.ps`` for PostScript,
301*4882a593Smuzhiyun    - ``.svg`` or ``svgz`` for Structured Vector Graphics,
302*4882a593Smuzhiyun    - ``.fig`` for XFIG graphics and
303*4882a593Smuzhiyun    - ``.png`` or ``gif`` for common bitmap graphics.
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun    """
306*4882a593Smuzhiyun    out_format = path.splitext(out_fname)[1][1:]
307*4882a593Smuzhiyun    cmd = [dot_cmd, '-T%s' % out_format, dot_fname]
308*4882a593Smuzhiyun    exit_code = 42
309*4882a593Smuzhiyun
310*4882a593Smuzhiyun    with open(out_fname, "w") as out:
311*4882a593Smuzhiyun        exit_code = subprocess.call(cmd, stdout = out)
312*4882a593Smuzhiyun        if exit_code != 0:
313*4882a593Smuzhiyun            kernellog.warn(app,
314*4882a593Smuzhiyun                          "Error #%d when calling: %s" % (exit_code, " ".join(cmd)))
315*4882a593Smuzhiyun    return bool(exit_code == 0)
316*4882a593Smuzhiyun
317*4882a593Smuzhiyundef svg2pdf(app, svg_fname, pdf_fname):
318*4882a593Smuzhiyun    """Converts SVG to PDF with ``convert(1)`` command.
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun    Uses ``convert(1)`` from ImageMagick (https://www.imagemagick.org) for
321*4882a593Smuzhiyun    conversion.  Returns ``True`` on success and ``False`` if an error occurred.
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun    * ``svg_fname`` pathname of the input SVG file with extension (``.svg``)
324*4882a593Smuzhiyun    * ``pdf_name``  pathname of the output PDF file with extension (``.pdf``)
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun    """
327*4882a593Smuzhiyun    cmd = [convert_cmd, svg_fname, pdf_fname]
328*4882a593Smuzhiyun    # use stdout and stderr from parent
329*4882a593Smuzhiyun    exit_code = subprocess.call(cmd)
330*4882a593Smuzhiyun    if exit_code != 0:
331*4882a593Smuzhiyun        kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd)))
332*4882a593Smuzhiyun    return bool(exit_code == 0)
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun
335*4882a593Smuzhiyun# image handling
336*4882a593Smuzhiyun# ---------------------
337*4882a593Smuzhiyun
338*4882a593Smuzhiyundef visit_kernel_image(self, node):    # pylint: disable=W0613
339*4882a593Smuzhiyun    """Visitor of the ``kernel_image`` Node.
340*4882a593Smuzhiyun
341*4882a593Smuzhiyun    Handles the ``image`` child-node with the ``convert_image(...)``.
342*4882a593Smuzhiyun    """
343*4882a593Smuzhiyun    img_node = node[0]
344*4882a593Smuzhiyun    convert_image(img_node, self)
345*4882a593Smuzhiyun
346*4882a593Smuzhiyunclass kernel_image(nodes.image):
347*4882a593Smuzhiyun    """Node for ``kernel-image`` directive."""
348*4882a593Smuzhiyun    pass
349*4882a593Smuzhiyun
350*4882a593Smuzhiyunclass KernelImage(images.Image):
351*4882a593Smuzhiyun    u"""KernelImage directive
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun    Earns everything from ``.. image::`` directive, except *remote URI* and
354*4882a593Smuzhiyun    *glob* pattern. The KernelImage wraps a image node into a
355*4882a593Smuzhiyun    kernel_image node. See ``visit_kernel_image``.
356*4882a593Smuzhiyun    """
357*4882a593Smuzhiyun
358*4882a593Smuzhiyun    def run(self):
359*4882a593Smuzhiyun        uri = self.arguments[0]
360*4882a593Smuzhiyun        if uri.endswith('.*') or uri.find('://') != -1:
361*4882a593Smuzhiyun            raise self.severe(
362*4882a593Smuzhiyun                'Error in "%s: %s": glob pattern and remote images are not allowed'
363*4882a593Smuzhiyun                % (self.name, uri))
364*4882a593Smuzhiyun        result = images.Image.run(self)
365*4882a593Smuzhiyun        if len(result) == 2 or isinstance(result[0], nodes.system_message):
366*4882a593Smuzhiyun            return result
367*4882a593Smuzhiyun        (image_node,) = result
368*4882a593Smuzhiyun        # wrap image node into a kernel_image node / see visitors
369*4882a593Smuzhiyun        node = kernel_image('', image_node)
370*4882a593Smuzhiyun        return [node]
371*4882a593Smuzhiyun
372*4882a593Smuzhiyun# figure handling
373*4882a593Smuzhiyun# ---------------------
374*4882a593Smuzhiyun
375*4882a593Smuzhiyundef visit_kernel_figure(self, node):   # pylint: disable=W0613
376*4882a593Smuzhiyun    """Visitor of the ``kernel_figure`` Node.
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun    Handles the ``image`` child-node with the ``convert_image(...)``.
379*4882a593Smuzhiyun    """
380*4882a593Smuzhiyun    img_node = node[0][0]
381*4882a593Smuzhiyun    convert_image(img_node, self)
382*4882a593Smuzhiyun
383*4882a593Smuzhiyunclass kernel_figure(nodes.figure):
384*4882a593Smuzhiyun    """Node for ``kernel-figure`` directive."""
385*4882a593Smuzhiyun
386*4882a593Smuzhiyunclass KernelFigure(Figure):
387*4882a593Smuzhiyun    u"""KernelImage directive
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun    Earns everything from ``.. figure::`` directive, except *remote URI* and
390*4882a593Smuzhiyun    *glob* pattern.  The KernelFigure wraps a figure node into a kernel_figure
391*4882a593Smuzhiyun    node. See ``visit_kernel_figure``.
392*4882a593Smuzhiyun    """
393*4882a593Smuzhiyun
394*4882a593Smuzhiyun    def run(self):
395*4882a593Smuzhiyun        uri = self.arguments[0]
396*4882a593Smuzhiyun        if uri.endswith('.*') or uri.find('://') != -1:
397*4882a593Smuzhiyun            raise self.severe(
398*4882a593Smuzhiyun                'Error in "%s: %s":'
399*4882a593Smuzhiyun                ' glob pattern and remote images are not allowed'
400*4882a593Smuzhiyun                % (self.name, uri))
401*4882a593Smuzhiyun        result = Figure.run(self)
402*4882a593Smuzhiyun        if len(result) == 2 or isinstance(result[0], nodes.system_message):
403*4882a593Smuzhiyun            return result
404*4882a593Smuzhiyun        (figure_node,) = result
405*4882a593Smuzhiyun        # wrap figure node into a kernel_figure node / see visitors
406*4882a593Smuzhiyun        node = kernel_figure('', figure_node)
407*4882a593Smuzhiyun        return [node]
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun
410*4882a593Smuzhiyun# render handling
411*4882a593Smuzhiyun# ---------------------
412*4882a593Smuzhiyun
413*4882a593Smuzhiyundef visit_kernel_render(self, node):
414*4882a593Smuzhiyun    """Visitor of the ``kernel_render`` Node.
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun    If rendering tools available, save the markup of the ``literal_block`` child
417*4882a593Smuzhiyun    node into a file and replace the ``literal_block`` node with a new created
418*4882a593Smuzhiyun    ``image`` node, pointing to the saved markup file. Afterwards, handle the
419*4882a593Smuzhiyun    image child-node with the ``convert_image(...)``.
420*4882a593Smuzhiyun    """
421*4882a593Smuzhiyun    app = self.builder.app
422*4882a593Smuzhiyun    srclang = node.get('srclang')
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun    kernellog.verbose(app, 'visit kernel-render node lang: "%s"' % (srclang))
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun    tmp_ext = RENDER_MARKUP_EXT.get(srclang, None)
427*4882a593Smuzhiyun    if tmp_ext is None:
428*4882a593Smuzhiyun        kernellog.warn(app, 'kernel-render: "%s" unknown / include raw.' % (srclang))
429*4882a593Smuzhiyun        return
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun    if not dot_cmd and tmp_ext == '.dot':
432*4882a593Smuzhiyun        kernellog.verbose(app, "dot from graphviz not available / include raw.")
433*4882a593Smuzhiyun        return
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun    literal_block = node[0]
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun    code      = literal_block.astext()
438*4882a593Smuzhiyun    hashobj   = code.encode('utf-8') #  str(node.attributes)
439*4882a593Smuzhiyun    fname     = path.join('%s-%s' % (srclang, sha1(hashobj).hexdigest()))
440*4882a593Smuzhiyun
441*4882a593Smuzhiyun    tmp_fname = path.join(
442*4882a593Smuzhiyun        self.builder.outdir, self.builder.imagedir, fname + tmp_ext)
443*4882a593Smuzhiyun
444*4882a593Smuzhiyun    if not path.isfile(tmp_fname):
445*4882a593Smuzhiyun        mkdir(path.dirname(tmp_fname))
446*4882a593Smuzhiyun        with open(tmp_fname, "w") as out:
447*4882a593Smuzhiyun            out.write(code)
448*4882a593Smuzhiyun
449*4882a593Smuzhiyun    img_node = nodes.image(node.rawsource, **node.attributes)
450*4882a593Smuzhiyun    img_node['uri'] = path.join(self.builder.imgpath, fname + tmp_ext)
451*4882a593Smuzhiyun    img_node['candidates'] = {
452*4882a593Smuzhiyun        '*': path.join(self.builder.imgpath, fname + tmp_ext)}
453*4882a593Smuzhiyun
454*4882a593Smuzhiyun    literal_block.replace_self(img_node)
455*4882a593Smuzhiyun    convert_image(img_node, self, tmp_fname)
456*4882a593Smuzhiyun
457*4882a593Smuzhiyun
458*4882a593Smuzhiyunclass kernel_render(nodes.General, nodes.Inline, nodes.Element):
459*4882a593Smuzhiyun    """Node for ``kernel-render`` directive."""
460*4882a593Smuzhiyun    pass
461*4882a593Smuzhiyun
462*4882a593Smuzhiyunclass KernelRender(Figure):
463*4882a593Smuzhiyun    u"""KernelRender directive
464*4882a593Smuzhiyun
465*4882a593Smuzhiyun    Render content by external tool.  Has all the options known from the
466*4882a593Smuzhiyun    *figure*  directive, plus option ``caption``.  If ``caption`` has a
467*4882a593Smuzhiyun    value, a figure node with the *caption* is inserted. If not, a image node is
468*4882a593Smuzhiyun    inserted.
469*4882a593Smuzhiyun
470*4882a593Smuzhiyun    The KernelRender directive wraps the text of the directive into a
471*4882a593Smuzhiyun    literal_block node and wraps it into a kernel_render node. See
472*4882a593Smuzhiyun    ``visit_kernel_render``.
473*4882a593Smuzhiyun    """
474*4882a593Smuzhiyun    has_content = True
475*4882a593Smuzhiyun    required_arguments = 1
476*4882a593Smuzhiyun    optional_arguments = 0
477*4882a593Smuzhiyun    final_argument_whitespace = False
478*4882a593Smuzhiyun
479*4882a593Smuzhiyun    # earn options from 'figure'
480*4882a593Smuzhiyun    option_spec = Figure.option_spec.copy()
481*4882a593Smuzhiyun    option_spec['caption'] = directives.unchanged
482*4882a593Smuzhiyun
483*4882a593Smuzhiyun    def run(self):
484*4882a593Smuzhiyun        return [self.build_node()]
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun    def build_node(self):
487*4882a593Smuzhiyun
488*4882a593Smuzhiyun        srclang = self.arguments[0].strip()
489*4882a593Smuzhiyun        if srclang not in RENDER_MARKUP_EXT.keys():
490*4882a593Smuzhiyun            return [self.state_machine.reporter.warning(
491*4882a593Smuzhiyun                'Unknown source language "%s", use one of: %s.' % (
492*4882a593Smuzhiyun                    srclang, ",".join(RENDER_MARKUP_EXT.keys())),
493*4882a593Smuzhiyun                line=self.lineno)]
494*4882a593Smuzhiyun
495*4882a593Smuzhiyun        code = '\n'.join(self.content)
496*4882a593Smuzhiyun        if not code.strip():
497*4882a593Smuzhiyun            return [self.state_machine.reporter.warning(
498*4882a593Smuzhiyun                'Ignoring "%s" directive without content.' % (
499*4882a593Smuzhiyun                    self.name),
500*4882a593Smuzhiyun                line=self.lineno)]
501*4882a593Smuzhiyun
502*4882a593Smuzhiyun        node = kernel_render()
503*4882a593Smuzhiyun        node['alt'] = self.options.get('alt','')
504*4882a593Smuzhiyun        node['srclang'] = srclang
505*4882a593Smuzhiyun        literal_node = nodes.literal_block(code, code)
506*4882a593Smuzhiyun        node += literal_node
507*4882a593Smuzhiyun
508*4882a593Smuzhiyun        caption = self.options.get('caption')
509*4882a593Smuzhiyun        if caption:
510*4882a593Smuzhiyun            # parse caption's content
511*4882a593Smuzhiyun            parsed = nodes.Element()
512*4882a593Smuzhiyun            self.state.nested_parse(
513*4882a593Smuzhiyun                ViewList([caption], source=''), self.content_offset, parsed)
514*4882a593Smuzhiyun            caption_node = nodes.caption(
515*4882a593Smuzhiyun                parsed[0].rawsource, '', *parsed[0].children)
516*4882a593Smuzhiyun            caption_node.source = parsed[0].source
517*4882a593Smuzhiyun            caption_node.line = parsed[0].line
518*4882a593Smuzhiyun
519*4882a593Smuzhiyun            figure_node = nodes.figure('', node)
520*4882a593Smuzhiyun            for k,v in self.options.items():
521*4882a593Smuzhiyun                figure_node[k] = v
522*4882a593Smuzhiyun            figure_node += caption_node
523*4882a593Smuzhiyun
524*4882a593Smuzhiyun            node = figure_node
525*4882a593Smuzhiyun
526*4882a593Smuzhiyun        return node
527*4882a593Smuzhiyun
528*4882a593Smuzhiyundef add_kernel_figure_to_std_domain(app, doctree):
529*4882a593Smuzhiyun    """Add kernel-figure anchors to 'std' domain.
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun    The ``StandardDomain.process_doc(..)`` method does not know how to resolve
532*4882a593Smuzhiyun    the caption (label) of ``kernel-figure`` directive (it only knows about
533*4882a593Smuzhiyun    standard nodes, e.g. table, figure etc.). Without any additional handling
534*4882a593Smuzhiyun    this will result in a 'undefined label' for kernel-figures.
535*4882a593Smuzhiyun
536*4882a593Smuzhiyun    This handle adds labels of kernel-figure to the 'std' domain labels.
537*4882a593Smuzhiyun    """
538*4882a593Smuzhiyun
539*4882a593Smuzhiyun    std = app.env.domains["std"]
540*4882a593Smuzhiyun    docname = app.env.docname
541*4882a593Smuzhiyun    labels = std.data["labels"]
542*4882a593Smuzhiyun
543*4882a593Smuzhiyun    for name, explicit in iteritems(doctree.nametypes):
544*4882a593Smuzhiyun        if not explicit:
545*4882a593Smuzhiyun            continue
546*4882a593Smuzhiyun        labelid = doctree.nameids[name]
547*4882a593Smuzhiyun        if labelid is None:
548*4882a593Smuzhiyun            continue
549*4882a593Smuzhiyun        node = doctree.ids[labelid]
550*4882a593Smuzhiyun
551*4882a593Smuzhiyun        if node.tagname == 'kernel_figure':
552*4882a593Smuzhiyun            for n in node.next_node():
553*4882a593Smuzhiyun                if n.tagname == 'caption':
554*4882a593Smuzhiyun                    sectname = clean_astext(n)
555*4882a593Smuzhiyun                    # add label to std domain
556*4882a593Smuzhiyun                    labels[name] = docname, labelid, sectname
557*4882a593Smuzhiyun                    break
558