def show( unused_import: List[Union[Import, ImportFrom]], py_path: Path ) -> None: for imp in unused_import: context = "" if ( isinstance(imp, ImportFrom) and imp.star and imp.module and imp.modules ): context = ( Color(f"from {imp.name} import *").red + " -> " + Color( f"from {imp.name} import {', '.join(imp.modules)}" ).green ) else: context = Color(imp.name).yellow print( context + " at " + Color(str(py_path)).green + ":" + Color(str(imp.lineno)).green )
def run_visit(self, source: Union[str, bytes], mode: str = "exec") -> None: try: tree = ast.parse(source, mode=mode) except SyntaxError as err: print(Color(str(err)).red) else: Relate(tree) self.visit(tree)
def read(self, path: Path) -> Tuple[str, str]: try: with tokenize.open(path) as stream: source = stream.read() encoding = stream.encoding except (OSError, SyntaxError) as err: if self.show_error: print(Color(str(err)).red) return "", "utf-8" return source, encoding
def refactor_string(source, unused_imports): try: wrapper = MetadataWrapper(cst.parse_module(source)) except cst.ParserSyntaxError as err: print(Color(str(err)).red) else: if unused_imports: fixed_module = wrapper.visit( RemoveUnusedImportTransformer(unused_imports)) return fixed_module.code return source
def main(argv=None): namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [value for key, value in vars(namespace).items()][5:-1]) session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, ) include_list, exclude_list = [], [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include or "") if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude or "") include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern _any_unimport = False for source_path in namespace.sources: for py_path in session._list_paths(source_path, include, exclude): if namespace.check: session.scanner.run_visit(source=session._read(py_path)[0]) unused_imports = list(session.scanner.get_unused_imports()) show(unused_imports, py_path) if not (not _any_unimport and not unused_imports): _any_unimport = True session.scanner.clear() if namespace.diff or namespace.permission: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": break elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session._read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not _any_unimport and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green)
def color_diff(sequence: Tuple[str, ...]) -> str: contents = "\n".join(sequence) lines = contents.split("\n") for i, line in enumerate(lines): paint = Color(line) if line.startswith("+++") or line.startswith("---"): line = paint.bold_white if line.startswith("@@"): line = paint.cyan if line.startswith("+"): line = paint.green elif line.startswith("-"): line = paint.red lines[i] = line return "\n".join(lines)
def refactor_string( source: str, unused_imports: "List[TYPE_IMPORT]", show_error: bool, ) -> str: try: wrapper = MetadataWrapper(cst.parse_module(source)) except cst.ParserSyntaxError as err: if show_error: print(Color(str(err)).red) else: if unused_imports: fixed_module = wrapper.visit( RemoveUnusedImportTransformer(unused_imports)) return fixed_module.code return source
def main(argv: Optional[List[str]] = None) -> None: namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [namespace.diff, namespace.remove, namespace.permission] ) namespace.diff = namespace.diff or namespace.permission session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, show_error=namespace.show_error, ) include_list = [] exclude_list = [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include) # type: ignore if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude) # type: ignore include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern is_unused_module = False unused_modules = set() for source_path in namespace.sources: for py_path in session.list_paths(source_path, include, exclude): session.scanner.scan(source=session.read(py_path)[0]) unused_imports = session.scanner.unused_imports if not is_unused_module and unused_imports: is_unused_module = True if unused_imports: unused_modules.update( { imp.module.__name__.split(".")[0] # type: ignore for imp in unused_imports if imp.module } ) if namespace.check: show(unused_imports, py_path) session.scanner.clear() if namespace.diff: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": return elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session.read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not is_unused_module and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green ) if namespace.requirements and unused_modules: requirements_path = Path("requirements.txt") if not requirements_path.exists(): return result = "" source = requirements_path.read_text() for index, requirement in enumerate(source.splitlines()): if requirement.split("==")[0] not in unused_modules: result += f"{requirement}\n" else: if namespace.check and requirement: print( f"{Color(requirement).cyan} at " f"{Color(str(requirements_path)).cyan}:{Color(str(index + 1)).cyan}" ) if namespace.diff: exists_diff = print_if_exists( tuple( difflib.unified_diff( source.splitlines(), result.splitlines(), fromfile=str(requirements_path), ) ) ) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(requirements_path)).cyan}' [Y/n] ? >" ).lower() if action == "y" or action == "": namespace.remove = True if namespace.remove: requirements_path.write_text(result) print(f"Refactoring '{Color(str(requirements_path)).cyan}'")
def main(argv: Optional[List[str]] = None) -> int: argv = argv if argv is not None else sys.argv[1:] parser = argparse.ArgumentParser( prog="unimport", description=C.DESCRIPTION, epilog="Get rid of all unused imports 🥳", ) exclusive_group = parser.add_mutually_exclusive_group(required=False) parser.add_argument( "sources", default=[Path(".")], nargs="*", help="files and folders to find the unused imports.", action="store", type=Path, ) parser.add_argument( "-c", "--config", default=".", help="read configuration from PATH.", metavar="PATH", action="store", type=Path, ) parser.add_argument( "--include", help="file include pattern.", metavar="include", action="store", default="", type=str, ) parser.add_argument( "--exclude", help="file exclude pattern.", metavar="exclude", action="store", default="", type=str, ) parser.add_argument( "--include-star-import", action="store_true", help="Include star imports during scanning and refactor.", ) parser.add_argument( "--show-error", action="store_true", help="Show or don't show errors captured during static analysis.", ) parser.add_argument( "-d", "--diff", action="store_true", help="Prints a diff of all the changes unimport would make to a file.", ) exclusive_group.add_argument( "-r", "--remove", action="store_true", help="remove unused imports automatically.", ) exclusive_group.add_argument( "-p", "--permission", action="store_true", help="Refactor permission after see diff.", ) parser.add_argument( "--requirements", action="store_true", help= "Include requirements.txt file, You can use it with all other arguments", ) parser.add_argument( "--check", action="store_true", help="Prints which file the unused imports are in.", ) parser.add_argument( "-v", "--version", action="version", version=f"Unimport {C.VERSION}", help="Prints version of unimport", ) namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [namespace.diff, namespace.remove, namespace.permission]) namespace.diff = namespace.diff or namespace.permission session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, show_error=namespace.show_error, ) include_list = [] exclude_list = [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include) # type: ignore if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude) # type: ignore include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern unused_modules = set() for source_path in namespace.sources: for py_path in session.list_paths(source_path, include, exclude): session.scanner.scan(source=session.read(py_path)[0]) unused_imports = session.scanner.unused_imports if unused_imports: unused_modules.update({ imp.module.__name__.split(".")[0] # type: ignore for imp in unused_imports if imp.module }) if namespace.check: show(unused_imports, py_path) session.scanner.clear() if namespace.diff: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": return 1 elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session.read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not unused_modules and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green) requirements_path = Path("requirements.txt") if (namespace.requirements and unused_modules and requirements_path.exists()): result = "" source = requirements_path.read_text() for index, requirement in enumerate(source.splitlines()): if requirement.split("==")[0] not in unused_modules: result += f"{requirement}\n" else: if namespace.check and requirement: print( f"{Color(requirement).cyan} at " f"{Color(str(requirements_path)).cyan}:{Color(str(index + 1)).cyan}" ) if namespace.diff: exists_diff = print_if_exists( tuple( difflib.unified_diff( source.splitlines(), result.splitlines(), fromfile=str(requirements_path), ))) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(requirements_path)).cyan}' [Y/n] ? >" ).lower() if action == "y" or action == "": namespace.remove = True if namespace.remove: requirements_path.write_text(result) print(f"Refactoring '{Color(str(requirements_path)).cyan}'") if unused_modules: return 1 else: return 0