diff options
Diffstat (limited to 'src/pallets_sphinx_themes/versions.py')
-rw-r--r-- | src/pallets_sphinx_themes/versions.py | 153 |
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)) |