diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bc7b0b18a..4099298ea 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,20 +1,14 @@ name: Documentation on: + push: + branches: + - main release: types: - published - workflow_dispatch: - inputs: - force: - description: 'Force' - default: false - required: false - type: boolean - tag: - description: 'Specify tag (empty builds branch HEAD)' - required: false - default: '' + workflow_dispatch: # Allow manually triggering the workflow + concurrency: group: ${{ github.workflow }} @@ -24,35 +18,77 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read + contents: write pages: write id-token: write steps: - uses: actions/checkout@v4 with: - ref: ${{ inputs.tag || github.ref }} + ref: ${{ github.ref }} - uses: actions/setup-python@v5 with: {python-version: "3.10"} - name: Install tox run: pip install tox-uv - - name: Build Docs ${{ inputs.force == true && '(Force)' || '' }} - run: tox -e docs-py310 ${{ inputs.force == true && '-- -f -r' || '-- -r' }} - - name: Upload docs artifact - uses: actions/upload-pages-artifact@v1 - with: - path: 'docs/build' - - deploy: - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v1 + - name: Build Docs + run: tox -e docs-py310 -- -r + - name: Configure sphinx bot for pushing and fetch branches + run: | + git config --local user.email "sphinx-upload[bot]@users.noreply.github.com" + git config --local user.name "sphinx-upload[bot]" + git fetch origin gh-pages:gh-pages + git checkout gh-pages + git fetch origin main:main + git show main:docs/scripts/utils.py > utils.py + - name: Check for Hotfix + id: hotfix + run: | + pip install packaging + git fetch --tags + tags=$(git tag -l) + if ${{ github.event_name == 'release' }}; then + HOTFIX_VERSION=$(python utils.py hotfix "${{github.event.release.tag_name}}" "$tags") + else + HOTFIX_VERSION="False" + fi + echo "$HOTFIX_VERSION" + echo "hotfix=$HOTFIX_VERSION" >> "$GITHUB_OUTPUT" + - name: Verify setting of hotfix + run: | + echo "The value of hotfix is ${{ steps.hotfix.outputs.hotfix }}" + - if: ${{ steps.hotfix.outputs.hotfix == 'False'}} + name: Copy latest folder to gh-pages branch and update branch + run: | + if [ -d "latest" ]; then + rm -rf latest + fi + mkdir ./latest + cp -r docs/build/* ./latest + echo "Successfully copied into latest" + python utils.py html latest + git add -f latest + - if: ${{ github.event_name == 'release' }} + name: Create numbered version + run: | + FOLDER=${{github.event.release.tag_name}} + mkdir ./$FOLDER + cp -r docs/build/* ./$FOLDER + python utils.py selector_page $FOLDER + python utils.py html $FOLDER + git add -f $FOLDER + git add -f versions.html + - if: ${{ (github.event_name == 'release') && (steps.hotfix.outputs.hotfix == 'False') }} + name: Copy stable folder to gh-pages branch when releasing + run: | + if [ -d "stable" ]; then + rm -rf stable + fi + mkdir ./stable + cp -rv docs/build/* ./stable + python utils.py html stable + git add -f stable + - name: Create git commit + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git commit -m "Sphinx documentation for ${git_hash}" + - name: Push changes to gh-pages branch + run: git push origin gh-pages diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcb475fc..b6e2266d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- Multi version documentation + ## [0.10.0] - 2024-08-02 ### Breaking Changes - Providing an explicit `batch_size` is now mandatory when asking for recommendations diff --git a/docs/conf.py b/docs/conf.py index 60516ae53..adb977025 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -188,6 +188,9 @@ # Color of the search text and icon "color-sidebar-search-text": "white", "color-sidebar-search-icon": BROWN, + # The announcement background and text color + "color-announcement-background": LIGHT_BLUE, + "color-announcement-text": LIGHT_GRAY, }, # Colors for dark mode. "dark_css_variables": { @@ -222,6 +225,9 @@ "color-toc-item-text--active": BROWN, # Color of search bar when clicking search "color-sidebar-search-background--focus": DARK_BLUE, + # The announcement background and text color + "color-announcement-background": LIGHT_BLUE, + "color-announcement-text": LIGHT_GRAY, }, # Logos. Location is relative to _static folder. "light_logo": "logo1.svg", # Logo for light mode diff --git a/docs/index.md b/docs/index.md index 3a43a9176..326e57eea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,10 @@ +```{toctree} +:titlesonly: +:hidden: + +Versions +``` + ```{toctree} :maxdepth: 2 :titlesonly: diff --git a/docs/scripts/utils.py b/docs/scripts/utils.py index e7f933947..cc250d943 100644 --- a/docs/scripts/utils.py +++ b/docs/scripts/utils.py @@ -1,5 +1,11 @@ """This file contains some utility function for building the documentation.""" +import re +import sys +from pathlib import Path + +from packaging.version import Version + def adjust_pictures( file_path: str, match: str, light_version: str, dark_version: str @@ -40,3 +46,120 @@ def adjust_pictures( with open(file_path, "w") as file: file.writelines(lines) + + +def add_version_to_selector_page(version: str) -> None: + """Add the newly built version to the version selection overview. + + Args: + version: The version that should be added. + """ + indent = " " + new_line = ( + f"{indent}
  • {version}' + "
  • \n" + ) + file = Path("versions.html") + modified_lines = [] + # Regular expression pattern to match the version number in the format X.Y.Z + pattern = r"\b\d+\.\d+\.\d+\b" + # Indices for memorizing the index of the line containing "stable" and the last + # version that was larger than the one to be inserted + stable_index = -1 + last_version_index = -1 + with file.open(mode="r") as f: + lines = f.readlines() + for ind, line in enumerate(lines): + # We might find strings that look like version numbers without wanting to + # We thus need to potentially reset `last_version_index` + if "

    Available versions

    " in line: + last_version_index = -1 + + # Search for the version number in the current line + if version_number := re.search(pattern, line): + # Memorize whether we saw a larger version number, implying that this is + # a hotfix + if Version(version_number.group()) > Version(version): + last_version_index = ind + # Add the already existing line + modified_lines.append(line) + + # Slightly weird string since the word "stable" appears is other places + if "stable" in line: + stable_index = ind + # We never found a larger number than the current, so we just insert after stable + index = last_version_index + 1 if last_version_index != -1 else stable_index + 1 + modified_lines.insert(index, new_line) + with file.open(mode="w") as f: + f.writelines(modified_lines) + + +def adjust_version_to_html(version: str) -> None: + """Adjust the shown version in the HTML files. + + This method includes the current version in the sidebar of all HTML files and + additionally adds a banner warning when the documentation is not ``stable``. + + Args: + version: The version that should be injected into the sidebar. + """ + prefix = '
  • ' + html_class = "reference external" + link = "https://avhopp.github.io/baybe_dev/versions" + new_line = ( + f'{prefix}Version: {version}
  • ' + ) + link_to_stable = 'stable' + # The content of the announcement that might be added. + announcement = ( + f"You are not viewing the documentation of the {link_to_stable} version." + ) + # Actual HTML code that will be inserted + announcement_html = ( + """
    \n""" + """ \n""" + """
    \n""" + ) + add_announcement = version != "stable" + path = Path(version) + if path.exists(): + # Recursively check all HTML files + for file in path.rglob("*.html"): + modified_lines = [] + with file.open(mode="r") as f: + lines = f.readlines() + for line in lines: + # Check if we need to add the announcement + if add_announcement: + # Add announcement at correct position + if line.strip() == '
    ': + modified_lines.insert(-2, announcement_html) + if "Versions" in line: + modified_lines.append(new_line) + else: + modified_lines.append(line) + with file.open(mode="w") as f: + f.writelines(modified_lines) + + +def check_for_hotfix(tags: list[str], version: str): + """Check whether the current build corresponds to a hotfix.""" + split_tags = tags.split("\n") + split_tags.sort(key=Version) + print(Version(version) < Version(split_tags[-1]), end="") + + +if __name__ == "__main__": + chosen_method = sys.argv[1] + version = sys.argv[2] + if chosen_method == "selector_page": + add_version_to_selector_page(version) + elif chosen_method == "html": + adjust_version_to_html(version) + elif chosen_method == "hotfix": + check_for_hotfix(tags=sys.argv[3], version=version) + else: + print(f"Invalid arguments {sys.argv} were chosen!")