def apply(self):
        sphinx = import_sphinx()
        env = self.document.settings.env
        for node in self.document.traverse(docutils.nodes.reference):
            base, suffix, fragment = _local_file_from_reference(node,
                                                                self.document)
            if not base:
                continue

            for s in env.config.source_suffix:
                if suffix.lower() == s.lower():
                    target = base
                    if fragment:
                        target_ext = suffix + fragment
                        reftype = 'ref'
                    else:
                        target_ext = ''
                        reftype = 'doc'
                    break
            else:
                continue  # Not a link to a potential Sphinx source file

            target_docname = nbconvert.filters.posix_path(os.path.normpath(
                os.path.join(os.path.dirname(env.docname), target)))
            if target_docname in env.found_docs:
                reftarget = '/' + target_docname + target_ext
                if reftype == 'ref':
                    reftarget = reftarget.lower()
                linktext = node.astext()
                xref = sphinx.addnodes.pending_xref(
                    reftype=reftype, reftarget=reftarget, refdomain='std',
                    refwarn=True, refexplicit=True, refdoc=env.docname)
                xref += docutils.nodes.Text(linktext, linktext)
                node.replace_self(xref)
Exemple #2
0
def setup(app):
    # type: (Sphinx) -> dict
    """Initialize Sphinx extension.

    """
    # delayed import of sphinx
    sphinx = import_sphinx()

    try:
        transforms = app.registry.get_transforms()
    except AttributeError:  # Sphinx < 1.7
        from sphinx.io import SphinxStandaloneReader

        transforms = SphinxStandaloneReader.transforms

    def add_transform(transform, post=False):
        if transform not in transforms:
            if post:
                app.add_post_transform(transform)
            else:
                app.add_transform(transform)

    # BibGlossary functionality
    app.connect("builder-inited", bibproc.init_bibgloss_cache)
    app.connect("doctree-resolved", bibproc.process_citations)
    app.connect("doctree-resolved", bibproc.process_citation_references)
    app.connect("env-purge-doc", bibproc.purge_bibgloss_cache)
    # app.connect("env-updated", bibproc.check_duplicate_labels)
    app.add_config_value("bibgloss_convert_latex", True, rebuild="html")
    app.add_config_value("bibgloss_default_style", "list", rebuild="html")

    app.add_directive("bibglossary", BibGlossaryDirective)
    # Note: because docutils.parsers.rst.roles.role(role_name)
    #       applies role_name.lower(), you can't have unique gls and Gls roles
    app.add_role("gls", GLSRole())
    app.add_role("glsc", GLSCapitalRole())
    app.add_role("glspl", GLSPluralRole())
    app.add_role("glscpl", GLSPluralCapitalRole())
    app.add_node(BibGlossaryNode, override=True)

    add_transform(BibGlossaryTransform)
    # these patches have been fixed (by me!) upstream
    if sphinx.version_info < (2, ):
        add_transform(OverrideCitationReferences)
        add_transform(HandleMissingCitesTransform, post=True)

    # Parallel read is not safe at the moment: in the current design,
    # the document that contains references must be read last for all
    # references to be resolved.
    parallel_read_safe = False

    return {
        "version": __version__,
        "parallel_read_safe": parallel_read_safe,
        "parallel_write_safe": True,
        "env_version": 1,
    }
