Created
December 25, 2024 08:10
-
-
Save pwschaedler/9de151297bb82e4598867b1e4a58a6b5 to your computer and use it in GitHub Desktop.
Get Pylint compatible rules for Ruff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env -S uv run | |
# /// script | |
# requires-python = ">=3.12" | |
# dependencies = [ | |
# "lxml-html-clean", | |
# "requests-html", | |
# "tomlkit", | |
# ] | |
# /// | |
""" | |
A script to determine which Pylint rules have been implemented in Ruff. | |
The list is parsed from [Ruff issue | |
#970](https://github.com/astral-sh/ruff/issues/970) which lists all Pylint rules | |
and which Ruff rule number they correspond to. Some rules may be covered under | |
sections other than the Pylint (PL) section of rules, so this script will | |
discover and organize those rules together in a way that can be added to a | |
`pyproject.toml` Ruff configuration. | |
If run without a command line argument, it will print the list to `stdout` in a | |
way that can be copied into a configuration. If provided the path to a | |
`pyproject.toml` file, it will automatically add the rules to the | |
`select-extends` key. | |
Run with: | |
uv run ruff_pylint_rules.py [pyproject.toml] | |
Alternatively: | |
chmod +x ruff_pylint_rules.py | |
./ruff_pylint_rules.py [pyproject.toml] | |
""" | |
import argparse | |
import re | |
import sys | |
from pathlib import Path | |
import tomlkit | |
from requests_html import HTMLSession | |
from tomlkit.toml_file import TOMLFile | |
def fetch_rules() -> list[str] | None: | |
"""Fetch the GitHub issue page and parse implemented rules.""" | |
session = HTMLSession() | |
resp = session.get('https://github.com/astral-sh/ruff/issues/970') | |
if resp.status_code != 200: | |
print(f'Page request failed: {resp.status_code}', file=sys.stderr) | |
return None | |
# Just getting close enough to the correct element and parsing text from | |
# there is the easiest approach | |
message_body = resp.html.find('div.markdown-body', first=True) | |
lines = message_body.text.splitlines() | |
matches = [re.match(r'.+ / [A-Z]+\d+ \(([A-Z]+\d+)\)', line) for line in lines] | |
rules = [match[1] for match in matches if match is not None] | |
# Ignore PL rules, those can all be selected at once easily | |
rules = sorted([rule for rule in rules if not rule.startswith('PL')]) | |
return rules | |
def write_toml(path: str, rules: list[str]) -> None: | |
"""Write rules to an existing `pyproject.toml` file.""" | |
file = TOMLFile(path) | |
pyproject = file.read() | |
tool = pyproject.get('tool') or tomlkit.table() | |
ruff = tool.get('ruff') or tomlkit.table() | |
lint = ruff.get('lint') or tomlkit.table() | |
select = lint.get('extend-select') or [] | |
select = sorted(set(select) | set(rules)) | |
lint['extend-select'] = select | |
ruff['lint'] = lint | |
tool['ruff'] = ruff | |
pyproject['tool'] = tool | |
file.write(pyproject) | |
def main() -> int: | |
parser = argparse.ArgumentParser() | |
parser.add_argument('pyproject', nargs='?', default=None) | |
args = parser.parse_args() | |
if args.pyproject is not None: | |
path = Path(args.pyproject) | |
if path.name != 'pyproject.toml': | |
print( | |
'Must supply the path to a `pyproject.toml` file, or leave blank to print to `stdout`.', | |
file=sys.stderr, | |
) | |
return 1 | |
if not path.exists(): | |
print('`pyproject.toml` at given path does not exist.', file=sys.stderr) | |
return 1 | |
rules = fetch_rules() | |
if rules is None: | |
print('Parsing rules failed.', file=sys.stderr) | |
return 2 | |
rules = ['PL'] + rules | |
if args.pyproject is None: | |
print(repr(rules).replace("'", '"')) | |
return 0 | |
write_toml(args.pyproject, rules) | |
return 0 | |
if __name__ == '__main__': | |
raise SystemExit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment