#!/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/ \ --prefix PIE """ from __future__ import annotations import argparse from _utils import ROOT_DIR, dir_name, get_indent, pascal_case def main(*, plugin: str, url: str, prefix_code: str) -> None: """Generate boilerplate for a new plugin.""" # Create the test fixture folder. (ROOT_DIR / "crates/ruff_linter/resources/test/fixtures" / dir_name(plugin)).mkdir( exist_ok=True, ) # Create the Plugin rules module. plugin_dir = ROOT_DIR / "crates/ruff_linter/src/rules" / dir_name(plugin) plugin_dir.mkdir(exist_ok=True) with (plugin_dir / "mod.rs").open("w+") as fp: fp.write(f"//! Rules from [{plugin}]({url}).\n") 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::test::test_path; use crate::{assert_messages, 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("%s").join(path).as_path(), &settings::LinterSettings::for_rule(rule_code), )?; assert_messages!(snapshot, diagnostics); Ok(()) } } """ # noqa: UP031 # Using an f-string here is ugly as all the curly parens need to be escaped % dir_name(plugin), ) # Create a subdirectory for rules and create a `mod.rs` placeholder rules_dir = plugin_dir / "rules" rules_dir.mkdir(exist_ok=True) (rules_dir / "mod.rs").touch() # Create the snapshots subdirectory (plugin_dir / "snapshots").mkdir(exist_ok=True) # Add the plugin to `rules/mod.rs`. rules_mod_path = ROOT_DIR / "crates/ruff_linter/src/rules/mod.rs" lines = rules_mod_path.read_text().strip().splitlines() lines.append(f"pub mod {dir_name(plugin)};") lines.sort() rules_mod_path.write_text("\n".join(lines) + "\n") # Add the relevant sections to `src/registry.rs`. content = (ROOT_DIR / "crates/ruff_linter/src/registry.rs").read_text() with (ROOT_DIR / "crates/ruff_linter/src/registry.rs").open("w") as fp: for line in content.splitlines(): indent = get_indent(line) if line.strip() == "// ruff": fp.write(f"{indent}// {plugin}") fp.write("\n") elif line.strip() == "/// Ruff-specific rules": fp.write(f"{indent}/// [{plugin}]({url})\n") fp.write(f'{indent}#[prefix = "{prefix_code}"]\n') fp.write(f"{indent}{pascal_case(plugin)},") fp.write("\n") fp.write(line) fp.write("\n") text = "" with (ROOT_DIR / "crates/ruff_linter/src/codes.rs").open("r") as fp: while (line := next(fp)).strip() != "// ruff": text += line text += " " * 8 + f"// {plugin}\n\n" text += line text += fp.read() with (ROOT_DIR / "crates/ruff_linter/src/codes.rs").open("w") as fp: fp.write(text) 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/" ), ) 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.", ) parser.add_argument( "--prefix", required=False, default="TODO", type=str, help="Prefix code for the plugin. Leave empty to manually fill.", ) args = parser.parse_args() main(plugin=args.plugin, url=args.url, prefix_code=args.prefix)