Skip to content

Commit

Permalink
Replace newline choices with --force-lf-newlines flag
Browse files Browse the repository at this point in the history
The default behavior remains identical to what was --newline=preserve.
  • Loading branch information
AndydeCleyre authored and webknjaz committed Oct 5, 2022
1 parent 6dad09d commit d247f43
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 70 deletions.
55 changes: 23 additions & 32 deletions piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,24 @@ def _get_default_option(option_name: str) -> Any:
return getattr(default_values, option_name)


def _determine_linesep(
strategy: str = "preserve", filenames: Tuple[str, ...] = ()
) -> str:
def _existing_linesep(*filenames: str) -> str:
"""
Determine and return linesep string for OutputWriter to use.
Valid strategies: "LF", "CRLF", "native", "preserve"
When preserving, files are checked in order for existing newlines.
Check files in order for an existing linesep and return it, if possible.
Otherwise, return LF ("\n").
"""
if strategy == "preserve":
for fname in filenames:
try:
with open(fname, "rb") as existing_file:
existing_text = existing_file.read()
except FileNotFoundError:
continue
if b"\r\n" in existing_text:
strategy = "CRLF"
break
elif b"\n" in existing_text:
strategy = "LF"
break
return {
"native": os.linesep,
"LF": "\n",
"CRLF": "\r\n",
"preserve": "\n",
}[strategy]
linesep = "\n"
for fname in filenames:
try:
with open(fname, "rb") as existing_file:
existing_text = existing_file.read()
except FileNotFoundError:
continue
if b"\r\n" in existing_text:
linesep = "\r\n"
break
elif b"\n" in existing_text:
break
return linesep


@click.command(context_settings={"help_option_names": ("-h", "--help")})
Expand Down Expand Up @@ -195,10 +186,10 @@ def _determine_linesep(
),
)
@click.option(
"--newline",
type=click.Choice(("LF", "CRLF", "native", "preserve"), case_sensitive=False),
default="preserve",
help="Override the newline control characters used",
"--force-lf-newlines",
is_flag=True,
default=False,
help="Always use LF newlines, rather than auto-detecting from existing files.",
)
@click.option(
"--allow-unsafe/--no-allow-unsafe",
Expand Down Expand Up @@ -314,7 +305,7 @@ def cli(
upgrade: bool,
upgrade_packages: Tuple[str, ...],
output_file: Union[LazyFile, IO[Any], None],
newline: str,
force_lf_newlines: bool,
allow_unsafe: bool,
strip_extras: bool,
generate_hashes: bool,
Expand Down Expand Up @@ -551,8 +542,8 @@ def cli(

log.debug("")

linesep = _determine_linesep(
strategy=newline, filenames=(output_file.name, *src_files)
linesep = (
"\n" if force_lf_newlines else _existing_linesep(output_file.name, *src_files)
)

##
Expand Down
58 changes: 20 additions & 38 deletions tests/test_cli_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,23 +946,8 @@ def test_generate_hashes_with_annotations(runner):
("--annotation-style", "split"),
),
)
@pytest.mark.parametrize(
("nl_options", "must_include", "must_exclude"),
(
pytest.param(("--newline", "lf"), "\n", "\r\n", id="LF"),
pytest.param(("--newline", "crlf"), "\r\n", "\n", id="CRLF"),
pytest.param(
("--newline", "native"),
os.linesep,
{"\n": "\r\n", "\r\n": "\n"}[os.linesep],
id="native",
),
),
)
def test_override_newline(
runner, gen_hashes, annotate_options, nl_options, must_include, must_exclude
):
opts = annotate_options + nl_options
def test_force_lf_newlines(runner, gen_hashes, annotate_options):
opts = (*annotate_options, "--force-lf-newlines")
if gen_hashes:
opts += ("--generate-hashes",)

Expand All @@ -975,39 +960,36 @@ def test_override_newline(
with open("requirements.txt", "rb") as req_txt:
txt = req_txt.read().decode()

assert must_include in txt
assert "\n" in txt
assert "\r\n" not in txt

if must_exclude in must_include:
txt = txt.replace(must_include, "")
assert must_exclude not in txt

# Do it again, with --newline=preserve:

opts = annotate_options + ("--newline", "preserve")
if gen_hashes:
opts += ("--generate-hashes",)
@pytest.mark.network
@pytest.mark.parametrize(
("linesep", "must_exclude"),
(pytest.param("\n", "\r\n", id="LF"), pytest.param("\r\n", "\n", id="CRLF")),
)
def test_preserve_newlines_from_output_or_input(runner, linesep, must_exclude):
with open("requirements.in", "wb") as req_in:
req_in.write(f"six{linesep}".encode())

runner.invoke(cli, [*opts, "requirements.in"])
runner.invoke(cli, ["requirements.in"])
with open("requirements.txt", "rb") as req_txt:
txt = req_txt.read().decode()

assert must_include in txt
assert linesep in txt

if must_exclude in must_include:
txt = txt.replace(must_include, "")
if must_exclude in linesep:
txt = txt.replace(linesep, "")
assert must_exclude not in txt

# Now that we have good output,
# see that it's preserved when we have bad input:

@pytest.mark.network
@pytest.mark.parametrize(
("linesep", "must_exclude"),
(pytest.param("\n", "\r\n", id="LF"), pytest.param("\r\n", "\n", id="CRLF")),
)
def test_preserve_newline_from_input(runner, linesep, must_exclude):
with open("requirements.in", "wb") as req_in:
req_in.write(f"six{linesep}".encode())
req_in.write(f"six{must_exclude}".encode())

runner.invoke(cli, ["--newline=preserve", "requirements.in"])
runner.invoke(cli, ["requirements.in"])
with open("requirements.txt", "rb") as req_txt:
txt = req_txt.read().decode()

Expand Down

0 comments on commit d247f43

Please sign in to comment.