Skip to content

Commit 0232bf4

Browse files
committed
feat: add more Ruff checks, and a helper script
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 141396d commit 0232bf4

File tree

6 files changed

+150
-8
lines changed

6 files changed

+150
-8
lines changed

helpers/cog_cc.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ def yaml(self) -> str:
2626
result.append(f" help: {self.prompt}")
2727
if self.choices:
2828
result.append(" choices:")
29-
for choice in self.choices:
30-
result.append(
31-
f' "{choice.description}": {choice.name}'
32-
if choice.description
33-
else f" - {choice.name}"
34-
)
29+
result.extend(
30+
f' "{choice.description}": {choice.name}'
31+
if choice.description
32+
else f" - {choice.name}"
33+
for choice in self.choices
34+
)
3535
return "\n".join(result)
3636

3737

helpers/extensions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CookiecutterContext(SmartDict):
3939

4040
@CookiecutterContext.register
4141
def __year(_):
42-
return str(datetime.datetime.today().year)
42+
return str(datetime.datetime.now(tz=datetime.timezone.utc).year)
4343

4444

4545
@CookiecutterContext.register

helpers/missing_ruff.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env -S uv run --script
2+
3+
# /// script
4+
# dependencies = ["ruff", "rich"]
5+
# ///
6+
7+
8+
import argparse
9+
import json
10+
import subprocess
11+
from collections.abc import Iterator
12+
from pathlib import Path
13+
14+
import tomllib
15+
from rich import print
16+
from rich.columns import Columns
17+
from rich.panel import Panel
18+
from ruff.__main__ import find_ruff_bin
19+
20+
libs = {"AIR", "ASYNC", "DJ", "FAST", "INT", "NPY", "PD"}
21+
specialty = {
22+
"CPY", # preview only
23+
"A", # Naming related
24+
"N", # Naming related
25+
"ANN", # Not all rules good
26+
"TID", # Not all rules good
27+
"C90", # Complexity
28+
"COM", # Trailing commas inform the formatter
29+
"D", # Requires everything documented
30+
"DOC", # Style-specific
31+
"ERA", # Check for commented code
32+
"FBT", # Can't be applied to old code very well
33+
"FIX", # TODO's are okay
34+
"TD", # Picky on todo rules
35+
"INP", # Namespace packages are correct sometimes
36+
"S", # Security (can be picky)
37+
"SLF", # Very picky
38+
}
39+
40+
41+
def process_dir(path: Path) -> None:
42+
with path.joinpath("pyproject.toml").open("rb") as f:
43+
pyproject = tomllib.load(f)
44+
45+
match pyproject:
46+
case {"tool": {"ruff": {"lint": {"extend-select": selection}}}}:
47+
selected = frozenset(selection)
48+
case _:
49+
raise SystemExit(1)
50+
51+
linter_txt = subprocess.run(
52+
[find_ruff_bin(), "linter", "--output-format=json"],
53+
check=True,
54+
capture_output=True,
55+
text=True,
56+
cwd=path,
57+
).stdout
58+
linter = json.loads(linter_txt)
59+
60+
lint_info = {r["prefix"]: r["name"] for r in linter if r["prefix"] not in {"", "F"}}
61+
62+
selected_items = {k: v for k, v in lint_info.items() if k in selected}
63+
all_uns_items = {k: v for k, v in lint_info.items() if k not in selected}
64+
unselected_items = {
65+
k: v for k, v in all_uns_items.items() if k not in libs | specialty
66+
}
67+
libs_items = {k: v for k, v in all_uns_items.items() if k in libs}
68+
spec_items = {k: v for k, v in all_uns_items.items() if k in specialty}
69+
70+
def print_each(items: dict[str, str]) -> Iterator[str]:
71+
for k, v in items.items():
72+
kk = f'[green]"{k}"[/green],'
73+
yield f" {kk:23} [dim]# {v}[/dim]"
74+
75+
panel_sel = Panel(
76+
"\n".join(print_each(selected_items)), title="Selected", border_style="green"
77+
)
78+
panel_lib = Panel(
79+
"\n".join(print_each(libs_items)),
80+
title="Library specific",
81+
border_style="yellow",
82+
)
83+
panel_spec = Panel(
84+
"\n".join(print_each(spec_items)), title="Specialized", border_style="yellow"
85+
)
86+
uns = "\n".join(print_each(unselected_items))
87+
88+
if uns:
89+
print(Columns([panel_sel, panel_lib, panel_spec]))
90+
print("[red]Unselected [dim](copy and paste ready)")
91+
print(uns)
92+
93+
94+
def main() -> None:
95+
parser = argparse.ArgumentParser(description="Look up Ruff rules in a directory")
96+
parser.add_argument(
97+
"path",
98+
nargs="?",
99+
type=Path,
100+
default=Path.cwd(),
101+
help="Directory to process (default: current working directory)",
102+
)
103+
args = parser.parse_args()
104+
105+
process_dir(args.path)
106+
107+
108+
if __name__ == "__main__":
109+
main()

