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

feat: lint-file subcommand #1055

Merged
merged 18 commits into from
Sep 10, 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
12 changes: 10 additions & 2 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
# SPDX-License-Identifier: GPL-3.0-or-later

- id: reuse
name: reuse
name: reuse lint
entry: reuse
args: ["lint"]
language: python
pass_filenames: false
description:
"Lint the project directory for compliance with the REUSE Specification"
"Lint the project directory for compliance with the REUSE Specification."

- id: reuse-lint-file
name: reuse lint-file
entry: reuse
args: ["lint-file"]
language: python
description:
"Lint the changed files for compliance with the REUSE Specification."
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ jobs=0

disable=duplicate-code,
logging-fstring-interpolation,
implicit-str-concat
implicit-str-concat,
inconsistent-quotes
enable=useless-suppression

[REPORTS]
Expand Down
2 changes: 2 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Contributors
- Jon Burdo <[email protected]>
- Josef Andersson <[email protected]>
- José Vieira <[email protected]>
- Kerry McAdams <github@klmcadams>
- Kevin Meagher <[email protected]>
- Lars Francke <[email protected]>
- Libor Pechacek <[email protected]>
Expand All @@ -139,6 +140,7 @@ Contributors
- Romain Tartière <[email protected]>
- Ryan Schmidt <[email protected]>
- Sebastian Crane <[email protected]>
- Sebastien Morais <github@SMoraisAnsys>
- T. E. Kalaycı <[email protected]>
- Vishesh Handa <[email protected]>
- Vlad-Stefan Harbuz <[email protected]>
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ Git. This uses [pre-commit](https://pre-commit.com/). Once you
```yaml
repos:
- repo: https://github.com/fsfe/reuse-tool
rev: v3.0.2
rev: v4.0.3
hooks:
- id: reuse
```
Expand All @@ -256,6 +256,17 @@ Then run `pre-commit install`. Now, every time you commit, `reuse lint` is run
in the background, and will prevent your commit from going through if there was
an error.

If you instead want to only lint files that were changed in your commit, you can
use the following configuration:

```yaml
repos:
- repo: https://github.com/fsfe/reuse-tool
rev: v4.0.3
hooks:
- id: reuse-lint-file
```

## Maintainers

- Carmen Bianca Bakker <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions changelog.d/added/lint-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add lint-file subcommand to enable running lint on specific files.
15 changes: 14 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
# The full version, including alpha/beta/rc tags.
release = get_version("reuse")
except PackageNotFoundError:
release = "3.0.2"
release = "4.0.3"

# The short X.Y.Z version.
version = ".".join(release.split(".")[:3])
Expand Down Expand Up @@ -115,6 +115,14 @@
"Free Software Foundation Europe",
1,
),
(
"man/reuse-lint-file",
"reuse-lint-file",
"Verify whether the specified files are compliant with the REUSE"
" Specification",
"Free Software Foundation Europe",
1,
),
(
"man/reuse-spdx",
"reuse-spdx",
Expand All @@ -130,6 +138,11 @@
1,
),
]
manpages_url = (
"https://reuse.readthedocs.io/en/v{version}/man/{page}.html".format(
version=version, page="{page}"
)
)

# -- Custom ------------------------------------------------------------------

Expand Down
50 changes: 50 additions & 0 deletions docs/man/reuse-lint-file.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
..
SPDX-FileCopyrightText: 2019 Free Software Foundation Europe e.V. <https://fsfe.org>
SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>

SPDX-License-Identifier: CC-BY-SA-4.0

reuse-lint-file
===============

Synopsis
--------

**reuse lint-file** [*options*] [*file* ...]

Description
-----------

:program:`reuse-lint-file` verifies whether the specified files are compliant
with the REUSE Specification located at `<https://reuse.software/spec>`_. It
runs the linter from :manpage:`reuse-lint(1)` against a subset of files, using a
subset of criteria.

Files that are ignored by :program:`reuse-lint` are also ignored by
:program:`reuse-lint-file`, even if specified.

Criteria
--------

The criteria are the same as used in :manpage:`reuse-lint(1)`, but using only a
subset:

- Missing licenses.
- Read errors.
- Files without copyright and license information.

Options
-------

.. option:: -q, --quiet

Do not print anything to STDOUT.

.. option:: -l, --lines

Output one line per error, prefixed by the file path. This option is the
default.

.. option:: -h, --help

Display help and exit.
2 changes: 1 addition & 1 deletion docs/man/reuse-lint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Options
.. option:: -p, --plain

