-
Notifications
You must be signed in to change notification settings - Fork 3
/
changelog-release
executable file
·130 lines (106 loc) · 3.91 KB
/
changelog-release
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
#!/usr/bin/env python3
import argparse
import datetime
import collections
import glob
import os
import os.path as p
import re
import subprocess
class Release(collections.namedtuple('Release', ['year', 'num'])):
"""ChangeLog infrastructure for a specific release."""
@property
def id(self):
"""Release ID without leading 'r'."""
return '{}_{:03n}'.format(self.year, self.num)
@property
def filename(self):
"""Where the ChangeLog for this release resides."""
return 'src/changes/{}/r{:03n}.md'.format(
self.year, self.num)
def read_changelog(self):
"""Returns ChangeLog contents."""
with open(self.filename, 'r') as f:
return f.read()
def write_changelog(self, string):
"""Replaces ChangeLog with `string`."""
with open(self.filename, 'w') as f:
f.write(string)
def last_release():
candidates = glob.glob('src/changes/{}/r*.md'.format(
datetime.date.today().year))
candidates.sort()
try:
last = candidates[-1]
except IndexError:
raise RuntimeError('cannot handle new year yet -- edit manually')
last = p.basename(last).split('.')[0].lstrip('r')
last = int(last, 10)
return Release(datetime.date.today().year, last)
def finalize_old_changelog(last_release, release_date):
subprocess.check_call(["scriv", "collect"])
text = last_release.read_changelog()
parts = text.split("---", maxsplit=3)
if len(parts) != 3:
raise ValueError("YAML front matter not found in current changelog!")
_, metadata, body = parts
r_publish_date = re.compile("Publish Date: 'YYYY-MM-DD'")
metadata = r_publish_date.sub(f"Publish Date: '{release_date}'", metadata)
r_headline = re.compile(r'(Release ...._...) \(unreleased\)')
body = r_headline.sub(fr'\1 ({release_date})', body)
r_empty_sec = re.compile(r'^## .*\n+^\- nothing yet\n+', re.M)
body = r_empty_sec.sub('', body)
finalized_text = "---".join(["", metadata, body])
last_release.write_changelog(finalized_text)
def prepare_new_changelog(new_release):
print('Preparing release {}'.format(new_release.id))
with open('src/changes/skel') as f:
changes = f.read()
changes = changes.replace('YYYY_NNN', new_release.id)
new_release.write_changelog(changes)
subprocess.check_call(['git', 'add', new_release.filename])
try:
os.unlink('CHANGES.md')
except FileNotFoundError:
pass
os.symlink(new_release.filename, 'CHANGES.md')
INDEX_TEMPLATE = """\
# {year}
Releases performed in {year}.
```{{toctree}}
:maxdepth: 1
{releases}
```
"""
def update_index(last, new):
year = datetime.date.today().year
releases = [
e.name.removesuffix(".md")
for e in os.scandir(p.join('src/changes', str(year)))
if e.is_file() and re.match(r"^r\d+\.md$", e.name)
]
index_content = INDEX_TEMPLATE.format(
year=year, releases="\n".join(sorted(releases))
)
with open('src/changes/{}/index.md'.format(year), 'w') as index:
index.write(index_content)
def main():
a = argparse.ArgumentParser()
default_release_date = datetime.date.today() + datetime.timedelta(days=1)
a.add_argument('release_date', nargs='?',
default=default_release_date.strftime('%Y-%m-%d'),
help='set planned roll-out date (default: %(default)s)')
args = a.parse_args()
if not re.match(r'\d{4}-\d{2}-\d{2}$', args.release_date):
a.error("Release date must be formatted as YYYY-MM-DD")
last = last_release()
new = Release(datetime.date.today().year, last.num + 1)
finalize_old_changelog(last, args.release_date)
prepare_new_changelog(new)
update_index(last, new)
print('Committing changes')
subprocess.check_call([
'git', 'commit', '-am', 'finalize changelog r{}, create r{}'.format(
last.id, new.id)])
if __name__ == '__main__':
main()