def associate_single_extension(app, extension, suffix='jupyter_notebook',
                               called_from_setup=False,
                               config_value=None):
    # type: (Sphinx, str) -> None
    """ associate a file extension with the NBParser

    Notes
    -----
    The Ugly hack to modify source_suffix and source_parsers.
    Once https://github.com/sphinx-doc/sphinx/pull/2209
    is merged it won't be necessary.
    See also https://github.com/sphinx-doc/sphinx/issues/2162.

    """
    if not isinstance(extension, string_types):
        raise AssertionError("extension is not a string: {}".format(extension))
    if not extension.startswith("."):
        raise AssertionError(
            "extension should start with a '.': {}".format(extension))

    sphinx = import_sphinx()

    try:
        # Available since Sphinx 1.8:
        app.add_source_suffix(extension, suffix)
    except AttributeError:

        if not called_from_setup:
            # too late to set up, see
            # https://github.com/sphinx-doc/sphinx/issues/2162#issuecomment-169193107
            raise sphinx.errors.ExtensionError(
                "Using sphinx<1.8, {0} cannot be used.\n"
                "Instead use: source_parsers = "
                "{{'{1}': 'ipypublish.ipysphinx.parser.NBParser'}} "
                "in conf.py".format(config_value, extension))

        source_suffix = app.config._raw_config.get('source_suffix', ['.rst'])

        if isinstance(source_suffix, sphinx.config.string_types):
            source_suffix = [source_suffix]
        if extension not in source_suffix:
            source_suffix.append(extension)
            app.config._raw_config['source_suffix'] = source_suffix
        source_parsers = app.config._raw_config.get('source_parsers', {})

        if (extension not in source_parsers
                and extension[1:] not in source_parsers):
            source_parsers[extension] = NBParser
            app.config._raw_config['source_parsers'] = source_parsers

    sphinx.util.logging.getLogger('nbparser').info(
        "ipypublish: associated {} with NBParser".format(extension))
Exemple #4
0
def associate_single_extension(app, extension, suffix="jupyter_notebook"):
    # type: (Sphinx, str) -> None
    """Associate a file extension with the NBParser."""
    if not isinstance(extension, six.string_types):
        raise AssertionError("extension is not a string: {}".format(extension))
    if not extension.startswith("."):
        raise AssertionError(
            "extension should start with a '.': {}".format(extension))

    sphinx = import_sphinx()

    app.add_source_suffix(extension, suffix)
    sphinx.util.logging.getLogger("nbparser").info(
        "ipypublish: associated {} with NBParser".format(extension),
        color="green")
Exemple #5
0
 def apply(self):
     sphinx = import_sphinx()
     env = self.document.settings.env
     file_ext = os.path.splitext(env.doc2path(env.docname))[1]
     for sig in self.document.traverse(sphinx.addnodes.desc_signature):
         try:
             title = sig["ids"][0]
         except IndexError:
             # Object has same name as another, so skip it
             continue
         link_id = title.replace(" ", "-")
         sig["ids"] = [link_id]
         label = "/" + env.docname + file_ext + "#" + link_id
         label = label.lower()
         env.domaindata["std"]["labels"][label] = (env.docname, link_id,
                                                   title)
         env.domaindata["std"]["anonlabels"][label] = (env.docname, link_id)
Exemple #6
0
def add_ipywidgets_js_path(app, env):
    """Insert the ipywidgets javascript url, to pages created from notebooks containing widgets."""
    if not (getattr(env, "ipysphinx_widgets", set())
            or app.config.ipysphinx_always_add_jsurls):
        return
    sphinx = import_sphinx()
    widgets_path = None
    if app.config.ipysphinx_widgets_jsurl is None:
        try:
            from ipywidgets.embed import DEFAULT_EMBED_REQUIREJS_URL
        except ImportError:
            logger = sphinx.util.logging.getLogger(__name__)
            logger.warning(
                "ipysphinx_widgets_jsurl not given and ipywidgets module unavailable"
            )
        else:
            widgets_path = DEFAULT_EMBED_REQUIREJS_URL
    else:
        widgets_path = app.config.ipysphinx_widgets_jsurl
    if widgets_path is not None:
        app.add_js_file(widgets_path, **app.config.ipysphinx_widgetsjs_options)
