Sort CHANGELOG entries in descending order

This script was used to make the changes:

```python
# This script makes the following changes to the CHANGELOG:
#
# * Sort versions in descending order.
# * Introduce predictable references for linking (`.. _changelog-{version}:`).
# * Bold subsections of version content.
# * Standardize dates ("2 Feb 2022" -> "2022-02-02").
# * Replace non-breaking spaces in the content.
#

import pathlib

months = {
    "Jan": "01",
    "Feb": "02",
    "Mar": "03",
    "Apr": "04",
    "May": "05",
    "Jun": "06",
    "Jul": "07",
    "Aug": "08",
    "Sep": "09",
    "Oct": "10",
    "Nov": "11",
    "Dec": "12",
}

block: list[str] = []
blocks: list[list[str]] = [block]
for line in pathlib.Path("CHANGES.rst").read_text().splitlines():
    if line.lower().startswith("version"):
        # Transform the subheader line and start a new block.
        _, version, date = line.split(maxsplit=2)
        # Standardize dates.
        if " " in date:
            day, month, year = date.split(" ")
            date = f"{year}-{months[month]}-{int(day):02}"
        line = f"v{version} - {date}"
        block = [f".. _changelog-{version}:", "", line, "=" * len(line), ""]
        blocks.append(block)
    elif line.lower().startswith("unreleased"):
        # Special case: start a new block for the "unreleased" subheader.
        block = [line, "=" * len(line), ""]
        blocks.append(block)
    elif line.startswith("========"):
        # Replace the page header's symbols.
        block.append("#" * len(line))
    elif line.startswith("--------"):
        # Ignore subheader underlines, which are managed above.
        pass
    elif line.endswith(":") and not line.startswith("-"):
        block.append(f"**{line.rstrip(':')}**")
    else:
        block.append(line)

# Pop out and reformat the first block, which is the page header.
rebuilt: list[str] = ["\n".join(blocks.pop(0)).strip()]
rebuilt.extend("\n".join(block).strip() for block in reversed(blocks))
new_content = "\n\n\n".join(rebuilt) + "\n"
new_content = new_content.replace("\xA0", " ")  # Replace NBSP
pathlib.Path("CHANGES.rst").write_text(new_content)
```
1 file changed