From 09935346023a6eb86e1b6f9e5963e346d6fb4d2e Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:06:10 +0100 Subject: [PATCH 1/6] Check for invalid fragment names --- docs/cli.rst | 5 ++ docs/configuration.rst | 7 +++ docs/tutorial.rst | 5 ++ src/towncrier/_builder.py | 23 +++++++++ src/towncrier/_settings/load.py | 1 + src/towncrier/build.py | 16 +++++- src/towncrier/check.py | 4 +- src/towncrier/newsfragments/622.feature.rst | 3 ++ src/towncrier/test/test_build.py | 57 +++++++++++++++++++++ src/towncrier/test/test_check.py | 20 ++++++++ 10 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/towncrier/newsfragments/622.feature.rst diff --git a/docs/cli.rst b/docs/cli.rst index 6adbb94d..8faf0551 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -64,6 +64,11 @@ also remove the fragments directory if now empty. Don't delete news fragments after the build and don't ask for confirmation whether to delete or keep the fragments. +.. option:: --strict + + Fail if there are any news fragments that have invalid filenames. + This is automatically turned on if ``build_ignore_filenames`` has been set in the configuration. + ``towncrier create`` -------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index aa4edfca..c3749738 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -130,6 +130,13 @@ Top level keys ``true`` by default. +``build_ignore_filenames`` + A list of filenames in the news fragments directory to ignore when building the news file. + + If this is configured, it turns on the ``--strict`` build mode which will fail if there are any news fragment files not in this list that have invalid filenames. Note that if a template is used, it should also be included here. + + ``None`` by default. + Extra top level keys for Python projects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 70b9c410..88a49db5 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -47,6 +47,11 @@ Create this folder:: The ``.gitignore`` will remain and keep Git from not tracking the directory. +If needed, you can also specify a list of filenames for ``towncrier`` to ignore in the news fragments directory:: + + [tool.towncrier] + build_ignore_filenames = ["README.rst"] + Detecting Version ----------------- diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index e7fbf12b..1c075478 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -12,6 +12,7 @@ from pathlib import Path from typing import Any, DefaultDict, Iterable, Iterator, Mapping, NamedTuple, Sequence +from click import ClickException from jinja2 import Template from towncrier._settings.load import Config @@ -106,10 +107,22 @@ def __call__(self, section_directory: str = "") -> str: def find_fragments( base_directory: str, config: Config, + strict: bool | None, ) -> tuple[Mapping[str, Mapping[tuple[str, str, int], str]], list[tuple[str, str]]]: """ Sections are a dictonary of section names to paths. + + In strict mode, raise ClickException if any fragments have an invalid name. """ + if strict is None: + # If strict mode is not set, turn it on only if build_ignore_filenames is set + # (this maintains backward compatibility). + strict = config.build_ignore_filenames is not None + + ignored_files = {".gitignore"} + if config.build_ignore_filenames: + ignored_files.update(config.build_ignore_filenames) + get_section_path = FragmentsPath(base_directory, config) content = {} @@ -129,10 +142,20 @@ def find_fragments( file_content = {} for basename in files: + # Skip files that are deliberately ignored + if basename in ignored_files: + continue + issue, category, counter = parse_newfragment_basename( basename, config.types ) if category is None: + if strict and issue is None: + raise ClickException( + f"Invalid news fragment name: {basename}\n" + "If this filename is deliberate, add it to " + "'build_ignore_filenames' in your configuration." + ) continue assert issue is not None assert counter is not None diff --git a/src/towncrier/_settings/load.py b/src/towncrier/_settings/load.py index 724a7768..9ca2a593 100644 --- a/src/towncrier/_settings/load.py +++ b/src/towncrier/_settings/load.py @@ -54,6 +54,7 @@ class Config: orphan_prefix: str = "+" create_eof_newline: bool = True create_add_extension: bool = True + build_ignore_filenames: list[str] | None = None class ConfigError(ClickException): diff --git a/src/towncrier/build.py b/src/towncrier/build.py index bf7cd350..139072e3 100644 --- a/src/towncrier/build.py +++ b/src/towncrier/build.py @@ -106,6 +106,17 @@ def _validate_answer(ctx: Context, param: Option, value: bool) -> bool: help="Do not ask for confirmations. But keep news fragments.", callback=_validate_answer, ) +@click.option( + "--strict", + "strict", + default=None, + flag_value=True, + help=( + "Fail if there are any news fragments that have invalid filenames (this is " + "automatically turned on if 'build_ignore_filenames' has been set in the " + "configuration)." + ), +) def _main( draft: bool, directory: str | None, @@ -115,6 +126,7 @@ def _main( project_date: str | None, answer_yes: bool, answer_keep: bool, + strict: bool | None, ) -> None: """ Build a combined news file from news fragment. @@ -129,6 +141,7 @@ def _main( project_date, answer_yes, answer_keep, + strict, ) except ConfigError as e: print(e, file=sys.stderr) @@ -144,6 +157,7 @@ def __main( project_date: str | None, answer_yes: bool, answer_keep: bool, + strict: bool | None, ) -> None: """ The main entry point. @@ -178,7 +192,7 @@ def __main( click.echo("Finding news fragments...", err=to_err) - fragment_contents, fragment_files = find_fragments(base_directory, config) + fragment_contents, fragment_files = find_fragments(base_directory, config, strict) fragment_filenames = [filename for (filename, _category) in fragment_files] click.echo("Rendering news fragments...", err=to_err) diff --git a/src/towncrier/check.py b/src/towncrier/check.py index a519f9ae..8b057545 100644 --- a/src/towncrier/check.py +++ b/src/towncrier/check.py @@ -101,12 +101,14 @@ def __main( click.echo(f"{n}. {change}") click.echo("----") + # This will fail if any fragment files have an invalid name: + all_fragment_files = find_fragments(base_directory, config, strict=True)[1] + news_file = os.path.normpath(os.path.join(base_directory, config.filename)) if news_file in files: click.echo("Checks SKIPPED: news file changes detected.") sys.exit(0) - all_fragment_files = find_fragments(base_directory, config)[1] fragments = set() # will only include fragments of types that are checked unchecked_fragments = set() # will include fragments of types that are not checked for fragment_filename, category in all_fragment_files: diff --git a/src/towncrier/newsfragments/622.feature.rst b/src/towncrier/newsfragments/622.feature.rst new file mode 100644 index 00000000..6845c471 --- /dev/null +++ b/src/towncrier/newsfragments/622.feature.rst @@ -0,0 +1,3 @@ +``towncrier check`` will now fail if any news fragments have invalid filenames. + +Added a new configuration option called ``build_ignore_filenames`` that allows you to specify a list of filenames that should be ignored. If this is set, ``towncrier build`` will also fail if any filenames are invalid, except for those in the list. diff --git a/src/towncrier/test/test_build.py b/src/towncrier/test/test_build.py index 5da7d828..1efa7495 100644 --- a/src/towncrier/test/test_build.py +++ b/src/towncrier/test/test_build.py @@ -1530,3 +1530,60 @@ def test_orphans_in_non_showcontent_markdown(self, runner): self.assertEqual(0, result.exit_code, result.output) self.assertEqual(expected_output, result.output) + + @with_project( + config=""" + [tool.towncrier] + package = "foo" + build_ignore_filenames = ["template.jinja", "CAPYBARAS.md"] + """ + ) + def test_invalid_fragment_names(self, runner): + """ + When build_ignore_filenames is set, files with those names are ignored. + """ + opts = ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] + # Valid filename: + with open("foo/newsfragments/123.feature", "w") as f: + f.write("Adds levitation") + # Files that should be ignored: + with open("foo/newsfragments/template.jinja", "w") as f: + f.write("Jinja template") + with open("foo/newsfragments/CAPYBARAS.md", "w") as f: + f.write("Peanut butter") + # Automatically ignored: + with open("foo/newsfragments/.gitignore", "w") as f: + f.write("!.gitignore") + + result = runner.invoke(_main, opts) + # Should succeed + self.assertEqual(0, result.exit_code, result.output) + + # Invalid filename: + with open("foo/newsfragments/feature.124", "w") as f: + f.write("Extends levitation") + + result = runner.invoke(_main, opts) + # Should now fail + self.assertEqual(1, result.exit_code, result.output) + self.assertIn("Invalid news fragment name: feature.124", result.output) + + @with_project() + def test_invalid_fragment_names_strict(self, runner): + """ + When using --strict, any invalid filenames will cause an error even if + build_ignore_filenames is NOT set. + """ + opts = ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] + # Invalid filename: + with open("foo/newsfragments/feature.124", "w") as f: + f.write("Extends levitation") + + result = runner.invoke(_main, opts) + # Should succeed in normal mode + self.assertEqual(0, result.exit_code, result.output) + + result = runner.invoke(_main, [*opts, "--strict"]) + # Should now fail + self.assertEqual(1, result.exit_code, result.output) + self.assertIn("Invalid news fragment name: feature.124", result.output) diff --git a/src/towncrier/test/test_check.py b/src/towncrier/test/test_check.py index 23db3264..d73966ee 100644 --- a/src/towncrier/test/test_check.py +++ b/src/towncrier/test/test_check.py @@ -470,3 +470,23 @@ def test_in_different_dir_with_nondefault_newsfragments_directory(self, runner): self.assertTrue( result.output.endswith("No new newsfragments found on this branch.\n") ) + + @with_isolated_runner + def test_invalid_fragment_name(self, runner): + create_project("pyproject.toml") + opts = ["--compare-with", "main"] + + write("foo/bar.py", "# Scorpions!") + write("foo/newsfragments/123.feature", "Adds scorpions") + write("foo/newsfragments/.gitignore", "!.gitignore") + commit("add stuff") + + result = runner.invoke(towncrier_check, opts) + self.assertEqual(0, result.exit_code, result.output) + + # Make invalid filename: + os.rename("foo/newsfragments/123.feature", "foo/newsfragments/feature.123") + + result = runner.invoke(towncrier_check, opts) + self.assertEqual(1, result.exit_code, result.output) + self.assertIn("Invalid news fragment name: feature.123", result.output) From 3c93f134eda738ca7e27eef541b08448bec80d88 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:17:29 +0100 Subject: [PATCH 2/6] Refactor in response to review - Change `build_ignore_filenames` to `ignore` in config - Remove `--strict` option from `towncrier build` --- docs/cli.rst | 5 -- docs/configuration.rst | 8 ++- docs/tutorial.rst | 5 -- src/towncrier/_builder.py | 16 ++--- src/towncrier/_settings/load.py | 2 +- src/towncrier/build.py | 22 +++---- src/towncrier/newsfragments/622.feature.rst | 2 +- src/towncrier/test/test_build.py | 66 +++++++++++---------- src/towncrier/test/test_check.py | 42 +++++++++---- 9 files changed, 86 insertions(+), 82 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 8faf0551..6adbb94d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -64,11 +64,6 @@ also remove the fragments directory if now empty. Don't delete news fragments after the build and don't ask for confirmation whether to delete or keep the fragments. -.. option:: --strict - - Fail if there are any news fragments that have invalid filenames. - This is automatically turned on if ``build_ignore_filenames`` has been set in the configuration. - ``towncrier create`` -------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index c3749738..562ec51c 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -130,10 +130,12 @@ Top level keys ``true`` by default. -``build_ignore_filenames`` - A list of filenames in the news fragments directory to ignore when building the news file. +``ignore`` + A list of filenames in the news fragments directory to ignore. - If this is configured, it turns on the ``--strict`` build mode which will fail if there are any news fragment files not in this list that have invalid filenames. Note that if a template is used, it should also be included here. + ``towncrier check`` will fail if there are any news fragment files that have invalid filenames, except for those in the list. ``towncrier build`` will likewise fail, but only if this list has been configured (set to an empty list if there are no files to ignore). + + Note that if a custom template is stored in the news fragment directory, you should add it to this list. ``None`` by default. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 88a49db5..70b9c410 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -47,11 +47,6 @@ Create this folder:: The ``.gitignore`` will remain and keep Git from not tracking the directory. -If needed, you can also specify a list of filenames for ``towncrier`` to ignore in the news fragments directory:: - - [tool.towncrier] - build_ignore_filenames = ["README.rst"] - Detecting Version ----------------- diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index 1c075478..6ad8f2e6 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -107,21 +107,16 @@ def __call__(self, section_directory: str = "") -> str: def find_fragments( base_directory: str, config: Config, - strict: bool | None, + strict: bool, ) -> tuple[Mapping[str, Mapping[tuple[str, str, int], str]], list[tuple[str, str]]]: """ Sections are a dictonary of section names to paths. - In strict mode, raise ClickException if any fragments have an invalid name. + If strict, raise ClickException if any fragments have an invalid name. """ - if strict is None: - # If strict mode is not set, turn it on only if build_ignore_filenames is set - # (this maintains backward compatibility). - strict = config.build_ignore_filenames is not None - ignored_files = {".gitignore"} - if config.build_ignore_filenames: - ignored_files.update(config.build_ignore_filenames) + if config.ignore: + ignored_files.update(config.ignore) get_section_path = FragmentsPath(base_directory, config) @@ -142,7 +137,6 @@ def find_fragments( file_content = {} for basename in files: - # Skip files that are deliberately ignored if basename in ignored_files: continue @@ -154,7 +148,7 @@ def find_fragments( raise ClickException( f"Invalid news fragment name: {basename}\n" "If this filename is deliberate, add it to " - "'build_ignore_filenames' in your configuration." + "'ignore' in your configuration." ) continue assert issue is not None diff --git a/src/towncrier/_settings/load.py b/src/towncrier/_settings/load.py index 9ca2a593..e282a392 100644 --- a/src/towncrier/_settings/load.py +++ b/src/towncrier/_settings/load.py @@ -54,7 +54,7 @@ class Config: orphan_prefix: str = "+" create_eof_newline: bool = True create_add_extension: bool = True - build_ignore_filenames: list[str] | None = None + ignore: list[str] | None = None class ConfigError(ClickException): diff --git a/src/towncrier/build.py b/src/towncrier/build.py index 139072e3..546ef113 100644 --- a/src/towncrier/build.py +++ b/src/towncrier/build.py @@ -106,17 +106,6 @@ def _validate_answer(ctx: Context, param: Option, value: bool) -> bool: help="Do not ask for confirmations. But keep news fragments.", callback=_validate_answer, ) -@click.option( - "--strict", - "strict", - default=None, - flag_value=True, - help=( - "Fail if there are any news fragments that have invalid filenames (this is " - "automatically turned on if 'build_ignore_filenames' has been set in the " - "configuration)." - ), -) def _main( draft: bool, directory: str | None, @@ -126,7 +115,6 @@ def _main( project_date: str | None, answer_yes: bool, answer_keep: bool, - strict: bool | None, ) -> None: """ Build a combined news file from news fragment. @@ -141,7 +129,6 @@ def _main( project_date, answer_yes, answer_keep, - strict, ) except ConfigError as e: print(e, file=sys.stderr) @@ -157,7 +144,6 @@ def __main( project_date: str | None, answer_yes: bool, answer_keep: bool, - strict: bool | None, ) -> None: """ The main entry point. @@ -192,7 +178,13 @@ def __main( click.echo("Finding news fragments...", err=to_err) - fragment_contents, fragment_files = find_fragments(base_directory, config, strict) + fragment_contents, fragment_files = find_fragments( + base_directory, + config, + # Fail if any fragment filenames are invalid only if ignore list is set + # (this maintains backward compatibility): + strict=(config.ignore is not None), + ) fragment_filenames = [filename for (filename, _category) in fragment_files] click.echo("Rendering news fragments...", err=to_err) diff --git a/src/towncrier/newsfragments/622.feature.rst b/src/towncrier/newsfragments/622.feature.rst index 6845c471..6ca64a71 100644 --- a/src/towncrier/newsfragments/622.feature.rst +++ b/src/towncrier/newsfragments/622.feature.rst @@ -1,3 +1,3 @@ ``towncrier check`` will now fail if any news fragments have invalid filenames. -Added a new configuration option called ``build_ignore_filenames`` that allows you to specify a list of filenames that should be ignored. If this is set, ``towncrier build`` will also fail if any filenames are invalid, except for those in the list. +Added a new configuration option called ``ignore`` that allows you to specify a list of filenames that should be ignored. If this is set, ``towncrier build`` will also fail if any filenames are invalid, except for those in the list. diff --git a/src/towncrier/test/test_build.py b/src/towncrier/test/test_build.py index 1efa7495..d4285153 100644 --- a/src/towncrier/test/test_build.py +++ b/src/towncrier/test/test_build.py @@ -1535,55 +1535,61 @@ def test_orphans_in_non_showcontent_markdown(self, runner): config=""" [tool.towncrier] package = "foo" - build_ignore_filenames = ["template.jinja", "CAPYBARAS.md"] + ignore = ["template.jinja", "CAPYBARAS.md"] """ ) - def test_invalid_fragment_names(self, runner): + def test_ignored_files(self, runner): """ - When build_ignore_filenames is set, files with those names are ignored. + When `ignore` is set in config, files with those names are ignored. """ - opts = ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] - # Valid filename: with open("foo/newsfragments/123.feature", "w") as f: - f.write("Adds levitation") - # Files that should be ignored: + f.write("This has valid filename (control case)") with open("foo/newsfragments/template.jinja", "w") as f: - f.write("Jinja template") + f.write("This template has been manually ignored") with open("foo/newsfragments/CAPYBARAS.md", "w") as f: - f.write("Peanut butter") - # Automatically ignored: + f.write("This markdown file has been manually ignored") with open("foo/newsfragments/.gitignore", "w") as f: - f.write("!.gitignore") + f.write("gitignore is automatically ignored") - result = runner.invoke(_main, opts) - # Should succeed + result = runner.invoke( + _main, ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] + ) self.assertEqual(0, result.exit_code, result.output) - # Invalid filename: + @with_project( + config=""" + [tool.towncrier] + package = "foo" + ignore = [] + """ + ) + def test_invalid_fragment_name(self, runner): + """ + When `ignore` is set in config, invalid filenames cause failure. + """ + with open("foo/newsfragments/123.feature", "w") as f: + f.write("This has valid filename (control case)") with open("foo/newsfragments/feature.124", "w") as f: - f.write("Extends levitation") + f.write("This has the issue and category the wrong way round") - result = runner.invoke(_main, opts) - # Should now fail + result = runner.invoke( + _main, ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] + ) self.assertEqual(1, result.exit_code, result.output) self.assertIn("Invalid news fragment name: feature.124", result.output) @with_project() - def test_invalid_fragment_names_strict(self, runner): + def test_no_ignore_configured(self, runner): """ - When using --strict, any invalid filenames will cause an error even if - build_ignore_filenames is NOT set. + When `ignore` is not set in config, invalid filenames are skipped. + + This maintains backward compatibility with before we added `ignore` + to the configuration spec. """ - opts = ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] - # Invalid filename: with open("foo/newsfragments/feature.124", "w") as f: - f.write("Extends levitation") + f.write("This has the issue and category the wrong way round") - result = runner.invoke(_main, opts) - # Should succeed in normal mode + result = runner.invoke( + _main, ["--draft", "--date", "01-01-2001", "--version", "1.0.0"] + ) self.assertEqual(0, result.exit_code, result.output) - - result = runner.invoke(_main, [*opts, "--strict"]) - # Should now fail - self.assertEqual(1, result.exit_code, result.output) - self.assertIn("Invalid news fragment name: feature.124", result.output) diff --git a/src/towncrier/test/test_check.py b/src/towncrier/test/test_check.py index d73966ee..ef8c32d6 100644 --- a/src/towncrier/test/test_check.py +++ b/src/towncrier/test/test_check.py @@ -472,21 +472,41 @@ def test_in_different_dir_with_nondefault_newsfragments_directory(self, runner): ) @with_isolated_runner - def test_invalid_fragment_name(self, runner): - create_project("pyproject.toml") - opts = ["--compare-with", "main"] + def test_ignored_files(self, runner): + """ + When `ignore` is set in config, files with those names are ignored. + """ + create_project("pyproject.toml", extra_config='ignore = ["template.jinja"]') - write("foo/bar.py", "# Scorpions!") - write("foo/newsfragments/123.feature", "Adds scorpions") - write("foo/newsfragments/.gitignore", "!.gitignore") + write( + "foo/newsfragments/123.feature", + "This fragment has valid name (control case)", + ) + write("foo/newsfragments/template.jinja", "This is manually ignored") + write("foo/newsfragments/.gitignore", "gitignore is automatically ignored") commit("add stuff") - result = runner.invoke(towncrier_check, opts) + result = runner.invoke(towncrier_check, ["--compare-with", "main"]) self.assertEqual(0, result.exit_code, result.output) - # Make invalid filename: - os.rename("foo/newsfragments/123.feature", "foo/newsfragments/feature.123") + @with_isolated_runner + def test_invalid_fragment_name(self, runner): + """ + Fails if a news fragment has an invalid name, even if `ignore` is not set in + the config. + """ + create_project("pyproject.toml") + + write( + "foo/newsfragments/123.feature", + "This fragment has valid name (control case)", + ) + write( + "foo/newsfragments/feature.124", + "This has issue and category wrong way round", + ) + commit("add stuff") - result = runner.invoke(towncrier_check, opts) + result = runner.invoke(towncrier_check, ["--compare-with", "main"]) self.assertEqual(1, result.exit_code, result.output) - self.assertIn("Invalid news fragment name: feature.123", result.output) + self.assertIn("Invalid news fragment name: feature.124", result.output) From 935a831e11470c8d9dffd40eb0ebff767b4352a6 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:20:48 +0100 Subject: [PATCH 3/6] Tidy docs --- docs/tutorial.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 70b9c410..a126eff2 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -45,8 +45,6 @@ Create this folder:: # This makes sure that Git will never delete the empty folder $ echo '!.gitignore' > src/myproject/newsfragments/.gitignore -The ``.gitignore`` will remain and keep Git from not tracking the directory. - Detecting Version ----------------- From 26c60ff68118c1e0e400be38204fe33d36b908c0 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:44:51 +0100 Subject: [PATCH 4/6] Make `config.ignore` filename list case-insensitive & add more automatically ignored filenames --- docs/configuration.rst | 4 ++-- src/towncrier/_builder.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 562ec51c..955f62e2 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -131,11 +131,11 @@ Top level keys ``true`` by default. ``ignore`` - A list of filenames in the news fragments directory to ignore. + A case-insensitive list of filenames in the news fragments directory to ignore. ``towncrier check`` will fail if there are any news fragment files that have invalid filenames, except for those in the list. ``towncrier build`` will likewise fail, but only if this list has been configured (set to an empty list if there are no files to ignore). - Note that if a custom template is stored in the news fragment directory, you should add it to this list. + Some filenames such as ``.gitignore`` and ``README`` are automatically ignored. However, if a custom template is stored in the news fragment directory, you should add it to this list. ``None`` by default. diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index 6ad8f2e6..62a7d3f3 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -114,9 +114,9 @@ def find_fragments( If strict, raise ClickException if any fragments have an invalid name. """ - ignored_files = {".gitignore"} + ignored_files = {".gitignore", ".keep", "readme", "readme.md", "readme.rst"} if config.ignore: - ignored_files.update(config.ignore) + ignored_files.update(filename.lower() for filename in config.ignore) get_section_path = FragmentsPath(base_directory, config) @@ -137,7 +137,7 @@ def find_fragments( file_content = {} for basename in files: - if basename in ignored_files: + if basename.lower() in ignored_files: continue issue, category, counter = parse_newfragment_basename( From 060c5d359e952623e7d3189c9e9962be3135b4dc Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:43:58 +0100 Subject: [PATCH 5/6] Improve test --- src/towncrier/test/test_check.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/towncrier/test/test_check.py b/src/towncrier/test/test_check.py index ef8c32d6..7305b11d 100644 --- a/src/towncrier/test/test_check.py +++ b/src/towncrier/test/test_check.py @@ -479,7 +479,7 @@ def test_ignored_files(self, runner): create_project("pyproject.toml", extra_config='ignore = ["template.jinja"]') write( - "foo/newsfragments/123.feature", + "foo/newsfragments/124.feature", "This fragment has valid name (control case)", ) write("foo/newsfragments/template.jinja", "This is manually ignored") @@ -498,15 +498,19 @@ def test_invalid_fragment_name(self, runner): create_project("pyproject.toml") write( - "foo/newsfragments/123.feature", + "foo/newsfragments/124.feature", "This fragment has valid name (control case)", ) write( - "foo/newsfragments/feature.124", + "foo/newsfragments/feature.125", "This has issue and category wrong way round", ) + write( + "NEWS.rst", + "Modification of news file should not skip check of invalid names", + ) commit("add stuff") result = runner.invoke(towncrier_check, ["--compare-with", "main"]) self.assertEqual(1, result.exit_code, result.output) - self.assertIn("Invalid news fragment name: feature.124", result.output) + self.assertIn("Invalid news fragment name: feature.125", result.output) From 9b685033b22bd3ee1bd81955a333e0e20bef02e6 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:30:39 +0100 Subject: [PATCH 6/6] Improve test coverage --- src/towncrier/test/test_builder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/towncrier/test/test_builder.py b/src/towncrier/test/test_builder.py index 1213e1a3..31108fdf 100644 --- a/src/towncrier/test/test_builder.py +++ b/src/towncrier/test/test_builder.py @@ -22,6 +22,10 @@ def test_invalid_category(self): parse_newfragment_basename("README.ext", ["feature"]), (None, None, None), ) + self.assertEqual( + parse_newfragment_basename("README", ["feature"]), + (None, None, None), + ) def test_counter(self): """.. generates a custom counter value."""