mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
release.py: simplify bumping the version
Additionally, update the 'in progress' header in the changelog: instead of using a specific version number, simply say 'Unreleased' since we do not know in advance what version will the changes be eventually released. This also simplifies latest changelog retrieval.
This commit is contained in:
parent
be778b8da0
commit
3e5e1eca87
2 changed files with 67 additions and 138 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
2.0.1 (in development)
|
Unreleased
|
||||||
----------------------
|
----------
|
||||||
|
|
||||||
Changelog goes here! Please add your entry to the bottom of one of the lists below!
|
Changelog goes here! Please add your entry to the bottom of one of the lists below!
|
||||||
|
|
||||||
|
|
|
||||||
201
extra/release.py
201
extra/release.py
|
|
@ -2,156 +2,86 @@
|
||||||
|
|
||||||
"""A utility script for automating the beets release process.
|
"""A utility script for automating the beets release process.
|
||||||
"""
|
"""
|
||||||
import datetime
|
from __future__ import annotations
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from packaging.version import Version, parse
|
||||||
|
|
||||||
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE = Path(__file__).parent.parent.absolute()
|
||||||
CHANGELOG = os.path.join(BASE, "docs", "changelog.rst")
|
BEETS_INIT = BASE / "beets" / "__init__.py"
|
||||||
|
|
||||||
# Locations (filenames and patterns) of the version number.
|
|
||||||
VERSION_LOCS = [
|
def update_docs_config(text: str, new: Version) -> str:
|
||||||
|
new_major_minor = f"{new.major}.{new.minor}"
|
||||||
|
text = re.sub(r"(?<=version = )[^\n]+", f'"{new_major_minor}"', text)
|
||||||
|
return re.sub(r"(?<=release = )[^\n]+", f'"{new}"', text)
|
||||||
|
|
||||||
|
|
||||||
|
def update_changelog(text: str, new: Version) -> str:
|
||||||
|
new_header = f"{new} ({datetime.now(timezone.utc).date():%B %d, %Y})"
|
||||||
|
return re.sub(
|
||||||
|
# do not match if the new version is already present
|
||||||
|
r"\nUnreleased\n--+\n",
|
||||||
|
rf"""
|
||||||
|
Unreleased
|
||||||
|
----------
|
||||||
|
|
||||||
|
Changelog goes here! Please add your entry to the bottom of one of the lists below!
|
||||||
|
|
||||||
|
{new_header}
|
||||||
|
{'-' * len(new_header)}
|
||||||
|
""",
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
UpdateVersionCallable = Callable[[str, Version], str]
|
||||||
|
FILENAME_AND_UPDATE_TEXT: list[tuple[Path, UpdateVersionCallable]] = [
|
||||||
(
|
(
|
||||||
os.path.join(BASE, "beets", "__init__.py"),
|
BEETS_INIT,
|
||||||
[
|
lambda text, new: re.sub(
|
||||||
(
|
r"(?<=__version__ = )[^\n]+", f'"{new}"', text
|
||||||
r'__version__\s*=\s*[\'"]([0-9\.]+)[\'"]',
|
),
|
||||||
'__version__ = "{version}"',
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
os.path.join(BASE, "docs", "conf.py"),
|
|
||||||
[
|
|
||||||
(
|
|
||||||
r'version\s*=\s*[\'"]([0-9\.]+)[\'"]',
|
|
||||||
'version = "{minor}"',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r'release\s*=\s*[\'"]([0-9\.]+)[\'"]',
|
|
||||||
'release = "{version}"',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
os.path.join(BASE, "setup.py"),
|
|
||||||
[
|
|
||||||
(
|
|
||||||
r'\s*version\s*=\s*[\'"]([0-9\.]+)[\'"]',
|
|
||||||
' version="{version}",',
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
(BASE / "docs" / "changelog.rst", update_changelog),
|
||||||
|
(BASE / "docs" / "conf.py", update_docs_config),
|
||||||
]
|
]
|
||||||
|
|
||||||
GITHUB_USER = "beetbox"
|
GITHUB_USER = "beetbox"
|
||||||
GITHUB_REPO = "beets"
|
GITHUB_REPO = "beets"
|
||||||
|
|
||||||
|
|
||||||
def bump_version(version: str):
|
def validate_new_version(
|
||||||
"""Update the version number in setup.py, docs config, changelog,
|
ctx: click.Context, param: click.Argument, value: Version
|
||||||
and root module.
|
) -> Version:
|
||||||
"""
|
"""Validate the version is newer than the current one."""
|
||||||
version_parts = [int(p) for p in version.split(".")]
|
with BEETS_INIT.open() as f:
|
||||||
assert len(version_parts) == 3, "invalid version number"
|
contents = f.read()
|
||||||
minor = "{}.{}".format(*version_parts)
|
|
||||||
major = "{}".format(*version_parts)
|
|
||||||
|
|
||||||
# Replace the version each place where it lives.
|
m = re.search(r'(?<=__version__ = ")[^"]+', contents)
|
||||||
for filename, locations in VERSION_LOCS:
|
assert m, "Current version not found in __init__.py"
|
||||||
# Read and transform the file.
|
current = parse(m.group())
|
||||||
out_lines = []
|
|
||||||
with open(filename) as f:
|
|
||||||
found = False
|
|
||||||
for line in f:
|
|
||||||
for pattern, template in locations:
|
|
||||||
match = re.match(pattern, line)
|
|
||||||
if match:
|
|
||||||
# Check that this version is actually newer.
|
|
||||||
old_version = match.group(1)
|
|
||||||
old_parts = [int(p) for p in old_version.split(".")]
|
|
||||||
assert (
|
|
||||||
version_parts > old_parts
|
|
||||||
), "version must be newer than {}".format(old_version)
|
|
||||||
|
|
||||||
# Insert the new version.
|
if not value > current:
|
||||||
out_lines.append(
|
msg = f"version must be newer than {current}"
|
||||||
template.format(
|
raise click.BadParameter(msg)
|
||||||
version=version,
|
|
||||||
major=major,
|
|
||||||
minor=minor,
|
|
||||||
)
|
|
||||||
+ "\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
found = True
|
return value
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Normal line.
|
|
||||||
out_lines.append(line)
|
|
||||||
if not found:
|
|
||||||
print(f"No pattern found in {filename}")
|
|
||||||
# Write the file back.
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
f.write("".join(out_lines))
|
|
||||||
|
|
||||||
update_changelog(version)
|
|
||||||
|
|
||||||
|
|
||||||
def update_changelog(version: str):
|
def bump_version(new: Version) -> None:
|
||||||
# Generate bits to insert into changelog.
|
"""Update the version number in specified files."""
|
||||||
header_line = f"In Development"
|
for path, perform_update in FILENAME_AND_UPDATE_TEXT:
|
||||||
header = "\n\n" + header_line + "\n" + "-" * len(header_line) + "\n\n"
|
with path.open("r+") as f:
|
||||||
header += (
|
contents = f.read()
|
||||||
"Changelog goes here! Please add your entry to the bottom of"
|
f.seek(0)
|
||||||
" one of the lists below!\n"
|
f.write(perform_update(contents, new))
|
||||||
)
|
f.truncate()
|
||||||
# Insert into the right place.
|
|
||||||
with open(CHANGELOG) as f:
|
|
||||||
contents = f.readlines()
|
|
||||||
|
|
||||||
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.
|
|
||||||
with open(CHANGELOG, "w") as f:
|
|
||||||
f.write(contents)
|
|
||||||
|
|
||||||
|
|
||||||
def datestamp():
|
|
||||||
"""Enter today's date as the release date in the changelog."""
|
|
||||||
dt = datetime.datetime.now()
|
|
||||||
stamp = "({} {}, {})".format(dt.strftime("%B"), dt.day, dt.year)
|
|
||||||
marker = "(in development)"
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
underline_length = None
|
|
||||||
with open(CHANGELOG) as f:
|
|
||||||
for line in f:
|
|
||||||
if marker in line:
|
|
||||||
# The header line.
|
|
||||||
line = line.replace(marker, stamp)
|
|
||||||
lines.append(line)
|
|
||||||
underline_length = len(line.strip())
|
|
||||||
elif underline_length:
|
|
||||||
# This is the line after the header. Rewrite the dashes.
|
|
||||||
lines.append("-" * underline_length + "\n")
|
|
||||||
underline_length = None
|
|
||||||
else:
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
with open(CHANGELOG, "w") as f:
|
|
||||||
for line in lines:
|
|
||||||
f.write(line)
|
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
|
@ -160,10 +90,9 @@ def cli():
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument("version")
|
@click.argument("version", type=Version, callback=validate_new_version)
|
||||||
def bump(version: str) -> None:
|
def bump(version: Version) -> None:
|
||||||
# Version number bump.
|
"""Bump the version in project files."""
|
||||||
datestamp()
|
|
||||||
bump_version(version)
|
bump_version(version)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue