ruff/scripts/add_plugin.py
2023-01-20 08:09:54 -05:00

137 lines
4.3 KiB
Python
Executable file

#!/usr/bin/env python3
"""Generate boilerplate for a new Flake8 plugin.
Example usage:
python scripts/add_plugin.py \
flake8-pie \
--url https://pypi.org/project/flake8-pie/0.16.0/
"""
import argparse
import os
from _utils import ROOT_DIR, dir_name, pascal_case
def main(*, plugin: str, url: str) -> None:
# Create the test fixture folder.
os.makedirs(
ROOT_DIR / "resources/test/fixtures" / dir_name(plugin),
exist_ok=True,
)
# Create the Rust module.
rust_module = ROOT_DIR / "src/rules" / dir_name(plugin)
os.makedirs(rust_module, exist_ok=True)
with open(rust_module / "rules.rs", "w+") as fp:
fp.write("use crate::checkers::ast::Checker;\n")
with open(rust_module / "mod.rs", "w+") as fp:
fp.write("pub(crate) mod rules;\n")
fp.write("\n")
fp.write(
"""#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::linter::test_path;
use crate::settings;
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics =test_path(
Path::new("./resources/test/fixtures/%s")
.join(path)
.as_path(),
&settings::Settings::for_rule(rule_code),
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}
"""
% dir_name(plugin)
)
# Add the plugin to `rules/mod.rs`.
with open(ROOT_DIR / "src/rules/mod.rs", "a") as fp:
fp.write(f"pub mod {dir_name(plugin)};")
# Add the relevant sections to `src/registry.rs`.
content = (ROOT_DIR / "src/registry.rs").read_text()
with open(ROOT_DIR / "src/registry.rs", "w") as fp:
for line in content.splitlines():
if line.strip() == "// Ruff":
indent = line.split("// Ruff")[0]
fp.write(f"{indent}// {plugin}")
fp.write("\n")
elif line.strip() == "Ruff,":
indent = line.split("Ruff,")[0]
fp.write(f"{indent}{pascal_case(plugin)},")
fp.write("\n")
elif line.strip() == 'RuleOrigin::Ruff => "Ruff-specific rules",':
indent = line.split('RuleOrigin::Ruff => "Ruff-specific rules",')[0]
fp.write(f'{indent}RuleOrigin::{pascal_case(plugin)} => "{plugin}",')
fp.write("\n")
elif line.strip() == "RuleOrigin::Ruff => vec![RuleCodePrefix::RUF],":
indent = line.split("RuleOrigin::Ruff => vec![RuleCodePrefix::RUF],")[0]
fp.write(
f"{indent}RuleOrigin::{pascal_case(plugin)} => vec![\n"
f'{indent} todo!("Fill-in prefix after generating codes")\n'
f"{indent}],"
)
fp.write("\n")
elif line.strip() == "RuleOrigin::Ruff => None,":
indent = line.split("RuleOrigin::Ruff => None,")[0]
fp.write(f"{indent}RuleOrigin::{pascal_case(plugin)} => " f'Some(("{url}", &Platform::PyPI)),')
fp.write("\n")
fp.write(line)
fp.write("\n")
# Add the relevant section to `src/violations.rs`.
content = (ROOT_DIR / "src/violations.rs").read_text()
with open(ROOT_DIR / "src/violations.rs", "w") as fp:
for line in content.splitlines():
if line.strip() == "// Ruff":
indent = line.split("// Ruff")[0]
fp.write(f"{indent}// {plugin}")
fp.write("\n")
fp.write(line)
fp.write("\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate boilerplate for a new Flake8 plugin.",
epilog=(
"Example usage: python scripts/add_plugin.py flake8-pie "
"--url https://pypi.org/project/flake8-pie/0.16.0/"
),
)
parser.add_argument(
"plugin",
type=str,
help="The name of the plugin to generate.",
)
parser.add_argument(
"--url",
required=True,
type=str,
help="The URL of the latest release in PyPI.",
)
args = parser.parse_args()
main(plugin=args.plugin, url=args.url)