diff --git a/tools/markdown_release_notes.py b/tools/markdown_release_notes.py index 73bdbf775..cdae474f5 100644 --- a/tools/markdown_release_notes.py +++ b/tools/markdown_release_notes.py @@ -1,14 +1,53 @@ #!/usr/bin/env python import re import sys +from collections import defaultdict +from functools import cache +from operator import call from pathlib import Path +from sphinx.ext.intersphinx import fetch_inventory + CHANGELOG = Path(__file__).parent.parent / 'Changelog' # Match release lines like "5.2.0 (Monday 11 December 2023)" RELEASE_REGEX = re.compile(r"""((?:\d+)\.(?:\d+)\.(?:\d+)) \(\w+ \d{1,2} \w+ \d{4}\)$""") +class MockConfig: + intersphinx_timeout: int | None = None + tls_verify = False + tls_cacerts: str | dict[str, str] | None = None + user_agent: str = '' + + +@call +class MockApp: + srcdir = '' + config = MockConfig() + + +fetch_inv = cache(fetch_inventory) + + +def get_intersphinx(obj): + module = obj.split('.', 1)[0] + + registry = defaultdict(lambda: 'https://docs.python.org/3') + registry.update( + numpy='https://numpy.org/doc/stable', + ) + + base_url = registry[module] + + inventory = fetch_inv(MockApp, '', f'{base_url}/objects.inv') + # Check py: first, then whatever + for objclass in sorted(inventory, key=lambda x: not x.startswith('py:')): + if obj in inventory[objclass]: + return f'{base_url}/{inventory[objclass][obj][2]}' + raise ValueError("Couldn't lookup {obj}") + + def main(): version = sys.argv[1] output = sys.argv[2] @@ -46,7 +85,7 @@ def main(): release_notes = re.sub(r'\n +', ' ', release_notes) # Replace pr/ with # for GitHub - release_notes = re.sub(r'\(pr/(\d+)\)', r'(#\1)', release_notes) + release_notes = re.sub(r'pr/(\d+)', r'#\1', release_notes) # Replace :mod:`package.X` with [package.X](...) release_notes = re.sub( @@ -76,6 +115,14 @@ def main(): r'[\3](https://nipy.org/nibabel/reference/\1.html#\1.\2.\3)', release_notes, ) + # Replace ::`` with intersphinx lookup + for ref in re.findall(r'(:[^:]*:`~?\w[\w.]+\w`)', release_notes): + objclass, tilde, module, obj = re.match(r':([^:]*):`(~?)([\w.]+)\.(\w+)`', ref).groups() + url = get_intersphinx(f'{module}.{obj}') + mdlink = f'[{"" if tilde else module}{obj}]({url})' + release_notes = release_notes.replace(ref, mdlink) + # Replace RST links with Markdown links + release_notes = re.sub(r'`([^<`]*) <([^>]*)>`_+', r'[\1](\2)', release_notes) def python_doc(match): module = match.group(1) @@ -84,10 +131,9 @@ def python_doc(match): release_notes = re.sub(r':meth:`~([\w.]+)\.(\w+)`', python_doc, release_notes) - output.write('## Release notes\n\n') - output.write(release_notes) - - output.close() + with output: + output.write('## Release notes\n\n') + output.write(release_notes) if __name__ == '__main__':