diff --git a/.github/workflows/make_release.yaml b/.github/workflows/make_release.yaml index 6c7d13e17..e51d2d0f3 100644 --- a/.github/workflows/make_release.yaml +++ b/.github/workflows/make_release.yaml @@ -11,22 +11,20 @@ jobs: increment_version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Install pandoc - run: sudo apt update && sudo apt install pandoc -y - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.9" - - name: Run version script - id: script - run: | + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Run version script + id: script + run: | python extra/release.py bump "${{ inputs.version }}" - - uses: EndBug/add-and-commit@v9 - name: Commit the changes - with: - message: 'Increment version numbers to ${{ inputs.version }}' - + - uses: EndBug/add-and-commit@v9 + name: Commit the changes + with: + message: "Increment version numbers to ${{ inputs.version }}" + build: runs-on: ubuntu-latest needs: increment_version @@ -55,6 +53,14 @@ jobs: needs: build steps: - uses: actions/checkout@v4 + - name: Install pandoc + run: sudo apt update && sudo apt install pandoc -y + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Obtain the changelog + run: echo CHANGELOG="$(python ./extra/release.py changelog)" >> $GITHUB_ENV - name: Bump version and push tag id: tag_version uses: mathieudutour/github-tag-action@v6.1 @@ -71,7 +77,7 @@ jobs: with: tag: ${{ steps.tag_version.outputs.new_tag }} name: Release ${{ steps.tag_version.outputs.new_tag }} - body: "Check [here](https://beets.readthedocs.io/en/stable/changelog.html) for the latest changes." + body: ${{ env.CHANGELOG }} artifacts: dist/* publish_to_pypi: @@ -90,5 +96,3 @@ jobs: path: dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - - diff --git a/extra/release.py b/extra/release.py index adaf64528..251b22109 100755 --- a/extra/release.py +++ b/extra/release.py @@ -5,6 +5,7 @@ from __future__ import annotations import re +import subprocess from datetime import datetime, timezone from pathlib import Path from typing import Callable @@ -14,6 +15,13 @@ from packaging.version import Version, parse BASE = Path(__file__).parent.parent.absolute() BEETS_INIT = BASE / "beets" / "__init__.py" +CHANGELOG = BASE / "docs" / "changelog.rst" + +MD_CHANGELOG_SECTION_LIST = re.compile(r"- .+?(?=\n\n###|$)", re.DOTALL) +version_header = r"\d+\.\d+\.\d+ \([^)]+\)" +RST_LATEST_CHANGES = re.compile( + rf"{version_header}\n--+\s+(.+?)\n\n+{version_header}", re.DOTALL +) def update_docs_config(text: str, new: Version) -> str: @@ -48,13 +56,10 @@ FILENAME_AND_UPDATE_TEXT: list[tuple[Path, UpdateVersionCallable]] = [ r"(?<=__version__ = )[^\n]+", f'"{new}"', text ), ), - (BASE / "docs" / "changelog.rst", update_changelog), + (CHANGELOG, update_changelog), (BASE / "docs" / "conf.py", update_docs_config), ] -GITHUB_USER = "beetbox" -GITHUB_REPO = "beets" - def validate_new_version( ctx: click.Context, param: click.Argument, value: Version @@ -84,6 +89,54 @@ def bump_version(new: Version) -> None: f.truncate() +def rst2md(text: str) -> str: + """Use Pandoc to convert text from ReST to Markdown.""" + # Other backslashes with verbatim ranges. + rst = re.sub(r"(?<=[\s(])`([^`]+)`(?=[^_])", r"``\1``", text) + + # Bug numbers. + rst = re.sub(r":bug:`(\d+)`", r":bug: (#\1)", rst) + + # Users. + rst = re.sub(r":user:`(\w+)`", r"@\1", rst) + return ( + subprocess.check_output( + ["/usr/bin/pandoc", "--from=rst", "--to=gfm", "--wrap=none"], + input=rst.encode(), + ) + .decode() + .strip() + ) + + +def changelog_as_markdown() -> str: + """Get the latest changelog entry as hacked up Markdown.""" + with CHANGELOG.open() as f: + contents = f.read() + + m = RST_LATEST_CHANGES.search(contents) + rst = m.group(1) if m else "" + + # Convert with Pandoc. + md = rst2md(rst) + + # Make sections stand out + md = re.sub(r"^(\w.+?):$", r"### \1", md, flags=re.M) + + # Highlight plugin names + md = re.sub( + r"^- `/?plugins/(\w+)`:?", r"- Plugin **`\1`**:", md, flags=re.M + ) + + # Highlights command names. + md = re.sub(r"^- `(\w+)-cmd`:?", r"- Command **`\1`**:", md, flags=re.M) + + # sort list items alphabetically for each of the sections + return MD_CHANGELOG_SECTION_LIST.sub( + lambda m: "\n".join(sorted(m.group().splitlines())), md + ) + + @click.group() def cli(): pass @@ -96,5 +149,11 @@ def bump(version: Version) -> None: bump_version(version) +@cli.command() +def changelog(): + """Get the most recent version's changelog as Markdown.""" + print(changelog_as_markdown()) + + if __name__ == "__main__": cli()