Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
holoviz
GitHub Repository: holoviz/panel
Path: blob/main/scripts/panelite/generate_panelite_content.py
2011 views
1
"""
2
Helper script to convert and copy example notebooks into JupyterLite build.
3
"""
4
import json
5
import os
6
import pathlib
7
import re
8
import shutil
9
10
from test.notebooks_with_panelite_issues import NOTEBOOK_ISSUES
11
12
import nbformat
13
14
HERE = pathlib.Path(__file__).parent
15
PANEL_BASE = HERE.parent.parent
16
EXAMPLES_DIR = PANEL_BASE / 'examples'
17
LITE_FILES = PANEL_BASE / 'lite' / 'files'
18
DOC_DIR = PANEL_BASE / 'doc'
19
BASE_DEPENDENCIES = []
20
MINIMUM_VERSIONS = {}
21
22
INLINE_DIRECTIVE = re.compile(r'\{.*\}`.*`\s*')
23
24
# Add piplite command to notebooks
25
with open(DOC_DIR / 'pyodide_dependencies.json', encoding='utf8') as file:
26
DEPENDENCIES = json.load(file)
27
28
class DependencyNotFound(Exception):
29
"""Raised if a dependency cannot be found"""
30
31
def _notebook_key(nbpath: pathlib.Path):
32
nbpath = str(nbpath).replace(os.path.sep, "/")
33
return nbpath.replace(str(EXAMPLES_DIR), '').replace(str(DOC_DIR), '')[1:]
34
35
def _get_dependencies(nbpath: pathlib.Path):
36
key = _notebook_key(nbpath)
37
dependencies = DEPENDENCIES.get(key, [])
38
if dependencies is None:
39
return []
40
for name, min_version in MINIMUM_VERSIONS.items():
41
if any(name in req for req in dependencies):
42
dependencies = [f'{name}>={min_version}' if name in req else req for req in dependencies]
43
return BASE_DEPENDENCIES + dependencies
44
45
def _to_piplite_install_code(dependencies):
46
dependencies = [repr(dep) for dep in dependencies]
47
return f"import piplite\nawait piplite.install([{', '.join(dependencies)}])"
48
49
def _get_install_code_cell(dependencies):
50
source = _to_piplite_install_code(dependencies)
51
install = nbformat.v4.new_code_cell(source=source)
52
del install['id']
53
return install
54
55
def _get_issues(key):
56
return NOTEBOOK_ISSUES.get(key, [])
57
58
def _to_issue_str(issue:str):
59
if issue.startswith("https://github.com/") and "/issues/" in issue:
60
issue = issue.replace("https://github.com/", "")
61
_, library, _, id = issue.split("/")
62
if library == "panel":
63
return f"#{id}"
64
return f"{library}#{id}"
65
if issue.startswith("https://gitlab.kitware.com/") and "/issues/" in issue:
66
issue = issue.replace("https://gitlab.kitware.com/", "")
67
_, library, _, _, id = issue.split("/")
68
if library == "panel":
69
return f"#{id}"
70
return f"{library}#{id}"
71
return issue
72
73
def _get_info_markdown_cell(nbpath):
74
key = _notebook_key(nbpath)
75
issues = _get_issues(key)
76
if issues:
77
issues_str = ", ".join([f'<a href="{issue}">{_to_issue_str(issue)}</a>' for issue in issues])
78
source = f"""<div class="alert alert-block alert-danger">
79
This notebook is not expected to run successfully in <em>Panelite</em>. It has the following known issues: {issues_str}.
80
81
Panelite is powered by young technologies like <a href="https://pyodide.org/en/stable/">Pyodide</a> and <a href="https://jupyterlite.readthedocs.io/en/latest/">Jupyterlite</a>. Some browsers may be poorly supported (e.g. mobile or 32-bit versions). If you experience other issues, please <a href="https://github.com/holoviz/panel/issues">report them</a>.
82
</div>"""
83
else:
84
source = """<div class="alert alert-block alert-success">
85
<em>Panelite</em> is powered by young technologies like <a href="https://pyodide.org/en/stable/">Pyodide</a> and <a href="https://jupyterlite.readthedocs.io/en/latest/">Jupyterlite</a>. Some browsers may be poorly supported (e.g. mobile or 32-bit versions). If you experience issues, please <a href="https://github.com/holoviz/panel/issues">report them</a>.
86
</div>"""
87
info = nbformat.v4.new_markdown_cell(source=source)
88
del info['id']
89
return info
90
91
def convert_md_to_nb(
92
filehandle, supported_syntax=('{pyodide}',)
93
) -> str:
94
"""
95
Extracts Panel application code from a Markdown file.
96
"""
97
nb = nbformat.v4.new_notebook()
98
cells = nb['cells']
99
inblock = False
100
block_opener = None
101
markdown, code = [], []
102
while True:
103
line = filehandle.readline()
104
if not line:
105
# EOF
106
break
107
108
# Remove MyST-Directives
109
line = INLINE_DIRECTIVE.sub('', line)
110
111
lsline = line.lstrip()
112
if inblock:
113
if lsline.startswith(block_opener):
114
inblock = False
115
code[-1] = code[-1].rstrip()
116
code_cell = nbformat.v4.new_code_cell(source=''.join(code))
117
code.clear()
118
cells.append(code_cell)
119
else:
120
code.append(line)
121
elif lsline.startswith("```"):
122
num_leading_backticks = len(lsline) - len(lsline.lstrip("`"))
123
block_opener = '`'*num_leading_backticks
124
syntax = line.strip()[num_leading_backticks:]
125
if syntax in supported_syntax:
126
if markdown:
127
md_cell = nbformat.v4.new_markdown_cell(source=''.join(markdown))
128
markdown.clear()
129
nb['cells'].append(md_cell)
130
inblock = True
131
else:
132
markdown.append(line)
133
else:
134
markdown.append(line)
135
if markdown:
136
md_cell = nbformat.v4.new_markdown_cell(source=''.join(markdown))
137
nb['cells'].append(md_cell)
138
return nb
139
140
def convert_docs():
141
mds = (
142
list(DOC_DIR.glob('getting_started/*.md')) +
143
list(DOC_DIR.glob('explanation/**/*.md')) +
144
list(DOC_DIR.glob('how_to/**/*.md'))
145
)
146
for md in mds:
147
out = LITE_FILES / md.relative_to(DOC_DIR).with_suffix('.ipynb')
148
out.parent.mkdir(parents=True, exist_ok=True)
149
if md.suffix != '.md' or md.name == 'index.md':
150
continue
151
with open(md, encoding="utf-8") as mdf:
152
nb = convert_md_to_nb(mdf)
153
dependencies = _get_dependencies(md)
154
if dependencies:
155
install = _get_install_code_cell(dependencies=dependencies)
156
nb['cells'].insert(0, install)
157
info = _get_info_markdown_cell(md)
158
nb['cells'].insert(0, info)
159
with open(out, 'w', encoding='utf-8') as fout:
160
nbformat.write(nb, fout)
161
162
def copy_examples():
163
nbs = list(EXAMPLES_DIR.glob('*/*/*.ipynb')) + list(EXAMPLES_DIR.glob('*/*.*'))
164
for nb in nbs:
165
if ".ipynb_checkpoints" in str(nb):
166
continue
167
nbpath = pathlib.Path(nb)
168
out = LITE_FILES / nbpath.relative_to(EXAMPLES_DIR)
169
out.parent.mkdir(parents=True, exist_ok=True)
170
171
if nb.suffix == '.ipynb':
172
dependencies = _get_dependencies(nbpath)
173
174
with open(nb, encoding='utf-8') as fin:
175
nb = nbformat.read(fin, 4)
176
if dependencies:
177
install = _get_install_code_cell(dependencies=dependencies)
178
nb['cells'].insert(0, install)
179
180
info = _get_info_markdown_cell(nbpath)
181
nb['cells'].insert(0, info)
182
with open(out, 'w', encoding='utf-8') as fout:
183
nbformat.write(nb, fout)
184
elif not nb.is_dir():
185
shutil.copyfile(nb, out)
186
187
def copy_assets():
188
shutil.copytree(
189
EXAMPLES_DIR / 'assets',
190
LITE_FILES / 'assets',
191
dirs_exist_ok=True
192
)
193
shutil.copytree(
194
DOC_DIR / '_static',
195
LITE_FILES / '_static',
196
dirs_exist_ok=True
197
)
198
199
if __name__=="__main__":
200
convert_docs()
201
copy_examples()
202
copy_assets()
203
204