{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "510ed03d-0c3b-4caf-910d-e6eaec08fd63", "metadata": {}, "outputs": [], "source": [ "#|default_exp quarto" ] }, { "cell_type": "markdown", "id": "f0f931c2", "metadata": {}, "source": [ "# quarto\n", "\n", "> Install and interact with Quarto from nbdev" ] }, { "cell_type": "code", "execution_count": null, "id": "6a35c7c4-748f-4c82-a9bf-c780a8d83e90", "metadata": {}, "outputs": [], "source": [ "#|export\n", "from __future__ import annotations\n", "import warnings\n", "\n", "from nbdev.config import *\n", "from nbdev.doclinks import *\n", "\n", "from fastcore.utils import *\n", "from fastcore.script import call_parse\n", "from fastcore.shutil import rmtree,move\n", "\n", "from os import system\n", "import subprocess,sys,shutil" ] }, { "cell_type": "code", "execution_count": null, "id": "1e623a3d-3e77-44c6-adf3-4768b78328c5", "metadata": {}, "outputs": [], "source": [ "#|hide\n", "from fastcore.test import *" ] }, { "cell_type": "code", "execution_count": null, "id": "aae2d2be-ad03-4536-bf70-c4575f39cea3", "metadata": {}, "outputs": [], "source": [ "#|export\n", "_def_file_re = '\\.(?:ipynb|qmd|html)$'\n", "\n", "def _sprun(cmd):\n", " try: subprocess.check_output(cmd, shell=True)\n", " except subprocess.CalledProcessError as cpe: sys.exit(cpe.returncode)" ] }, { "cell_type": "code", "execution_count": null, "id": "75e4b6a1", "metadata": {}, "outputs": [], "source": [ "#|export\n", "BASE_QUARTO_URL='https://www.quarto.org/download/latest/'\n", "\n", "def _install_linux():\n", " system(f'curl -LO {BASE_QUARTO_URL}quarto-linux-amd64.deb')\n", " system('sudo dpkg -i *64.deb && rm *64.deb')\n", " \n", "def _install_mac():\n", " system(f'curl -LO {BASE_QUARTO_URL}quarto-macos.pkg')\n", " system('sudo installer -pkg quarto-macos.pkg -target /')\n", "\n", "@call_parse\n", "def install_quarto():\n", " \"Install latest Quarto on macOS or Linux, prints instructions for Windows\"\n", " if sys.platform not in ('darwin','linux'):\n", " return print('Please visit https://quarto.org/docs/get-started/ to install quarto')\n", " print(\"Installing or upgrading quarto -- this requires root access.\")\n", " system('sudo touch .installing')\n", " try:\n", " installing = Path('.installing')\n", " if not installing.exists(): return print(\"Cancelled. Please download and install Quarto from quarto.org.\")\n", " if 'darwin' in sys.platform: _install_mac()\n", " elif 'linux' in sys.platform: _install_linux()\n", " finally: system('sudo rm -f .installing')" ] }, { "cell_type": "code", "execution_count": null, "id": "7580d48f-e10e-4bb0-937e-90645b5dfd08", "metadata": {}, "outputs": [], "source": [ "#| export\n", "@call_parse\n", "def install():\n", " \"Install Quarto and the current library\"\n", " install_quarto.__wrapped__()\n", " d = get_config().path('lib_path')\n", " if (d/'__init__.py').exists(): system(f'pip install -e \"{d.parent}[dev]\"')" ] }, { "cell_type": "code", "execution_count": null, "id": "5192dfae", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _doc_paths(path:str=None, doc_path:str=None):\n", " cfg = get_config()\n", " cfg_path = cfg.config_path\n", " path = cfg.path('nbs_path') if not path else Path(path)\n", " doc_path = cfg.path('doc_path') if not doc_path else Path(doc_path)\n", " tmp_doc_path = path/f\"{cfg['doc_path']}\"\n", " return cfg,cfg_path,path,doc_path,tmp_doc_path" ] }, { "cell_type": "code", "execution_count": null, "id": "25e4b6a7", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _f(a,b): return Path(a),b\n", "def _pre(p,b=True): return ' ' * (len(p.parts)) + ('- ' if b else ' ')\n", "def _sort(a):\n", " x,y = a\n", " if y.startswith('index.'): return x,'00'\n", " return a" ] }, { "cell_type": "code", "execution_count": null, "id": "d879c7e1", "metadata": {}, "outputs": [], "source": [ "#|export\n", "@call_parse\n", "def nbdev_sidebar(\n", " path:str=None, # Path to notebooks\n", " symlinks:bool=False, # Follow symlinks?\n", " file_glob:str=None, # Only include files matching glob\n", " file_re:str=_def_file_re, # Only include files matching regex\n", " folder_re:str=None, # Only enter folders matching regex\n", " skip_file_glob:str=None, # Skip files matching glob\n", " skip_file_re:str='^[_.]', # Skip files matching regex\n", " skip_folder_re:str='(?:^[_.]|^www$)', # Skip folders matching regex\n", " printit:bool=False, # Print YAML for debugging\n", " force:bool=False, # Create sidebar even if settings.ini custom_sidebar=False\n", " returnit:bool=False # Return list of files found\n", "):\n", " \"Create sidebar.yml\"\n", " if not force and str2bool(get_config().custom_sidebar): return\n", " path = get_config().path('nbs_path') if not path else Path(path)\n", " files = nbglob(path, func=_f, symlinks=symlinks, file_re=file_re, folder_re=folder_re, file_glob=file_glob,\n", " skip_file_glob=skip_file_glob, skip_file_re=skip_file_re, skip_folder_re=skip_folder_re).sorted(key=_sort)\n", " lastd,res = Path(),[]\n", " for d,name in files:\n", " d = d.relative_to(path)\n", " if d != lastd:\n", " res.append(_pre(d.parent) + f'section: {d.name}')\n", " res.append(_pre(d.parent, False) + 'contents:')\n", " lastd = d\n", " res.append(f'{_pre(d)}{d.joinpath(name)}')\n", "\n", " yml_path = path/'sidebar.yml'\n", " yml = \"website:\\n sidebar:\\n contents:\\n\"\n", " yml += '\\n'.join(f' {o}' for o in res)\n", " if printit: return print(yml)\n", " yml_path.write_text(yml)\n", " if returnit: return files" ] }, { "cell_type": "code", "execution_count": null, "id": "8a7c2db2", "metadata": {}, "outputs": [], "source": [ "# nbdev_sidebar(printit=True, force=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "9080eebd", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _render_readme(path):\n", " idx_path = path/get_config().readme_nb\n", " if not idx_path.exists(): return\n", "\n", " yml_path = path/'sidebar.yml'\n", " moved=False\n", " if yml_path.exists():\n", " # move out of the way to avoid rendering whole website\n", " yml_path.rename(path/'sidebar.yml.bak')\n", " moved=True\n", " try:\n", " _sprun(f'cd \"{path}\" && quarto render \"{idx_path}\" -o README.md -t gfm --no-execute')\n", " finally:\n", " if moved: (path/'sidebar.yml.bak').rename(yml_path)" ] }, { "cell_type": "code", "execution_count": null, "id": "4e995496", "metadata": {}, "outputs": [], "source": [ "#|export\n", "@call_parse\n", "def nbdev_readme(\n", " path:str=None, # Path to notebooks\n", " doc_path:str=None): # Path to output docs\n", " \"Render README.md from index.ipynb\"\n", " cfg,cfg_path,path,doc_path,tmp_doc_path = _doc_paths(path, doc_path)\n", " _render_readme(path)\n", " if (tmp_doc_path/'README.md').exists():\n", " _rdm = cfg_path/'README.md'\n", " if _rdm.exists(): _rdm.unlink() # py37 doesn't have arg missing_ok so have to check first\n", " move(str(tmp_doc_path/'README.md'), cfg_path) # README.md is temporarily in nbs/_docs" ] }, { "cell_type": "code", "execution_count": null, "id": "ba297c19", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _ensure_quarto():\n", " if shutil.which('quarto'): return\n", " print(\"Quarto is not installed. We will download and install it for you.\")\n", " install.__wrapped__()" ] }, { "cell_type": "code", "execution_count": null, "id": "aabf2f15", "metadata": {}, "outputs": [], "source": [ "#|export\n", "_quarto_yml=\"\"\"ipynb-filters: [nbdev_filter]\n", "\n", "project:\n", " type: website\n", " output-dir: {doc_path}\n", " preview:\n", " port: 3000\n", " browser: false\n", "\n", "format:\n", " html:\n", " theme: cosmo\n", " css: styles.css\n", " toc: true\n", " toc-depth: 4\n", "\n", "website:\n", " title: \"{title}\"\n", " site-url: \"{doc_host}{doc_baseurl}\"\n", " description: \"{description}\"\n", " twitter-card: true\n", " open-graph: true\n", " reader-mode: true\n", " repo-branch: {branch}\n", " repo-url: \"{git_url}\"\n", " repo-actions: [issue]\n", " navbar:\n", " background: primary\n", " search: true\n", " right:\n", " - icon: github\n", " href: \"{git_url}\"\n", " sidebar:\n", " style: \"floating\"\n", "\n", "metadata-files: \n", " - sidebar.yml\n", " - custom.yml\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "38124450", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def refresh_quarto_yml():\n", " \"Generate `_quarto.yml` from `settings.ini`.\"\n", " cfg = get_config()\n", " p = cfg.path('nbs_path')/'_quarto.yml'\n", " vals = {k:cfg.get(k) for k in ['doc_path', 'title', 'description', 'branch', 'git_url', 'doc_host', 'doc_baseurl']}\n", " # Do not build _quarto_yml if custom_quarto_yml is set to True\n", " if str2bool(get_config().custom_quarto_yml): return\n", " if 'title' not in vals: vals['title'] = vals['lib_name']\n", " yml=_quarto_yml.format(**vals)\n", " p.write_text(yml)" ] }, { "cell_type": "code", "execution_count": null, "id": "0efe744e", "metadata": {}, "outputs": [], "source": [ "#|export\n", "@call_parse\n", "def nbdev_quarto(\n", " path:str=None, # Path to notebooks\n", " doc_path:str=None, # Path to output docs\n", " symlinks:bool=False, # Follow symlinks?\n", " file_re:str=_def_file_re, # Only include files matching regex\n", " folder_re:str=None, # Only enter folders matching regex\n", " skip_file_glob:str=None, # Skip files matching glob\n", " skip_file_re:str=None, # Skip files matching regex\n", " preview:bool=False, # Preview the site instead of building it\n", " port:int=3000 # The port on which to run preview\n", "):\n", " \"Create Quarto docs and README.md\"\n", " _ensure_quarto()\n", " cfg,cfg_path,path,doc_path,tmp_doc_path = _doc_paths(path, doc_path)\n", " refresh_quarto_yml()\n", " nbdev_sidebar.__wrapped__(path, symlinks=symlinks, file_re=file_re, folder_re=folder_re,\n", " skip_file_glob=skip_file_glob, skip_file_re=skip_file_re)\n", " if preview: os.system(f'cd \"{path}\" && quarto preview --port {port}')\n", " else: _sprun(f'cd \"{path}\" && quarto render')\n", " if not preview:\n", " nbdev_readme.__wrapped__(path, doc_path)\n", " if tmp_doc_path.parent != cfg_path: # move docs folder to root of repo if it doesn't exist there\n", " rmtree(doc_path, ignore_errors=True)\n", " move(tmp_doc_path, cfg_path)" ] }, { "cell_type": "code", "execution_count": null, "id": "f5ddbc96", "metadata": {}, "outputs": [], "source": [ "#|export\n", "@call_parse\n", "def preview(\n", " path:str=None, # Path to notebooks\n", " doc_path:str=None, # Path to output docs\n", " symlinks:bool=False, # Follow symlinks?\n", " file_re:str=_def_file_re, # Only include files matching regex\n", " folder_re:str=None, # Only enter folders matching regex\n", " skip_file_glob:str=None, # Skip files matching glob\n", " skip_file_re:str=None, # Skip files matching regex\n", " port:int=3000 # The port on which to run preview\n", "):\n", " \"Preview docs locally\"\n", " nbdev_quarto.__wrapped__(path, doc_path=doc_path, symlinks=symlinks, file_re=file_re, folder_re=folder_re,\n", " skip_file_glob=skip_file_glob, skip_file_re=skip_file_re, preview=True, port=port)" ] }, { "cell_type": "code", "execution_count": null, "id": "a5c29641", "metadata": {}, "outputs": [], "source": [ "#|export\n", "@call_parse\n", "def deploy(\n", " path:str=None, # Path to notebooks\n", " doc_path:str=None, # Path to output docs\n", " skip_build:bool=False, # Don't build docs first\n", " symlinks:bool=False, # Follow symlinks?\n", " file_re:str=_def_file_re, # Only include files matching regex\n", " folder_re:str=None, # Only enter folders matching regex\n", " skip_file_glob:str=None, # Skip files matching glob\n", " skip_file_re:str=None, # Skip files matching regex\n", "):\n", " \"Deploy docs to GitHub Pages\"\n", " if not skip_build:\n", " nbdev_quarto.__wrapped__(path, doc_path=doc_path, symlinks=symlinks, file_re=file_re, folder_re=folder_re,\n", " skip_file_glob=skip_file_glob, skip_file_re=skip_file_re)\n", " try: from ghp_import import ghp_import\n", " except: return warnings.warn('Please install ghp-import with `pip install ghp-import`')\n", " ghp_import(get_config().path('doc_path'), push=True, stderr=True, no_history=True)" ] }, { "cell_type": "markdown", "id": "aa35b010", "metadata": {}, "source": [ "## Export -" ] }, { "cell_type": "code", "execution_count": null, "id": "3d8031ce", "metadata": {}, "outputs": [], "source": [ "#|hide\n", "import nbdev; nbdev.nbdev_export()" ] }, { "cell_type": "code", "execution_count": null, "id": "12588a26-43a6-42c4-bacd-896293c871ab", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }