Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions hvplot/tests/scripts/test_llms_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Tests for the nbsite LLM documentation build config (scripts/llms_config.py).

The ``llms_config`` module requires ``nbsite``, which is only a doc
dependency, so the tests are skipped when it is not installed.
"""

import sys
from pathlib import Path

import pytest

pytest.importorskip('nbsite')

REPO_ROOT = Path(__file__).parents[3]
sys.path.insert(0, str(REPO_ROOT / 'scripts'))

import llms_config # noqa: E402
from nbsite.scripts import LlmsBuildConfig # noqa: E402

SOURCE_SUFFIXES = ('.md', '.ipynb', '.rst')


def _test_paths() -> set[Path]:
return llms_config.PAGES | llms_config.ROOT_PAGES


def _resolve_doc_path(path: Path) -> Path | None:
"""Return the source file in ``doc/`` a configured page maps to, if any."""
stem = path.with_suffix('')
for suffix in SOURCE_SUFFIXES:
candidate = llms_config.DOC_DIR / f'{stem}{suffix}'
if candidate.exists():
return candidate
return None


def _matches(section, path: Path) -> bool:
prefix = section.path_prefix
matches_prefix = prefix in {Path(), Path('.')} or path.is_relative_to(prefix)
return matches_prefix and section.path_filter(path)


def test_config_is_valid() -> None:
assert isinstance(llms_config.CONFIG, LlmsBuildConfig)


@pytest.mark.parametrize('path', sorted(_test_paths()))
def test_referenced_pages_exist(path: Path) -> None:
assert _resolve_doc_path(path) is not None


def test_sections_match_at_least_one_page() -> None:
for section in llms_config.CONFIG.sections:
assert any(_matches(section, path) for path in _test_paths())


@pytest.mark.parametrize('section', llms_config.CONFIG.sections, ids=lambda s: s.title)
def test_section_labels_are_unique_and_non_empty(section) -> None:
paths = [path for path in _test_paths() if _matches(section, path)]
labels = [section.label_builder(path) for path in paths]
assert all(labels)
assert len(labels) == len(set(labels))
3 changes: 2 additions & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,10 @@ HVPLOT_PATCH_PLOT_DOCSTRING_SIGNATURE = "false"
[feature.doc.tasks]
docs-build-sphinx = 'sphinx-build -j auto -b html doc builtdocs'
_docs-install = 'python -m pip install --no-deps --disable-pip-version-check -e .'
_docs_markdown = 'python -m nbsite build-llms --config scripts/llms_config.py'
# Depends on _docs-install instead of install as install
# in the default environment
docs-build = { depends-on = ["_docs-install", "docs-build-sphinx"] }
docs-build = { depends-on = ["_docs-install", "docs-build-sphinx", "_docs_markdown"] }
docs-server = 'python -m http.server 5500 --directory ./builtdocs'

# ================== BUILD ====================
Expand Down
116 changes: 116 additions & 0 deletions scripts/llms_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Config for building hvPlot markdown docs and llms.txt."""

from __future__ import annotations

from pathlib import Path

from nbsite.scripts import LlmsBuildConfig, LlmsSection, MarkdownSource

ROOT = Path(__file__).parent.parent
DOC_DIR = ROOT / 'doc'
BUILTDOCS_DIR = ROOT / 'builtdocs'
OUTPUT_DIR = BUILTDOCS_DIR / 'markdown'
MARKDOWN_BASE_URL = '/markdown'

PAGES = {
Path('index.md'),
Path('about.md'),
Path('developer_guide.md'),
Path('releases.md'),
Path('roadmap.md'),
Path('ref/index.md'),
Path('ref/deprecations.md'),
Path('ref/installation.md'),
Path('ref/api/index.md'),
Path('ref/api_compatibility/pandas/index.md'),
Path('ref/plotting_options/index.md'),
Path('ref/plotting_options/axis.md'),
Path('ref/plotting_options/data.md'),
Path('ref/plotting_options/geographic.md'),
Path('ref/plotting_options/interactivity.md'),
Path('ref/plotting_options/legend.md'),
Path('ref/plotting_options/size_layout.md'),
Path('ref/plotting_options/styling.md'),
Path('ref/api/manual/hvplot.hvPlot.area.md'),
Path('ref/api/manual/hvplot.hvPlot.bar.md'),
Path('ref/api/manual/hvplot.hvPlot.explorer.md'),
Path('ref/api/manual/hvplot.hvPlot.heatmap.md'),
Path('ref/api/manual/hvplot.hvPlot.line.md'),
Path('ref/api/manual/hvplot.hvPlot.points.md'),
Path('ref/api/manual/hvplot.hvPlot.scatter.md'),
}

ROOT_PAGES = {
Path('index.md'),
Path('about.md'),
Path('developer_guide.md'),
Path('releases.md'),
Path('roadmap.md'),
}


def _index_label(path: Path) -> str:
return 'home' if path.parent == Path('.') else path.parent.as_posix().replace('-', ' ')


def _label(path: Path) -> str:
if path.stem == 'index':
return _index_label(path)
return path.stem.replace('_', ' ')


def _api_label(path: Path) -> str:
if path.stem == 'index':
return _index_label(path)
name = path.stem
for prefix in ('hvplot.hvPlot.', 'hvplot.plotting.', 'hvplot.ui.', 'hvplot.networkx.'):
if name.startswith(prefix):
name = name.removeprefix(prefix)
break
return name.replace('_', ' ')


CONFIG = LlmsBuildConfig(
project_title='hvPlot',
project_description='hvPlot documentation selected for LLM-friendly browsing.',
markdown_root=OUTPUT_DIR,
llms_output_path=BUILTDOCS_DIR / 'llms.txt',
markdown_base_url=MARKDOWN_BASE_URL,
sources=(
MarkdownSource(
source_dir=DOC_DIR,
output_dir=OUTPUT_DIR,
exclude_dir_names=('.ipynb_checkpoints', 'user_guide'),
),
),
sections=(
LlmsSection(
title='Home',
description='Top-level pages and project overview.',
path_prefix=Path('.'),
path_filter=lambda path: path in ROOT_PAGES,
label_builder=_label,
),
LlmsSection(
title='Reference',
description='Installation notes and reference overview pages.',
path_prefix=Path('ref'),
path_filter=lambda path: path in PAGES,
label_builder=_api_label,
),
LlmsSection(
title='Plotting Options',
description='Core plotting configuration topics.',
path_prefix=Path('ref/plotting_options'),
path_filter=lambda path: path in PAGES,
label_builder=_label,
),
LlmsSection(
title='API Manual',
description='Representative hvPlot API examples.',
path_prefix=Path('ref/api/manual'),
path_filter=lambda path: path in PAGES,
label_builder=_api_label,
),
),
)
Loading