diff --git a/blurb/blurb.py b/blurb/blurb.py index fd5c0fa..3a21dab 100755 --- a/blurb/blurb.py +++ b/blurb/blurb.py @@ -38,7 +38,6 @@ ## Licensed to the Python Software Foundation under a contributor agreement. ## - # TODO # # automatic git adds and removes @@ -110,19 +109,46 @@ sections.append(section.strip()) +_sanitize_section = { + "C API": "C_API", + "Core and Builtins": "Core_and_Builtins", + "Tools/Demos": "Tools-Demos", + } + + def sanitize_section(section): """ -Cleans up a section string, making it viable as a directory name. + Clean up a section string, making it viable as a directory name. + """ + return _sanitize_section.get(section, section) + + +def sanitize_section_legacy(section): + """ + Clean up a section string, making it viable as a directory name (allow spaces). """ return section.replace("/", "-") + _unsanitize_section = { + "C_API": "C API", + "Core_and_Builtins": "Core and Builtins", "Tools-Demos": "Tools/Demos", } + def unsanitize_section(section): return _unsanitize_section.get(section, section) +def next_filename_unsanitize_sections(filename): + s = filename + for key, value in _unsanitize_section.items(): + for separator in "/\\": + key = f"{separator}{key}{separator}" + value = f"{separator}{value}{separator}" + filename = filename.replace(key, value) + return filename + def textwrap_body(body, *, subsequent_indent=''): """ @@ -300,14 +326,18 @@ def glob_blurbs(version): wildcard = base + ".rst" filenames.extend(glob.glob(wildcard)) else: - for section in sections: - wildcard = os.path.join(base, sanitize_section(section), "*.rst") + 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) - entries.sort(reverse=True) 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 @@ -537,8 +567,8 @@ def save(self, path): @staticmethod def _parse_next_filename(filename): """ -Parses a "next" filename into its equivalent blurb metadata. -Returns a dict. + Parses a "next" filename into its equivalent blurb metadata. + Returns a dict. """ components = filename.split(os.sep) section, filename = components[-2:] @@ -552,7 +582,7 @@ def _parse_next_filename(filename): metadata = {"date": fields[0], "nonce": fields[-2], "section": section} for field in fields[1:-2]: - for name in ("gh-issue","bpo"): + for name in ("gh-issue", "bpo"): _, got, value = field.partition(name + "-") if got: metadata[name] = value.strip() @@ -589,7 +619,7 @@ def _extract_next_filename(self): metadata, body = self[-1] metadata['section'] = sanitize_section(metadata['section']) metadata['root'] = root - if int(metadata["gh-issue"]) > 0 : + if int(metadata["gh-issue"]) > 0: path = "{root}/Misc/NEWS.d/next/{section}/{date}.gh-issue-{gh-issue}.{nonce}.rst".format_map(metadata) elif int(metadata["bpo"]) > 0: # assume it's a GH issue number diff --git a/blurb/tests/test_blurb.py b/blurb/tests/test_blurb.py index b2d06b5..515f6b2 100644 --- a/blurb/tests/test_blurb.py +++ b/blurb/tests/test_blurb.py @@ -5,14 +5,12 @@ UNCHANGED_SECTIONS = ( - "C API", - "Core and Builtins", "Library", ) @pytest.mark.parametrize("section", UNCHANGED_SECTIONS) -def test_sanitize_section_no_change(section: str) -> None: +def test_sanitize_section_no_change(section): sanitized = blurb.sanitize_section(section) assert sanitized == section @@ -20,16 +18,18 @@ def test_sanitize_section_no_change(section: str) -> None: @pytest.mark.parametrize( "section, expected", ( + ("C API", "C_API"), + ("Core and Builtins", "Core_and_Builtins"), ("Tools/Demos", "Tools-Demos"), ), ) -def test_sanitize_section_changed(section: str, expected: str) -> None: +def test_sanitize_section_changed(section, expected): sanitized = blurb.sanitize_section(section) assert sanitized == expected @pytest.mark.parametrize("section", UNCHANGED_SECTIONS) -def test_unsanitize_section_no_change(section: str) -> None: +def test_unsanitize_section_no_change(section): unsanitized = blurb.unsanitize_section(section) assert unsanitized == section @@ -40,12 +40,12 @@ def test_unsanitize_section_no_change(section: str) -> None: ("Tools-Demos", "Tools/Demos"), ), ) -def test_unsanitize_section_changed(section: str, expected: str) -> None: +def test_unsanitize_section_changed(section, expected): unsanitized = blurb.unsanitize_section(section) assert unsanitized == expected -def test_glob_blurbs_next(fs: FakeFilesystem) -> None: +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", @@ -69,28 +69,71 @@ def test_glob_blurbs_next(fs: FakeFilesystem) -> None: 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 + + @pytest.mark.parametrize( "news_entry, expected_section", ( ( - "Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-55555.pC7gnM.rst", + "Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-33333.pC7gnM.rst", "Library", ), ( - "Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-33333.Pf_BI7.rst", + "Misc/NEWS.d/next/Core_and_Builtins/2023-03-17-12-09-45.gh-issue-44444.Pf_BI7.rst", + "Core and Builtins", + ), + ( + "Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-55555.Pf_BI7.rst", "Core and Builtins", ), ( - "Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-44444.2F1Byz.rst", + "Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-66666.2F1Byz.rst", "Tools/Demos", ), ( - "Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst", + "Misc/NEWS.d/next/C_API/2023-03-27-22-09-07.gh-issue-77777.3SN8Bs.rst", + "C API", + ), + ( + "Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-88888.3SN8Bs.rst", "C API", ), ), ) -def test_load_next(news_entry: str, expected_section: str, fs: FakeFilesystem) -> None: +def test_load_next(news_entry, expected_section, fs): # Arrange fs.create_file(news_entry, contents="testing") blurbs = blurb.Blurbs() @@ -107,26 +150,24 @@ def test_load_next(news_entry: str, expected_section: str, fs: FakeFilesystem) - "news_entry, expected_path", ( ( - "Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-55555.pC7gnM.rst", - "root/Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-55555.pC7gnM.rst", + "Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-33333.pC7gnM.rst", + "root/Misc/NEWS.d/next/Library/2022-04-11-18-34-33.gh-issue-33333.pC7gnM.rst", ), ( - "Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-33333.Pf_BI7.rst", - "root/Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-33333.Pf_BI7.rst", + "Misc/NEWS.d/next/Core and Builtins/2023-03-17-12-09-45.gh-issue-44444.Pf_BI7.rst", + "root/Misc/NEWS.d/next/Core_and_Builtins/2023-03-17-12-09-45.gh-issue-44444.Pf_BI7.rst", ), ( - "Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-44444.2F1Byz.rst", - "root/Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-44444.2F1Byz.rst", + "Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-55555.2F1Byz.rst", + "root/Misc/NEWS.d/next/Tools-Demos/2023-03-21-01-27-07.gh-issue-55555.2F1Byz.rst", ), ( "Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst", - "root/Misc/NEWS.d/next/C API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst", + "root/Misc/NEWS.d/next/C_API/2023-03-27-22-09-07.gh-issue-66666.3SN8Bs.rst", ), ), ) -def test_extract_next_filename( - news_entry: str, expected_path: str, fs: FakeFilesystem -) -> None: +def test_extract_next_filename(news_entry, expected_path, fs): # Arrange fs.create_file(news_entry, contents="testing") blurb.root = "root"