Skip to content

Move remaining utility functions #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/blurb/_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 11 additions & 3 deletions src/blurb/_blurb_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
2 changes: 1 addition & 1 deletion src/blurb/_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
9 changes: 5 additions & 4 deletions src/blurb/_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions src/blurb/_utils/globs.py
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions src/blurb/_utils/text.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import base64
import hashlib
import itertools
import textwrap

Expand Down Expand Up @@ -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')
43 changes: 0 additions & 43 deletions src/blurb/blurb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
File renamed without changes.
68 changes: 0 additions & 68 deletions tests/test_blurb.py

This file was deleted.

8 changes: 6 additions & 2 deletions tests/test_blurb_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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"
60 changes: 60 additions & 0 deletions tests/test_utils_globs.py
Original file line number Diff line number Diff line change
@@ -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