Exemple #7
0
    def __init__(self, *args, **kwargs):

        self.app = None
        self.config = None
        self.env = None

        try:
            sphinx = import_sphinx()

            class NotebookError(sphinx.errors.SphinxError):
                """Error during notebook parsing."""

                category = 'Notebook error'

            self.error_nb = NotebookError
            self.error_config = sphinx.errors.ConfigError
            self.logger = sphinx.util.logging.getLogger('nbparser')

        except (ImportError, AttributeError):
            self.error_nb = IOError
            self.error_config = TypeError
            self.logger = logging.getLogger('nbparser')

        super(NBParser, self).__init__(*args, **kwargs)
Exemple #8
0
def setup(app):
    # type: (Sphinx) -> dict
    """Initialize Sphinx extension.


    Notes
    -----

    TODO better latex output
    but not really interested in this as it would be duplication of effort,
    and if much better todo ipynb -> tex, rather than ipynb -> rst -> tex
    TODO handling of svg in latex
    ipypublish sets latex to output svg rather than pdf, so we don't have to
    split output into '.. only html/latex', which is an issue its something
    with a label (duplication error), however,
    this requires sphinx.ext.imgconverter to work

    """
    # delayed import of sphinx
    sphinx = import_sphinx()

    try:
        transforms = app.registry.get_transforms()
    except AttributeError:  # Sphinx < 1.7
        from sphinx.io import SphinxStandaloneReader
        transforms = SphinxStandaloneReader.transforms

    def add_transform(transform, post=False):
        if transform not in transforms:
            if post:
                app.add_post_transform(transform)
            else:
                app.add_transform(transform)

    try:
        # Available since Sphinx 1.8:
        app.add_source_parser(NBParser)
    except TypeError:
        # Available since Sphinx 1.4:
        app.add_source_parser('jupyter_notebook', NBParser)

    associate_single_extension(app, '.ipynb', called_from_setup=True)

    # config for export config
    app.add_config_value('ipysphinx_export_config', 'sphinx_ipypublish_all.ext', rebuild='env')

    # config for contolling conversion process
    # where to dump internal images, etc of the notebook
    app.add_config_value('ipysphinx_folder_suffix', '_nbfiles', rebuild='env')
    # whether to raise error if nb_name.rst already exists
    app.add_config_value('ipysphinx_overwrite_existing', False, rebuild='env')
    # additional folders containing conversion files
    app.add_config_value('ipysphinx_config_folders', (), rebuild='env')

    # config for cell prompts
    app.add_config_value('ipysphinx_show_prompts', False, rebuild='env')
    app.add_config_value('ipysphinx_input_prompt', '[{count}]:', rebuild='env')
    app.add_config_value('ipysphinx_output_prompt', '[{count}]:', rebuild='env')

    # config for cell toggling
    app.add_config_value('ipysphinx_input_toggle', False, rebuild='env')
    app.add_config_value('ipysphinx_output_toggle', False, rebuild='env')

    # config for html css
    app.add_config_value('ipysphinx_responsive_width', '540px', rebuild='html')
    app.add_config_value('ipysphinx_prompt_width', None, rebuild='html')
    # setup html style
    app.connect('builder-inited', set_css_prompts)
    app.connect('html-page-context', html_add_css)
    # add javascript
    app.connect('html-page-context', html_add_javascript)

    # config for additions to the output rst (per file)
    # these strings are processed by the exporters jinja template
    app.add_config_value('ipysphinx_prolog', None, rebuild='env')
    app.add_config_value('ipysphinx_epilog', None, rebuild='env')

    # map additional file extensions to pre-converters
    # NB: jupytext is already a default for .Rmd
    app.add_config_value('ipysphinx_preconverters', {}, rebuild='env')
    app.connect('builder-inited', associate_extensions)

    # add the main directives
    app.add_directive('nbinput', NbInput)
    app.add_directive('nboutput', NbOutput)
    app.add_directive('nbinfo', NbInfo)
    app.add_directive('nbwarning', NbWarning)
    app.add_directive('nbinput-toggle-all', NBInputToggle)
    app.add_directive('nboutput-toggle-all', NBOutputToggle)

    # add docutils nodes and visit/depart wraps
    app.add_node(
        CodeAreaNode,
        html=(lambda self, node: None, depart_codearea_html),
        #  latex=(
        #      lambda self, node: self.pushbody([]),  # used in depart
        #      lambda self, node: None,
        #  )
    )
    app.add_node(
        FancyOutputNode,
        html=(
            lambda self, node: None,
            lambda self, node: None,
        ),
        #  latex=(
        #      lambda self, node: None,
        #      lambda self, node: None,
        #  )
    )
    app.add_node(
        AdmonitionNode,
        html=(visit_admonition_html, lambda self, node: self.body.append('</div>\n')),
        #  latex=(
        #      lambda self, node:
        #      self.body.append(
        #          '\n\\begin{{sphinxadmonition}}{{{class}}}'
        #          '{{}}\\unskip'.format(node['classes'][1])),
        #      lambda self, node:
        #      self.body.append('\\end{sphinxadmonition}\n')
        #  )
    )

    # add transformations
    add_transform(CreateSectionLabels)
    add_transform(CreateDomainObjectLabels)
    add_transform(RewriteLocalLinks)

    # Work-around until https://github.com/sphinx-doc/sphinx/pull/5504 is done:
    mathjax_config = app.config._raw_config.setdefault('mathjax_config', {})
    mathjax_config.setdefault(
        'tex2jax', {
            'inlineMath': [['$', '$'], ['\\(', '\\)']],
            'processEscapes': True,
            'ignoreClass': 'document',
            'processClass': 'math|output_area',
        })

    # Make docutils' "code" directive (generated by markdown2rst/pandoc)
    # behave like Sphinx's "code-block",
    # see https://github.com/sphinx-doc/sphinx/issues/2155:
    rst.directives.register_directive('code', sphinx.directives.code.CodeBlock)

    return {
        'version': __version__,
        'parallel_read_safe': True,
        'parallel_write_safe': True,
        'env_version': 1,
    }
