mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Fix script for changelog
This commit is contained in:
parent
3800593046
commit
8c898ce524
1 changed files with 31 additions and 227 deletions
258
extra/release.py
258
extra/release.py
|
|
@ -2,31 +2,15 @@
|
||||||
|
|
||||||
"""A utility script for automating the beets release process.
|
"""A utility script for automating the beets release process.
|
||||||
"""
|
"""
|
||||||
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
CHANGELOG = os.path.join(BASE, "docs", "changelog.rst")
|
CHANGELOG = os.path.join(BASE, "docs", "changelog.rst")
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("version", type=str)
|
||||||
@contextmanager
|
|
||||||
def chdir(d):
|
|
||||||
"""A context manager that temporary changes the working directory."""
|
|
||||||
olddir = os.getcwd()
|
|
||||||
os.chdir(d)
|
|
||||||
yield
|
|
||||||
os.chdir(olddir)
|
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
|
||||||
def release():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Locations (filenames and patterns) of the version number.
|
# Locations (filenames and patterns) of the version number.
|
||||||
VERSION_LOCS = [
|
VERSION_LOCS = [
|
||||||
|
|
@ -67,7 +51,7 @@ GITHUB_USER = "beetbox"
|
||||||
GITHUB_REPO = "beets"
|
GITHUB_REPO = "beets"
|
||||||
|
|
||||||
|
|
||||||
def bump_version(version):
|
def bump_version(version: str):
|
||||||
"""Update the version number in setup.py, docs config, changelog,
|
"""Update the version number in setup.py, docs config, changelog,
|
||||||
and root module.
|
and root module.
|
||||||
"""
|
"""
|
||||||
|
|
@ -105,129 +89,45 @@ def bump_version(version):
|
||||||
|
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Normal line.
|
# Normal line.
|
||||||
out_lines.append(line)
|
out_lines.append(line)
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
print(f"No pattern found in {filename}")
|
print(f"No pattern found in {filename}")
|
||||||
|
|
||||||
# Write the file back.
|
# Write the file back.
|
||||||
with open(filename, "w") as f:
|
with open(filename, "w") as f:
|
||||||
f.write("".join(out_lines))
|
f.write("".join(out_lines))
|
||||||
|
|
||||||
|
update_changelog(version)
|
||||||
|
|
||||||
|
|
||||||
|
def update_changelog(version: str):
|
||||||
|
# Generate bits to insert into changelog.
|
||||||
|
header_line = f"{version} (in development)"
|
||||||
|
header = "\n\n" + header_line + "\n" + "-" * len(header_line) + "\n\n"
|
||||||
|
header += (
|
||||||
|
"Changelog goes here! Please add your entry to the bottom of"
|
||||||
|
" one of the lists below!\n"
|
||||||
|
)
|
||||||
# Insert into the right place.
|
# Insert into the right place.
|
||||||
with open(CHANGELOG) as f:
|
with open(CHANGELOG) as f:
|
||||||
contents = f.read()
|
contents = f.readlines()
|
||||||
location = contents.find("\n\n") # First blank line.
|
|
||||||
contents = contents[:location] + contents[location:]
|
|
||||||
|
|
||||||
|
contents = [
|
||||||
|
line
|
||||||
|
for line in contents
|
||||||
|
if not re.match(r"Changelog goes here!.*", line)
|
||||||
|
]
|
||||||
|
contents = "".join(contents)
|
||||||
|
contents = re.sub("\n{3,}", "\n\n", contents)
|
||||||
|
|
||||||
|
location = contents.find("\n\n") # First blank line.
|
||||||
|
contents = contents[:location] + header + contents[location:]
|
||||||
# Write back.
|
# Write back.
|
||||||
with open(CHANGELOG, "w") as f:
|
with open(CHANGELOG, "w") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
@click.argument("version")
|
|
||||||
def bump(version):
|
|
||||||
"""Bump the version number."""
|
|
||||||
bump_version(version)
|
|
||||||
|
|
||||||
|
|
||||||
def get_latest_changelog():
|
|
||||||
"""Extract the first section of the changelog."""
|
|
||||||
started = False
|
|
||||||
lines = []
|
|
||||||
with open(CHANGELOG) as f:
|
|
||||||
for line in f:
|
|
||||||
if re.match(r"^--+$", line.strip()):
|
|
||||||
# Section boundary. Start or end.
|
|
||||||
if started:
|
|
||||||
# Remove last line, which is the header of the next
|
|
||||||
# section.
|
|
||||||
del lines[-1]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
started = True
|
|
||||||
|
|
||||||
elif started:
|
|
||||||
lines.append(line)
|
|
||||||
return "".join(lines[2:]).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def rst2md(text):
|
|
||||||
"""Use Pandoc to convert text from ReST to Markdown."""
|
|
||||||
pandoc = subprocess.Popen(
|
|
||||||
["pandoc", "--from=rst", "--to=markdown", "--wrap=none"],
|
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
stdout, _ = pandoc.communicate(text.encode("utf-8"))
|
|
||||||
md = stdout.decode("utf-8").strip()
|
|
||||||
|
|
||||||
# Fix up odd spacing in lists.
|
|
||||||
return re.sub(r"^- ", "- ", md, flags=re.M)
|
|
||||||
|
|
||||||
|
|
||||||
def changelog_as_markdown():
|
|
||||||
"""Get the latest changelog entry as hacked up Markdown."""
|
|
||||||
rst = get_latest_changelog()
|
|
||||||
|
|
||||||
# Replace plugin links with plugin names.
|
|
||||||
rst = re.sub(r":doc:`/plugins/(\w+)`", r"``\1``", rst)
|
|
||||||
|
|
||||||
# References with text.
|
|
||||||
rst = re.sub(r":ref:`([^<]+)(<[^>]+>)`", r"\1", rst)
|
|
||||||
|
|
||||||
# Other backslashes with verbatim ranges.
|
|
||||||
rst = re.sub(r"(\s)`([^`]+)`([^_])", r"\1``\2``\3", rst)
|
|
||||||
|
|
||||||
# Command links with command names.
|
|
||||||
rst = re.sub(r":ref:`(\w+)-cmd`", r"``\1``", rst)
|
|
||||||
|
|
||||||
# Bug numbers.
|
|
||||||
rst = re.sub(r":bug:`(\d+)`", r"#\1", rst)
|
|
||||||
|
|
||||||
# Users.
|
|
||||||
rst = re.sub(r":user:`(\w+)`", r"@\1", rst)
|
|
||||||
|
|
||||||
# Convert with Pandoc.
|
|
||||||
md = rst2md(rst)
|
|
||||||
|
|
||||||
# Restore escaped issue numbers.
|
|
||||||
md = re.sub(r"\\#(\d+)\b", r"#\1", md)
|
|
||||||
|
|
||||||
return md
|
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
def changelog():
|
|
||||||
"""Get the most recent version's changelog as Markdown."""
|
|
||||||
print(changelog_as_markdown())
|
|
||||||
|
|
||||||
|
|
||||||
def get_version(index=0):
|
|
||||||
"""Read the current version from the changelog."""
|
|
||||||
with open(CHANGELOG) as f:
|
|
||||||
cur_index = 0
|
|
||||||
for line in f:
|
|
||||||
match = re.search(r"^\d+\.\d+\.\d+", line)
|
|
||||||
if match:
|
|
||||||
if cur_index == index:
|
|
||||||
return match.group(0)
|
|
||||||
else:
|
|
||||||
cur_index += 1
|
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
def version():
|
|
||||||
"""Display the current version."""
|
|
||||||
print(get_version())
|
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
def datestamp():
|
def datestamp():
|
||||||
"""Enter today's date as the release date in the changelog."""
|
"""Enter today's date as the release date in the changelog."""
|
||||||
dt = datetime.datetime.now()
|
dt = datetime.datetime.now()
|
||||||
|
|
@ -255,108 +155,12 @@ def datestamp():
|
||||||
f.write(line)
|
f.write(line)
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
def prep(args: argparse.Namespace):
|
||||||
def prep():
|
|
||||||
"""Run all steps to prepare a release.
|
|
||||||
|
|
||||||
- Tag the commit.
|
|
||||||
- Build the sdist package.
|
|
||||||
- Generate the Markdown changelog to ``changelog.md``.
|
|
||||||
- Bump the version number to the next version.
|
|
||||||
"""
|
|
||||||
cur_version = get_version()
|
|
||||||
|
|
||||||
# Tag.
|
|
||||||
subprocess.check_call(["git", "tag", f"v{cur_version}"])
|
|
||||||
|
|
||||||
# Build.
|
|
||||||
with chdir(BASE):
|
|
||||||
subprocess.check_call(["python", "setup.py", "sdist"])
|
|
||||||
|
|
||||||
# Generate Markdown changelog.
|
|
||||||
cl = changelog_as_markdown()
|
|
||||||
with open(os.path.join(BASE, "changelog.md"), "w") as f:
|
|
||||||
f.write(cl)
|
|
||||||
|
|
||||||
# Version number bump.
|
# Version number bump.
|
||||||
# FIXME It should be possible to specify this as an argument.
|
datestamp()
|
||||||
version_parts = [int(n) for n in cur_version.split(".")]
|
bump_version(args.version)
|
||||||
version_parts[-1] += 1
|
|
||||||
next_version = ".".join(map(str, version_parts))
|
|
||||||
bump_version(next_version)
|
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
def publish():
|
|
||||||
"""Unleash a release unto the world.
|
|
||||||
|
|
||||||
- Push the tag to GitHub.
|
|
||||||
- Upload to PyPI.
|
|
||||||
"""
|
|
||||||
version = get_version(1)
|
|
||||||
|
|
||||||
# Push to GitHub.
|
|
||||||
with chdir(BASE):
|
|
||||||
subprocess.check_call(["git", "push"])
|
|
||||||
subprocess.check_call(["git", "push", "--tags"])
|
|
||||||
|
|
||||||
# Upload to PyPI.
|
|
||||||
path = os.path.join(BASE, "dist", f"beets-{version}.tar.gz")
|
|
||||||
subprocess.check_call(["twine", "upload", path])
|
|
||||||
|
|
||||||
|
|
||||||
@release.command()
|
|
||||||
def ghrelease():
|
|
||||||
"""Create a GitHub release using the `github-release` command-line
|
|
||||||
tool.
|
|
||||||
|
|
||||||
Reads the changelog to upload from `changelog.md`. Uploads the
|
|
||||||
tarball from the `dist` directory.
|
|
||||||
"""
|
|
||||||
version = get_version(1)
|
|
||||||
tag = "v" + version
|
|
||||||
|
|
||||||
# Load the changelog.
|
|
||||||
with open(os.path.join(BASE, "changelog.md")) as f:
|
|
||||||
cl_md = f.read()
|
|
||||||
|
|
||||||
# Create the release.
|
|
||||||
subprocess.check_call(
|
|
||||||
[
|
|
||||||
"github-release",
|
|
||||||
"release",
|
|
||||||
"-u",
|
|
||||||
GITHUB_USER,
|
|
||||||
"-r",
|
|
||||||
GITHUB_REPO,
|
|
||||||
"--tag",
|
|
||||||
tag,
|
|
||||||
"--name",
|
|
||||||
f"{GITHUB_REPO} {version}",
|
|
||||||
"--description",
|
|
||||||
cl_md,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attach the release tarball.
|
|
||||||
tarball = os.path.join(BASE, "dist", f"beets-{version}.tar.gz")
|
|
||||||
subprocess.check_call(
|
|
||||||
[
|
|
||||||
"github-release",
|
|
||||||
"upload",
|
|
||||||
"-u",
|
|
||||||
GITHUB_USER,
|
|
||||||
"-r",
|
|
||||||
GITHUB_REPO,
|
|
||||||
"--tag",
|
|
||||||
tag,
|
|
||||||
"--name",
|
|
||||||
os.path.basename(tarball),
|
|
||||||
"--file",
|
|
||||||
tarball,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
release()
|
args = parser.parse_args()
|
||||||
|
prep(args)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue