| import argparse | |
| import ast | |
| import traceback | |
| from typing import List | |
| from typing import NamedTuple | |
| from typing import Optional | |
| from typing import Sequence | |
| DEBUG_STATEMENTS = { | |
| 'ipdb', | |
| 'pdb', | |
| 'pdbr', | |
| 'pudb', | |
| 'pydevd_pycharm', | |
| 'q', | |
| 'rdb', | |
| 'rpdb', | |
| 'wdb', | |
| } | |
| class Debug(NamedTuple): | |
| line: int | |
| col: int | |
| name: str | |
| reason: str | |
| class DebugStatementParser(ast.NodeVisitor): | |
| def __init__(self) -> None: | |
| self.breakpoints: List[Debug] = [] | |
| def visit_Import(self, node: ast.Import) -> None: | |
| for name in node.names: | |
| if name.name in DEBUG_STATEMENTS: | |
| st = Debug(node.lineno, node.col_offset, name.name, 'imported') | |
| self.breakpoints.append(st) | |
| def visit_ImportFrom(self, node: ast.ImportFrom) -> None: | |
| if node.module in DEBUG_STATEMENTS: | |
| st = Debug(node.lineno, node.col_offset, node.module, 'imported') | |
| self.breakpoints.append(st) | |
| def visit_Call(self, node: ast.Call) -> None: | |
| """python3.7+ breakpoint()""" | |
| if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint': | |
| st = Debug(node.lineno, node.col_offset, node.func.id, 'called') | |
| self.breakpoints.append(st) | |
| self.generic_visit(node) | |
| def check_file(filename: str) -> int: | |
| try: | |
| with open(filename, 'rb') as f: | |
| ast_obj = ast.parse(f.read(), filename=filename) | |
| except SyntaxError: | |
| print(f'{filename} - Could not parse ast') | |
| print() | |
| print('\t' + traceback.format_exc().replace('\n', '\n\t')) | |
| print() | |
| return 1 | |
| visitor = DebugStatementParser() | |
| visitor.visit(ast_obj) | |
| for bp in visitor.breakpoints: | |
| print(f'{filename}:{bp.line}:{bp.col} - {bp.name} {bp.reason}') | |
| return int(bool(visitor.breakpoints)) | |
| def main(argv: Optional[Sequence[str]] = None) -> int: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('filenames', nargs='*', help='Filenames to run') | |
| args = parser.parse_args(argv) | |
| retv = 0 | |
| for filename in args.filenames: | |
| retv |= check_file(filename) | |
| return retv | |
| if __name__ == '__main__': | |
| exit(main()) |