#!/usr/bin/env python3 """Extensible script to run one or more Python Linters across a subset of files in parallel.""" import argparse import logging import os import sys import structlog mongo_dir = os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) # Get relative imports to work when the package is not installed on the PYTHONPATH. if __name__ == "__main__" and __package__ is None: sys.path.append(mongo_dir) # pylint: disable=wrong-import-position from buildscripts.linter import ruffchecker, ruffformatter, runner # pylint: enable=wrong-import-position # List of supported linters _LINTERS = [ ruffchecker.RuffChecker(), ruffformatter.RuffFormatter(), ] def lint(file_names: list[str]): """Lint files command entry point.""" lint_runner = runner.LintRunner() linter_instances = runner.find_linters(_LINTERS, {}) any_failed = False for linter in linter_instances: lint_success = lint_runner.run_lint(linter, file_names, mongo_dir) any_failed |= not lint_success if any_failed: print("ERROR: Code Style does not match coding style") print("To fix formatting errors, run `buildscripts/pylinters.py fix`.") sys.exit(1) def fix_func(file_names: list[str]): """Fix a list of files with linters if possible.""" lint_runner = runner.LintRunner() linter_instances = runner.find_linters(_LINTERS, {}) any_failed = False for linter in linter_instances: lint_success = lint_runner.run_fix(linter, file_names, mongo_dir) any_failed |= not lint_success if any_failed: print("ERROR: Code Style does not match coding style") print("Code could not be automatically fixed.") sys.exit(1) def main(): # type: () -> None """Execute Main entry point.""" parser = argparse.ArgumentParser( description="PyLinter frontend; see more details at https://wiki.corp.mongodb.com/x/1vP5BQ" ) parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging") sub = parser.add_subparsers(title="Linter subcommands", help="sub-command help") parser_lint = sub.add_parser( "lint", help="Lint files and exit with nonzero status if any violations are found" ) parser_lint.add_argument("file_names", nargs="*", help="Globs of files to check") parser_lint.set_defaults(func=lint) parser_fix = sub.add_parser("fix", help="Fix files if possible") parser_fix.add_argument("file_names", nargs="*", help="Globs of files to check") parser_fix.set_defaults(func=fix_func) # No args given? Fall back to usage screen: if len(sys.argv) == 1: parser.print_help() return args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.DEBUG) structlog.configure(logger_factory=structlog.stdlib.LoggerFactory()) args.func(args.file_names) if __name__ == "__main__": main()