From f0005e70efb8c35129938dbae8231ebf47b744f9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 2 Jul 2024 16:52:22 +0200 Subject: [PATCH 01/16] Make `versionchanged:: next`` expand to current (unreleased) version - versionadded, versionchanged, and similar directives expand "next" to e.g. "3.14.0a0 (unreleased)". - A tool is provided for release managers to replace all such occurences of "next" with the given string. --- Doc/tools/extensions/pyspecific.py | 31 +++++- Doc/tools/version_next.py | 76 ++++++++++++++ Lib/test/test_tools/test_docs_version_next.py | 99 +++++++++++++++++++ 3 files changed, 201 insertions(+), 5 deletions(-) create mode 100755 Doc/tools/version_next.py create mode 100644 Lib/test/test_tools/test_docs_version_next.py diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index 8b592d4b4adcea..159e3e7b64ee0e 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -28,6 +28,7 @@ from sphinx.util.docutils import SphinxDirective from sphinx.writers.text import TextWriter, TextTranslator from sphinx.util.display import status_iterator +import sphinx.directives.other ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s' @@ -393,7 +394,23 @@ def run(self): return PyMethod.run(self) -# Support for documenting version of removal in deprecations +# Support for documenting version of changes, additions, deprecations + +def expand_version_arg(argument, env): + """Expand "next" to the current version""" + if argument == 'next': + return sphinx_gettext('{} (unreleased)').format(env.config.release) + return argument + + +class PyVersionChange(sphinx.directives.other.VersionChange): + def run(self): + env = self.state.document.settings.env + self.arguments = ( + expand_version_arg(self.arguments[0], env), + *self.arguments[1:]) + return super().run() + class DeprecatedRemoved(Directive): has_content = True @@ -409,9 +426,10 @@ def run(self): node = addnodes.versionmodified() node.document = self.state.document node['type'] = 'deprecated-removed' - version = (self.arguments[0], self.arguments[1]) - node['version'] = version env = self.state.document.settings.env + deprecated = expand_version_arg(self.arguments[0], env) + version = (deprecated, self.arguments[1]) + node['version'] = version current_version = tuple(int(e) for e in env.config.version.split('.')) removed_version = tuple(int(e) for e in self.arguments[1].split('.')) if current_version < removed_version: @@ -419,7 +437,7 @@ def run(self): else: label = self._removed_label - text = label.format(deprecated=self.arguments[0], removed=self.arguments[1]) + text = label.format(deprecated=deprecated, removed=self.arguments[1]) if len(self.arguments) == 3: inodes, messages = self.state.inline_text(self.arguments[2], self.lineno+1) @@ -448,7 +466,6 @@ def run(self): env.get_domain('changeset').note_changeset(node) return [node] + messages - # Support for including Misc/NEWS issue_re = re.compile('(?:[Ii]ssue #|bpo-)([0-9]+)', re.I) @@ -698,6 +715,10 @@ def setup(app): app.add_directive('availability', Availability) app.add_directive('audit-event', AuditEvent) app.add_directive('audit-event-table', AuditEventListDirective) + app.add_directive('versionadded', PyVersionChange, override=True) + app.add_directive('versionchanged', PyVersionChange, override=True) + app.add_directive('versionremoved', PyVersionChange, override=True) + app.add_directive('deprecated', PyVersionChange, override=True) app.add_directive('deprecated-removed', DeprecatedRemoved) app.add_builder(PydocTopicsBuilder) app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py new file mode 100755 index 00000000000000..38b7c2cd6b7196 --- /dev/null +++ b/Doc/tools/version_next.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Replace `.. versionchanged:: next` lines in docs files by the given version. + +Applies to all the VersionChange directives. For deprecated-removed, only +handle the first argument (deprecation version, not the removal version). + +""" + +import re +import sys +import argparse +from pathlib import Path + +DIRECTIVE_RE = re.compile( + r''' + (?P + \s*\.\.\s+ + (version(added|changed|removed)|deprecated(-removed)?) + \s*::\s* + ) + next + (?P + .* + ) + ''', + re.VERBOSE | re.DOTALL, +) + +basedir = (Path(__file__) + .parent # cpython/Tools/doc + .parent # cpython/Tools + .parent # cpython + .resolve() + ) +docdir = basedir / 'Doc' + +parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument('version', + help='String to replace "next" with.') +parser.add_argument('directory', type=Path, nargs='?', + help=f'Directory to process. Default: {docdir}', + default=docdir) +parser.add_argument('--verbose', '-v', action='count', default=0) + +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) as file: + for lineno, line in enumerate(file, start=1): + if match := DIRECTIVE_RE.fullmatch(line): + line = match['before'] + version + match['after'] + num_changed_lines += 1 + lines.append(line) + if num_changed_lines: + if args.verbose: + print(f'Updating file {path} ({num_changed_lines} changes)', + file=sys.stderr) + with open(path, 'w') 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:]) diff --git a/Lib/test/test_tools/test_docs_version_next.py b/Lib/test/test_tools/test_docs_version_next.py new file mode 100644 index 00000000000000..e8b208a5d812fe --- /dev/null +++ b/Lib/test/test_tools/test_docs_version_next.py @@ -0,0 +1,99 @@ +"""Sanity-check tests for the "docs/replace_versoin_next" tool.""" + +from pathlib import Path +import unittest + +from test.test_tools import basepath +from test.support.import_helper import DirsOnSysPath, import_module +from test.support import os_helper +from test import support + +print(basepath) +tooldir = Path(basepath) / 'Doc' / 'tools' +print(tooldir) + +with DirsOnSysPath(str(tooldir)): + import sys + print(sys.path) + version_next = import_module('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] + if support.verbose: + args.append('-vv') + 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') From 0c86239ab786b706ff2ada38709b2fc01610027e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 2 Jul 2024 16:59:16 +0200 Subject: [PATCH 02/16] Use in existing docs --- Doc/c-api/long.rst | 2 +- Doc/c-api/unicode.rst | 2 +- Doc/library/ast.rst | 2 +- Doc/library/ctypes.rst | 2 +- Doc/library/dis.rst | 4 ++-- Doc/library/pathlib.rst | 4 ++-- Doc/library/symtable.rst | 8 ++++---- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 42162914c0aec8..e4684e8c746932 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -511,7 +511,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. On failure, return -1 with an exception set. This function always succeeds if *obj* is a :c:type:`PyLongObject` or its subtype. - .. versionadded:: 3.14 + .. versionadded:: next .. c:function:: int PyUnstable_Long_IsCompact(const PyLongObject* op) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 246cf47df62e78..2425b6ceb70329 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -1509,7 +1509,7 @@ PyUnicodeWriter The :c:type:`PyUnicodeWriter` API can be used to create a Python :class:`str` object. -.. versionadded:: 3.14 +.. versionadded:: next .. c:type:: PyUnicodeWriter diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index f7e8afa7000392..48a7d30604bab5 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2487,7 +2487,7 @@ effects on the compilation of a program: differ in whitespace or similar details. Attributes include line numbers and column offsets. - .. versionadded:: 3.14 + .. versionadded:: next .. _ast-cli: diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index a56e0eef5d11b1..537564beb50c3b 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -2299,7 +2299,7 @@ These are the fundamental ctypes data types: Represents the C :c:expr:`double complex` datatype, if available. The constructor accepts an optional :class:`complex` initializer. - .. versionadded:: 3.14 + .. versionadded:: next .. class:: c_int diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index e932b865a825a0..5905be7754be63 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -926,7 +926,7 @@ iterations of the loop. list of constants supported by this instruction. Used by the :keyword:`assert` statement to load :exc:`AssertionError`. - .. versionadded:: 3.14 + .. versionadded:: next .. opcode:: LOAD_BUILD_CLASS @@ -1798,7 +1798,7 @@ iterations of the loop. If ``type(STACK[-1]).__xxx__`` is not a method, leave ``STACK[-1].__xxx__; NULL`` on the stack. - .. versionadded:: 3.14 + .. versionadded:: next **Pseudo-instructions** diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 0918bbb47e9ea6..cfd821eb600484 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1559,7 +1559,7 @@ Copying, renaming and deleting raises :exc:`OSError` when a symlink to a directory is encountered and *follow_symlinks* is false. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: Path.copytree(target, *, follow_symlinks=True, dirs_exist_ok=False, \ @@ -1586,7 +1586,7 @@ Copying, renaming and deleting nothing, in which case the copying operation continues. If *on_error* isn't given, exceptions are propagated to the caller. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: Path.rename(target) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index aa5f8d95925ada..bc6836224df9cc 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -239,7 +239,7 @@ Examining Symbol Tables Return ``True`` if the symbol is a type parameter. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: is_global() @@ -286,7 +286,7 @@ Examining Symbol Tables be free from the perspective of ``C.method``, thereby allowing the latter to return *1* at runtime and not *2*. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: is_assigned() @@ -296,13 +296,13 @@ Examining Symbol Tables Return ``True`` if the symbol is a comprehension iteration variable. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: is_comp_cell() Return ``True`` if the symbol is a cell in an inlined comprehension. - .. versionadded:: 3.14 + .. versionadded:: next .. method:: is_namespace() From e270ddc4ce2914e4d077c5c2b490423a77aef7f9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 11:54:42 +0200 Subject: [PATCH 03/16] Apply extension suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/tools/extensions/pyspecific.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index 159e3e7b64ee0e..a807371aec817c 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -396,19 +396,17 @@ def run(self): # Support for documenting version of changes, additions, deprecations -def expand_version_arg(argument, env): +def expand_version_arg(argument, release): """Expand "next" to the current version""" if argument == 'next': - return sphinx_gettext('{} (unreleased)').format(env.config.release) + return sphinx_gettext('{} (unreleased)').format(release) return argument -class PyVersionChange(sphinx.directives.other.VersionChange): +class PyVersionChange(sphinx.domains.changeset.VersionChange): def run(self): - env = self.state.document.settings.env - self.arguments = ( - expand_version_arg(self.arguments[0], env), - *self.arguments[1:]) + # Replace the 'next' special token with the current development version + self.arguments[0] = expand_version_arg(self.arguments[0], self.config.release) return super().run() @@ -426,8 +424,8 @@ def run(self): node = addnodes.versionmodified() node.document = self.state.document node['type'] = 'deprecated-removed' - env = self.state.document.settings.env - deprecated = expand_version_arg(self.arguments[0], env) + release = self.state.document.settings.env.config.release + deprecated = expand_version_arg(self.arguments[0], release) version = (deprecated, self.arguments[1]) node['version'] = version current_version = tuple(int(e) for e in env.config.version.split('.')) From 0022e1ae0478bfcf27a1ff7f0d04aaf63a664bf4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 12:17:12 +0200 Subject: [PATCH 04/16] Update Doc/tools/extensions/pyspecific.py Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/tools/extensions/pyspecific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index a807371aec817c..e59d4d1ee3c3b3 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -28,7 +28,7 @@ from sphinx.util.docutils import SphinxDirective from sphinx.writers.text import TextWriter, TextTranslator from sphinx.util.display import status_iterator -import sphinx.directives.other +import sphinx.domains.changeset ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s' From 342838d26a612be758ace246e0f3d4a5de73e598 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 11:40:06 +0200 Subject: [PATCH 05/16] Fix location of the the Doc dir Co-Authored-By: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/tools/version_next.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index 38b7c2cd6b7196..4eb2294187457e 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -27,13 +27,11 @@ re.VERBOSE | re.DOTALL, ) -basedir = (Path(__file__) - .parent # cpython/Tools/doc - .parent # cpython/Tools - .parent # cpython - .resolve() - ) -docdir = basedir / 'Doc' +docdir = (Path(__file__) + .parent # cpython/Doc/tools + .parent # cpython/Doc + .resolve() + ) parser = argparse.ArgumentParser( description=__doc__, From 1d9a61879d7d54eb9448c097903451a31cc5eab5 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 11:48:01 +0200 Subject: [PATCH 06/16] Specify encoding --- Doc/tools/version_next.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index 4eb2294187457e..dcb7f01d9aa119 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -53,7 +53,7 @@ def main(argv): for path in Path(args.directory).glob('**/*.rst'): num_changed_lines = 0 lines = [] - with open(path) as file: + with open(path, encoding='utf-8') as file: for lineno, line in enumerate(file, start=1): if match := DIRECTIVE_RE.fullmatch(line): line = match['before'] + version + match['after'] @@ -63,7 +63,7 @@ def main(argv): if args.verbose: print(f'Updating file {path} ({num_changed_lines} changes)', file=sys.stderr) - with open(path, 'w') as file: + with open(path, 'w', encoding='utf-8') as file: file.writelines(lines) else: if args.verbose > 1: From 311bceb73dd09398e7320cd279359e4cdf5d6f46 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 11:52:17 +0200 Subject: [PATCH 07/16] Improve the docstring/usage --- Doc/tools/version_next.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index dcb7f01d9aa119..c64fce78ba9248 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -2,6 +2,11 @@ """ 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). From 72a3162fb2692b0377425f553af2a037300faa62 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 11:54:03 +0200 Subject: [PATCH 08/16] Re-add stray removed line --- Doc/tools/extensions/pyspecific.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index e59d4d1ee3c3b3..f36590393e4e29 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -464,6 +464,7 @@ def run(self): env.get_domain('changeset').note_changeset(node) return [node] + messages + # Support for including Misc/NEWS issue_re = re.compile('(?:[Ii]ssue #|bpo-)([0-9]+)', re.I) From 91bd51186f588f948d9d803ab55c923e900a1044 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 12:18:39 +0200 Subject: [PATCH 09/16] Split long line --- Doc/tools/extensions/pyspecific.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index f36590393e4e29..8b7b4920abddac 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -406,7 +406,8 @@ def expand_version_arg(argument, release): class PyVersionChange(sphinx.domains.changeset.VersionChange): def run(self): # Replace the 'next' special token with the current development version - self.arguments[0] = expand_version_arg(self.arguments[0], self.config.release) + self.arguments[0] = expand_version_arg(self.arguments[0], + self.config.release) return super().run() From 46ae8143a786b841901ef94768b7f73cb5617234 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 12:19:43 +0200 Subject: [PATCH 10/16] Set `env` before using it --- Doc/tools/extensions/pyspecific.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index 8b7b4920abddac..ac9341f9e8f618 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -425,7 +425,8 @@ def run(self): node = addnodes.versionmodified() node.document = self.state.document node['type'] = 'deprecated-removed' - release = self.state.document.settings.env.config.release + env = self.state.document.settings.env + release = env.config.release deprecated = expand_version_arg(self.arguments[0], release) version = (deprecated, self.arguments[1]) node['version'] = version @@ -461,7 +462,6 @@ def run(self): classes=['versionmodified']), translatable=False) node.append(para) - env = self.state.document.settings.env env.get_domain('changeset').note_changeset(node) return [node] + messages From 9c4502c621e5a30563c7253672a33a5e74f89e17 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 12:22:51 +0200 Subject: [PATCH 11/16] Add a blurb --- .../2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst diff --git a/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst b/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst new file mode 100644 index 00000000000000..60f75ae0c21326 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst @@ -0,0 +1,2 @@ +Writers of CPython's documentation can now use ``next`` as the version for +the ``versionchanged``, ``versionadded``, ``deprecated`` directives. From 993dd5cfc8cb91c4f72a018bc4f8c03d948a6675 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 19 Jul 2024 13:23:31 +0200 Subject: [PATCH 12/16] Consolidate VersionChange imports Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/tools/extensions/pyspecific.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index 35faf5d9985878..7a62da0db2f348 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -29,7 +29,6 @@ from sphinx.util.docutils import SphinxDirective from sphinx.writers.text import TextWriter, TextTranslator from sphinx.util.display import status_iterator -import sphinx.domains.changeset ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s' @@ -403,7 +402,7 @@ def expand_version_arg(argument, release): return argument -class PyVersionChange(sphinx.domains.changeset.VersionChange): +class PyVersionChange(VersionChange): def run(self): # Replace the 'next' special token with the current development version self.arguments[0] = expand_version_arg(self.arguments[0], From 5f2b6127f7b7b75cd66e0dbb9274ddf6eb8af097 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 11:03:28 +0200 Subject: [PATCH 13/16] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/tools/version_next.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index c64fce78ba9248..e7c56f660dac27 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -32,11 +32,11 @@ re.VERBOSE | re.DOTALL, ) -docdir = (Path(__file__) - .parent # cpython/Doc/tools - .parent # cpython/Doc - .resolve() - ) +doc_dir = (Path(__file__) + .parent # cpython/Doc/tools + .parent # cpython/Doc + .resolve() + ) parser = argparse.ArgumentParser( description=__doc__, @@ -48,6 +48,7 @@ default=docdir) parser.add_argument('--verbose', '-v', action='count', default=0) + def main(argv): args = parser.parse_args(argv) version = args.version @@ -67,7 +68,7 @@ def main(argv): if num_changed_lines: if args.verbose: print(f'Updating file {path} ({num_changed_lines} changes)', - file=sys.stderr) + file=sys.stderr) with open(path, 'w', encoding='utf-8') as file: file.writelines(lines) else: From 8b8a2f92512b3c598bbeb68a5a8693e8e31fc6a5 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 11:09:20 +0200 Subject: [PATCH 14/16] Expand help --- Doc/tools/version_next.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index e7c56f660dac27..04fb5aaeb92272 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -42,11 +42,13 @@ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('version', - help='String to replace "next" with.') + 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: {docdir}', - default=docdir) -parser.add_argument('--verbose', '-v', action='count', default=0) + 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): From d163647ea500874a1d79831c73aee29b2bbe620a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 22 Jul 2024 11:11:16 +0200 Subject: [PATCH 15/16] Add lineno to exceptions Thanks to Ruff for reminding me --- Doc/tools/version_next.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py index 04fb5aaeb92272..351cb16a315ae1 100755 --- a/Doc/tools/version_next.py +++ b/Doc/tools/version_next.py @@ -63,10 +63,14 @@ def main(argv): lines = [] with open(path, encoding='utf-8') as file: for lineno, line in enumerate(file, start=1): - if match := DIRECTIVE_RE.fullmatch(line): - line = match['before'] + version + match['after'] - num_changed_lines += 1 - lines.append(line) + 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)', From 757cf6b04d02509a9f0e7e650493a292bc2c8cd5 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 24 Sep 2024 11:11:12 -0700 Subject: [PATCH 16/16] Remove the tool; that now lives in the release-tools repo --- Doc/tools/version_next.py | 86 ---------------- Lib/test/test_tools/test_docs_version_next.py | 99 ------------------- 2 files changed, 185 deletions(-) delete mode 100755 Doc/tools/version_next.py delete mode 100644 Lib/test/test_tools/test_docs_version_next.py diff --git a/Doc/tools/version_next.py b/Doc/tools/version_next.py deleted file mode 100755 index 351cb16a315ae1..00000000000000 --- a/Doc/tools/version_next.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/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 re -import sys -import argparse -from pathlib import Path - -DIRECTIVE_RE = re.compile( - r''' - (?P - \s*\.\.\s+ - (version(added|changed|removed)|deprecated(-removed)?) - \s*::\s* - ) - next - (?P - .* - ) - ''', - 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:]) diff --git a/Lib/test/test_tools/test_docs_version_next.py b/Lib/test/test_tools/test_docs_version_next.py deleted file mode 100644 index e8b208a5d812fe..00000000000000 --- a/Lib/test/test_tools/test_docs_version_next.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Sanity-check tests for the "docs/replace_versoin_next" tool.""" - -from pathlib import Path -import unittest - -from test.test_tools import basepath -from test.support.import_helper import DirsOnSysPath, import_module -from test.support import os_helper -from test import support - -print(basepath) -tooldir = Path(basepath) / 'Doc' / 'tools' -print(tooldir) - -with DirsOnSysPath(str(tooldir)): - import sys - print(sys.path) - version_next = import_module('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] - if support.verbose: - args.append('-vv') - 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') pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy