From 2b7ac96fb8575912e3f76f1ef27f3f03808d8930 Mon Sep 17 00:00:00 2001 From: AbhiRKeesara Date: Thu, 17 Sep 2026 00:49:20 -0700 Subject: [PATCH] DOC: Add version switcher to documentation Add pydata-sphinx-theme version switcher dropdown to navigate between documentation versions. The switcher appears in the navbar and lists dev, stable, and historical releases. Implementation: - Trigger workflow on v* tags (in addition to main branch) - Deploy release docs to /version/X.Y.Z/, dev docs to /dev/ - Generate versions.json by scanning existing directories on gh-pages - Mark highest non-prerelease version as stable with preferred flag - Update latest symlink on releases Simplifications per maintainer feedback: - Regenerate versions.json on every build by scanning directories - Remove static versions.json from source (generated dynamically) - Always use absolute URL in conf.py (no local fallback) Safety improvements: - Explicit VERSION=dev check prevents unexpected triggers - Handle re-runs gracefully (no-op if nothing changed) Closes #78 --- .github/workflows/publish_docs_to_pages.yml | 116 ++++++++++++++++---- RELEASE.md | 32 +++--- doc/source/conf.py | 38 ++++++- 3 files changed, 147 insertions(+), 39 deletions(-) diff --git a/.github/workflows/publish_docs_to_pages.yml b/.github/workflows/publish_docs_to_pages.yml index 5eb67ae..d43604e 100644 --- a/.github/workflows/publish_docs_to_pages.yml +++ b/.github/workflows/publish_docs_to_pages.yml @@ -4,18 +4,19 @@ on: push: branches: - main + tags: + - v* permissions: contents: write jobs: - build: + build-and-deploy: if: github.repository_owner == 'numpy' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: - # The deploy step below checks out gh-pages and pushes to it. fetch-depth: 0 - uses: actions/setup-python@v7 with: @@ -24,32 +25,109 @@ jobs: run: | python -m pip install --upgrade pip pip install .[doc] + - name: Determine docs version + id: version + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + VERSION="${GITHUB_REF#refs/tags/v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT + echo "docs_path=version/$VERSION" >> $GITHUB_OUTPUT + else + echo "version=dev" >> $GITHUB_OUTPUT + echo "is_release=false" >> $GITHUB_OUTPUT + echo "docs_path=dev" >> $GITHUB_OUTPUT + fi - name: Build documentation with Sphinx + env: + DOCS_VERSION: ${{ steps.version.outputs.version }} run: | cd doc make html - mv build/html /tmp + mv build/html /tmp/html cd .. - name: Deploy to gh-pages branch + env: + DOCS_PATH: ${{ steps.version.outputs.docs_path }} + IS_RELEASE: ${{ steps.version.outputs.is_release }} + VERSION: ${{ steps.version.outputs.version }} run: | git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - echo "Checking out gh-pages branch" git fetch origin gh-pages git checkout gh-pages - echo "Removing old dev documentation" - git clean -xdf . - cd dev - git rm -r '*' - cd .. - echo "Committing emptied dev directory" - git commit -m "Remove old dev documentation" - mkdir -p dev - cd dev - echo "Copying /tmp/html here" - cp -v -r /tmp/html/* . - echo "Adding new dev documentation" + + if [ "$IS_RELEASE" = "true" ]; then + mkdir -p "$DOCS_PATH" + rm -rf "${DOCS_PATH:?}"/* + else + git clean -xdf . + if [ -d "dev" ]; then + cd dev + git rm -r '*' || true + cd .. + git commit -m "Remove old dev documentation" || true + fi + mkdir -p dev + fi + + cp -r /tmp/html/* "$DOCS_PATH/" + + # Generate versions.json by scanning existing version directories + python3 << 'EOF' + import json + import os + from packaging.version import Version, InvalidVersion + + base_url = "https://numpy.org/numpy-financial" + versions = [] + + # Add dev entry + if os.path.isdir('dev'): + versions.append({"name": "dev", "version": "dev", "url": f"{base_url}/dev/"}) + + # Scan version/ directory for released versions + if os.path.isdir('version'): + release_versions = [] + for name in os.listdir('version'): + if os.path.isdir(f'version/{name}'): + try: + release_versions.append((Version(name), name)) + except InvalidVersion: + pass + # Sort by version descending + release_versions.sort(reverse=True) + # Find the highest non-prerelease version for stable + stable_name = next((name for ver, name in release_versions if not ver.is_prerelease), None) + for ver, name in release_versions: + entry = {"name": name, "version": name, "url": f"{base_url}/version/{name}/"} + if name == stable_name: + entry["name"] = f"{name} (stable)" + entry["preferred"] = True + versions.append(entry) + + os.makedirs('_static', exist_ok=True) + with open('_static/versions.json', 'w') as f: + json.dump(versions, f, indent=4) + EOF + + # Update latest symlink on releases + if [ "$IS_RELEASE" = "true" ]; then + rm -f latest + ln -s "version/$VERSION" latest + git add latest + fi + git add -A . - git commit -m "Add new dev documentation" - echo "Pushing new documentation to origin" - git push -v origin gh-pages + if git diff --cached --quiet; then + echo "No changes to commit" + elif [ "$IS_RELEASE" = "true" ]; then + git commit -m "Add documentation for version $VERSION" + git push -v origin gh-pages + elif [ "$VERSION" = "dev" ]; then + git commit -m "Update dev documentation" + git push -v origin gh-pages + else + echo "Unexpected state: IS_RELEASE=$IS_RELEASE, VERSION=$VERSION" + exit 1 + fi diff --git a/RELEASE.md b/RELEASE.md index d108c16..7f186f6 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -77,25 +77,19 @@ PyPI trusted publishing. Do not build or upload artifacts by hand. - paste the contents of ${CHANGES} in the `Describe this release section` - if pre-release check the box labelled `Set as a pre-release` -- Publish the documentation for the release. A GH workflow builds - `dev/` from `main`; the released versions have to be published by - hand. Skip this step for a pre-release. - - - wait for the workflow to rebuild `dev/` from the release - commit, and check that https://numpy.org/numpy-financial/dev/ - shows ${VERSION}. Do this before the version bump below, which - returns `dev/` to a development version. - - - in a clone of the `gh-pages` branch, copy those docs to their - permanent location and point `latest` at them: - - cp -r dev version/${VERSION} - git add version/${VERSION} - git rm -rf latest - ln -s version/${VERSION} latest - git add latest - - - commit and push `gh-pages` +- Publish the documentation for the release. The `Publish docs to gh-pages` + workflow automatically builds and deploys versioned documentation when + a `v*` tag is pushed: + + - Dev docs are deployed to `/dev/` from `main` branch pushes + - Release docs are deployed to `/version/${VERSION}/` from `v*` tags + - The workflow regenerates `_static/versions.json` by scanning existing + directories, marking the highest non-prerelease version as stable + + Verify the deployment: + - Check https://numpy.org/numpy-financial/version/${VERSION}/ shows + the new version's documentation + - Check the version switcher dropdown includes the new version - Update https://github.com/numpy/numpy-financial/milestones: diff --git a/doc/source/conf.py b/doc/source/conf.py index d564272..819de72 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -4,6 +4,9 @@ # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html +import os +from datetime import datetime + import numpy_financial # -- Path setup -------------------------------------------------------------- @@ -20,7 +23,7 @@ # -- Project information ----------------------------------------------------- project = 'numpy-financial' -copyright = '2023, numpy-financial developers' +copyright = f'2005-{datetime.now().year}, NumPy Developers' author = 'numpy-financial developers' @@ -61,3 +64,36 @@ html_logo = "_static/numpy_financial_logov.svg" html_favicon = "_static/numpy_financial_favicon.png" + +# -- Version switcher configuration ------------------------------------------ + +# Determine the version string for the switcher. +# Must match a "version" field in versions.json for the dropdown to show it selected. +# +# In production (CI), DOCS_VERSION is set explicitly: +# - "dev" for main branch builds (deployed to /dev/) +# - "X.Y.Z" for release tag builds (deployed to /version/X.Y.Z/) +# +# For local development, default to showing stable version to test the typical UX. +if os.environ.get("DOCS_VERSION"): + # CI sets this explicitly based on branch/tag + switcher_version = os.environ["DOCS_VERSION"] +else: + # Local development: show stable version (1.1.0) as default for UX testing + # This matches what users see when visiting the main docs site + switcher_version = "1.1.0" + +# versions.json is generated dynamically on gh-pages by the CI workflow. +# Always use the absolute URL (local development requires internet access). +json_url = "https://numpy.org/numpy-financial/_static/versions.json" + +html_theme_options = { + "github_url": "https://github.com/numpy/numpy-financial", + # Navbar layout: theme switcher, version switcher, then GitHub icon + "navbar_end": ["theme-switcher", "version-switcher", "navbar-icon-links"], + "switcher": { + "json_url": json_url, + "version_match": switcher_version, + }, + "show_version_warning_banner": True, +}