pyproject.toml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,23 +161,40 @@ extend-select = [
161161
"ARG", # flake8-unused-arguments
162162
"B", # flake8-bugbear
163163
"C4", # flake8-comprehensions
164+
"DTZ", # flake8-datetimez
164165
"EM", # flake8-errmsg
166+
"EXE", # flake8-executable
165167
"FA", # flake8-future-annotations
168+
"FURB", # refurb
169+
"G", # flake8-logging-format
166170
"I", # isort
167171
"ICN", # flake8-import-conventions
172+
"NPY", # NumPy specific rules
173+
"PD", # pandas-vet
174+
"PERF", # perflint
168175
"PGH", # pygrep-hooks
169176
"PIE", # flake8-pie
170177
"PL", # pylint
171178
"PT", # flake8-pytest-style
172179
"PTH", # flake8-use-pathlib
180+
"PYI", # flake8-pyi
173181
"RET", # flake8-return
174182
"RUF", # Ruff-specific
175183
"SIM", # flake8-simplify
184+
"SLOT", # flake8-slots
176185
"T20", # flake8-print
177-
"TC", # flake8-type-check
186+
"TC", # flake8-type-checking
178187
"TID251", # flake8-tidy-imports.banned-api
179188
"UP", # pyupgrade
180189
"YTT", # flake8-2020
190+
"BLE", # flake8-blind-except
191+
"T10", # flake8-debugger
192+
"ISC", # flake8-implicit-str-concat
193+
"LOG", # flake8-logging
194+
"Q", # flake8-quotes
195+
"RSE", # flake8-raise
196+
"FLY", # flynt
197+
"TRY", # tryceratops
181198
]
182199
ignore = [
183200
"PLR", # Design related pylint codes

src/sp_repo_review/families.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,19 @@ def ruff_description(ruff: dict[str, Any]) -> str:
4848
common = {
4949
"ARG",
5050
"B",
51+
"BLE",
5152
"C4",
5253
"DTZ",
5354
"EM",
5455
"EXE",
5556
"FA",
57+
"FLY",
5658
"FURB",
5759
"G",
5860
"I",
5961
"ICN",
62+
"ISC",
63+
"LOG",
6064
"NPY",
6165
"PD",
6266
"PERF",
@@ -66,12 +70,16 @@ def ruff_description(ruff: dict[str, Any]) -> str:
6670
"PT",
6771
"PTH",
6872
"PYI",
73+
"Q",
6974
"RET",
75+
"RSE",
7076
"RUF",
7177
"SIM",
7278
"SLOT",
79+
"T10",
7380
"T20",
7481
"TC",
82+
"TRY",
7583
"UP",
7684
"YTT",
7785
}

tests/test_families.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,19 @@ def test_python_requires():
6060
ALL_RULES = [
6161
"ARG",
6262
"B",
63+
"BLE",
6364
"C4",
6465
"DTZ",
6566
"EM",
6667
"EXE",
6768
"FA",
69+
"FLY",
6870
"FURB",
6971
"G",
7072
"I",
7173
"ICN",
74+
"ISC",
75+
"LOG",
7276
"NPY",
7377
"PD",
7478
"PERF",
@@ -78,12 +82,16 @@ def test_python_requires():
7882
"PT",
7983
"PTH",
8084
"PYI",
85+
"Q",
8186
"RET",
87+
"RSE",
8288
"RUF",
8389
"SIM",
8490
"SLOT",
91+
"T10",
8592
"T20",
8693
"TC",
94+
"TRY",
8795
"UP",
8896
"YTT",
8997
]

0 commit comments

Comments
 (0)