""" Automatically document Bokeh Jinja2 templates. The ``bokeh-jinja`` directive generates useful type information for the property attribute, including cross links to the relevant property types. Additionally, any per-attribute docstrings are also displayed. This directive takes the path to an attribute on a Bokeh model class as an argument:: .. bokeh-jinja:: bokeh.core.templates.FOO """ from __future__ import absolute_import, print_function import importlib from os.path import basename import re import sys import textwrap from docutils import nodes from docutils.statemachine import ViewList import jinja2 from sphinx.util.compat import Directive from sphinx.util.nodes import nested_parse_with_titles JINJA_TEMPLATE = jinja2.Template(u""" .. data:: {{ name }} :module: {{ module }} :annotation: = {{ objrepr }} {% if doc %}{{ doc|indent(4) }}{% endif %} .. collapsible-code-block:: {{ lang }} :heading: Template: {{ filename }} {{ template_text|indent(8) }} """) DOCPAT = re.compile(r"\{\#(.+?)\#\}", flags=re.MULTILINE|re.DOTALL) class BokehJinjaDirective(Directive): has_content = True required_arguments = 1 def run(self): env = self.state.document.settings.env app = env.app template_path = self.arguments[0] module_path, template_name = template_path.rsplit('.', 1) try: module = importlib.import_module(module_path) except ImportError: msg = "Unable to import Bokeh template module: %s" % module_path app.warn(msg) node = nodes.error(None, nodes.paragraph(text=msg), nodes.paragraph(text=str(sys.exc_info()[1]))) return [node] template = getattr(module, template_name, None) if template is None: msg = "Unable to find Bokeh template: %s" % template_path app.warn(msg) node = nodes.error(None, nodes.paragraph(text=msg)) return [node] template_text = open(template.filename).read() m = DOCPAT.match(template_text) if m: doc = m.group(1) else: doc = None filename = basename(template.filename) rst_text = JINJA_TEMPLATE.render( name=template_name, module=module_path, objrepr=repr(template), doc="" if doc is None else textwrap.dedent(doc), lang=filename.rsplit('.', 1)[-1], filename=filename, template_text=DOCPAT.sub("", template_text), ) result = ViewList() for line in rst_text.split("\n"): result.append(line, "") node = nodes.paragraph() node.document = self.state.document nested_parse_with_titles(self.state, result, node) return node.children def setup(app): app.add_directive_to_domain('py', 'bokeh-jinja', BokehJinjaDirective)