1import subprocess
2import sys
3import tomllib
4from pathlib import Path
5
6import click
7
8from plain.cli.print import print_event
9
10DEFAULT_RUFF_CONFIG = Path(__file__).parent / "ruff_defaults.toml"
11
12
13@click.group()
14def cli():
15 """Standard code formatting and linting."""
16 pass
17
18
19@cli.command()
20@click.argument("path", default=".")
21def check(path):
22 """Check the given path for formatting or linting issues."""
23 ruff_args = ["--config", str(DEFAULT_RUFF_CONFIG)]
24
25 for e in get_code_config().get("exclude", []):
26 ruff_args.extend(["--exclude", e])
27
28 print_event("Ruff check")
29 result = subprocess.run(["ruff", "check", path, *ruff_args])
30
31 if result.returncode != 0:
32 sys.exit(result.returncode)
33
34 print_event("Ruff format check")
35 result = subprocess.run(["ruff", "format", path, "--check", *ruff_args])
36
37 if result.returncode != 0:
38 sys.exit(result.returncode)
39
40
41@cli.command()
42@click.argument("path", default=".")
43@click.option("--unsafe-fixes", is_flag=True, help="Apply ruff unsafe fixes")
44@click.option("--add-noqa", is_flag=True, help="Add noqa comments to suppress errors")
45def fix(path, unsafe_fixes, add_noqa):
46 """Lint and format the given path."""
47 ruff_args = ["--config", str(DEFAULT_RUFF_CONFIG)]
48
49 for e in get_code_config().get("exclude", []):
50 ruff_args.extend(["--exclude", e])
51
52 if unsafe_fixes and add_noqa:
53 print("Cannot use both --unsafe-fixes and --add-noqa")
54 sys.exit(1)
55
56 if unsafe_fixes:
57 print_event("Ruff fix (with unsafe fixes)")
58 result = subprocess.run(
59 ["ruff", "check", path, "--fix", "--unsafe-fixes", *ruff_args]
60 )
61 elif add_noqa:
62 print_event("Ruff fix (add noqa)")
63 result = subprocess.run(["ruff", "check", path, "--add-noqa", *ruff_args])
64 else:
65 print_event("Ruff fix")
66 result = subprocess.run(["ruff", "check", path, "--fix", *ruff_args])
67
68 if result.returncode != 0:
69 sys.exit(result.returncode)
70
71 print_event("Ruff format")
72 result = subprocess.run(["ruff", "format", path, *ruff_args])
73
74 if result.returncode != 0:
75 sys.exit(result.returncode)
76
77
78def get_code_config():
79 pyproject = Path("pyproject.toml")
80 if not pyproject.exists():
81 return {}
82 with pyproject.open("rb") as f:
83 return tomllib.load(f).get("tool", {}).get("plain", {}).get("code", {})