diff --git a/README.md b/README.md index 0f0f4529..7f8dd4aa 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,12 @@ Python. - `pyproject`: Include validate pyproject with schema store. - `all`: All extras +## Helper utility + +There's also a script, accessible as `sp-ruff-checks`, that will compare your +ruff checks to the known values. It's a little more elegant on the command line +than the Ruff family description, which will only print out a basic list. + ## Other ways to use You can also use GitHub Actions: diff --git a/pyproject.toml b/pyproject.toml index 0f2eed5d..e72e1372 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ Source = "https://github.com/scientific-python/cookie" [project.scripts] sp-repo-review = "repo_review.__main__:main" +"sp-ruff-checks" = "sp_repo_review.ruff_checks.__main__:main" [project.entry-points."repo_review.checks"] general = "sp_repo_review.checks.general:repo_review_checks" diff --git a/src/sp_repo_review/ruff_checks/__init__.py b/src/sp_repo_review/ruff_checks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/helpers/missing_ruff.py b/src/sp_repo_review/ruff_checks/__main__.py old mode 100755 new mode 100644 similarity index 70% rename from helpers/missing_ruff.py rename to src/sp_repo_review/ruff_checks/__main__.py index cf30d5ad..16b8e619 --- a/helpers/missing_ruff.py +++ b/src/sp_repo_review/ruff_checks/__main__.py @@ -1,22 +1,16 @@ -#!/usr/bin/env -S uv run --script - -# /// script -# dependencies = ["ruff", "rich"] -# /// - - import argparse +import importlib.resources import json -import subprocess import sys from collections.abc import Iterator from pathlib import Path -import tomllib from rich import print from rich.columns import Columns from rich.panel import Panel -from ruff.__main__ import find_ruff_bin + +from .._compat import tomllib +from ..checks.ruff import get_rule_selection, ruff libs = {"AIR", "ASYNC", "DJ", "FAST", "INT", "NPY", "PD"} specialty = { @@ -40,32 +34,41 @@ def process_dir(path: Path) -> None: - with path.joinpath("pyproject.toml").open("rb") as f: - pyproject = tomllib.load(f) - - match pyproject: - case { - "tool": { - "ruff": {"lint": {"extend-select": selection} | {"select": selection}} - } - }: - selected = frozenset(selection) - case _: - print("[red]Rules not found", file=sys.stderr) - raise SystemExit(1) - - linter_txt = subprocess.run( - [find_ruff_bin(), "linter", "--output-format=json"], - check=True, - capture_output=True, - text=True, - cwd=path, - ).stdout - linter = json.loads(linter_txt) + try: + with path.joinpath("pyproject.toml").open("rb") as f: + pyproject = tomllib.load(f) + except FileNotFoundError: + pyproject = {} + + ruff_config = ruff(pyproject=pyproject, root=path) + if ruff_config is None: + print( + "[red]Could not find a ruff config [dim](.ruff.toml, ruff.toml, or pyproject.toml)", + file=sys.stderr, + ) + raise SystemExit(1) + selected = get_rule_selection(ruff_config) + if not selected: + print( + "[red]No rules selected", + file=sys.stderr, + ) + raise SystemExit(2) + + # Create using ruff linter --output-format=json > src/sp_repo_review/ruff/linter.json + with ( + importlib.resources.files("sp_repo_review.ruff_checks") + .joinpath("linter.json") + .open(encoding="utf-8") as ff + ): + linter = json.load(ff) lint_info = {r["prefix"]: r["name"] for r in linter if r["prefix"] not in {"", "F"}} lint_info = dict(sorted(lint_info.items())) + if "ALL" in selected: + selected = frozenset(lint_info.keys()) + selected_items = {k: v for k, v in lint_info.items() if k in selected} all_uns_items = {k: v for k, v in lint_info.items() if k not in selected} unselected_items = { diff --git a/src/sp_repo_review/ruff_checks/linter.json b/src/sp_repo_review/ruff_checks/linter.json new file mode 100644 index 00000000..362d79b1 --- /dev/null +++ b/src/sp_repo_review/ruff_checks/linter.json @@ -0,0 +1,266 @@ +[ + { + "prefix": "AIR", + "name": "Airflow" + }, + { + "prefix": "ERA", + "name": "eradicate" + }, + { + "prefix": "FAST", + "name": "FastAPI" + }, + { + "prefix": "YTT", + "name": "flake8-2020" + }, + { + "prefix": "ANN", + "name": "flake8-annotations" + }, + { + "prefix": "ASYNC", + "name": "flake8-async" + }, + { + "prefix": "S", + "name": "flake8-bandit" + }, + { + "prefix": "BLE", + "name": "flake8-blind-except" + }, + { + "prefix": "FBT", + "name": "flake8-boolean-trap" + }, + { + "prefix": "B", + "name": "flake8-bugbear" + }, + { + "prefix": "A", + "name": "flake8-builtins" + }, + { + "prefix": "COM", + "name": "flake8-commas" + }, + { + "prefix": "C4", + "name": "flake8-comprehensions" + }, + { + "prefix": "CPY", + "name": "flake8-copyright" + }, + { + "prefix": "DTZ", + "name": "flake8-datetimez" + }, + { + "prefix": "T10", + "name": "flake8-debugger" + }, + { + "prefix": "DJ", + "name": "flake8-django" + }, + { + "prefix": "EM", + "name": "flake8-errmsg" + }, + { + "prefix": "EXE", + "name": "flake8-executable" + }, + { + "prefix": "FIX", + "name": "flake8-fixme" + }, + { + "prefix": "FA", + "name": "flake8-future-annotations" + }, + { + "prefix": "INT", + "name": "flake8-gettext" + }, + { + "prefix": "ISC", + "name": "flake8-implicit-str-concat" + }, + { + "prefix": "ICN", + "name": "flake8-import-conventions" + }, + { + "prefix": "LOG", + "name": "flake8-logging" + }, + { + "prefix": "G", + "name": "flake8-logging-format" + }, + { + "prefix": "INP", + "name": "flake8-no-pep420" + }, + { + "prefix": "PIE", + "name": "flake8-pie" + }, + { + "prefix": "T20", + "name": "flake8-print" + }, + { + "prefix": "PYI", + "name": "flake8-pyi" + }, + { + "prefix": "PT", + "name": "flake8-pytest-style" + }, + { + "prefix": "Q", + "name": "flake8-quotes" + }, + { + "prefix": "RSE", + "name": "flake8-raise" + }, + { + "prefix": "RET", + "name": "flake8-return" + }, + { + "prefix": "SLF", + "name": "flake8-self" + }, + { + "prefix": "SIM", + "name": "flake8-simplify" + }, + { + "prefix": "SLOT", + "name": "flake8-slots" + }, + { + "prefix": "TID", + "name": "flake8-tidy-imports" + }, + { + "prefix": "TD", + "name": "flake8-todos" + }, + { + "prefix": "TC", + "name": "flake8-type-checking" + }, + { + "prefix": "ARG", + "name": "flake8-unused-arguments" + }, + { + "prefix": "PTH", + "name": "flake8-use-pathlib" + }, + { + "prefix": "FLY", + "name": "flynt" + }, + { + "prefix": "I", + "name": "isort" + }, + { + "prefix": "C90", + "name": "mccabe" + }, + { + "prefix": "NPY", + "name": "NumPy-specific rules" + }, + { + "prefix": "PD", + "name": "pandas-vet" + }, + { + "prefix": "N", + "name": "pep8-naming" + }, + { + "prefix": "PERF", + "name": "Perflint" + }, + { + "prefix": "", + "name": "pycodestyle", + "categories": [ + { + "prefix": "E", + "name": "Error" + }, + { + "prefix": "W", + "name": "Warning" + } + ] + }, + { + "prefix": "DOC", + "name": "pydoclint" + }, + { + "prefix": "D", + "name": "pydocstyle" + }, + { + "prefix": "F", + "name": "Pyflakes" + }, + { + "prefix": "PGH", + "name": "pygrep-hooks" + }, + { + "prefix": "PL", + "name": "Pylint", + "categories": [ + { + "prefix": "C", + "name": "Convention" + }, + { + "prefix": "E", + "name": "Error" + }, + { + "prefix": "R", + "name": "Refactor" + }, + { + "prefix": "W", + "name": "Warning" + } + ] + }, + { + "prefix": "UP", + "name": "pyupgrade" + }, + { + "prefix": "FURB", + "name": "refurb" + }, + { + "prefix": "RUF", + "name": "Ruff-specific rules" + }, + { + "prefix": "TRY", + "name": "tryceratops" + } +] diff --git a/tests/test_cmd.py b/tests/test_cmd.py index 04c00b43..efaf80a0 100644 --- a/tests/test_cmd.py +++ b/tests/test_cmd.py @@ -19,3 +19,7 @@ def test_cmd_html(): def test_cmd_json(): subprocess.run(["repo-review", ".", "--format", "json"], check=True) + + +def test_ruff_checks(): + subprocess.run(["sp-ruff-checks"], check=True)