-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #956 from fsfe/feature/lint-lines-output
feat: lint output per line
- Loading branch information
Showing
4 changed files
with
150 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org> | ||
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]> | ||
# SPDX-FileCopyrightText: 2023 DB Systel GmbH | ||
# SPDX-FileCopyrightText: 2024 Nico Rikken <[email protected]> | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
|
@@ -15,7 +16,7 @@ | |
from io import StringIO | ||
from pathlib import Path | ||
from textwrap import TextWrapper | ||
from typing import IO, Any | ||
from typing import IO, Any, Optional | ||
|
||
from . import __REUSE_version__ | ||
from .project import Project | ||
|
@@ -37,6 +38,12 @@ def add_arguments(parser: ArgumentParser) -> None: | |
action="store_true", | ||
help=_("formats output as plain text"), | ||
) | ||
mutex_group.add_argument( | ||
"-l", | ||
"--lines", | ||
action="store_true", | ||
help=_("formats output as errors per line"), | ||
) | ||
|
||
|
||
# pylint: disable=too-many-branches,too-many-statements,too-many-locals | ||
|
@@ -257,6 +264,78 @@ def custom_serializer(obj: Any) -> Any: | |
) | ||
|
||
|
||
def format_lines(report: ProjectReport) -> str: | ||
"""Formats data dictionary as plaintext strings to be printed to sys.stdout | ||
Sorting of output is not guaranteed. | ||
Symbolic links can result in multiple entries per file. | ||
Args: | ||
report: ProjectReport data | ||
Returns: | ||
String (in plaintext) that can be output to sys.stdout | ||
""" | ||
output = StringIO() | ||
|
||
def license_path(lic: str) -> Optional[Path]: | ||
"""Resolve a license identifier to a license path.""" | ||
return report.licenses.get(lic) | ||
|
||
if not report.is_compliant: | ||
# Bad licenses | ||
for lic, files in sorted(report.bad_licenses.items()): | ||
for path in sorted(files): | ||
output.write( | ||
_("{path}: bad license {lic}\n").format(path=path, lic=lic) | ||
) | ||
|
||
# Deprecated licenses | ||
for lic in sorted(report.deprecated_licenses): | ||
lic_path = license_path(lic) | ||
output.write( | ||
_("{lic_path}: deprecated license\n").format(lic_path=lic_path) | ||
) | ||
|
||
# Licenses without extension | ||
for lic in sorted(report.licenses_without_extension): | ||
lic_path = license_path(lic) | ||
output.write( | ||
_("{lic_path}: license without file extension\n").format( | ||
lic_path=lic_path | ||
) | ||
) | ||
|
||
# Unused licenses | ||
for lic in sorted(report.unused_licenses): | ||
lic_path = license_path(lic) | ||
output.write( | ||
_("{lic_path}: unused license\n").format(lic_path=lic_path) | ||
) | ||
|
||
# Missing licenses | ||
for lic, files in sorted(report.missing_licenses.items()): | ||
for path in sorted(files): | ||
output.write( | ||
_("{path}: missing license {lic}\n").format( | ||
path=path, lic=lic | ||
) | ||
) | ||
|
||
# Read errors | ||
for path in sorted(report.read_errors): | ||
output.write(_("{path}: read error\n").format(path=path)) | ||
|
||
# Without licenses | ||
for path in report.files_without_licenses: | ||
output.write(_("{path}: no license identifier\n").format(path=path)) | ||
|
||
# Without copyright | ||
for path in report.files_without_copyright: | ||
output.write(_("{path}: no copyright notice\n").format(path=path)) | ||
|
||
return output.getvalue() | ||
|
||
|
||
def run(args: Namespace, project: Project, out: IO[str] = sys.stdout) -> int: | ||
"""List all non-compliant files.""" | ||
report = ProjectReport.generate( | ||
|
@@ -267,6 +346,8 @@ def run(args: Namespace, project: Project, out: IO[str] = sys.stdout) -> int: | |
pass | ||
elif args.json: | ||
out.write(format_json(report)) | ||
elif args.lines: | ||
out.write(format_lines(report)) | ||
else: | ||
out.write(format_plain(report)) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,17 @@ | ||
# SPDX-FileCopyrightText: 2019 Free Software Foundation Europe e.V. <https://fsfe.org> | ||
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]> | ||
# SPDX-FileCopyrightText: 2024 Nico Rikken <[email protected]> | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
"""All tests for reuse.lint""" | ||
|
||
import re | ||
import shutil | ||
|
||
from conftest import cpython, posix | ||
|
||
from reuse.lint import format_plain | ||
from reuse.lint import format_lines, format_plain | ||
from reuse.project import Project | ||
from reuse.report import ProjectReport | ||
|
||
|
@@ -212,4 +214,61 @@ def test_lint_json_output(fake_repository): | |
) | ||
|
||
|
||
def test_lint_lines_output(fake_repository): | ||
"""Complete test for lint with lines output.""" | ||
# Prepare a repository that includes all types of situations: | ||
# missing_licenses, unused_licenses, bad_licenses, deprecated_licenses, | ||
# licenses_without_extension, files_without_copyright, | ||
# files_without_licenses, read_errors | ||
(fake_repository / "invalid-license.py").write_text( | ||
"SPDX-License-Identifier: invalid" | ||
) | ||
(fake_repository / "no-license.py").write_text( | ||
"SPDX-FileCopyrightText: Jane Doe" | ||
) | ||
(fake_repository / "LICENSES" / "invalid-license-text").write_text( | ||
"An invalid license text" | ||
) | ||
(fake_repository / "LICENSES" / "Nokia-Qt-exception-1.1.txt").write_text( | ||
"Deprecated" | ||
) | ||
(fake_repository / "LICENSES" / "MIT").write_text("foo") | ||
(fake_repository / "file with spaces.py").write_text("foo") | ||
|
||
project = Project.from_directory(fake_repository) | ||
report = ProjectReport.generate(project) | ||
|
||
lines_result = format_lines(report) | ||
lines_result_lines = lines_result.splitlines() | ||
|
||
assert len(lines_result_lines) == 12 | ||
|
||
for line in lines_result_lines: | ||
assert re.match(".+: [^:]+", line) | ||
|
||
assert lines_result.count("invalid-license.py") == 3 | ||
assert lines_result.count("no-license.py") == 1 | ||
assert lines_result.count("LICENSES") == 6 | ||
assert lines_result.count("invalid-license-text") == 3 | ||
assert lines_result.count("Nokia-Qt-exception-1.1.txt") == 2 | ||
assert lines_result.count("MIT") == 2 | ||
assert lines_result.count("file with spaces.py") == 2 | ||
|
||
|
||
@cpython | ||
@posix | ||
def test_lint_lines_read_errors(fake_repository): | ||
"""Check read error output""" | ||
(fake_repository / "restricted.py").write_text("foo") | ||
(fake_repository / "restricted.py").chmod(0o000) | ||
project = Project.from_directory(fake_repository) | ||
report = ProjectReport.generate(project) | ||
result = format_lines(report) | ||
print(result) | ||
|
||
assert len(result.splitlines()) == 1 | ||
assert "restricted.py" in result | ||
assert "read error" in result | ||
|
||
|
||
# REUSE-IgnoreEnd |