-
Notifications
You must be signed in to change notification settings - Fork 260
/
Copy pathmarkdown_release_notes.py
140 lines (111 loc) · 4.32 KB
/
markdown_release_notes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/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]
if output == '-':
output = sys.stdout
else:
output = open(output, 'w')
release_notes = []
in_release_notes = False
with open(CHANGELOG) as f:
for line in f:
match = RELEASE_REGEX.match(line)
if match:
if in_release_notes:
break
in_release_notes = match.group(1) == version
next(f) # Skip the underline
continue
if in_release_notes:
release_notes.append(line)
# Drop empty lines at start and end
while release_notes and not release_notes[0].strip():
release_notes.pop(0)
while release_notes and not release_notes[-1].strip():
release_notes.pop()
# Join lines
release_notes = ''.join(release_notes)
# Remove line breaks when they are followed by a space
release_notes = re.sub(r'\n +', ' ', release_notes)
# Replace pr/<number> with #<number> for GitHub
release_notes = re.sub(r'pr/(\d+)', r'#\1', release_notes)
# Replace :mod:`package.X` with [package.X](...)
release_notes = re.sub(
r':mod:`nibabel\.(.*)`',
r'[nibabel.\1](https://nipy.org/nibabel/reference/nibabel.\1.html)',
release_notes,
)
# Replace :class/func/attr:`package.module.X` with [package.module.X](...)
release_notes = re.sub(
r':(?:class|func|attr):`(nibabel\.\w*)(\.[\w.]*)?\.(\w+)`',
r'[\1\2.\3](https://nipy.org/nibabel/reference/\1.html#\1\2.\3)',
release_notes,
)
release_notes = re.sub(
r':(?:class|func|attr):`~(nibabel\.\w*)(\.[\w.]*)?\.(\w+)`',
r'[\3](https://nipy.org/nibabel/reference/\1.html#\1\2.\3)',
release_notes,
)
# Replace :meth:`package.module.class.X` with [package.module.class.X](...)
release_notes = re.sub(
r':meth:`(nibabel\.[\w.]*)\.(\w+)\.(\w+)`',
r'[\1.\2.\3](https://nipy.org/nibabel/reference/\1.html#\1.\2.\3)',
release_notes,
)
release_notes = re.sub(
r':meth:`~(nibabel\.[\w.]*)\.(\w+)\.(\w+)`',
r'[\3](https://nipy.org/nibabel/reference/\1.html#\1.\2.\3)',
release_notes,
)
# Replace :<any>:`<ref>` 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)
name = match.group(2)
return f'[{name}](https://docs.python.org/3/library/{module.lower()}.html#{module}.{name})'
release_notes = re.sub(r':meth:`~([\w.]+)\.(\w+)`', python_doc, release_notes)
with output:
output.write('## Release notes\n\n')
output.write(release_notes)
if __name__ == '__main__':
main()