Skip to content

Commit

Permalink
Add GitHub workflow commands output
Browse files Browse the repository at this point in the history
This allows GitHub to parse it and display annotations on pull requests.
  • Loading branch information
nijel committed Jul 19, 2024
1 parent a4f4850 commit e60c289
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.d/added/github-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added `--github` output option for `lint`.
4 changes: 4 additions & 0 deletions docs/man/reuse-lint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Options

Output one line per error, prefixed by the file path.

.. option:: -g, --github

Output one line per error in GitHub workflow command syntax.

.. option:: -h, --help

Display help and exit.
33 changes: 31 additions & 2 deletions src/reuse/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ def add_arguments(parser: ArgumentParser) -> None:
action="store_true",
help=_("formats output as errors per line"),
)
mutex_group.add_argument(
"-g",
"--github",
action="store_true",
help=_("formats output as GitHub workflow commands per line"),
)


# pylint: disable=too-many-branches,too-many-statements,too-many-locals
Expand Down Expand Up @@ -268,15 +274,15 @@ def custom_serializer(obj: Any) -> Any:
def get_errors(
report: ProjectReport,
) -> Iterable[tuple[Path | str | None, str]]:
"""Formats data dictionary as plaintext strings to be printed to sys.stdout
"""Returns data dictionary iterable of paths and errors.
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
Iterable of tuples containing path and error message.
"""

def license_path(lic: str) -> Optional[Path]:
Expand Down Expand Up @@ -341,6 +347,27 @@ def format_lines(report: ProjectReport) -> str:
return ""


def format_github(report: ProjectReport) -> str:
"""Formats data dictionary as GitHub workflow commands
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
"""
if not report.is_compliant:
return "".join(
f"::error file={path}::{error}\n"
for path, error in get_errors(report)
)

return ""


def run(args: Namespace, project: Project, out: IO[str] = sys.stdout) -> int:
"""List all non-compliant files."""
report = ProjectReport.generate(
Expand All @@ -353,6 +380,8 @@ def run(args: Namespace, project: Project, out: IO[str] = sys.stdout) -> int:
out.write(format_json(report))
elif args.lines:
out.write(format_lines(report))
elif args.github:
out.write(format_github(report))
else:
out.write(format_plain(report))

Expand Down
44 changes: 43 additions & 1 deletion tests/test_lint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 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-FileCopyrightText: 2024 Michal Čihař <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -11,7 +12,7 @@

from conftest import cpython, posix

from reuse.lint import format_lines, format_plain
from reuse.lint import format_github, format_lines, format_plain
from reuse.project import Project
from reuse.report import ProjectReport

Expand Down Expand Up @@ -271,4 +272,45 @@ def test_lint_lines_read_errors(fake_repository):
assert "read error" in result


def test_lint_github_output(fake_repository):
"""Complete test for lint with github 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_github(report)
lines_result_lines = lines_result.splitlines()

assert len(lines_result_lines) == 12

for line in lines_result_lines:
assert re.match("::error file=.+::[^:]+", 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


# REUSE-IgnoreEnd

0 comments on commit e60c289

Please sign in to comment.