Path: blob/main/scripts/panelite/generate_panelite_content.py
2011 views
"""1Helper script to convert and copy example notebooks into JupyterLite build.2"""3import json4import os5import pathlib6import re7import shutil89from test.notebooks_with_panelite_issues import NOTEBOOK_ISSUES1011import nbformat1213HERE = pathlib.Path(__file__).parent14PANEL_BASE = HERE.parent.parent15EXAMPLES_DIR = PANEL_BASE / 'examples'16LITE_FILES = PANEL_BASE / 'lite' / 'files'17DOC_DIR = PANEL_BASE / 'doc'18BASE_DEPENDENCIES = []19MINIMUM_VERSIONS = {}2021INLINE_DIRECTIVE = re.compile(r'\{.*\}`.*`\s*')2223# Add piplite command to notebooks24with open(DOC_DIR / 'pyodide_dependencies.json', encoding='utf8') as file:25DEPENDENCIES = json.load(file)2627class DependencyNotFound(Exception):28"""Raised if a dependency cannot be found"""2930def _notebook_key(nbpath: pathlib.Path):31nbpath = str(nbpath).replace(os.path.sep, "/")32return nbpath.replace(str(EXAMPLES_DIR), '').replace(str(DOC_DIR), '')[1:]3334def _get_dependencies(nbpath: pathlib.Path):35key = _notebook_key(nbpath)36dependencies = DEPENDENCIES.get(key, [])37if dependencies is None:38return []39for name, min_version in MINIMUM_VERSIONS.items():40if any(name in req for req in dependencies):41dependencies = [f'{name}>={min_version}' if name in req else req for req in dependencies]42return BASE_DEPENDENCIES + dependencies4344def _to_piplite_install_code(dependencies):45dependencies = [repr(dep) for dep in dependencies]46return f"import piplite\nawait piplite.install([{', '.join(dependencies)}])"4748def _get_install_code_cell(dependencies):49source = _to_piplite_install_code(dependencies)50install = nbformat.v4.new_code_cell(source=source)51del install['id']52return install5354def _get_issues(key):55return NOTEBOOK_ISSUES.get(key, [])5657def _to_issue_str(issue:str):58if issue.startswith("https://github.com/") and "/issues/" in issue:59issue = issue.replace("https://github.com/", "")60_, library, _, id = issue.split("/")61if library == "panel":62return f"#{id}"63return f"{library}#{id}"64if issue.startswith("https://gitlab.kitware.com/") and "/issues/" in issue:65issue = issue.replace("https://gitlab.kitware.com/", "")66_, library, _, _, id = issue.split("/")67if library == "panel":68return f"#{id}"69return f"{library}#{id}"70return issue7172def _get_info_markdown_cell(nbpath):73key = _notebook_key(nbpath)74issues = _get_issues(key)75if issues:76issues_str = ", ".join([f'<a href="{issue}">{_to_issue_str(issue)}</a>' for issue in issues])77source = f"""<div class="alert alert-block alert-danger">78This notebook is not expected to run successfully in <em>Panelite</em>. It has the following known issues: {issues_str}.7980Panelite 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>.81</div>"""82else:83source = """<div class="alert alert-block alert-success">84<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>.85</div>"""86info = nbformat.v4.new_markdown_cell(source=source)87del info['id']88return info8990def convert_md_to_nb(91filehandle, supported_syntax=('{pyodide}',)92) -> str:93"""94Extracts Panel application code from a Markdown file.95"""96nb = nbformat.v4.new_notebook()97cells = nb['cells']98inblock = False99block_opener = None100markdown, code = [], []101while True:102line = filehandle.readline()103if not line:104# EOF105break106107# Remove MyST-Directives108line = INLINE_DIRECTIVE.sub('', line)109110lsline = line.lstrip()111if inblock:112if lsline.startswith(block_opener):113inblock = False114code[-1] = code[-1].rstrip()115code_cell = nbformat.v4.new_code_cell(source=''.join(code))116code.clear()117cells.append(code_cell)118else:119code.append(line)120elif lsline.startswith("```"):121num_leading_backticks = len(lsline) - len(lsline.lstrip("`"))122block_opener = '`'*num_leading_backticks123syntax = line.strip()[num_leading_backticks:]124if syntax in supported_syntax:125if markdown:126md_cell = nbformat.v4.new_markdown_cell(source=''.join(markdown))127markdown.clear()128nb['cells'].append(md_cell)129inblock = True130else:131markdown.append(line)132else:133markdown.append(line)134if markdown:135md_cell = nbformat.v4.new_markdown_cell(source=''.join(markdown))136nb['cells'].append(md_cell)137return nb138139def convert_docs():140mds = (141list(DOC_DIR.glob('getting_started/*.md')) +142list(DOC_DIR.glob('explanation/**/*.md')) +143list(DOC_DIR.glob('how_to/**/*.md'))144)145for md in mds:146out = LITE_FILES / md.relative_to(DOC_DIR).with_suffix('.ipynb')147out.parent.mkdir(parents=True, exist_ok=True)148if md.suffix != '.md' or md.name == 'index.md':149continue150with open(md, encoding="utf-8") as mdf:151nb = convert_md_to_nb(mdf)152dependencies = _get_dependencies(md)153if dependencies:154install = _get_install_code_cell(dependencies=dependencies)155nb['cells'].insert(0, install)156info = _get_info_markdown_cell(md)157nb['cells'].insert(0, info)158with open(out, 'w', encoding='utf-8') as fout:159nbformat.write(nb, fout)160161def copy_examples():162nbs = list(EXAMPLES_DIR.glob('*/*/*.ipynb')) + list(EXAMPLES_DIR.glob('*/*.*'))163for nb in nbs:164if ".ipynb_checkpoints" in str(nb):165continue166nbpath = pathlib.Path(nb)167out = LITE_FILES / nbpath.relative_to(EXAMPLES_DIR)168out.parent.mkdir(parents=True, exist_ok=True)169170if nb.suffix == '.ipynb':171dependencies = _get_dependencies(nbpath)172173with open(nb, encoding='utf-8') as fin:174nb = nbformat.read(fin, 4)175if dependencies:176install = _get_install_code_cell(dependencies=dependencies)177nb['cells'].insert(0, install)178179info = _get_info_markdown_cell(nbpath)180nb['cells'].insert(0, info)181with open(out, 'w', encoding='utf-8') as fout:182nbformat.write(nb, fout)183elif not nb.is_dir():184shutil.copyfile(nb, out)185186def copy_assets():187shutil.copytree(188EXAMPLES_DIR / 'assets',189LITE_FILES / 'assets',190dirs_exist_ok=True191)192shutil.copytree(193DOC_DIR / '_static',194LITE_FILES / '_static',195dirs_exist_ok=True196)197198if __name__=="__main__":199convert_docs()200copy_examples()201copy_assets()202203204