diff --git a/nbdev/__init__.py b/nbdev/__init__.py index f244760cc..0af6c802d 100644 --- a/nbdev/__init__.py +++ b/nbdev/__init__.py @@ -4,6 +4,7 @@ - `nbdev.extract_attachments`: A preprocessor that extracts all of the attachments from the notebook file. The extracted attachments are returned in the 'resources' dictionary. - `nbdev.moddocs`: # Module docs: creating them from existing notebooks +- `nbdev.octavo`: Build nbdev documentation with Octavo - `nbdev.skill`: Write, read and review nbdev notebooks as source, documentation, examples and tests. You MUST read this before any work on a project with notebooks.""" __version__ = "3.3.26" diff --git a/nbdev/_modidx.py b/nbdev/_modidx.py index 1456e861a..79dda9ad3 100644 --- a/nbdev/_modidx.py +++ b/nbdev/_modidx.py @@ -213,6 +213,28 @@ 'nbdev.migrate.migrate_nb': ('api/migrate.html#migrate_nb', 'nbdev/migrate.py'), 'nbdev.migrate.nbdev_migrate': ('api/migrate.html#nbdev_migrate', 'nbdev/migrate.py'), 'nbdev.migrate.nbdev_migrate_config': ('api/migrate.html#nbdev_migrate_config', 'nbdev/migrate.py')}, + 'nbdev.octavo': { 'nbdev.octavo.OctavoProc': ('api/octavo.html#octavoproc', 'nbdev/octavo.py'), + 'nbdev.octavo.OctavoProc.__call__': ('api/octavo.html#octavoproc.__call__', 'nbdev/octavo.py'), + 'nbdev.octavo.OctavoProc.__init__': ('api/octavo.html#octavoproc.__init__', 'nbdev/octavo.py'), + 'nbdev.octavo.OctavoProc.add_links': ('api/octavo.html#octavoproc.add_links', 'nbdev/octavo.py'), + 'nbdev.octavo.OctavoProc.nb_proc': ('api/octavo.html#octavoproc.nb_proc', 'nbdev/octavo.py'), + 'nbdev.octavo.OctavoProc.procs': ('api/octavo.html#octavoproc.procs', 'nbdev/octavo.py'), + 'nbdev.octavo._create_octavo_cfg': ('api/octavo.html#_create_octavo_cfg', 'nbdev/octavo.py'), + 'nbdev.octavo._migrate_octavo': ('api/octavo.html#_migrate_octavo', 'nbdev/octavo.py'), + 'nbdev.octavo._octavo_output': ('api/octavo.html#_octavo_output', 'nbdev/octavo.py'), + 'nbdev.octavo._octavo_sidebar': ('api/octavo.html#_octavo_sidebar', 'nbdev/octavo.py'), + 'nbdev.octavo._quarto_config': ('api/octavo.html#_quarto_config', 'nbdev/octavo.py'), + 'nbdev.octavo._script_md': ('api/octavo.html#_script_md', 'nbdev/octavo.py'), + 'nbdev.octavo.local_libraries': ('api/octavo.html#local_libraries', 'nbdev/octavo.py'), + 'nbdev.octavo.nbdev_migrate_octavo': ('api/octavo.html#nbdev_migrate_octavo', 'nbdev/octavo.py'), + 'nbdev.octavo.octavo_config': ('api/octavo.html#octavo_config', 'nbdev/octavo.py'), + 'nbdev.octavo.octavo_docs': ('api/octavo.html#octavo_docs', 'nbdev/octavo.py'), + 'nbdev.octavo.octavo_preview': ('api/octavo.html#octavo_preview', 'nbdev/octavo.py'), + 'nbdev.octavo.octavo_readme': ('api/octavo.html#octavo_readme', 'nbdev/octavo.py'), + 'nbdev.octavo.render_script': ('api/octavo.html#render_script', 'nbdev/octavo.py'), + 'nbdev.octavo.repl_script_frontmatter_keys': ( 'api/octavo.html#repl_script_frontmatter_keys', + 'nbdev/octavo.py'), + 'nbdev.octavo.script_pages': ('api/octavo.html#script_pages', 'nbdev/octavo.py')}, 'nbdev.process': { 'nbdev.process.NBProcessor': ('api/process.html#nbprocessor', 'nbdev/process.py'), 'nbdev.process.NBProcessor.__init__': ('api/process.html#nbprocessor.__init__', 'nbdev/process.py'), 'nbdev.process.NBProcessor._proc': ('api/process.html#nbprocessor._proc', 'nbdev/process.py'), @@ -304,6 +326,7 @@ 'nbdev.quarto._install_linux': ('api/quarto.html#_install_linux', 'nbdev/quarto.py'), 'nbdev.quarto._install_mac': ('api/quarto.html#_install_mac', 'nbdev/quarto.py'), 'nbdev.quarto._nbglob_docs': ('api/quarto.html#_nbglob_docs', 'nbdev/quarto.py'), + 'nbdev.quarto._octavo': ('api/quarto.html#_octavo', 'nbdev/quarto.py'), 'nbdev.quarto._pre': ('api/quarto.html#_pre', 'nbdev/quarto.py'), 'nbdev.quarto._pre_docs': ('api/quarto.html#_pre_docs', 'nbdev/quarto.py'), 'nbdev.quarto._readme_cands': ('api/quarto.html#_readme_cands', 'nbdev/quarto.py'), diff --git a/nbdev/doclinks.py b/nbdev/doclinks.py index 9f49f55fa..9d52e5fcd 100644 --- a/nbdev/doclinks.py +++ b/nbdev/doclinks.py @@ -213,27 +213,27 @@ def _get_exps(mod): def _lineno(sym, fname): return _get_exps(fname).get(sym, None) if fname else None # %% ../nbs/api/05_doclinks.ipynb #c1b8c9fa -def _qual_sym(s, settings): +def _qual_sym(s, settings, local=False): "Get qualified nb, py, and github paths for a symbol s" if not isinstance(s,tuple): return s nb,py = s nbbase = urljoin(settings["doc_host"]+'/',settings["doc_baseurl"]) - nb = urljoin(nbbase+'/', nb) + nb = '/' + nb.lstrip('/') if local else urljoin(nbbase+'/', nb) gh = urljoin(settings["git_url"]+'/', f'blob/{settings["branch"]}/{py}') return nb,py,gh -def _qual_mod(mod_d, settings): return {sym:_qual_sym(s, settings) for sym,s in mod_d.items()} -def _qual_syms(entries): +def _qual_mod(mod_d, settings, local=False): return {sym:_qual_sym(s, settings, local) for sym,s in mod_d.items()} +def _qual_syms(entries, local=False): settings = entries['settings'] if 'doc_host' not in settings: return entries - return {'syms': {mod:_qual_mod(d, settings) for mod,d in entries['syms'].items()}, 'settings':settings} + return {'syms': {mod:_qual_mod(d, settings, local) for mod,d in entries['syms'].items()}, 'settings':settings} # %% ../nbs/api/05_doclinks.ipynb #53505fa4 _re_backticks = re.compile(r'`([^`\s]+?)(?:\(\))?`') # %% ../nbs/api/05_doclinks.ipynb #3a24b883 @lru_cache(None) -def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None): +def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None, local_lib=None): cfg = get_config(also_settings=True) if strip_libs is None: try: strip_libs = cfg.get('strip_libs') or cfg.lib_name @@ -248,7 +248,7 @@ def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None): for o in eps: if incl_libs is not None and o.dist.name not in incl_libs: continue - try: entries[o.name] = _qual_syms(o.load()) + try: entries[o.name] = _qual_syms(o.load(), local=o.name in tuplify(local_lib)) except Exception: pass py_syms = merge(*L(o['syms'].values() for o in entries.values()).concat()) for m in strip_libs: @@ -266,8 +266,8 @@ def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None): # %% ../nbs/api/05_doclinks.ipynb #3257b6bf class NbdevLookup: "Mapping from symbol names to docs and source URLs" - def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None, ns=None): - self.entries,self.syms = _build_lookup_table(strip_libs, incl_libs, skip_mods) + def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None, ns=None, local_lib=None): + self.entries,self.syms = _build_lookup_table(strip_libs, incl_libs, skip_mods, local_lib) self.aliases = {n:o.__name__ for n,o in (ns or {}).items() if isinstance(o, ModuleType)} def __getitem__(self, s): diff --git a/nbdev/octavo.py b/nbdev/octavo.py new file mode 100644 index 000000000..86e0b5da0 --- /dev/null +++ b/nbdev/octavo.py @@ -0,0 +1,288 @@ +r"""Build nbdev documentation with Octavo + +Octavo is an alternative to Quarto for building a documentation website from nbdev notebooks. Install `nbdev[octavo]` to use it. A project selects Octavo by placing `octavo.yml` in its configured notebooks directory. Projects without that file continue to use Quarto and do not import Octavo. + +The usual `nbdev-docs` command builds the site. Octavo currently builds all pages together. File-selection options are rejected unless left at their defaults; `n_workers` affects only Quarto builds. + +Run `nbdev-export` before building to update the generated Python modules and symbol indexes. `nbdev-readme` and `nbdev-contributing` also use Octavo for these projects. `nbdev-prepare` still exports, tests, and cleans notebooks, but skips Quarto configuration generation. + +For an existing Quarto project, `nbdev-migrate-octavo` converts documentation sources in place. The converted sources are not intended to work unchanged with both builders. Use `nbdev-preview` to serve the site and rebuild after edits. Refresh the browser manually. + +## Migration instructions (share this with your AI) + +Octavo builds into the same output directory as Quarto and replaces what's there. The migration also converts the sources in place, and they won't build with Quarto afterwards, even if you keep `_quarto.yml`. So save the Quarto site first: + +1. Commit or back up the project. +2. From the project root, run `nbdev-docs` while the project still uses Quarto. Check that the build works, and note any failures it already has, so you don't mistake them for migration problems later. +3. Copy the whole built site, including assets and search files, to a directory outside the project's source and output directories. This copy is the baseline you compare against. +4. Run `nbdev-migrate-octavo` from the project root. Read its changes and everything it prints: it reports the settings it couldn't carry over. +5. Run `nbdev-docs` again to build with Octavo. If the build fails, keep its output for diagnosis. + +`nbdev-migrate-octavo` renames `.qmd` sources to `.md`, updates references to them, converts directives, and moves a page's `subtitle` to `description`. Octavo reads Quarto's other front matter and settings names as they are. The migration writes `octavo.yml` from `_quarto.yml` and `nbdev.yml`, and leaves an existing `octavo.yml` alone. It doesn't port the site's design. It leaves stylesheet files unchanged, and it doesn't add the stylesheets listed under `format.html.css` in `_quarto.yml` to `octavo.yml`. Stylesheets that a page lists in its own front matter still load. + +Read the original `_quarto.yml`, `nbdev.yml` and `pyproject.toml`, and the new `octavo.yml`. Go through the checks below that apply to the project, skipping features it doesn't use, and propose a short migration plan. Ask me before accepting anything that loses behavior. + +- **Styles and custom pages.** Octavo's HTML has no Bootstrap or Quarto classes. Look at the rendered Octavo HTML before reusing old CSS, and copy over only the rules that still apply. Don't enable an old stylesheet wholesale or add a compatibility layer. Check narrow screens too. +- **Wide content.** Octavo has no equivalent of Quarto's `page-layout: full` or column classes such as `.column-page` and `.column-screen-inset-right`. Widen those pages with your own CSS, in a stylesheet listed in the page's `css` front matter. +- **Layouts and tables of contents.** Octavo reads Quarto's `page-layout`, `toc` and `toc-depth` with the same meanings, but of the `page-layout` values it supports only `custom`, for a hand-built page. Check pages that used `article` or `full`, and compare tables of contents with the baseline on ordinary pages as well as custom ones. +- **Sidebar.** If the project has a hand-written `sidebar.yml` (`custom_sidebar` in `pyproject.toml`), the migration turns it into `sidebar-links` in `octavo.yml`. Compare its sections and order with the baseline. Add the pages behind any entries it reported as dropped, such as `auto` or globs, as explicit links. +- **Scripts.** The migration doesn't carry over scripts that Quarto added through `include-in-header`, `include-after-body` or raw HTML. Load them with Octavo's `js` setting, in `octavo.yml` for the whole site or in a page's front matter. Octavo loads them as module scripts (`