Skip to content

Commit

Permalink
Update versionadded:: next (and similar directives) on release
Browse files Browse the repository at this point in the history
  • Loading branch information
encukou committed Aug 16, 2024
1 parent 6d6948d commit 7977f16
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 0 deletions.
10 changes: 10 additions & 0 deletions release.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ def committed_at(self) -> datetime.datetime:
int(proc.stdout.decode().strip()), tz=datetime.timezone.utc
)

@property
def doc_version(self) -> str:
"""Text used for notes in docs like 'Added in x.y'"""
# - ignore levels (alpha/beta/rc are preparatiomn for the full release)
# - use just X.Y for patch 0
if self.patch == 0:
return f"{self.major}.{self.minor}"
else:
return f"{self.major}.{self.minor}.{self.patch}"


def error(*msgs: str) -> None:
print("**ERROR**", file=sys.stderr)
Expand Down
9 changes: 9 additions & 0 deletions run_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import sbom
from buildbotapi import BuildBotAPI, Builder
from release import ReleaseShelf, Tag, Task
import update_version_next

API_KEY_REGEXP = re.compile(r"(?P<user>\w+):(?P<key>\w+)")
RELEASE_REGEXP = re.compile(
Expand Down Expand Up @@ -497,6 +498,13 @@ def bump_version(db: ReleaseShelf) -> None:
)


def bump_version_in_docs(db: ReleaseShelf) -> None:
update_version_next.main([db['release'].doc_version, str(db["git_repo"])])
subprocess.check_call(
["git", "commit", "-a", "--amend", "--no-edit"], cwd=db["git_repo"]
)


def create_tag(db: ReleaseShelf) -> None:
with cd(db["git_repo"]):
if not release_mod.make_tag(db["release"], sign_gpg=db["sign_gpg"]):
Expand Down Expand Up @@ -1251,6 +1259,7 @@ def _api_key(api_key: str) -> str:
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(prepare_pydoc_topics, "Preparing pydoc topics"),
Task(bump_version, "Bump version"),
Task(bump_version_in_docs, "Bump version in docs"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(run_autoconf, "Running autoconf"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Expand Down
87 changes: 87 additions & 0 deletions tests/test_update_version_next.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Tests for the update_version_next tool."""

from pathlib import Path
import unittest

from test.support import os_helper

import update_version_next

TO_CHANGE = """
Directives to change
--------------------
Here, all occurences of NEXT (lowercase) should be changed:
.. versionadded:: next
.. versionchanged:: next
.. deprecated:: next
.. deprecated-removed:: next 4.0
whitespace:
.. versionchanged:: next
.. versionchanged :: next
.. versionadded:: next
arguments:
.. versionadded:: next
Foo bar
.. versionadded:: next as ``previousname``
"""

UNCHANGED = """
Unchanged
---------
Here, the word "next" should NOT be changed:
.. versionchanged:: NEXT
..versionchanged:: NEXT
... versionchanged:: next
foo .. versionchanged:: next
.. otherdirective:: next
.. VERSIONCHANGED: next
.. deprecated-removed: 3.0 next
"""

EXPECTED_CHANGED = TO_CHANGE.replace('next', 'VER')


class TestVersionNext(unittest.TestCase):
maxDiff = len(TO_CHANGE + UNCHANGED) * 10

def test_freeze_simple_script(self):
with os_helper.temp_dir() as testdir:
path = Path(testdir)
path.joinpath('source.rst').write_text(TO_CHANGE + UNCHANGED)
path.joinpath('subdir').mkdir()
path.joinpath('subdir/change.rst').write_text(
'.. versionadded:: next')
path.joinpath('subdir/keep.not-rst').write_text(
'.. versionadded:: next')
path.joinpath('subdir/keep.rst').write_text(
'nothing to see here')
args = ['VER', testdir]
update_version_next.main(args)
self.assertEqual(path.joinpath('source.rst').read_text(),
EXPECTED_CHANGED + UNCHANGED)
self.assertEqual(path.joinpath('subdir/change.rst').read_text(),
'.. versionadded:: VER')
self.assertEqual(path.joinpath('subdir/keep.not-rst').read_text(),
'.. versionadded:: next')
self.assertEqual(path.joinpath('subdir/keep.rst').read_text(),
'nothing to see here')
86 changes: 86 additions & 0 deletions update_version_next.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
Replace `.. versionchanged:: next` lines in docs files by the given version.
Run this at release time to replace `next` with the just-released version
in the sources.
No backups are made; add/commit to Git before running the script.
Applies to all the VersionChange directives. For deprecated-removed, only
handle the first argument (deprecation version, not the removal version).
"""

import argparse
import re
import sys
from pathlib import Path

DIRECTIVE_RE = re.compile(
r'''
(?P<before>
\s*\.\.\s+
(version(added|changed|removed)|deprecated(-removed)?)
\s*::\s*
)
next
(?P<after>
.*
)
''',
re.VERBOSE | re.DOTALL,
)

doc_dir = (Path(__file__)
.parent # cpython/Doc/tools
.parent # cpython/Doc
.resolve()
)

parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('version',
help='String to replace "next" with. Usually `x.y`, '
+ 'but can be anything.')
parser.add_argument('directory', type=Path, nargs='?',
help=f'Directory to process. Default: {doc_dir}',
default=doc_dir)
parser.add_argument('--verbose', '-v', action='count', default=0,
help='Increase verbosity. Can be repeated (`-vv`).')


def main(argv):
args = parser.parse_args(argv)
version = args.version
if args.verbose:
print(
f'Updating "next" versions in {args.directory} to {version!r}',
file=sys.stderr)
for path in Path(args.directory).glob('**/*.rst'):
num_changed_lines = 0
lines = []
with open(path, encoding='utf-8') as file:
for lineno, line in enumerate(file, start=1):
try:
if match := DIRECTIVE_RE.fullmatch(line):
line = match['before'] + version + match['after']
num_changed_lines += 1
lines.append(line)
except Exception as exc:
exc.add_note(f'processing line {path}:{lineno}')
raise
if num_changed_lines:
if args.verbose:
print(f'Updating file {path} ({num_changed_lines} changes)',
file=sys.stderr)
with open(path, 'w', encoding='utf-8') as file:
file.writelines(lines)
else:
if args.verbose > 1:
print(f'Unchanged file {path}', file=sys.stderr)


if __name__ == '__main__':
main(sys.argv[1:])

0 comments on commit 7977f16

Please sign in to comment.