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 single year parameter flag to pontos-update-header #1021

Merged
merged 6 commits into from
Aug 13, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pontos/updateheader/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,15 @@ def parse_args(args: Optional[Sequence[str]] = None) -> Namespace:
help="Do a cleanup: Remove lines from outdated header format",
)

parser.add_argument(
"--single-year",
action="store_true",
default=False,
help=(
"If set, will format license headers in from-to year format "
"into single (creation) year format. "
"Default is %(default)s."
),
)

return parser.parse_args(args)
126 changes: 91 additions & 35 deletions pontos/updateheader/updateheader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Also it appends a header if it is missing in the file.
"""

import io
import re
import sys
from dataclasses import dataclass
Expand Down Expand Up @@ -67,7 +68,12 @@

def _get_modified_year(f: Path) -> str:
"""In case of the changed arg, update year to last modified year"""
return Git().log("-1", "--date=format:%Y", str(f), format="%ad")[0]
try:
ret = Git().log("-1", "--date=format:%Y", str(f), format="%ad")[0]
except IndexError:
raise PontosError(f'Empty "git log -1" output for {f}.')

Check warning on line 74 in pontos/updateheader/updateheader.py

View check run for this annotation

Codecov / codecov/patch

pontos/updateheader/updateheader.py#L74

Added line #L74 was not covered by tests

return ret


@dataclass
Expand Down Expand Up @@ -149,6 +155,7 @@
company: str,
*,
cleanup: bool = False,
single_year: bool = False,
) -> None:
"""Function to update the header of the given file

Expand Down Expand Up @@ -202,35 +209,62 @@
return

