-
Notifications
You must be signed in to change notification settings - Fork 745
/
md2ipynb.py
95 lines (69 loc) · 2.4 KB
/
md2ipynb.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
import re
import sys
import time
from pathlib import Path
import black
import click
import nbformat
import notedown
from nbclient import NotebookClient
from jinja2 import Environment
env = Environment()
def run_notebook(text, kernel_name, timeout, execute) -> str:
# We add two blank lines at the end to ensure
# that the final cell also runs.
text += "\n" * 2
notebook = notedown.MarkdownReader().reads(text)
if execute:
kwargs = {}
if kernel_name is not None:
kwargs["kernel_name"] = kernel_name
client = NotebookClient(
notebook,
timeout=timeout,
resources={"metadata": {"path": "."}},
**kwargs,
)
client.execute()
# need to add language info to for syntax highlight
notebook["metadata"].update(language_info={"name": "python"})
return nbformat.writes(notebook)
def black_cells(text):
CODE_RE = r"```py(?:thon)?\s*\n(.*?)```"
text = re.sub(r"^%", r"# %-% #", text, flags=re.M)
def apply_black(match):
code = match.group(1)
formatted = black.format_str(code, mode=black.Mode())
return "\n".join(["```", formatted.rstrip(), "```"])
formatted = re.sub(CODE_RE, apply_black, text, flags=re.S)
return re.sub(r"^# %-% #", r"%", formatted, flags=re.M)
def convert(path, mode, kernel_name=None, timeout=40 * 60):
print(f"=== {path.name} ", end="")
sys.stdout.flush()
with path.open() as in_file:
template = env.from_string(in_file.read())
markdown = template.render(mode=mode)
markdown = black_cells(markdown)
start = time.time()
output = run_notebook(
markdown,
kernel_name,
timeout,
execute=mode != "skip",
)
print(f"finished evaluation in {time.time() - start} sec")
# XXX.md.input -> XXX.ipynb
# `with_suffix` only operates on last suffix, so we need some more involved
# logic.
stem = path.name.split(".", 1)[0]
with path.with_name(stem).with_suffix(".ipynb").open("w") as outfile:
outfile.write(output)
@click.command()
@click.argument("paths", type=click.Path(), nargs=-1)
@click.option("--kernel", "-k", help="Name of iPython kernel to use.")
@click.option("--mode", "-m", default="release")
def cli(paths, kernel, mode):
for file in map(Path, paths):
convert(file, kernel_name=kernel, mode=mode)
if __name__ == "__main__":
cli()