summaryrefslogtreecommitdiff
path: root/script
diff options
context:
space:
mode:
authorDudeNr33 <3929834+DudeNr33@users.noreply.github.com>2022-07-17 17:31:41 +0200
committerAndreas Finkler <3929834+DudeNr33@users.noreply.github.com>2022-07-17 18:13:46 +0200
commitbd130ecd7ec5220c5371e2e056507ca8b1f02219 (patch)
treedd654ee2ef0429ad9f130e1ad12acebb33023d87 /script
parent09a0ce221db656a59f6261ccb3bb1bc22645769a (diff)
downloadpylint-git-bd130ecd7ec5220c5371e2e056507ca8b1f02219.tar.gz
Edit `bump_changelog.py` to use `towncrier` and update its configuration
Diffstat (limited to 'script')
-rw-r--r--script/bump_changelog.py245
1 files changed, 100 insertions, 145 deletions
diff --git a/script/bump_changelog.py b/script/bump_changelog.py
index f27195bdc..420ca1d22 100644
--- a/script/bump_changelog.py
+++ b/script/bump_changelog.py
@@ -2,167 +2,122 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
-# ORIGINAL here: https://github.com/PyCQA/astroid/blob/main/script/bump_changelog.py
-# DO NOT MODIFY DIRECTLY
-
-"""This script permits to upgrade the changelog in astroid or pylint when releasing a version."""
-# pylint: disable=logging-fstring-interpolation
-
+"""This script updates towncrier.toml and creates a new newsfile and intermediate
+folders if necessary.
+"""
from __future__ import annotations
import argparse
-import enum
-import logging
-from datetime import datetime
+import re
from pathlib import Path
+from subprocess import check_call
+
+NEWSFILE_PATTERN = re.compile(r"doc/whatsnew/\d/\d.\d+/index\.rst")
+NEWSFILE_PATH = "doc/whatsnew/{major}/{major}.{minor}/index.rst"
+TOWNCRIER_CONFIG_FILE = Path("towncrier.toml")
+TOWNCRIER_VERSION_PATTERN = re.compile(r"version = \"(\d+\.\d+\.\d+)\"")
+
+NEWSFILE_CONTENT_TEMPLATE = """
+***************************
+ What's New in Pylint {major}.{minor}
+***************************
-# TODO: 2.15.0 Modify the way we handle the patch version
-# release notes
-DEFAULT_CHANGELOG_PATH = Path("doc/whatsnew/2/2.14/full.rst")
+.. toctree::
+ :maxdepth: 2
-RELEASE_DATE_TEXT = "Release date: TBA"
-WHATS_NEW_TEXT = "What's New in Pylint"
-TODAY = datetime.now()
-FULL_WHATS_NEW_TEXT = WHATS_NEW_TEXT + " {version}?"
-NEW_RELEASE_DATE_MESSAGE = f"Release date: {TODAY.strftime('%Y-%m-%d')}"
+:Release:{major}.{minor}
+:Date: TBA
+
+Summary -- Release highlights
+=============================
+
+
+.. towncrier release notes start
+"""
def main() -> None:
- parser = argparse.ArgumentParser(__doc__)
- parser.add_argument("version", help="The version we want to release")
+ parser = argparse.ArgumentParser()
+ parser.add_argument("version", help="The new version to set")
parser.add_argument(
- "-v", "--verbose", action="store_true", default=False, help="Logging or not"
+ "--dry-run",
+ action="store_true",
+ help="Just show what would be done, don't write anything",
)
args = parser.parse_args()
- if args.verbose:
- logging.basicConfig(level=logging.DEBUG)
- logging.debug(f"Launching bump_changelog with args: {args}")
+
if "dev" in args.version:
- return
- with open(DEFAULT_CHANGELOG_PATH, encoding="utf-8") as f:
- content = f.read()
- content = transform_content(content, args.version)
- with open(DEFAULT_CHANGELOG_PATH, "w", encoding="utf-8") as f:
- f.write(content)
-
-
-class VersionType(enum.Enum):
- MAJOR = 0
- MINOR = 1
- PATCH = 2
-
-
-def get_next_version(version: str, version_type: VersionType) -> str:
- new_version = version.split(".")
- part_to_increase = new_version[version_type.value]
- if "-" in part_to_increase:
- part_to_increase = part_to_increase.split("-")[0]
- for i in range(version_type.value, 3):
- new_version[i] = "0"
- new_version[version_type.value] = str(int(part_to_increase) + 1)
- return ".".join(new_version)
-
-
-def get_next_versions(version: str, version_type: VersionType) -> list[str]:
-
- if version_type == VersionType.PATCH:
- # "2.6.1" => ["2.6.2"]
- return [get_next_version(version, VersionType.PATCH)]
- if version_type == VersionType.MINOR:
- # "2.6.0" => ["2.7.0", "2.6.1"]
- assert version.endswith(".0"), f"{version} does not look like a minor version"
- else:
- # "3.0.0" => ["3.1.0", "3.0.1"]
- assert version.endswith(".0.0"), f"{version} does not look like a major version"
- next_minor_version = get_next_version(version, VersionType.MINOR)
- next_patch_version = get_next_version(version, VersionType.PATCH)
- logging.debug(f"Getting the new version for {version} - {version_type.name}")
- return [next_minor_version, next_patch_version]
-
-
-def get_version_type(version: str) -> VersionType:
- if version.endswith(".0.0"):
- version_type = VersionType.MAJOR
- elif version.endswith(".0"):
- version_type = VersionType.MINOR
- else:
- version_type = VersionType.PATCH
- return version_type
-
-
-def get_whats_new(
- version: str, add_date: bool = False, change_date: bool = False
-) -> str:
- whats_new_text = FULL_WHATS_NEW_TEXT.format(version=version)
- result = [whats_new_text, "-" * len(whats_new_text)]
- if add_date and change_date:
- result += [NEW_RELEASE_DATE_MESSAGE]
- elif add_date:
- result += [RELEASE_DATE_TEXT]
- elif change_date:
- raise ValueError("Can't use change_date=True with add_date=False")
- logging.debug(
- f"version='{version}', add_date='{add_date}', change_date='{change_date}': {result}"
- )
- return "\n".join(result)
-
-
-def get_all_whats_new(version: str, version_type: VersionType) -> str:
- result = ""
- for version_ in get_next_versions(version, version_type=version_type):
- result += get_whats_new(version_, add_date=True) + "\n" * 4
- return result
-
-
-def transform_content(content: str, version: str) -> str:
- version_type = get_version_type(version)
- next_version = get_next_version(version, version_type)
- old_date = get_whats_new(version, add_date=True)
- new_date = get_whats_new(version, add_date=True, change_date=True)
- next_version_with_date = get_all_whats_new(version, version_type)
- do_checks(content, next_version, version, version_type)
- index = content.find(old_date)
- logging.debug(f"Replacing\n'{old_date}'\nby\n'{new_date}'\n")
- content = content.replace(old_date, new_date)
- end_content = content[index:]
- content = content[:index]
- logging.debug(f"Adding:\n'{next_version_with_date}'\n")
- content += next_version_with_date + end_content
- return content
-
-
-def do_checks(content, next_version, version, version_type):
- err = "in the changelog, fix that first!"
- NEW_VERSION_ERROR_MSG = (
- # pylint: disable-next=consider-using-f-string
- "The text for this version '{version}' did not exists %s"
- % err
+ print("'-devXY' will be cut from version in towncrier.toml")
+ match = re.match(
+ r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-\w+\d*)*", args.version
)
- NEXT_VERSION_ERROR_MSG = (
- # pylint: disable-next=consider-using-f-string
- "The text for the next version '{version}' already exists %s"
- % err
- )
- wn_next_version = get_whats_new(next_version)
- wn_this_version = get_whats_new(version)
- # There is only one field where the release date is TBA
- if version_type in [VersionType.MAJOR, VersionType.MINOR]:
- assert (
- content.count(RELEASE_DATE_TEXT) <= 1
- ), f"There should be only one release date 'TBA' ({version}) {err}"
- else:
- next_minor_version = get_next_version(version, VersionType.MINOR)
- assert (
- content.count(RELEASE_DATE_TEXT) <= 2
- ), f"There should be only two release dates 'TBA' ({version} and {next_minor_version}) {err}"
- # There is already a release note for the version we want to release
- assert content.count(wn_this_version) == 1, NEW_VERSION_ERROR_MSG.format(
- version=version
+ if not match:
+ print(
+ "Fatal error - new version did not match the "
+ "expected format (major.minor.patch[.*]). Abort!"
+ )
+ return
+ major, minor, patch, suffix = match.groups()
+ new_version = f"{major}.{minor}.{patch}"
+
+ new_newsfile = NEWSFILE_PATH.format(major=major, minor=minor)
+ create_new_newsfile_if_necessary(new_newsfile, major, minor, args.dry_run)
+ patch_towncrier_toml(new_newsfile, new_version, args.dry_run)
+ build_changelog(suffix, args.dry_run)
+
+
+def create_new_newsfile_if_necessary(
+ new_newsfile: str, major: str, minor: str, dry_run: bool
+) -> None:
+ new_newsfile_path = Path(new_newsfile)
+ if new_newsfile_path.exists():
+ return
+
+ # create new file and add boiler plate content
+ if dry_run:
+ print(
+ f"Dry run enabled - would create file {new_newsfile} "
+ "and intermediate folders"
+ )
+ return
+
+ print("Creating new newsfile:", new_newsfile)
+ new_newsfile_path.parent.mkdir(parents=True, exist_ok=True)
+ new_newsfile_path.touch()
+ new_newsfile_path.write_text(
+ NEWSFILE_CONTENT_TEMPLATE.format(major=major, minor=minor),
+ encoding="utf8",
)
- # There is no release notes for the next version
- assert content.count(wn_next_version) == 0, NEXT_VERSION_ERROR_MSG.format(
- version=next_version
+
+ # tbump does not add and commit new files, so we add it ourselves
+ print("Adding new newsfile to git")
+ check_call(["git", "add", new_newsfile])
+
+
+def patch_towncrier_toml(new_newsfile: str, version: str, dry_run: bool) -> None:
+ file_content = TOWNCRIER_CONFIG_FILE.read_text(encoding="utf-8")
+ patched_newsfile_path = NEWSFILE_PATTERN.sub(new_newsfile, file_content)
+ new_file_content = TOWNCRIER_VERSION_PATTERN.sub(
+ f'version = "{version}"', patched_newsfile_path
)
+ if dry_run:
+ print("Dry run enabled - this is what I would write:\n")
+ print(new_file_content)
+ return
+ TOWNCRIER_CONFIG_FILE.write_text(new_file_content, encoding="utf-8")
+
+
+def build_changelog(suffix: str | None, dry_run: bool) -> None:
+ if suffix:
+ print("Not a release version, skipping changelog generation")
+ return
+
+ if dry_run:
+ print("Dry run enabled - not building changelog")
+ return
+
+ print("Building changelog")
+ check_call(["towncrier", "build", "--yes"])
if __name__ == "__main__":