summaryrefslogtreecommitdiffstats
path: root/src/pallets_sphinx_themes/versions.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pallets_sphinx_themes/versions.py')
-rw-r--r--src/pallets_sphinx_themes/versions.py153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/pallets_sphinx_themes/versions.py b/src/pallets_sphinx_themes/versions.py
new file mode 100644
index 0000000..62c77e4
--- /dev/null
+++ b/src/pallets_sphinx_themes/versions.py
@@ -0,0 +1,153 @@
+import json
+import os
+from collections import namedtuple
+
+from jinja2 import pass_context
+from packaging import version as pv
+
+from .theme_check import only_pallets_theme
+
+
+@only_pallets_theme()
+def load_versions(app):
+ if os.environ.get("READTHEDOCS"):
+ versions = readthedocs_versions(app)
+ else:
+ versions = local_versions(app)
+
+ context = app.config.html_context
+ context["versions"] = versions
+ context["current_version"] = next((v for v in versions if v.current), None)
+ context["latest_version"] = next((v for v in versions if v.latest), None)
+
+
+def local_versions(app):
+ config_versions = app.config.html_context.get("versions")
+
+ if isinstance(config_versions, str):
+ if os.path.isfile(config_versions):
+ with open(config_versions, encoding="utf8") as f:
+ config_versions = json.load(f)
+ else:
+ config_versions = json.loads(config_versions)
+
+ if not config_versions:
+ return []
+
+ versions = []
+
+ for version in config_versions:
+ if isinstance(version, dict):
+ version = DocVersion(**version)
+
+ versions.append(version)
+
+ slug = app.config.version
+ dev = "dev" in app.config.release
+ seen_latest = False
+
+ for i, version in enumerate(versions):
+ if version.slug == "dev":
+ versions[i] = version._replace(dev=True, current=dev)
+
+ if version.slug == slug:
+ versions[i] = version._replace(current=True)
+
+ if not seen_latest and version.version is not None:
+ seen_latest = True
+ versions[i] = version._replace(latest=True)
+
+ return versions
+
+
+def readthedocs_versions(app):
+ config_versions = app.config.html_context["versions"]
+ current_slug = app.config.html_context["current_version"]
+ number_versions = []
+ name_versions = []
+
+ for slug, _ in config_versions:
+ dev = slug in {"main", "master", "default", "latest"}
+ version = _parse_version(slug)
+
+ if version is not None:
+ name = slug
+ append_to = number_versions
+ else:
+ name = "Development" if dev else slug.title()
+ append_to = name_versions
+
+ append_to.append(
+ DocVersion(name=name, slug=slug, dev=dev, current=slug == current_slug)
+ )
+
+ # put the newest numbered version first
+ number_versions.sort(key=lambda x: x.version, reverse=True)
+ # put non-dev named versions first
+ name_versions.sort(key=lambda x: x.dev, reverse=True)
+ versions = number_versions + name_versions
+
+ # if there are non-dev versions, mark the newest one as the latest
+ if versions and not versions[0].dev:
+ versions[0] = versions[0]._replace(latest=True)
+
+ return versions
+
+
+def _parse_version(value: str, placeholder: str = "x"):
+ if value.endswith(f".{placeholder}"):
+ value = value[: -(len(placeholder) + 1)]
+
+ try:
+ return pv.Version(value)
+ except pv.InvalidVersion:
+ return None
+
+
+class DocVersion(
+ namedtuple("DocVersion", ("name", "slug", "version", "latest", "dev", "current"))
+):
+ __slots__ = ()
+
+ def __new__(cls, name, slug=None, latest=False, dev=False, current=False):
+ slug = slug or name
+ version = _parse_version(slug)
+
+ if version is not None:
+ name = "Version " + name
+
+ return super().__new__(cls, name, slug, version, latest, dev, current)
+
+ @pass_context
+ def href(self, context):
+ pathto = context["pathto"]
+ master_doc = context["master_doc"]
+ pagename = context["pagename"]
+
+ builder = pathto.__closure__[0].cell_contents
+ master = pathto(master_doc).rstrip("#/") or "."
+ path = builder.get_target_uri(pagename)
+ return "/".join((master, "..", self.slug, path))
+
+ @pass_context
+ def banner(self, context):
+ if self.latest:
+ return
+
+ latest = context["latest_version"]
+
+ # Don't show a banner if the latest version couldn't be determined, or if this
+ # is the "stable" version.
+ if latest is None or self.name == "stable":
+ return
+
+ if self.dev:
+ return (
+ "This is the development version. The latest stable"
+ ' version is <a href="{href}">{latest}</a>.'
+ ).format(latest=latest.name, href=latest.href(context))
+
+ return (
+ "This is an old version. The latest stable version is"
+ ' <a href="{href}">{latest}</a>.'
+ ).format(latest=latest.name, href=latest.href(context))