Exemple #9
0
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

    return [outer_node]
Exemple #10
0
    def run_postprocess(self, stream, mimetype, filepath, resources):

        # check sphinx is available and the correct version
        try:
            import_sphinx()
        except ImportError as err:
            self.handle_error(err.msg, ImportError)

        self.logger.info("Creating Sphinx files")

        titlepage = {}
        if "titlepage" in resources.get("ipub", {}):
            titlepage = resources["ipub"]["titlepage"]
        #  includes ['author', 'email', 'supervisors', 'title', 'subtitle',
        #            'tagline', 'institution', 'logo']

        # create a conf.py
        kwargs = {} if not self.conf_kwargs else self.conf_kwargs
        kwargs["ipysphinx_show_prompts"] = self.show_prompts
        kwargs["ipysphinx_input_prompt"] = self.prompt_style
        kwargs["ipysphinx_output_prompt"] = self.prompt_style
        if "author" in titlepage:
            kwargs["author"] = titlepage["author"]
        if "tagline" in titlepage:
            kwargs["description"] = titlepage["tagline"]
        if "email" in titlepage:
            kwargs["email"] = titlepage["email"]

        conf_str = make_conf(overwrite=self.override_defaults, **kwargs)
        conf_path = filepath.parent.joinpath("conf.py")
        with conf_path.open("w", encoding="utf8") as f:
            f.write(u(conf_str))

        # create an index.rst
        toc_files = [filepath.name]

        toc_depth = 3
        title = None
        prolog = []
        epilog = []
        if "author" in titlepage:
            prolog.append(".. sectionauthor:: {0}".format(titlepage["author"]))
            prolog.append("")
        if "title" in titlepage:
            title = titlepage["title"]
        if "tagline" in titlepage:
            prolog.append(titlepage["tagline"])
        if "institution" in titlepage:
            for inst in titlepage["institution"]:
                epilog.append("| " + inst)

        epilog.append("")
        epilog.append('Created by IPyPublish (version {})'.format(__version__))

        toc = resources.get("ipub", {}).get("toc", {})
        if hasattr(toc, "get") and "depth" in toc:
            toc_depth = toc["depth"]

        index_str = make_index(toc_files,
                               toc_depth=toc_depth,
                               header=title,
                               toc_numbered=self.numbered,
                               prolog="\n".join(prolog),
                               epilog="\n".join(epilog))

        index_path = filepath.parent.joinpath("index.rst")
        with index_path.open("w", encoding="utf8") as f:
            f.write(u(index_str))

        # clear any existing build
        build_dir = filepath.parent.joinpath('build/html')
        if build_dir.exists():
            # >> rm -r build/html
            shutil.rmtree(str(build_dir))
        build_dir.mkdir(parents=True)

        # run sphinx
        exec_path = find_executable('sphinx-build')
        args = [exec_path, "-b", "html"]
        if self.nitpick:
            args.append("-n")
        args.extend(
            [str(filepath.parent.absolute()),
             str(build_dir.absolute())])

        self.logger.info("running: " + " ".join(args))

        # this way overrides the logging
        # sphinx_build = find_entry_point("sphinx-build", "console_scripts",
        #                                 self.logger, "sphinx")

        def log_process_output(pipe):
            for line in iter(pipe.readline, b''):
                self.logger.info('{}'.format(line.decode("utf-8").strip()))

        process = Popen(args, stdout=PIPE, stderr=STDOUT)
        with process.stdout:
            log_process_output(process.stdout)
        exitcode = process.wait()  # 0 means success

        if exitcode:
            self.logger.warn(
                "sphinx-build exited with code: {}".format(exitcode))

        if self.open_in_browser and not exitcode:
            # get entry path
            entry_path = filepath.parent.joinpath('build/html')
            entry_path = entry_path.joinpath(
                os.path.splitext(filepath.name)[0] + '.html')
            if entry_path.exists():
                #  2 opens the url in a new tab
                webbrowser.open(entry_path.as_uri(), new=2)
            else:
                self.handle_error("can't find {0} to open".format(entry_path),
                                  IOError)

        return stream, filepath, resources