Output the results of the lint as descriptive text. The text is valid
Markdown.
Markdown. This option is the default.

.. option:: -l, --lines

Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ push = false
"src/reuse/__init__.py" = [
'__version__ = "{pep440_version}"$',
]
"docs/conf.py" = [
'release = "{pep440_version}"$',
]
"README.md" = [
'rev: {version}$',
]

[tool.protokolo]
changelog = "CHANGELOG.md"
Expand Down
63 changes: 63 additions & 0 deletions src/reuse/_lint_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-FileCopyrightText: 2024 Kerry McAdams <github@klmcadams>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Linting specific files happens here. The linting here is nothing more than
reading the reports and printing some conclusions.
"""

import sys
from argparse import ArgumentParser, Namespace
from gettext import gettext as _
from pathlib import Path
from typing import IO

from ._util import PathType, is_relative_to
from .lint import format_lines_subset
from .project import Project
from .report import ProjectSubsetReport


def add_arguments(parser: ArgumentParser) -> None:
"""Add arguments to parser."""
mutex_group = parser.add_mutually_exclusive_group()
mutex_group.add_argument(
"-q", "--quiet", action="store_true", help=_("prevents output")
)
mutex_group.add_argument(
"-l",
"--lines",
action="store_true",
help=_("formats output as errors per line (default)"),
)
parser.add_argument(
"files",
action="store",
nargs="*",
type=PathType("r"),
help=_("files to lint"),
)


def run(args: Namespace, project: Project, out: IO[str] = sys.stdout) -> int:
"""List all non-compliant files from specified file list."""
subset_files = {Path(file_) for file_ in args.files}
for file_ in subset_files:
if not is_relative_to(file_.resolve(), project.root.resolve()):
args.parser.error(
_("'{file}' is not inside of '{root}'").format(
file=file_, root=project.root
)
)
report = ProjectSubsetReport.generate(
project,
subset_files,
multiprocessing=not args.no_multiprocessing,
)

if args.quiet:
pass
else:
out.write(format_lines_subset(report))

return 0 if report.is_compliant else 1
10 changes: 10 additions & 0 deletions src/reuse/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]>
# SPDX-FileCopyrightText: 2024 Carmen Bianca BAKKER <[email protected]>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2024 Kerry McAdams <github@klmcadams>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -20,6 +21,7 @@
__REUSE_version__,
__version__,
_annotate,
_lint_file,
convert_dep5,
download,
lint,
Expand Down Expand Up @@ -173,6 +175,14 @@ def parser() -> argparse.ArgumentParser:
),
)

add_command(
subparsers,
"lint-file",
_lint_file.add_arguments,
_lint_file.run,
help=_("list non-compliant files from specified list of files"),
)

add_command(
subparsers,
"spdx",
Expand Down
13 changes: 11 additions & 2 deletions src/reuse/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Misc. utilities for reuse."""


import contextlib
import logging
import os
import re
Expand All @@ -29,7 +29,7 @@
from inspect import cleandoc
from itertools import chain
from os import PathLike
from pathlib import Path
from pathlib import Path, PurePath
from typing import (
IO,
Any,
Expand Down Expand Up @@ -665,4 +665,13 @@ def cleandoc_nl(text: str) -> str:
return cleandoc(text) + "\n"


def is_relative_to(path: PurePath, target: PurePath) -> bool:
"""Like Path.is_relative_to, but working for Python <3.9."""
# TODO: When Python 3.8 is dropped, remove this function.
with contextlib.suppress(ValueError):
path.relative_to(target)
return True
return False


# REUSE-IgnoreEnd
7 changes: 2 additions & 5 deletions src/reuse/global_licensing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

# mypy: disable-error-code=attr-defined

import contextlib
import logging
import re
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -40,7 +39,7 @@
from license_expression import ExpressionError

from . import ReuseInfo, SourceType
from ._util import _LICENSING, StrPath
from ._util import _LICENSING, StrPath, is_relative_to

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -555,9 +554,7 @@ def _find_relevant_tomls(self, path: StrPath) -> List[ReuseTOML]:
found = []
for toml in self.reuse_tomls:
# TODO: When Python 3.8 is dropped, use is_relative_to instead.
with contextlib.suppress(ValueError):
PurePath(path).relative_to(toml.directory)
# No error.
if is_relative_to(PurePath(path), toml.directory):
found.append(toml)
# Sort from topmost to deepest directory.
found.sort(key=lambda toml: toml.directory.parts)
Expand Down
Loading
Loading