Add language and version switchers by AA-Turner · Pull Request #193 · python/python-docs-theme · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
72 changes: 71 additions & 1 deletion python_docs_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,85 @@

import hashlib
import os
import sys
from functools import lru_cache
from pathlib import Path
from typing import Any
from typing import Any, Literal

import httpx
import sphinx.application
from sphinx.builders.html import StandaloneHTMLBuilder

if sys.version_info[:2] >= (3, 11):
import tomllib
else:
import tomli as tomllib

THEME_PATH = Path(__file__).parent.resolve()


def _version_label(
version_name: str,
status: Literal["feature", "prerelease", "bugfix", "security", "end-of-life"],
) -> str:
if status == "feature":
return f"dev ({version_name})"
if status == "prerelease":
return f"pre ({version_name})"
if status in {"end-of-life", "security", "bugfix"}:
return version_name
msg = f"Unknown status: {status}"
raise ValueError(msg)


def _builder_inited(app):
html_context = app.config.html_context
language = app.config.language
release = app.config.release
if app.config.html_theme != "python_docs_theme":
return

# Get the current branch statuses
releases = httpx.get(
"https://raw.githubusercontent.com/python/devguide/main/include/release-cycle.json",
timeout=30,
).json()
# Get appropriate version labels
release_labels = {
name: _version_label(name, release["status"])
for name, release in releases.items()
}
# Update the current version to be the full release string
if (short_version := ".".join(release.split(".", 2)[:2])) in release_labels:
release_labels[short_version] = release

# Store the versions in the context as a sorted list of tuples
html_context["switchers_versions"] = sorted(
release_labels.items(),
key=lambda release_label: tuple(map(int, release_label[0].split("."))),
reverse=True,
)

# Get the languages from the docsbuild-scripts config
docsbuild_config = httpx.get(
"https://raw.githubusercontent.com/python/docsbuild-scripts/main/config.toml",
timeout=30,
).text
# Convert language tags and extract language names
languages = [
(iso639_tag.replace("_", "-").lower(), section["name"])
for iso639_tag, section in tomllib.loads(docsbuild_config)["languages"].items()
if section.get("in_prod", True)
]

# If we are working on a language that is not in the list, add it
if language and language not in dict(languages):
languages.append((language, language))

# Store the versions in the context as a sorted list of tuples
html_context["switchers_languages"] = sorted(languages)


@lru_cache(maxsize=None)
def _asset_hash(path: str) -> str:
"""Append a `?digest=` to an url based on the file content."""
Expand Down Expand Up @@ -56,6 +125,7 @@ def setup(app):
current_dir = os.path.abspath(os.path.dirname(__file__))
app.add_html_theme("python_docs_theme", current_dir)

app.connect("builder-inited", _builder_inited)
app.connect("html-page-context", _html_page_context)

return {
Expand Down
17 changes: 15 additions & 2 deletions python_docs_theme/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@ <h3>{{ _('Navigation') }}</h3>
<li><img src="{{ pathto('_static/' ~ theme_root_icon, 1) }}" alt="{{ theme_root_icon_alt_text }}" style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="{{theme_root_url}}">{{theme_root_name}}</a>{{ reldelim1 }}</li>
<li class="switchers">
<div class="language_switcher_placeholder"></div>
<div class="version_switcher_placeholder"></div>
<div class="language_switcher_placeholder">{% if switchers_languages %}
<select class="language">
{% for lang_code, lang_name in switchers_languages -%}
<option value="{{ lang_code }}" {%- if lang_code == language %} selected="true" {%- endif %}>{{ lang_name }}</option>
{% endfor -%}
</select>
{% endif -%}</div>
<div class="version_switcher_placeholder">{% if switchers_versions %}
<select class="version-select">
{% for (version_name, version_title) in switchers_versions -%}
<option value="{{ version_name }}" {%- if version_title == release %} selected="true" {%- endif %}>{{ version_title }}</option>
{% endfor -%}
</select>
{% endif %}</div>
</li>
<li>
{% if theme_root_include_title %}
Expand Down Expand Up @@ -74,6 +86,7 @@ <h3>{{ _('Navigation') }}</h3>
<link rel="shortcut icon" type="image/png" href="{{ pathto('_static/' ~ theme_root_icon, 1) }}" />
{%- if builder != "htmlhelp" %}
{%- if not embedded %}
<script type="text/javascript" src="{{ pathto('_static/switchers.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/copybutton.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/menu.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/search-focus.js', 1) }}"></script>
Expand Down
131 changes: 131 additions & 0 deletions python_docs_theme/static/switchers.js