diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index e7fbf12b..af65b4ce 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -17,15 +17,18 @@ from towncrier._settings.load import Config -# Returns issue, category and counter or (None, None, None) if the basename -# could not be parsed or doesn't contain a valid category. +# Returns issue, category and counter. But if the basename could not be parsed +# or doesn't contain a valid category, returns (None, None, None) or raises +# a ValueError if in strict mode. def parse_newfragment_basename( - basename: str, frag_type_names: Iterable[str] + basename: str, frag_type_names: Iterable[str], strict: bool = False ) -> tuple[str, str, int] | tuple[None, None, None]: invalid = (None, None, None) parts = basename.split(".") if len(parts) == 1: + if strict: + raise ValueError(f"Invalid news fragment name: {basename}") return invalid # There are at least 2 parts. Search for a valid category from the second @@ -53,6 +56,8 @@ def parse_newfragment_basename( return issue, category, counter else: # No valid category found. + if strict: + raise ValueError(f"Invalid news fragment name: {basename}") return invalid @@ -106,11 +111,16 @@ def __call__(self, section_directory: str = "") -> str: def find_fragments( base_directory: str, config: Config, + strict: bool = False, ) -> tuple[Mapping[str, Mapping[tuple[str, str, int], str]], list[tuple[str, str]]]: """ Sections are a dictonary of section names to paths. """ get_section_path = FragmentsPath(base_directory, config) + template_filename = ( + os.path.basename(config.template) if isinstance(config.template, str) + else config.template[1] + ) content = {} fragment_files = [] @@ -129,8 +139,10 @@ def find_fragments( file_content = {} for basename in files: + if basename == template_filename: + continue issue, category, counter = parse_newfragment_basename( - basename, config.types + basename, config.types, strict ) if category is None: continue diff --git a/src/towncrier/build.py b/src/towncrier/build.py index bf7cd350..81bb1ca9 100644 --- a/src/towncrier/build.py +++ b/src/towncrier/build.py @@ -106,6 +106,16 @@ 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( + "--check-names", + "check_names", + default=False, + flag_value=True, + help=( + "Fail if there are any files in the news fragments directory that " + "have invalid names (excluding the template file)." + ), +) def _main( draft: bool, directory: str | None, @@ -115,6 +125,7 @@ def _main( project_date: str | None, answer_yes: bool, answer_keep: bool, + check_names: bool, ) -> None: """ Build a combined news file from news fragment. @@ -129,6 +140,7 @@ def _main( project_date, answer_yes, answer_keep, + check_names, ) except ConfigError as e: print(e, file=sys.stderr) @@ -144,6 +156,7 @@ def __main( project_date: str | None, answer_yes: bool, answer_keep: bool, + check_names: bool, ) -> None: """ The main entry point. @@ -178,7 +191,9 @@ 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=check_names + ) fragment_filenames = [filename for (filename, _category) in fragment_files] click.echo("Rendering news fragments...", err=to_err)