From f10c35df04bd6667c35a89fa5ee00a007a04e677 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 13 Aug 2025 02:54:58 +0100 Subject: [PATCH] Move remaining utility functions --- src/blurb/_add.py | 3 +- src/blurb/_blurb_file.py | 14 +++-- src/blurb/_merge.py | 2 +- src/blurb/_release.py | 9 ++-- src/blurb/_utils/globs.py | 27 ++++++++++ src/blurb/_utils/text.py | 7 +++ src/blurb/blurb.py | 43 --------------- tests/{test_blurb_add.py => test_add.py} | 0 tests/test_blurb.py | 68 ------------------------ tests/test_blurb_file.py | 8 ++- tests/test_utils_globs.py | 60 +++++++++++++++++++++ 11 files changed, 118 insertions(+), 123 deletions(-) create mode 100644 src/blurb/_utils/globs.py rename tests/{test_blurb_add.py => test_add.py} (100%) delete mode 100644 tests/test_blurb.py create mode 100644 tests/test_utils_globs.py diff --git a/src/blurb/_add.py b/src/blurb/_add.py index f1b2cd6..88fc97d 100644 --- a/src/blurb/_add.py +++ b/src/blurb/_add.py @@ -8,11 +8,10 @@ import sys import tempfile -from blurb._blurb_file import Blurbs +from blurb._blurb_file import BlurbError, Blurbs from blurb._cli import subcommand,error,prompt from blurb._git import flush_git_add_files, git_add_files from blurb._template import sections, template -from blurb.blurb import BlurbError TYPE_CHECKING = False if TYPE_CHECKING: diff --git a/src/blurb/_blurb_file.py b/src/blurb/_blurb_file.py index d0c0df4..4986119 100644 --- a/src/blurb/_blurb_file.py +++ b/src/blurb/_blurb_file.py @@ -81,15 +81,19 @@ import os import re +import time from blurb._template import sanitize_section, sections, unsanitize_section -from blurb._utils.text import textwrap_body -from blurb.blurb import BlurbError, sortable_datetime, nonceify +from blurb._utils.text import generate_nonce, textwrap_body root = None # Set by chdir_to_repo_root() lowest_possible_gh_issue_number = 32426 +class BlurbError(RuntimeError): + pass + + class Blurbs(list): def parse(self, text: str, *, metadata: dict[str, str] | None = None, filename: str = 'input') -> None: @@ -258,7 +262,7 @@ def ensure_metadata(self) -> None: ('gh-issue', '0'), ('bpo', '0'), ('date', sortable_datetime()), - ('nonce', nonceify(body)), + ('nonce', generate_nonce(body)), ): if name not in metadata: metadata[name] = default @@ -287,3 +291,7 @@ def save_next(self) -> str: filename = blurb._extract_next_filename() blurb.save(filename) return filename + + +def sortable_datetime() -> str: + return time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) diff --git a/src/blurb/_merge.py b/src/blurb/_merge.py index aa9aaee..244d648 100644 --- a/src/blurb/_merge.py +++ b/src/blurb/_merge.py @@ -4,9 +4,9 @@ from blurb._blurb_file import Blurbs from blurb._cli import require_ok, subcommand +from blurb._utils.globs import glob_blurbs from blurb._utils.text import textwrap_body from blurb._versions import glob_versions, printable_version -from blurb.blurb import glob_blurbs original_dir: str = os.getcwd() diff --git a/src/blurb/_release.py b/src/blurb/_release.py index 60e76c8..e0285d6 100644 --- a/src/blurb/_release.py +++ b/src/blurb/_release.py @@ -3,12 +3,13 @@ import os import time -import blurb.blurb +import blurb._blurb_file from blurb._blurb_file import Blurbs from blurb._cli import error, subcommand from blurb._git import (flush_git_add_files, flush_git_rm_files, git_rm_files, git_add_files) -from blurb.blurb import glob_blurbs, nonceify +from blurb._utils.globs import glob_blurbs +from blurb._utils.text import generate_nonce @subcommand @@ -20,7 +21,7 @@ def release(version: str) -> None: if version == '.': # harvest version number from dirname of repo # I remind you, we're in the Misc subdir right now - version = os.path.basename(blurb.blurb.root) + version = os.path.basename(blurb._blurb_file.root) existing_filenames = glob_blurbs(version) if existing_filenames: @@ -34,7 +35,7 @@ def release(version: str) -> None: if not filenames: print(f'No blurbs found. Setting {version} as having no changes.') body = f'There were no new changes in version {version}.\n' - metadata = {'no changes': 'True', 'gh-issue': '0', 'section': 'Library', 'date': date, 'nonce': nonceify(body)} + metadata = {'no changes': 'True', 'gh-issue': '0', 'section': 'Library', 'date': date, 'nonce': generate_nonce(body)} blurbs.append((metadata, body)) else: count = len(filenames) diff --git a/src/blurb/_utils/globs.py b/src/blurb/_utils/globs.py new file mode 100644 index 0000000..c8e5519 --- /dev/null +++ b/src/blurb/_utils/globs.py @@ -0,0 +1,27 @@ +import glob +import os + +from blurb._template import ( + next_filename_unsanitize_sections, sanitize_section, + sanitize_section_legacy, sections, +) + + +def glob_blurbs(version: str) -> list[str]: + filenames = [] + base = os.path.join('Misc', 'NEWS.d', version) + if version != 'next': + wildcard = f'{base}.rst' + filenames.extend(glob.glob(wildcard)) + else: + sanitized_sections = set(map(sanitize_section, sections)) + sanitized_sections |= set(map(sanitize_section_legacy, sections)) + for section in sanitized_sections: + wildcard = os.path.join(base, section, '*.rst') + entries = glob.glob(wildcard) + deletables = [x for x in entries if x.endswith('/README.rst')] + for filename in deletables: + entries.remove(filename) + filenames.extend(entries) + filenames.sort(reverse=True, key=next_filename_unsanitize_sections) + return filenames diff --git a/src/blurb/_utils/text.py b/src/blurb/_utils/text.py index b5b7d02..c2391e9 100644 --- a/src/blurb/_utils/text.py +++ b/src/blurb/_utils/text.py @@ -1,5 +1,7 @@ from __future__ import annotations +import base64 +import hashlib import itertools import textwrap @@ -97,3 +99,8 @@ def textwrap_body(body: str | Iterable[str], *, subsequent_indent: str = '') -> if not text.endswith('\n'): text += '\n' return text + + +def generate_nonce(body: str) -> str: + digest = hashlib.md5(body.encode('utf-8')).digest() + return base64.urlsafe_b64encode(digest)[0:6].decode('ascii') diff --git a/src/blurb/blurb.py b/src/blurb/blurb.py index 4357bd9..371778b 100755 --- a/src/blurb/blurb.py +++ b/src/blurb/blurb.py @@ -39,51 +39,8 @@ # # automatic git adds and removes -import base64 -import glob -import hashlib -import os import sys -import time -from blurb._template import ( - next_filename_unsanitize_sections, sanitize_section, - sanitize_section_legacy, sections, -) - -def sortable_datetime(): - return time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) - - -def nonceify(body): - digest = hashlib.md5(body.encode("utf-8")).digest() - return base64.urlsafe_b64encode(digest)[0:6].decode('ascii') - - -def glob_blurbs(version): - filenames = [] - base = os.path.join("Misc", "NEWS.d", version) - if version != "next": - wildcard = base + ".rst" - filenames.extend(glob.glob(wildcard)) - else: - sanitized_sections = ( - {sanitize_section(section) for section in sections} | - {sanitize_section_legacy(section) for section in sections} - ) - for section in sanitized_sections: - wildcard = os.path.join(base, section, "*.rst") - entries = glob.glob(wildcard) - deletables = [x for x in entries if x.endswith("/README.rst")] - for filename in deletables: - entries.remove(filename) - filenames.extend(entries) - filenames.sort(reverse=True, key=next_filename_unsanitize_sections) - return filenames - - -class BlurbError(RuntimeError): - pass def error(*a): s = " ".join(str(x) for x in a) diff --git a/tests/test_blurb_add.py b/tests/test_add.py similarity index 100% rename from tests/test_blurb_add.py rename to tests/test_add.py diff --git a/tests/test_blurb.py b/tests/test_blurb.py deleted file mode 100644 index 4f11726..0000000 --- a/tests/test_blurb.py +++ /dev/null @@ -1,68 +0,0 @@ -import pytest -import time_machine - -from blurb import blurb - - -@time_machine.travel("2025-01-07 16:28:41") -def test_sortable_datetime(): - assert blurb.sortable_datetime() == "2025-01-07-16-28-41" - - -def test_glob_blurbs_next(fs): - # Arrange - fake_news_entries = ( - "Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-11111.pC7gnM.rst", - "Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-33333.Pf_BI7.rst", - "Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-44444.2F1Byz.rst", - "Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst", - ) - fake_readmes = ( - "Misc/NEWS.d/next/Library/README.rst", - "Misc/NEWS.d/next/Core and Builtins/README.rst", - "Misc/NEWS.d/next/Tools-Demos/README.rst", - "Misc/NEWS.d/next/C API/README.rst", - ) - for fn in fake_news_entries + fake_readmes: - fs.create_file(fn) - - # Act - filenames = blurb.glob_blurbs("next") - - # Assert - assert set(filenames) == set(fake_news_entries) - - -def test_glob_blurbs_sort_order(fs): - """ - It shouldn't make a difference to sorting whether - section names have spaces or underscores. - """ - # Arrange - fake_news_entries = ( - "Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-01-00.gh-issue-33331.Pf_BI1.rst", - "Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-02-00.gh-issue-33332.Pf_BI2.rst", - "Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-03-00.gh-issue-33333.Pf_BI3.rst", - "Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-04-00.gh-issue-33334.Pf_BI4.rst", - ) - # As fake_news_entries, but reverse sorted by *filename* only - expected = [ - "Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-04-00.gh-issue-33334.Pf_BI4.rst", - "Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-03-00.gh-issue-33333.Pf_BI3.rst", - "Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-02-00.gh-issue-33332.Pf_BI2.rst", - "Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-01-00.gh-issue-33331.Pf_BI1.rst", - ] - fake_readmes = ( - "Misc/NEWS.d/next/Library/README.rst", - "Misc/NEWS.d/next/Core and Builtins/README.rst", - "Misc/NEWS.d/next/Tools-Demos/README.rst", - "Misc/NEWS.d/next/C API/README.rst", - ) - for fn in fake_news_entries + fake_readmes: - fs.create_file(fn) - - # Act - filenames = blurb.glob_blurbs("next") - - # Assert - assert filenames == expected diff --git a/tests/test_blurb_file.py b/tests/test_blurb_file.py index d21f40b..b24ffed 100644 --- a/tests/test_blurb_file.py +++ b/tests/test_blurb_file.py @@ -2,8 +2,7 @@ import time_machine import blurb._blurb_file -from blurb._blurb_file import Blurbs -from blurb.blurb import BlurbError +from blurb._blurb_file import Blurbs, BlurbError, sortable_datetime @pytest.mark.parametrize( @@ -146,3 +145,8 @@ def test_parse_no_body(contents, expected_error): # Act / Assert with pytest.raises(BlurbError, match=expected_error): blurbs.parse(contents) + + +@time_machine.travel("2025-01-07 16:28:41") +def test_sortable_datetime(): + assert sortable_datetime() == "2025-01-07-16-28-41" diff --git a/tests/test_utils_globs.py b/tests/test_utils_globs.py new file mode 100644 index 0000000..97d9cae --- /dev/null +++ b/tests/test_utils_globs.py @@ -0,0 +1,60 @@ +from blurb._utils.globs import glob_blurbs + + +def test_glob_blurbs_next(fs) -> None: + # Arrange + fake_news_entries = ( + 'Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-11111.pC7gnM.rst', + 'Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-33333.Pf_BI7.rst', + 'Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-44444.2F1Byz.rst', + 'Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst', + ) + fake_readmes = ( + 'Misc/NEWS.d/next/Library/README.rst', + 'Misc/NEWS.d/next/Core and Builtins/README.rst', + 'Misc/NEWS.d/next/Tools-Demos/README.rst', + 'Misc/NEWS.d/next/C API/README.rst', + ) + for fn in fake_news_entries + fake_readmes: + fs.create_file(fn) + + # Act + filenames = glob_blurbs('next') + + # Assert + assert set(filenames) == set(fake_news_entries) + + +def test_glob_blurbs_sort_order(fs) -> None: + """ + It shouldn't make a difference to sorting whether + section names have spaces or underscores. + """ + # Arrange + fake_news_entries = ( + 'Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-01-00.gh-issue-33331.Pf_BI1.rst', + 'Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-02-00.gh-issue-33332.Pf_BI2.rst', + 'Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-03-00.gh-issue-33333.Pf_BI3.rst', + 'Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-04-00.gh-issue-33334.Pf_BI4.rst', + ) + # As fake_news_entries, but reverse sorted by *filename* only + expected = [ + 'Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-04-00.gh-issue-33334.Pf_BI4.rst', + 'Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-03-00.gh-issue-33333.Pf_BI3.rst', + 'Misc/NEWS.d/next/Core_and_Builtins/2023-07-23-12-02-00.gh-issue-33332.Pf_BI2.rst', + 'Misc/NEWS.d/next/Core and Builtins/2023-07-23-12-01-00.gh-issue-33331.Pf_BI1.rst', + ] + fake_readmes = ( + 'Misc/NEWS.d/next/Library/README.rst', + 'Misc/NEWS.d/next/Core and Builtins/README.rst', + 'Misc/NEWS.d/next/Tools-Demos/README.rst', + 'Misc/NEWS.d/next/C API/README.rst', + ) + for fn in fake_news_entries + fake_readmes: + fs.create_file(fn) + + # Act + filenames = glob_blurbs('next') + + # Assert + assert filenames == expected