Exemple #11
0
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]
Exemple #12
0
def setup(app):
    # type: (Sphinx) -> dict
    """Initialize Sphinx extension.


    Notes
    -----

    TODO better latex output
    but not really interested in this as it would be duplication of effort,
    and if much better todo ipynb -> tex, rather than ipynb -> rst -> tex
    TODO handling of svg in latex
    ipypublish sets latex to output svg rather than pdf, so we don't have to
    split output into '.. only html/latex', which is an issue its something
    with a label (duplication error), however,
    this requires sphinx.ext.imgconverter to work

    """
    # delayed import of sphinx
    sphinx = import_sphinx()

    transforms = app.registry.get_transforms()

    def add_transform(transform, post=False):
        if transform not in transforms:
            if post:
                app.add_post_transform(transform)
            else:
                app.add_transform(transform)

    app.add_source_parser(NBParser)

    associate_single_extension(app, ".ipynb")

    # config for export config
    app.add_config_value("ipysphinx_export_config",
                         "sphinx_ipypublish_all.ext",
                         rebuild="env")

    # config for contolling conversion process
    # where to dump internal images, etc of the notebook
    app.add_config_value("ipysphinx_folder_suffix", "_nbfiles", rebuild="env")
    # whether to raise error if nb_name.rst already exists
    app.add_config_value("ipysphinx_overwrite_existing", False, rebuild="env")
    # additional folders containing conversion files
    app.add_config_value("ipysphinx_config_folders", (), rebuild="env")

    # config for cell prompts
    app.add_config_value("ipysphinx_show_prompts", False, rebuild="env")
    app.add_config_value("ipysphinx_input_prompt", "[{count}]:", rebuild="env")
    app.add_config_value("ipysphinx_output_prompt",
                         "[{count}]:",
                         rebuild="env")

    # config for cell toggling
    app.add_config_value("ipysphinx_input_toggle", False, rebuild="env")
    app.add_config_value("ipysphinx_output_toggle", False, rebuild="env")

    # config for html css
    app.add_config_value("ipysphinx_responsive_width", "540px", rebuild="html")
    app.add_config_value("ipysphinx_prompt_width", None, rebuild="html")
    # setup html style
    app.connect("config-inited", set_css_prompts)
    app.connect("html-page-context", html_add_css)
    # add javascript
    app.connect("html-page-context", html_add_javascript)
    # config for displaying ipywidgets
    app.add_config_value("ipysphinx_always_add_jsurls", False, rebuild="html")
    app.add_config_value("ipysphinx_require_jsurl", None, rebuild="html")
    app.add_config_value("ipysphinx_requirejs_options", None, rebuild="html")
    app.connect("env-updated", add_require_js_path)
    app.add_config_value("ipysphinx_widgets_jsurl", None, rebuild="html")
    app.add_config_value("ipysphinx_widgetsjs_options", {}, rebuild="html")
    app.connect("env-updated", add_ipywidgets_js_path)
    app.connect("env-purge-doc", discard_document_variables)

    # config for additions to the output rst (per file)
    # these strings are processed by the exporters jinja template
    app.add_config_value("ipysphinx_prolog", None, rebuild="env")
    app.add_config_value("ipysphinx_epilog", None, rebuild="env")

    # map additional file extensions to pre-converters
    # NB: jupytext is already a default for .Rmd
    app.add_config_value("ipysphinx_preconverters", {}, rebuild="env")
    app.connect("config-inited", associate_extensions)

    # add the main directives
    app.add_directive("nbinput", NbInput)
    app.add_directive("nboutput", NbOutput)
    app.add_directive("nbinfo", NbInfo)
    app.add_directive("nbwarning", NbWarning)
    app.add_directive("nbinput-toggle-all", NBInputToggle)
    app.add_directive("nboutput-toggle-all", NBOutputToggle)

    # add docutils nodes and visit/depart wraps
    app.add_node(
        CodeAreaNode,
        html=(lambda self, node: None, depart_codearea_html),
        #  latex=(
        #      lambda self, node: self.pushbody([]),  # used in depart
        #      lambda self, node: None,
        #  )
    )
    app.add_node(
        FancyOutputNode,
        html=(lambda self, node: None, lambda self, node: None),
        #  latex=(
        #      lambda self, node: None,
        #      lambda self, node: None,
        #  )
    )
    app.add_node(
        AdmonitionNode,
        html=(visit_admonition_html,
              lambda self, node: self.body.append("</div>\n")),
        #  latex=(
        #      lambda self, node:
        #      self.body.append(
        #          '\n\\begin{{sphinxadmonition}}{{{class}}}'
        #          '{{}}\\unskip'.format(node['classes'][1])),
        #      lambda self, node:
        #      self.body.append('\\end{sphinxadmonition}\n')
        #  )
    )

    # add transformations
    add_transform(CreateSectionLabels)
    add_transform(CreateDomainObjectLabels)
    add_transform(RewriteLocalLinks)

    # Work-around until https://github.com/sphinx-doc/sphinx/pull/5504 is done:
    mathjax_config = app.config._raw_config.setdefault("mathjax_config", {})
    mathjax_config.setdefault(
        "tex2jax",
        {
            "inlineMath": [["$", "$"], ["\\(", "\\)"]],
            "processEscapes": True,
            "ignoreClass": "document",
            "processClass": "math|output_area",
        },
    )

    # Make docutils' "code" directive (generated by markdown2rst/pandoc)
    # behave like Sphinx's "code-block",
    # see https://github.com/sphinx-doc/sphinx/issues/2155:
    rst.directives.register_directive("code", sphinx.directives.code.CodeBlock)

    return {
        "version": __version__,
        "parallel_read_safe": True,
        "parallel_write_safe": True,
        "env_version": 1,
    }