/
__init__.py
196 lines (160 loc) · 5.96 KB
/
__init__.py
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
__version__ = "0.8.4"
from pathlib import Path
from docutils import nodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util import logging
from myst_parser.myst_refs import MystReferenceResolver
from jupyter_sphinx.ast import ( # noqa: F401
JupyterWidgetStateNode,
JupyterWidgetViewNode,
JupyterCell,
)
from .cache import execution_cache
from .parser import (
NotebookParser,
CellNode,
CellInputNode,
CellOutputNode,
CellOutputBundleNode,
)
from .transform import CellOutputsToNodes
from .nb_glue import glue # noqa: F401
from .nb_glue.domain import (
NbGlueDomain,
PasteMathNode,
PasteNode,
PasteTextNode,
PasteInlineNode,
)
from .nb_glue.transform import PasteNodesToDocutils
LOGGER = logging.getLogger(__name__)
def static_path(app):
static_path = Path(__file__).absolute().with_name("_static")
app.config.html_static_path.append(str(static_path))
def set_valid_execution_paths(app):
"""Set files excluded from execution, and valid file suffixes
Patterns given in execution_excludepatterns conf variable from executing.
"""
app.env.excluded_nb_exec_paths = {
str(path)
for pat in app.config["execution_excludepatterns"]
for path in Path().cwd().rglob(pat)
}
LOGGER.verbose("MyST-NB: Excluded Paths: %s", app.env.excluded_nb_exec_paths)
app.env.allowed_nb_exec_suffixes = {
suffix
for suffix, parser_type in app.config["source_suffix"].items()
if parser_type in ("myst-nb",)
}
def add_exclude_patterns(app, config):
"""Add default exclude patterns (if not already present)."""
if "**.ipynb_checkpoints" not in config.exclude_patterns:
config.exclude_patterns.append("**.ipynb_checkpoints")
def update_togglebutton_classes(app, config):
to_add = [
".tag_hide_input div.cell_input",
".tag_hide-input div.cell_input",
".tag_hide_output div.cell_output",
".tag_hide-output div.cell_output",
".tag_hide_cell.cell",
".tag_hide-cell.cell",
]
for selector in to_add:
config.togglebutton_selector += f", {selector}"
def save_glue_cache(app, env):
NbGlueDomain.from_env(env).write_cache()
class CodeCell(SphinxDirective):
"""Raises a warning if it is triggered, it should not make it to the doctree."""
optional_arguments = 1
final_argument_whitespace = True
has_content = True
def run(self):
LOGGER.warning(
(
"Found a `code-cell` directive. To execute the `code-cell`, you must "
"add Jupytext header content to the page. See "
"https://myst-nb.readthedocs.io/en/latest/use/markdown.html "
"for more information."
),
location=(self.env.docname, self.lineno),
)
return []
def setup(app):
"""Initialize Sphinx extension."""
# Allow parsing ipynb files
app.add_source_suffix(".md", "myst-nb")
app.add_source_suffix(".ipynb", "myst-nb")
app.add_source_parser(NotebookParser)
app.setup_extension("sphinx_togglebutton")
# Helper functions for the registry, pulled from jupyter-sphinx
def skip(self, node):
raise nodes.SkipNode
# Used to render an element node as HTML
def visit_element_html(self, node):
self.body.append(node.html())
raise nodes.SkipNode
# Shortcut for registering our container nodes
render_container = (
lambda self, node: self.visit_container(node),
lambda self, node: self.depart_container(node),
)
# Register our container nodes, these should behave just like a regular container
for node in [CellNode, CellInputNode, CellOutputNode]:
app.add_node(
node,
override=True,
html=(render_container),
latex=(render_container),
textinfo=(render_container),
text=(render_container),
man=(render_container),
)
# Register the output bundle node.
# No translators should touch this node because we'll replace it in a post-transform
app.add_node(
CellOutputBundleNode,
override=True,
html=(skip, None),
latex=(skip, None),
textinfo=(skip, None),
text=(skip, None),
man=(skip, None),
)
# Register our inline nodes so they can be parsed as a part of titles
# No translators should touch these nodes because we'll replace them in a transform
for node in [PasteMathNode, PasteNode, PasteTextNode, PasteInlineNode]:
app.add_node(
node,
override=True,
html=(skip, None),
latex=(skip, None),
textinfo=(skip, None),
text=(skip, None),
man=(skip, None),
)
# Add configuration for the cache
app.add_config_value("jupyter_cache", "", "env")
app.add_config_value("execution_excludepatterns", [], "env")
app.add_config_value("jupyter_execute_notebooks", "auto", "env")
app.add_config_value("execution_timeout", 30, "env")
# Register our post-transform which will convert output bundles to nodes
app.add_post_transform(PasteNodesToDocutils)
app.add_post_transform(CellOutputsToNodes)
# Myst transforms
app.add_post_transform(MystReferenceResolver)
# Events
app.connect("builder-inited", static_path)
app.connect("builder-inited", set_valid_execution_paths)
app.connect("env-get-outdated", execution_cache)
app.connect("config-inited", add_exclude_patterns)
app.connect("config-inited", update_togglebutton_classes)
app.connect("env-updated", save_glue_cache)
# Misc
app.add_css_file("mystnb.css")
app.add_js_file("mystnb.js")
app.setup_extension("jupyter_sphinx")
app.add_domain(NbGlueDomain)
app.add_directive("code-cell", CodeCell)
# TODO need to deal with key clashes in NbGlueDomain.merge_domaindata
# before this is parallel_read_safe
return {"version": __version__, "parallel_read_safe": False}