Skip to content

Commit 8c898ce

Browse files
committed
Fix script for changelog
1 parent 3800593 commit 8c898ce

File tree

1 file changed

+31
-227
lines changed

1 file changed

+31
-227
lines changed

extra/release.py

Lines changed: 31 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,15 @@
22

33
"""A utility script for automating the beets release process.
44
"""
5+
import argparse
56
import datetime
67
import os
78
import re
8-
import subprocess
9-
from contextlib import contextmanager
10-
11-
import click
129

1310
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1411
CHANGELOG = os.path.join(BASE, "docs", "changelog.rst")
15-
16-
17-
@contextmanager
18-
def chdir(d):
19-
"""A context manager that temporary changes the working directory."""
20-
olddir = os.getcwd()
21-
os.chdir(d)
22-
yield
23-
os.chdir(olddir)
24-
25-
26-
@click.group()
27-
def release():
28-
pass
29-
12+
parser = argparse.ArgumentParser()
13+
parser.add_argument("version", type=str)
3014

3115
# Locations (filenames and patterns) of the version number.
3216
VERSION_LOCS = [
@@ -67,7 +51,7 @@ def release():
6751
GITHUB_REPO = "beets"
6852

6953

70-
def bump_version(version):
54+
def bump_version(version: str):
7155
"""Update the version number in setup.py, docs config, changelog,
7256
and root module.
7357
"""
@@ -105,129 +89,45 @@ def bump_version(version):
10589

10690
found = True
10791
break
108-
10992
else:
11093
# Normal line.
11194
out_lines.append(line)
112-
11395
if not found:
11496
print(f"No pattern found in {filename}")
115-
11697
# Write the file back.
11798
with open(filename, "w") as f:
11899
f.write("".join(out_lines))
119100

120-
# Insert into the right place.
121-
with open(CHANGELOG) as f:
122-
contents = f.read()
123-
location = contents.find("\n\n") # First blank line.
124-
contents = contents[:location] + contents[location:]
125-
126-
# Write back.
127-
with open(CHANGELOG, "w") as f:
128-
f.write(contents)
129-
130-
131-
@release.command()
132-
@click.argument("version")
133-
def bump(version):
134-
"""Bump the version number."""
135-
bump_version(version)
101+
update_changelog(version)
136102

137103

138-
def get_latest_changelog():
139-
"""Extract the first section of the changelog."""
140-
started = False
141-
lines = []
142-
with open(CHANGELOG) as f:
143-
for line in f:
144-
if re.match(r"^--+$", line.strip()):
145-
# Section boundary. Start or end.
146-
if started:
147-
# Remove last line, which is the header of the next
148-
# section.
149-
del lines[-1]
150-
break
151-
else:
152-
started = True
153-
154-
elif started:
155-
lines.append(line)
156-
return "".join(lines[2:]).strip()
157-
158-
159-
def rst2md(text):
160-
"""Use Pandoc to convert text from ReST to Markdown."""
161-
pandoc = subprocess.Popen(
162-
["pandoc", "--from=rst", "--to=markdown", "--wrap=none"],
163-
stdin=subprocess.PIPE,
164-
stdout=subprocess.PIPE,
165-
stderr=subprocess.PIPE,
104+
def update_changelog(version: str):
105+
# Generate bits to insert into changelog.
106+
header_line = f"{version} (in development)"
107+
header = "\n\n" + header_line + "\n" + "-" * len(header_line) + "\n\n"
108+
header += (
109+
"Changelog goes here! Please add your entry to the bottom of"
110+
" one of the lists below!\n"
166111
)
167-
stdout, _ = pandoc.communicate(text.encode("utf-8"))
168-
md = stdout.decode("utf-8").strip()
169-
170-
# Fix up odd spacing in lists.
171-
return re.sub(r"^- ", "- ", md, flags=re.M)
172-
173-
174-
def changelog_as_markdown():
175-
"""Get the latest changelog entry as hacked up Markdown."""
176-
rst = get_latest_changelog()
177-
178-
# Replace plugin links with plugin names.
179-
rst = re.sub(r":doc:`/plugins/(\w+)`", r"``\1``", rst)
180-
181-
# References with text.
182-
rst = re.sub(r":ref:`([^<]+)(<[^>]+>)`", r"\1", rst)
183-
184-
# Other backslashes with verbatim ranges.
185-
rst = re.sub(r"(\s)`([^`]+)`([^_])", r"\1``\2``\3", rst)
186-
187-
# Command links with command names.
188-
rst = re.sub(r":ref:`(\w+)-cmd`", r"``\1``", rst)
189-
190-
# Bug numbers.
191-
rst = re.sub(r":bug:`(\d+)`", r"#\1", rst)
192-
193-
# Users.
194-
rst = re.sub(r":user:`(\w+)`", r"@\1", rst)
195-
196-
# Convert with Pandoc.
197-
md = rst2md(rst)
198-
199-
# Restore escaped issue numbers.
200-
md = re.sub(r"\\#(\d+)\b", r"#\1", md)
201-
202-
return md
203-
204-
205-
@release.command()
206-
def changelog():
207-
"""Get the most recent version's changelog as Markdown."""
208-
print(changelog_as_markdown())
209-
210-
211-
def get_version(index=0):
212-
"""Read the current version from the changelog."""
112+
# Insert into the right place.
213113
with open(CHANGELOG) as f:
214-
cur_index = 0
215-
for line in f:
216-
match = re.search(r"^\d+\.\d+\.\d+", line)
217-
if match:
218-
if cur_index == index:
219-
return match.group(0)
220-
else:
221-
cur_index += 1
114+
contents = f.readlines()
222115

116+
contents = [
117+
line
118+
for line in contents
119+
if not re.match(r"Changelog goes here!.*", line)
120+
]
121+
contents = "".join(contents)
122+
contents = re.sub("\n{3,}", "\n\n", contents)
223123

224-
@release.command()
225-
def version():
226-
"""Display the current version."""
227-
print(get_version())
124+
location = contents.find("\n\n") # First blank line.
125+
contents = contents[:location] + header + contents[location:]
126+
# Write back.
127+
with open(CHANGELOG, "w") as f:
128+
f.write(contents)
228129

229130

230-
@release.command()
231131
def datestamp():
232132
"""Enter today's date as the release date in the changelog."""
233133
dt = datetime.datetime.now()
@@ -255,108 +155,12 @@ def datestamp():
255155
f.write(line)
256156

257157

258-
@release.command()
259-
def prep():
260-
"""Run all steps to prepare a release.
261-
262-
- Tag the commit.
263-
- Build the sdist package.
264-
- Generate the Markdown changelog to ``changelog.md``.
265-
- Bump the version number to the next version.
266-
"""
267-
cur_version = get_version()
268-
269-
# Tag.
270-
subprocess.check_call(["git", "tag", f"v{cur_version}"])
271-
272-
# Build.
273-
with chdir(BASE):
274-
subprocess.check_call(["python", "setup.py", "sdist"])
275-
276-
# Generate Markdown changelog.
277-
cl = changelog_as_markdown()
278-
with open(os.path.join(BASE, "changelog.md"), "w") as f:
279-
f.write(cl)
280-
158+
def prep(args: argparse.Namespace):
281159
# Version number bump.
282-
# FIXME It should be possible to specify this as an argument.
283-
version_parts = [int(n) for n in cur_version.split(".")]
284-
version_parts[-1] += 1
285-
next_version = ".".join(map(str, version_parts))
286-
bump_version(next_version)
287-
288-
289-
@release.command()
290-
def publish():
291-
"""Unleash a release unto the world.
292-
293-
- Push the tag to GitHub.
294-
- Upload to PyPI.
295-
"""
296-
version = get_version(1)
297-
298-
# Push to GitHub.
299-
with chdir(BASE):
300-
subprocess.check_call(["git", "push"])
301-
subprocess.check_call(["git", "push", "--tags"])
302-
303-
# Upload to PyPI.
304-
path = os.path.join(BASE, "dist", f"beets-{version}.tar.gz")
305-
subprocess.check_call(["twine", "upload", path])
306-
307-
308-
@release.command()
309-
def ghrelease():
310-
"""Create a GitHub release using the `github-release` command-line
311-
tool.
312-
313-
Reads the changelog to upload from `changelog.md`. Uploads the
314-
tarball from the `dist` directory.
315-
"""
316-
version = get_version(1)
317-
tag = "v" + version
318-
319-
# Load the changelog.
320-
with open(os.path.join(BASE, "changelog.md")) as f:
321-
cl_md = f.read()
322-
323-
# Create the release.
324-
subprocess.check_call(
325-
[
326-
"github-release",
327-
"release",
328-
"-u",
329-
GITHUB_USER,
330-
"-r",
331-
GITHUB_REPO,
332-
"--tag",
333-
tag,
334-
"--name",
335-
f"{GITHUB_REPO} {version}",
336-
"--description",
337-
cl_md,
338-
]
339-
)
340-
341-
# Attach the release tarball.
342-
tarball = os.path.join(BASE, "dist", f"beets-{version}.tar.gz")
343-
subprocess.check_call(
344-
[
345-
"github-release",
346-
"upload",
347-
"-u",
348-
GITHUB_USER,
349-
"-r",
350-
GITHUB_REPO,
351-
"--tag",
352-
tag,
353-
"--name",
354-
os.path.basename(tarball),
355-
"--file",
356-
tarball,
357-
]
358-
)
160+
datestamp()
161+
bump_version(args.version)
359162

360163

361164
if __name__ == "__main__":
362-
release()
165+
args = parser.parse_args()
166+
prep(args)

0 commit comments

Comments
 (0)