Skip to content

Commit

Permalink
Merge pull request #1055 from klmcadams/feat/lint-file
Browse files Browse the repository at this point in the history
feat: lint-file subcommand
  • Loading branch information
carmenbianca authored Sep 10, 2024
2 parents 5279713 + 15861d2 commit 7bdfa54
Show file tree
Hide file tree
Showing 24 changed files with 842 additions and 270 deletions.
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 <jon@jonburdo.com>
- Josef Andersson <josef.andersson@gmail.com>
- José Vieira <jvieira33@sapo.pt>
- Kerry McAdams <github@klmcadams>
- Kevin Meagher <11620178+kjmeagher@users.noreply.github.com>
- Lars Francke <lars.francke@stackable.de>
- Libor Pechacek <lpechacek@gmx.com>
Expand All @@ -139,6 +140,7 @@ Contributors
- Romain Tartière <romain@blogreen.org>
- Ryan Schmidt <git@ryandesign.com>
- Sebastian Crane <seabass@fsfe.org>
- Sebastien Morais <github@SMoraisAnsys>
- T. E. Kalaycı <tekrei@tutanota.com>
- Vishesh Handa <me@vhanda.in>
- Vlad-Stefan Harbuz <vlad@vladh.net>
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 <carmenbianca@fsfe.org>
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 <florian@familysnow.net>
# SPDX-FileCopyrightText: 2024 Carmen Bianca BAKKER <carmenbianca@fsfe.org>
# 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

0 comments on commit 7bdfa54

Please sign in to comment.