Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

docs-rst: automatically convert Graphviz and SVG images

This patch brings scalable figure, image handling and a concept to
embed *render* markups:

* DOT (http://www.graphviz.org)
* SVG

For image handling use the 'image' replacement::

.. kernel-image:: svg_image.svg
:alt: simple SVG image

For figure handling use the 'figure' replacement::

.. kernel-figure:: svg_image.svg
:alt: simple SVG image

SVG image example

Embed *render* markups (or languages) like Graphviz's **DOT** is
provided by the *render* directive.::

.. kernel-render:: DOT
:alt: foobar digraph
:caption: Embedded **DOT** (Graphviz) code.

digraph foo {
"bar" -> "baz";
}

The *render* directive is a concept to integrate *render* markups and
languages, yet supported markups:

* DOT: render embedded Graphviz's **DOT**
* SVG: render embedded Scalable Vector Graphics (**SVG**)

Cc: Jani Nikula <jani.nikula@linux.intel.com>
Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Tested-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Tested-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com> (v2 - v5)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de> (v1, v6)
Signed-off-by: Jonathan Corbet <corbet@lwn.net>

authored by

Markus Heiser and committed by
Jonathan Corbet
db6ccf23 c3c53600

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