# replace found header and write it to file
if copyright_match and (
not copyright_match.modification_year
and copyright_match.creation_year < year
or copyright_match.modification_year
and copyright_match.modification_year < year
):
copyright_term = (
f"SPDX-FileCopyrightText: "
f"{copyright_match.creation_year}"
f"-{year} {company}"
if copyright_match:

# use different target license formats depending on provided single_year argument
if single_year:
copyright_term = (
f"SPDX-FileCopyrightText: "
f"{copyright_match.creation_year} "
f"{company}"
)
else:
copyright_term = (
f"SPDX-FileCopyrightText: "
f"{copyright_match.creation_year}"
f"-{year} {company}"
)

with_multi_year = (
copyright_match.creation_year
and copyright_match.modification_year
)
new_line = re.sub(copyright_regex, copyright_term, line)
fp_write = fp.tell() - len(line) # save position to insert
rest_of_file = fp.read()
fp.seek(fp_write)
fp.write(new_line)
fp.write(rest_of_file)
# in some cases we replace "YYYY - YYYY" with "YYYY-YYYY"
# resulting in 2 characters left at the end of the file
# so we truncate the file, just in case!
fp.truncate()
print(
f"{file}: Changed License Header Copyright Year "
f"{copyright_match.modification_year} -> "
f"{year}"
with_single_year_outdated = (
not copyright_match.modification_year
and int(copyright_match.creation_year) < int(year)
)

else:
print(f"{file}: License Header is ok.")
with_multi_year_outdated = False
if with_multi_year:
# assert to silence mypy
assert isinstance(copyright_match.modification_year, str)
with_multi_year_outdated = int(
copyright_match.modification_year
) < int(year)

if single_year and with_multi_year:
_substitute_license_text(
fp, line, copyright_regex, copyright_term
)
print(
f"{file}: Changed License Header Copyright Year format to single year "
f"{copyright_match.creation_year}-{year} -> "
f"{copyright_match.creation_year}"
)
elif not single_year and (
with_multi_year_outdated or with_single_year_outdated
):
_substitute_license_text(
fp, line, copyright_regex, copyright_term
)
print(
f"{file}: Changed License Header Copyright Year "
f"{copyright_match.modification_year} -> "
f"{year}"
)
else:
print(f"{file}: License Header is ok.")

except FileNotFoundError as e:
print(f"{file}: File is not existing.")
raise e
Expand All @@ -248,6 +282,25 @@
print(f"{file}: Cleaned up!")


def _substitute_license_text(
fp: io.TextIOWrapper,
line: str,
copyright_regex: re.Pattern,
copyright_term: str,
) -> None:
"""Substitute the old license text in file fp, starting on provided line, with the new one provided in copyright_term"""
new_line = re.sub(copyright_regex, copyright_term, line)
fp_write = fp.tell() - len(line) # save position to insert
rest_of_file = fp.read()
fp.seek(fp_write)
fp.write(new_line)
fp.write(rest_of_file)
# in some cases we replace "YYYY - YYYY" with "YYYY-YYYY"
# resulting in 2 characters left at the end of the file
# so we truncate the file, just in case!
fp.truncate()


def _get_exclude_list(
exclude_file: Path, directories: list[Path]
) -> list[Path]:
Expand Down Expand Up @@ -309,6 +362,7 @@
changed: bool = parsed_args.changed
quiet: bool = parsed_args.quiet
cleanup: bool = parsed_args.cleanup
single_year: bool = parsed_args.single_year

if quiet:
term: Union[NullTerminal, RichTerminal] = NullTerminal()
Expand Down Expand Up @@ -345,25 +399,27 @@
sys.exit(1)

for file in files:
if changed:
try:
year = _get_modified_year(file)
except PontosError:
term.warning(
f"{file}: Could not get date of last modification"
f" via git, using {year} instead."
)

try:
if file.absolute() in exclude_list:
term.warning(f"{file}: Ignoring file from exclusion list.")
else:
if changed:
try:
year = _get_modified_year(file)
except PontosError:
term.warning(
f"{file}: Could not get date of last modification"
f" via git, using {year} instead."
)
y0urself marked this conversation as resolved.
Show resolved Hide resolved

update_file(
file,
year,
license_id,
company,
cleanup=cleanup,
single_year=single_year,
)
except (FileNotFoundError, UnicodeDecodeError, ValueError):
continue
Expand Down
51 changes: 51 additions & 0 deletions tests/updateheader/test_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,26 @@ def test_update_create_header(self, mock_stdout):
expected_header, test_file.read_text(encoding="utf-8")
)

@patch("sys.stdout", new_callable=StringIO)
def test_update_create_header_single_year(self, mock_stdout):
year = "1995"
license_id = "AGPL-3.0-or-later"

expected_header = HEADER.format(date="1995") + "\n\n"

with temp_file(name="test.py", change_into=True) as test_file:
update_file(
test_file, year, license_id, self.company, single_year=True
)
ret = mock_stdout.getvalue()
self.assertEqual(
f"{test_file}: Added license header.\n",
ret,
)
self.assertEqual(
expected_header, test_file.read_text(encoding="utf-8")
)

@patch("sys.stdout", new_callable=StringIO)
def test_update_header_in_file(self, mock_stdout):
year = "2021"
Expand Down Expand Up @@ -318,6 +338,35 @@ def test_update_header_in_file(self, mock_stdout):
test_file.read_text(encoding="utf-8"),
)

@patch("sys.stdout", new_callable=StringIO)
def test_update_header_in_file_single_year(self, mock_stdout):
year = "2021"
license_id = "AGPL-3.0-or-later"

header = HEADER.format(date="2020-2021")
with temp_file(
content=header, name="test.py", change_into=True
) as test_file:
update_file(
test_file,
year,
license_id,
self.company,
single_year=True,
)

ret = mock_stdout.getvalue()
self.assertEqual(
ret,
f"{test_file}: Changed License Header Copyright Year format to single year "
"2020-2021 -> 2020\n",
)

self.assertIn(
"# SPDX-FileCopyrightText: 2020 Greenbone AG",
test_file.read_text(encoding="utf-8"),
)

@patch("sys.stdout", new_callable=StringIO)
def test_update_header_ok_in_file(self, mock_stdout):
year = "2021"
Expand Down Expand Up @@ -510,6 +559,7 @@ def test_defaults(self):
self.assertEqual(args.files, ["foo.txt"])
self.assertIsNone(args.directories)
self.assertIsNone(args.exclude_file)
self.assertFalse(args.single_year)
self.assertFalse(args.cleanup)

def test_files_and_directories_mutual_exclusive(self):
Expand Down Expand Up @@ -569,6 +619,7 @@ def test_main_never_happen(self, argparser_mock, mock_stdout):
self.args.log_file = None
self.args.quiet = False
self.args.cleanup = False
self.args.single_year = False

argparser_mock.return_value = self.args

Expand Down
Loading