File: notebook_sphinxext.py

package info (click to toggle)
mdtraj 1.11.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,324 kB
  • sloc: python: 25,217; ansic: 6,266; cpp: 5,685; xml: 1,252; makefile: 192
file content (138 lines) | stat: -rw-r--r-- 3,974 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# Copied from the yt_project, commit e8fb57e
# yt/doc/extensions/notebook_sphinxext.py
#  https://bitbucket.org/yt_analysis/yt/src/e8fb57e66ca42e26052dadf054a5c782740abec9/doc/extensions/notebook_sphinxext.py?at=yt

# Almost completely re-written by Matthew Harrigan to use nbconvert v4


import os
import shutil

import nbformat
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from nbconvert import HTMLExporter, PythonExporter


def _read(wd, name):
    with open(f"{wd}/{name}.ipynb") as f:
        notebook = nbformat.read(f, as_version=4)
    return notebook


def export_html(wd, name):
    nb = _read(wd, name)

    config = {
        "Exporter": {
            "template_file": "embed",
            "template_path": ["./sphinxext/"],
        },
        "ExecutePreprocessor": {"enabled": True},
        "ExtractOutputPreprocessor": {"enabled": True},
        "CSSHTMLHeaderPreprocessor": {"enabled": True},
    }

    exporter = HTMLExporter(config)

    try:
        body, resources = exporter.from_notebook_node(nb)

        for fn, data in resources["outputs"].items():
            with open(f"{wd}/{fn}", "wb") as f:
                f.write(data)
        return body
    except Exception as e:
        return str(e)


def export_python(wd, name):
    nb = _read(wd, name)
    exporter = PythonExporter()
    body, resources = exporter.from_notebook_node(nb)
    with open(f"{wd}/{name}.py", "w") as f:
        f.write(body)


class NotebookDirective(Directive):
    """Insert an evaluated notebook into a document"""

    required_arguments = 1
    optional_arguments = 1
    option_spec = {"skip_exceptions": directives.flag}
    final_argument_whitespace = True

    def run(self):
        # check if raw html is supported
        if not self.state.document.settings.raw_enabled:
            raise self.warning('"%s" directive disabled.' % self.name)

        # get path to notebook
        nb_rel_path = self.arguments[0]
        nb_abs_path = f"{setup.confdir}/../{nb_rel_path}"
        nb_abs_path = os.path.abspath(nb_abs_path)
        nb_name = os.path.basename(nb_rel_path).split(".")[0]
        dest_dir = f"{setup.app.builder.outdir}/{os.path.dirname(nb_rel_path)}/{nb_name}"
        fmt = {"wd": dest_dir, "name": nb_name}

        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)

        shutil.copyfile(nb_abs_path, "{wd}/{name}.ipynb".format(**fmt))

        # TODO: Actually save evaluated notebook
        shutil.copyfile(nb_abs_path, "{wd}/{name}_eval.ipynb".format(**fmt))

        html = export_html(**fmt)
        export_python(**fmt)

        # Create link to notebook and script files
        link_rst = "({uneval}; {eval}; {py})".format(
            uneval=formatted_link("{wd}/{name}.ipynb".format(**fmt)),
            eval=formatted_link("{wd}/{name}_eval.ipynb".format(**fmt)),
            py=formatted_link("{wd}/{name}.py".format(**fmt)),
        )

        rst_file = self.state_machine.document.attributes["source"]
        self.state_machine.insert_input([link_rst], rst_file)

        # create notebook node
        attributes = {"format": "html", "source": "nb_path"}
        nb_node = notebook_node("", html, **attributes)
        nb_node.source, nb_node.line = self.state_machine.get_source_and_line(
            self.lineno,
        )

        # add dependency
        self.state.document.settings.record_dependencies.add(nb_abs_path)

        return [nb_node]


class notebook_node(nodes.raw):
    pass


def formatted_link(path):
    return f"`{os.path.basename(path)} <{path}>`__"


def visit_notebook_node(self, node):
    self.visit_raw(node)


def depart_notebook_node(self, node):
    self.depart_raw(node)


def setup(app):
    setup.app = app
    setup.config = app.config
    setup.confdir = app.confdir

    app.add_node(
        notebook_node,
        html=(visit_notebook_node, depart_notebook_node),
    )

    app.add_directive("notebook", NotebookDirective)