Source code for ipypublish.sphinx.notebook.directives

"""
adapted from nbsphinx
"""
import docutils
from docutils import nodes  # noqa E501
from docutils.parsers import rst
from docutils.statemachine import StringList

from ipypublish.sphinx.utils import import_sphinx
from ipypublish.sphinx.notebook.nodes import (
    AdmonitionNode,
    CodeAreaNode,
    FancyOutputNode,
)


[docs]class NbAdmonition(rst.Directive): """Base class for NbInfo and NbWarning.""" required_arguments = 0 optional_arguments = 0 option_spec = {} has_content = True
[docs] def run(self): """This is called by the reST parser.""" node = AdmonitionNode(classes=["admonition", self._class]) self.state.nested_parse(self.content, self.content_offset, node) return [node]
[docs]class NbWarning(NbAdmonition): """A warning box.""" _class = "warning"
[docs]class NbInfo(NbAdmonition): """An information box.""" _class = "note"
[docs]class NBInputToggle(rst.Directive): """ a toggle button for nbinput cells """ _class = "nbinput-toggle-all" _default_text = "Toggle Input Cells" required_arguments = 0 optional_arguments = 0 option_spec = {} has_content = True # button text
[docs] def run(self): """This is called by the reST parser.""" node = nodes.container() node["classes"].append(self._class) if self.content: self.state.nested_parse(self.content, self.content_offset, node) else: text = ( self.arguments[0] if self.arguments and self.arguments[0] else self._default_text ) paragraph = nodes.paragraph(text=text) node += paragraph return [node]
[docs]class NBOutputToggle(NBInputToggle): """ a toggle button for nboutput cells """ _class = "nboutput-toggle-all" _default_text = "Toggle Output Cells"
[docs]class NbInput(rst.Directive): """A notebook input cell with prompt and code area.""" required_arguments = 0 optional_arguments = 1 # lexer name final_argument_whitespace = False option_spec = { "execution-count": rst.directives.positive_int, "empty-lines-before": rst.directives.nonnegative_int, "empty-lines-after": rst.directives.nonnegative_int, "no-output": rst.directives.flag, "caption": rst.directives.unchanged, "name": rst.directives.unchanged, "add-toggle": rst.directives.flag, } has_content = True
[docs] def run(self): """This is called by the reST parser.""" self.state.document["ipysphinx_include_css"] = True return _create_nbcell_nodes(self)
[docs]class NbOutput(rst.Directive): """A notebook output cell with optional prompt.""" required_arguments = 0 optional_arguments = 1 # 'rst' or nothing (which means literal text) final_argument_whitespace = False option_spec = { "execution-count": rst.directives.positive_int, "more-to-come": rst.directives.flag, "empty-lines-before": rst.directives.nonnegative_int, "empty-lines-after": rst.directives.nonnegative_int, "class": rst.directives.unchanged, "add-toggle": rst.directives.flag, } has_content = True
[docs] def run(self): """This is called by the reST parser.""" self.state.document["ipysphinx_include_css"] = True return _create_nbcell_nodes(self)
def _create_nbcell_nodes(directive): """Create nodes for an input or output notebook cell.""" sphinx = import_sphinx() language = "none" prompt = "" fancy_output = False execution_count = directive.options.get("execution-count") config = directive.state.document.settings.env.config if isinstance(directive, NbInput): outer_classes = ["nbinput"] if "no-output" in directive.options: outer_classes.append("nblast") inner_classes = ["input_area"] if directive.arguments: language = directive.arguments[0] prompt_template = config.ipysphinx_input_prompt if not execution_count: execution_count = " " elif isinstance(directive, NbOutput): outer_classes = ["nboutput"] if "more-to-come" not in directive.options: outer_classes.append("nblast") inner_classes = ["output_area"] # 'class' can be 'stderr' inner_classes.append(directive.options.get("class", "")) prompt_template = config.ipysphinx_output_prompt if directive.arguments and directive.arguments[0] in ["rst", "ansi"]: fancy_output = True else: raise AssertionError("directive should be NbInput or NbOutput") outer_node = docutils.nodes.container(classes=outer_classes) # add prompts if config.ipysphinx_show_prompts and execution_count: prompt = prompt_template.format(count=execution_count) prompt_node = docutils.nodes.literal_block( prompt, prompt, language="none", classes=["prompt"] ) elif config.ipysphinx_show_prompts: prompt = "" prompt_node = docutils.nodes.container(classes=["prompt", "empty"]) if config.ipysphinx_show_prompts: # NB: Prompts are added manually in LaTeX output outer_node += sphinx.addnodes.only("", prompt_node, expr="html") if fancy_output: inner_node = docutils.nodes.container(classes=inner_classes) sphinx.util.nodes.nested_parse_with_titles( directive.state, directive.content, inner_node ) outtype = directive.arguments[0] if outtype == "rst": outer_node += FancyOutputNode("", inner_node, prompt=prompt) elif outtype == "ansi": outer_node += inner_node else: raise AssertionError( "`.. nboutput:: type` should be 'rst' or 'ansi', " "not: {}".format(outtype) ) else: text = "\n".join(directive.content.data) inner_node = docutils.nodes.literal_block( text, text, language=language, classes=inner_classes ) codearea_node = CodeAreaNode("", inner_node, prompt=prompt) # create a literal text block (e.g. with the code-block directive), # that starts or ends with a blank line # (see http://stackoverflow.com/q/34050044/) for attr in "empty-lines-before", "empty-lines-after": value = directive.options.get(attr, 0) if value: codearea_node[attr] = value # add caption and label, see: if directive.options.get("caption", False): caption = directive.options.get("caption") wrapper = container_wrapper(directive, inner_node, caption, inner_classes) # add label directive.add_name(wrapper) outer_node += wrapper else: outer_node += codearea_node if isinstance(directive, NbInput) and ( config.ipysphinx_input_toggle or "add-toggle" in directive.options ): directive.state.document["ipysphinx_include_js"] = True outer_node += sphinx.addnodes.only( "", docutils.nodes.container(classes=["toggle-nbinput", "empty"]), expr="html", ) if isinstance(directive, NbOutput) and ( config.ipysphinx_output_toggle or "add-toggle" in directive.options ): directive.state.document["ipysphinx_include_js"] = True outer_node += sphinx.addnodes.only( "", docutils.nodes.container(classes=["toggle-nboutput", "empty"]), expr="html", ) return [outer_node]
[docs]def container_wrapper(directive, literal_node, caption, classes): """adapted from https://github.com/sphinx-doc/sphinx/blob/master/sphinx/directives/code.py """ container_node = docutils.nodes.container( "", literal_block=True, classes=classes ) # ['literal-block-wrapper'] parsed = docutils.nodes.Element() directive.state.nested_parse( StringList([caption], source=""), directive.content_offset, parsed ) if isinstance(parsed[0], docutils.nodes.system_message): msg = "Invalid caption: %s" % parsed[0].astext() raise ValueError(msg) elif isinstance(parsed[0], docutils.nodes.Element): caption_node = docutils.nodes.caption( parsed[0].rawsource, "", *parsed[0].children ) caption_node.source = literal_node.source caption_node.line = literal_node.line container_node += caption_node container_node += literal_node return container_node else: raise RuntimeError # never reached