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