| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- # Licensed under the Apache License, Version 2.0 (the "License");
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- import glob
- import importlib
- import inspect
- import logging
- import os
- import re
- import sys
- from collections.abc import Iterable
- from typing import Optional, Union
- def _transform_changelog(path_in: str, path_out: str) -> None:
- """Adjust changelog headers to avoid duplication of short subtitles.
- Args:
- path_in: Input Markdown file path.
- path_out: Output Markdown file path.
- """
- with open(path_in) as fp:
- chlog_lines = fp.readlines()
- # enrich short subsub-titles to be unique
- chlog_ver = ""
- for i, ln in enumerate(chlog_lines):
- if ln.startswith("## "):
- chlog_ver = ln[2:].split("-")[0].strip()
- elif ln.startswith("### "):
- ln = ln.replace("###", f"### {chlog_ver} -")
- chlog_lines[i] = ln
- with open(path_out, "w") as fp:
- fp.writelines(chlog_lines)
- def _linkcode_resolve(
- domain: str,
- info: dict,
- github_user: str,
- github_repo: str,
- main_branch: str = "master",
- stable_branch: str = "release/stable",
- ) -> str:
- def find_source() -> tuple[str, int, int]:
- # try to find the file and line number, based on code from numpy:
- # https://github.com/numpy/numpy/blob/master/doc/source/conf.py#L286
- obj = sys.modules[info["module"]]
- for part in info["fullname"].split("."):
- obj = getattr(obj, part)
- fname = str(inspect.getsourcefile(obj))
- # https://github.com/rtfd/readthedocs.org/issues/5735
- if any(s in fname for s in ("readthedocs", "rtfd", "checkouts")):
- # /home/docs/checkouts/readthedocs.org/user_builds/pytorch_lightning/checkouts/
- # devel/pytorch_lightning/utilities/cls_experiment.py#L26-L176
- path_top = os.path.abspath(os.path.join("..", "..", ".."))
- fname = str(os.path.relpath(fname, start=path_top))
- else:
- # Local build, imitate master
- fname = f"{main_branch}/{os.path.relpath(fname, start=os.path.abspath('..'))}"
- source, line_start = inspect.getsourcelines(obj)
- return fname, line_start, line_start + len(source) - 1
- if domain != "py" or not info["module"]:
- return ""
- try:
- filename = "%s#L%d-L%d" % find_source() # noqa: UP031
- except Exception:
- filename = info["module"].replace(".", "/") + ".py"
- # import subprocess
- # tag = subprocess.Popen(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE,
- # universal_newlines=True).communicate()[0][:-1]
- branch = filename.split("/")[0]
- # do mapping from latest tags to master
- branch = {"latest": main_branch, "stable": stable_branch}.get(branch, branch)
- filename = "/".join([branch, *filename.split("/")[1:]])
- return f"https://github.com/{github_user}/{github_repo}/blob/{filename}"
- def _load_pypi_versions(package_name: str) -> list[str]:
- """Load the versions of the package from PyPI.
- >>> _load_pypi_versions("numpy") # doctest: +ELLIPSIS
- ['0.9.6', '0.9.8', '1.0', ...]
- >>> _load_pypi_versions("scikit-learn") # doctest: +ELLIPSIS
- ['0.9', '0.10', '0.11', '0.12', ...]
- """
- import requests
- from packaging.version import Version
- url = f"https://pypi.org/pypi/{package_name}/json"
- data = requests.get(url, timeout=10).json()
- versions = data["releases"].keys()
- # filter all version which include only numbers and dots
- versions = {k for k in versions if re.match(r"^\d+(\.\d+)*$", k)}
- return sorted(versions, key=Version)
- def _update_link_based_imported_package(link: str, pkg_ver: str, version_digits: Optional[int]) -> str:
- """Resolve a ``{package.version}`` placeholder in a link using the latest available version.
- Args:
- link: The link template containing a ``{...}`` placeholder to replace.
- pkg_ver: A dotted path to resolve the version (e.g., ``"numpy.__version__"``).
- version_digits: Number of version components to keep (e.g., ``2`` -> ``"1.26"``). If ``None``, keep all.
- Returns:
- The link with the ``{...}`` placeholder replaced by a version string.
- """
- pkg_att = pkg_ver.split(".")
- try:
- ver = _load_pypi_versions(pkg_att[0])[-1]
- except Exception:
- # load the package with all additional sub-modules
- module = importlib.import_module(".".join(pkg_att[:-1]))
- # load the attribute
- ver = getattr(module, pkg_att[0])
- # drop any additional context after `+`
- ver = ver.split("+")[0]
- # crop the version to the number of digits
- ver = ".".join(ver.split(".")[:version_digits])
- # replace the version
- return link.replace(f"{{{pkg_ver}}}", ver)
- def adjust_linked_external_docs(
- source_link: str,
- target_link: str,
- browse_folder: Union[str, Iterable[str]],
- file_extensions: Iterable[str] = (".rst", ".py"),
- version_digits: int = 2,
- ) -> None:
- r"""Adjust the linked external docs to be local.
- Args:
- source_link: the link to be replaced
- target_link: the link to be replaced, if ``{package.version}`` is included it will be replaced accordingly
- browse_folder: the location of the browsable folder
- file_extensions: what kind of files shall be scanned
- version_digits: for semantic versioning, how many digits to be considered
- Examples:
- >>> adjust_linked_external_docs(
- ... "https://numpy.org/doc/stable/",
- ... "https://numpy.org/doc/{numpy.__version__}/",
- ... "docs/source",
- ... )
- """
- list_files = []
- if isinstance(browse_folder, str):
- browse_folder = [browse_folder]
- for folder in browse_folder:
- for ext in file_extensions:
- list_files += glob.glob(os.path.join(folder, "**", f"*{ext}"), recursive=True)
- if not list_files:
- logging.warning(f'No files were listed in folder "{browse_folder}" and pattern "{file_extensions}"')
- return
- # find the expression for package version in {} brackets if any, use re to find it
- pkg_ver_all = re.findall(r"{(.+)}", target_link)
- for pkg_ver in pkg_ver_all:
- target_link = _update_link_based_imported_package(target_link, pkg_ver, version_digits)
- # replace the source link with target link
- for fpath in set(list_files):
- with open(fpath, encoding="UTF-8") as fopen:
- lines = fopen.readlines()
- found, skip = False, False
- for i, ln in enumerate(lines):
- # prevent the replacement its own function calls
- if f"{adjust_linked_external_docs.__name__}(" in ln:
- skip = True
- if not skip and source_link in ln:
- # replace the link if any found
- lines[i] = ln.replace(source_link, target_link)
- # record the found link for later write file
- found = True
- if skip and ")" in ln:
- skip = False
- if not found:
- continue
- logging.debug(f'links adjusting in {fpath}: "{source_link}" -> "{target_link}"')
- with open(fpath, "w", encoding="UTF-8") as fw:
- fw.writelines(lines)
|