Skip to content
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

Add --keep option to allow to generate newsfile, but keep newsfragmen… #453

Merged
merged 9 commits into from Dec 19, 2022
5 changes: 5 additions & 0 deletions docs/cli.rst
Expand Up @@ -50,6 +50,11 @@ Build the combined news file from news fragments.
Do not ask for confirmations.
Useful for automated tasks.

.. option:: --keep

Do not ask for confirmations. But keep news fragments.
Useful for automated tasks. Where you have to generate several different news fragments for different parties.

fizyk marked this conversation as resolved.
Show resolved Hide resolved

``towncrier create``
--------------------
Expand Down
10 changes: 9 additions & 1 deletion src/towncrier/_git.py
Expand Up @@ -10,18 +10,26 @@
import click


def remove_files(fragment_filenames: list[str], answer_yes: bool) -> None:
def remove_files(
fragment_filenames: list[str], answer_yes: bool, answer_keep: bool
) -> None:
fizyk marked this conversation as resolved.
Show resolved Hide resolved
if not fragment_filenames:
return

if answer_yes:
click.echo("Removing the following files:")
elif answer_keep:
fizyk marked this conversation as resolved.
Show resolved Hide resolved
click.echo("Keeping the following files:")
else:
click.echo("I want to remove the following files:")

for filename in fragment_filenames:
click.echo(filename)

if answer_keep:
# Not proceeding with the removal of the files.
return

if answer_yes or click.confirm("Is it okay if I remove those files?", default=True):
call(["git", "rm", "--quiet"] + fragment_filenames)

Expand Down
30 changes: 28 additions & 2 deletions src/towncrier/build.py
Expand Up @@ -15,6 +15,8 @@

import click

from click import Context, Option

from ._builder import find_fragments, render_fragments, split_fragments
from ._git import remove_files, stage_newsfile
from ._project import get_project_name, get_version
Expand All @@ -26,6 +28,18 @@ def _get_date() -> str:
return date.today().isoformat()


def _validate_answer(ctx: Context, param: Option, value: bool) -> bool:
value_check = (
ctx.params.get("answer_yes")
if param.name == "answer_keep"
else ctx.params.get("answer_keep")
)
if value_check and value:
click.echo("You can not choose both --yes and --keep at the same time")
ctx.abort()
return value


@click.command(name="build")
@click.option(
"--draft",
Expand Down Expand Up @@ -67,9 +81,18 @@ def _get_date() -> str:
@click.option(
"--yes",
"answer_yes",
default=False,
default=None,
flag_value=True,
help="Do not ask for confirmation to remove news fragments.",
callback=_validate_answer,
)
@click.option(
"--keep",
"answer_keep",
default=None,
flag_value=True,
help="Do not ask for confirmations. But keep news fragments.",
callback=_validate_answer,
)
def _main(
draft: bool,
Expand All @@ -79,6 +102,7 @@ def _main(
project_version: str | None,
project_date: str | None,
answer_yes: bool,
answer_keep: bool,
) -> None:
"""
Build a combined news file from news fragment.
Expand All @@ -92,6 +116,7 @@ def _main(
project_version,
project_date,
answer_yes,
answer_keep,
)
except ConfigError as e:
print(e, file=sys.stderr)
Expand All @@ -106,6 +131,7 @@ def __main(
project_version: str | None,
project_date: str | None,
answer_yes: bool,
answer_keep: bool,
) -> None:
"""
The main entry point.
Expand Down Expand Up @@ -237,7 +263,7 @@ def __main(
stage_newsfile(base_directory, news_file)

click.echo("Removing news fragments...", err=to_err)
remove_files(fragment_filenames, answer_yes)
remove_files(fragment_filenames, answer_yes, answer_keep)

click.echo("Done!", err=to_err)

Expand Down
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/129.feature
@@ -0,0 +1 @@
Added `--keep` option to build command that allows to generate newsfile but keep newsfragments in place. This option can not be used together with `--yes`.
fizyk marked this conversation as resolved.
Show resolved Hide resolved
64 changes: 64 additions & 0 deletions src/towncrier/test/test_build.py
Expand Up @@ -407,6 +407,70 @@ def test_no_confirmation(self):
self.assertFalse(os.path.isfile(fragment_path1))
self.assertFalse(os.path.isfile(fragment_path2))

def test_keep_fragments(self):
"""
The `--keep` option will build the full final news file
without deleting the fragment files and without
any extra CLI interaction or confirmation.
"""
runner = CliRunner()

with runner.isolated_filesystem():
fizyk marked this conversation as resolved.
Show resolved Hide resolved
setup_simple_project()
fragment_path1 = "foo/newsfragments/123.feature"
fragment_path2 = "foo/newsfragments/124.feature.rst"
with open(fragment_path1, "w") as f:
f.write("Adds levitation")
with open(fragment_path2, "w") as f:
f.write("Extends levitation")

call(["git", "init"])
call(["git", "config", "user.name", "user"])
call(["git", "config", "user.email", "user@example.com"])
call(["git", "add", "."])
call(["git", "commit", "-m", "Initial Commit"])

result = runner.invoke(_main, ["--date", "01-01-2001", "--keep"])

self.assertEqual(0, result.exit_code)
# The NEWS file is created.
# So this is not just `--draft`.
self.assertTrue(os.path.isfile("NEWS.rst"))
self.assertTrue(os.path.isfile(fragment_path1))
self.assertTrue(os.path.isfile(fragment_path2))

def test_yes_keep_error(self):
"""
It will fail to perform any action when the
conflicting --keep and --yes options are provided.

Called twice with the different order of --keep and --yes options
to make sure both orders are validated since click triggers the validator
in the order it parses the command line.
"""
runner = CliRunner()

with runner.isolated_filesystem():
fizyk marked this conversation as resolved.
Show resolved Hide resolved
setup_simple_project()
fragment_path1 = "foo/newsfragments/123.feature"
fragment_path2 = "foo/newsfragments/124.feature.rst"
with open(fragment_path1, "w") as f:
f.write("Adds levitation")
with open(fragment_path2, "w") as f:
f.write("Extends levitation")

call(["git", "init"])
call(["git", "config", "user.name", "user"])
call(["git", "config", "user.email", "user@example.com"])
call(["git", "add", "."])
call(["git", "commit", "-m", "Initial Commit"])

result = runner.invoke(_main, ["--date", "01-01-2001", "--yes", "--keep"])
self.assertEqual(1, result.exit_code)

result = runner.invoke(_main, ["--date", "01-01-2001", "--keep", "--yes"])
self.assertEqual(1, result.exit_code)

def test_confirmation_says_no(self):
"""
If the user says "no" to removing the newsfragements, we end up with
Expand Down