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

If a shebang already exists when adding a preamble, preserve it. #19133

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/python/pants/backend/tools/preamble/rules.py
Expand Up @@ -23,10 +23,20 @@ class PreambleRequest(FmtFilesRequest):

@memoized
def _template_checker_regex(template: str) -> re.Pattern:
maybe_shebang = r"(#!.*\n)?"
shebang = r"(?P<shebang>#!.*\n)"
if re.match(shebang, template):
# If the template already contains a shebang, don't attempt to match one.
shebang = r"(?P<shebang>)"
maybe_shebang = f"{shebang}?"

subbed = string.Template(template).safe_substitute(year=r"====YEAR====")
raw_regex = re.escape(subbed).replace("====YEAR====", r"\d{4}")
return re.compile(maybe_shebang + raw_regex)
maybe_template = f"(?P<template>{raw_regex})?"

# Wrap in `?s` to enable matching newlines with `.`.
body = "(?s:(?P<body>.*))"

return re.compile(maybe_shebang + maybe_template + body)


@memoized
Expand Down Expand Up @@ -57,10 +67,15 @@ async def preamble_fmt(
for path, template in template_by_path.items():
regex = _template_checker_regex(template)
file_content = contents_by_path[path].content
if not regex.match(file_content.decode("utf-8")):
new_content = _substituted_template(template).encode("utf-8") + file_content
matched = regex.fullmatch(file_content.decode("utf-8"))
# The regex produced by `_template_checker_regex` is infallible.
assert matched
if not matched.group("template"):
shebang = matched.group("shebang")
body = matched.group("body")
new_content = (shebang if shebang else "") + _substituted_template(template) + body
contents_by_path[path] = dataclasses.replace(
contents_by_path[path], content=new_content
contents_by_path[path], content=new_content.encode("utf-8")
)

output_snapshot = await Get(Snapshot, CreateDigest(contents_by_path.values()))
Expand Down
21 changes: 21 additions & 0 deletions src/python/pants/backend/tools/preamble/rules_integration_test.py
Expand Up @@ -141,6 +141,27 @@ def test_ignores_shebang(rule_runner: RuleRunner) -> None:
assert fmt_result.did_change is False


def test_preserves_shebang(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"foo.py": "#!/usr/bin/env python3\n...",
}
)
fmt_result = run_preamble(
rule_runner,
{
"**/*.py": "# Copyright\n",
},
)

assert fmt_result.did_change is True
assert fmt_result.output == rule_runner.make_snapshot(
{
"foo.py": "#!/usr/bin/env python3\n# Copyright\n...",
}
)


def test_preamble_includes_shebang(rule_runner: RuleRunner) -> None:
files_before = {"foo.py": "#!/usr/bin/env python3\n# Copyright"}

Expand Down