diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10201019..ee7ce6a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,4 +60,4 @@ repos: - id: pyall args: - --refactor - - --exclude=tests|setup.py + - --exclude=tests|setup.py|__init__.py| diff --git a/docs/changelog.md b/docs/changelog.md index 0eefa7e9..a5389cf9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. - []() +## [0.9.4] - 29/January/2021 + +- [๐Ÿ’ช ๐Ÿ”ฅ ๐Ÿงช i199 Refactor options & commands & Option color output by @hakancelik96](https://github.com/hakancelik96/unimport/pull/205) +- [๐Ÿ’ช ๐Ÿ”ฅ Build an Docker image #202 by @hakancelik96](https://github.com/hakancelik96/unimport/issues/202) + ## [0.9.2] - 14/September/2021 - ๐Ÿž Fix setup.py diff --git a/docs/index.md b/docs/index.md index 02ccb07a..f00f0903 100644 --- a/docs/index.md +++ b/docs/index.md @@ -154,6 +154,18 @@ You can automatically delete unused modules from the requirements.txt file - `unimport --requirements --permission` to refactor permission after seeing the diff. - `unimport --requirements --remove` to remove automatically. +## Color + +> (optional: default `auto`) choices: (always, never, auto) + +Select whether to use color in the output. + +**Usage** + +- `unimport --color always` +- `unimport --color never` +- `unimport --color auto` + ## Typing Unimport can understand that imports are used these cases. @@ -351,9 +363,9 @@ class Klass: You can list many options by running unimport --help ``` -usage: unimport [-h] [--check] [-c PATH] [--include include] [--exclude exclude] [--gitignore] - [--ignore-init] [--include-star-import] [-d] [-r | -p] [--requirements] [-v] - [sources [sources ...]] +usage: unimport [-h] [--check] [-c PATH] [--color {auto,always,never}] [--include include] [--exclude exclude] [--gitignore] [--ignore-init] [--include-star-import] [-d] + [-r | -p] [--requirements] [-v] + [sources ...] A linter, formatter for finding and removing unused import statements. @@ -365,6 +377,8 @@ optional arguments: --check Prints which file the unused imports are in. -c PATH, --config PATH Read configuration from PATH. + --color {auto,always,never} + Select whether to use color in the output. Defaults to `auto`. --include include File include pattern. --exclude exclude File exclude pattern. --gitignore Exclude .gitignore patterns. if present. @@ -438,3 +452,28 @@ repos: args: [--remove, --requirements, --include-star-import, --ignore-init, --gitignore] ``` + +## Use as a Docker image + +Install from the command line: + +To use the latest + +``` +$ docker pull ghcr.io/hakancelik96/unimport:latest +``` + +To use the stable + +``` +$ docker pull ghcr.io/hakancelik96/unimport:stable +``` + +To use the other versions + +``` +$ docker pull ghcr.io/hakancelik96/unimport:{version_number} +``` + +For more information see: +https://github.com/hakancelik96/unimport/pkgs/container/unimport diff --git a/setup.py b/setup.py index 1611143e..a8aab621 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def get_long_description(): DESCRIPTION = ( "A linter, formatter for finding and removing unused import statements." ) -VERSION = "0.9.2" +VERSION = "0.9.4" setup( @@ -38,7 +38,7 @@ def get_long_description(): license="MIT", license_file="LICENSE", python_requires=">=3.6", - packages=["unimport"], + packages=["unimport", "unimport.commands"], install_requires=[ "libcst>=0.3.7; python_version >= '3.9'", "libcst>=0.3.0; python_version <= '3.8'", diff --git a/tests/analyzer/test__analyzer.py b/tests/analyzer/test__analyzer.py new file mode 100644 index 00000000..a8083354 --- /dev/null +++ b/tests/analyzer/test__analyzer.py @@ -0,0 +1,22 @@ +import textwrap +import unittest + +from unimport.analyzer import Analyzer +from unimport.statement import Import + + +class AnalyzerTestCase(unittest.TestCase): + def setUp(self) -> None: + Analyzer.clear() + + def test_context_manager(self): + with Analyzer( + source=textwrap.dedent( + """\ + import x + """ + ) + ): + self.assertEqual(1, len(Import.imports)) + + self.assertEqual(0, len(Import.imports)) diff --git a/tests/test_color.py b/tests/test_color.py index 11f7f222..9b690ae6 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,14 +1,16 @@ import sys import unittest -from unimport.color import RED, RESET, TERMINAL_SUPPORT_COLOR, paint +import pytest + +from unimport.color import RED, RESET, TERMINAL_SUPPORT_COLOR, paint, use_color class ColorTestCase(unittest.TestCase): maxDiff = None def setUp(self): - self.content = "test content" + self.text = "test text" @unittest.skipUnless(sys.platform == "win32", "requires Windows32") def test_terminal_support_color_on_win(self): @@ -25,17 +27,36 @@ def test_terminal_support_color_on_win(self): def test_terminal_support_color(self): self.assertTrue(TERMINAL_SUPPORT_COLOR) - @unittest.skipUnless(sys.platform == "win32", "requires Windows32") - def test_red_paint_on_win(self): - action_content = paint(self.content, RED) - if TERMINAL_SUPPORT_COLOR: - expected_content = RED + self.content + RESET - else: - expected_content = self.content - self.assertEqual(expected_content, action_content) - - @unittest.skipUnless(sys.platform != "win32", "requires Windows32") def test_red_paint(self): - action_content = paint(self.content, RED) - expected_content = RED + self.content + RESET - self.assertEqual(expected_content, action_content) + action_text = paint(self.text, RED) + expected_text = RED + self.text + RESET + self.assertEqual(expected_text, action_text) + + def test_use_color_setting_false(self): + action_text = paint(self.text, RED, False) + expected_text = self.text + self.assertEqual(expected_text, action_text) + + def test_use_color_setting_true(self): + action_text = paint(self.text, RED, True) + expected_text = RED + self.text + RESET + self.assertEqual(expected_text, action_text) + + +@pytest.mark.parametrize( + "option,expected_result", + [ + ("auto", TERMINAL_SUPPORT_COLOR and sys.stderr.isatty()), + ("always", True), + ("never", False), + ], +) +def test_use_color(option, expected_result): + assert expected_result == use_color(option) + + +def test_use_color_none_of_them(): + with pytest.raises(ValueError) as cm: + use_color("none-of-them") + + assert "none-of-them" in str(cm.value) diff --git a/tests/test_utils.py b/tests/test_utils.py index 58adfcc5..f75571e7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -21,7 +21,7 @@ class UtilsTestCase(unittest.TestCase): ) def test_list_paths(self): - self.assertEqual(len(list(utils.list_paths(Path("tests")))), 33) + self.assertEqual(len(list(utils.list_paths(Path("tests")))), 34) self.assertEqual( len(list(utils.list_paths(Path("tests/test_config.py")))), 1 ) diff --git a/unimport/__main__.py b/unimport/__main__.py index 581f69b2..5572d771 100644 --- a/unimport/__main__.py +++ b/unimport/__main__.py @@ -1,6 +1,4 @@ -import sys - from unimport.main import main if __name__ == "__main__": - sys.exit(main()) + raise SystemExit(main()) diff --git a/unimport/analyzer.py b/unimport/analyzer.py index 0f04ec9c..c1fc20f1 100644 --- a/unimport/analyzer.py +++ b/unimport/analyzer.py @@ -447,6 +447,13 @@ def __init__( self.path = path self.include_star_import = include_star_import + def __enter__(self): + self.traverse() + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.clear() + def traverse(self) -> None: if self.skip_file(): return None diff --git a/unimport/color.py b/unimport/color.py index 3490ccdf..93ad860e 100644 --- a/unimport/color.py +++ b/unimport/color.py @@ -1,3 +1,4 @@ +import argparse import sys from typing import Tuple @@ -5,6 +6,7 @@ "BLACK", "BLUE", "BOLD_WHITE", + "COLOR_CHOICES", "CYAN", "GREEN", "MAGENTA", @@ -13,8 +15,10 @@ "TERMINAL_SUPPORT_COLOR", "WHITE", "YELLOW", + "add_color_option", "difference", "paint", + "use_color", ] if sys.platform == "win32": # pragma: no cover (windows) @@ -81,16 +85,37 @@ def bool_errcheck(result, func, args): WHITE = "\033[97m" BOLD_WHITE = "\033[1;37m" +COLOR_CHOICES = ("auto", "always", "never") -def paint(content: str, color: str) -> str: - if TERMINAL_SUPPORT_COLOR: - return color + content + RESET + +def paint(text: str, color: str, use_color_setting: bool = True) -> str: + if use_color_setting: + return color + text + RESET else: - return content + return text + + +def use_color(setting: str) -> bool: + if setting not in COLOR_CHOICES: + raise ValueError(setting) + + return setting == "always" or ( + setting == "auto" and sys.stderr.isatty() and TERMINAL_SUPPORT_COLOR + ) + + +def add_color_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--color", + default="auto", + type=use_color, + metavar="{" + ",".join(COLOR_CHOICES) + "}", + help="Select whether to use color in the output. Defaults to `%(default)s`.", + ) -def difference(content: Tuple[str, ...]) -> str: # pragma: no cover - lines = list(content) +def difference(text: Tuple[str, ...]) -> str: # pragma: no cover + lines = list(text) for i, line in enumerate(lines): if line.startswith("+++") or line.startswith("---"): lines[i] = paint(line, BOLD_WHITE) diff --git a/unimport/commands/__init__.py b/unimport/commands/__init__.py new file mode 100644 index 00000000..a20bc3a7 --- /dev/null +++ b/unimport/commands/__init__.py @@ -0,0 +1,15 @@ +from unimport.commands.check import check, requirements_check +from unimport.commands.diff import diff, requirements_diff +from unimport.commands.permission import permission, requirements_permission +from unimport.commands.remove import remove, requirements_remove + +__all__ = [ + "check", + "diff", + "permission", + "remove", + "requirements_check", + "requirements_diff", + "requirements_permission", + "requirements_remove", +] diff --git a/unimport/commands/check.py b/unimport/commands/check.py new file mode 100644 index 00000000..a2b94288 --- /dev/null +++ b/unimport/commands/check.py @@ -0,0 +1,43 @@ +from pathlib import Path +from typing import List, Union + +from unimport.color import CYAN, GREEN, RED, YELLOW, paint +from unimport.statement import Import, ImportFrom + +__all__ = ["check", "requirements_check"] + + +def check( + path: Path, + unused_imports: List[Union[Import, ImportFrom]], + use_color_setting: bool, +) -> None: + for imp in unused_imports: + if isinstance(imp, ImportFrom) and imp.star and imp.suggestions: + context = ( + paint(f"from {imp.name} import *", RED, use_color_setting) + + " -> " + + paint( + f"from {imp.name} import {', '.join(imp.suggestions)}", + GREEN, + use_color_setting, + ) + ) + else: + context = paint(imp.name, YELLOW, use_color_setting) + print( + context + + " at " + + paint(path.as_posix(), GREEN, use_color_setting) + + ":" + + paint(str(imp.lineno), GREEN, use_color_setting) + ) + + +def requirements_check( + path: Path, index: int, requirement: str, use_color_setting: bool +) -> None: + print( + f"{paint(requirement, CYAN), use_color_setting} at " + f"{paint(path.as_posix(), CYAN, use_color_setting)}:{paint(str(index + 1), CYAN, use_color_setting)}" + ) diff --git a/unimport/commands/diff.py b/unimport/commands/diff.py new file mode 100644 index 00000000..ccfa4400 --- /dev/null +++ b/unimport/commands/diff.py @@ -0,0 +1,34 @@ +from pathlib import Path + +from unimport import utils +from unimport.color import difference as color_difference + +__all__ = ["diff", "requirements_diff"] + + +def diff( + path: Path, + source: str, + refactor_result: str, +) -> bool: + diff = utils.diff( + source=source, refactor_result=refactor_result, fromfile=path + ) + exists_diff = bool(diff) + if exists_diff: + print(color_difference(diff)) + + return exists_diff + + +def requirements_diff(path: Path, source: str, refactor_result: str) -> bool: + diff = utils.diff( + source=source, + refactor_result=refactor_result, + fromfile=path, + ) + exists_diff = bool(diff) + if exists_diff: + print(color_difference(diff)) + + return exists_diff diff --git a/unimport/commands/options.py b/unimport/commands/options.py new file mode 100644 index 00000000..17c47e2e --- /dev/null +++ b/unimport/commands/options.py @@ -0,0 +1,153 @@ +import argparse +from pathlib import Path + +from unimport import constants as C +from unimport.config import DefaultConfig + +__all__ = [ + "add_sources_option", + "add_check_option", + "add_config_option", + "add_include_option", + "add_exclude_option", + "add_gitignore_option", + "add_ignore_init_option", + "add_include_star_import_option", + "add_diff_option", + "add_remove_option", + "add_permission_option", + "add_requirements_option", + "add_version_option", +] + +default_config = DefaultConfig() + + +def add_sources_option(parser: argparse.ArgumentParser): + parser.add_argument( + "sources", + default=default_config.sources, + nargs="*", + help="Files and folders to find the unused imports.", + action="store", + type=Path, + ) + + +def add_check_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--check", + action="store_true", + help="Prints which file the unused imports are in.", + default=default_config.check, + ) + + +def add_config_option(parser: argparse.ArgumentParser): + parser.add_argument( + "-c", + "--config", + default=".", + help="Read configuration from PATH.", + metavar="PATH", + action="store", + type=Path, + ) + + +def add_include_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--include", + help="File include pattern.", + metavar="include", + action="store", + default=default_config.include, + type=str, + ) + + +def add_exclude_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--exclude", + help="File exclude pattern.", + metavar="exclude", + action="store", + default=default_config.exclude, + type=str, + ) + + +def add_gitignore_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--gitignore", + action="store_true", + help="Exclude .gitignore patterns. if present.", + default=default_config.gitignore, + ) + + +def add_ignore_init_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--ignore-init", + action="store_true", + help="Ignore the __init__.py file.", + default=default_config.ignore_init, + ) + + +def add_include_star_import_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--include-star-import", + action="store_true", + help="Include star imports during scanning and refactor.", + default=default_config.include_star_import, + ) + + +def add_diff_option(parser: argparse.ArgumentParser): + parser.add_argument( + "-d", + "--diff", + action="store_true", + help="Prints a diff of all the changes unimport would make to a file.", + default=default_config.diff, + ) + + +def add_remove_option(exclusive_group: argparse._MutuallyExclusiveGroup): + exclusive_group.add_argument( + "-r", + "--remove", + action="store_true", + help="Remove unused imports automatically.", + default=default_config.remove, + ) + + +def add_permission_option(exclusive_group: argparse._MutuallyExclusiveGroup): + exclusive_group.add_argument( + "-p", + "--permission", + action="store_true", + help="Refactor permission after see diff.", + default=default_config.permission, + ) + + +def add_requirements_option(parser: argparse.ArgumentParser): + parser.add_argument( + "--requirements", + action="store_true", + help="Include requirements.txt file, You can use it with all other arguments", + default=default_config.requirements, + ) + + +def add_version_option(parser: argparse.ArgumentParser): + parser.add_argument( + "-v", + "--version", + action="version", + version=f"Unimport {C.VERSION}", + help="Prints version of unimport", + ) diff --git a/unimport/commands/permission.py b/unimport/commands/permission.py new file mode 100644 index 00000000..6065801b --- /dev/null +++ b/unimport/commands/permission.py @@ -0,0 +1,38 @@ +from pathlib import Path + +from unimport import utils +from unimport.color import CYAN, YELLOW, paint + +__all__ = ["permission", "requirements_permission"] + + +def permission( + path: Path, + encoding: str, + newline: str, + refactor_result: str, + use_color_setting: bool, +) -> None: + action = input( + f"Apply suggested changes to '{paint(str(path), YELLOW, use_color_setting)}' [Y/n/q] ? >" + ).lower() + if action == "q": + raise SystemExit(1) + elif utils.actiontobool(action): + from unimport.commands import remove + + remove(path, encoding, newline, refactor_result, use_color_setting) + + +def requirements_permission( + path: Path, refactor_result: str, use_color_setting: bool +): + action = input( + f"Apply suggested changes to '{paint(path.as_posix(), CYAN, use_color_setting)}' [Y/n/q] ? >" + ).lower() + if action == "q": + return 1 + if utils.actiontobool(action): + from unimport.commands import requirements_remove + + requirements_remove(path, refactor_result, use_color_setting) diff --git a/unimport/commands/remove.py b/unimport/commands/remove.py new file mode 100644 index 00000000..2a0892e4 --- /dev/null +++ b/unimport/commands/remove.py @@ -0,0 +1,21 @@ +from pathlib import Path + +from unimport.color import CYAN, GREEN, paint + +__all__ = ["remove", "requirements_remove"] + + +def remove( + path: Path, encoding, newline, refactor_result: str, use_color_setting +) -> None: + with open(path, mode="w", encoding=encoding, newline=newline) as py_file: + py_file.write(refactor_result) + + print(f"Refactoring '{paint(str(path), GREEN, use_color_setting)}'") + + +def requirements_remove( + path: Path, refactor_result: str, use_color_setting: bool +) -> None: + path.write_text(refactor_result) + print(f"Refactoring '{paint(path.as_posix(), CYAN, use_color_setting)}'") diff --git a/unimport/config.py b/unimport/config.py index 9c084cd3..5263bd98 100644 --- a/unimport/config.py +++ b/unimport/config.py @@ -1,7 +1,8 @@ +import argparse import configparser from ast import literal_eval from pathlib import Path -from typing import List, NamedTuple +from typing import Iterator, List, NamedTuple import toml @@ -54,6 +55,15 @@ def merge(self, **kwargs): ) return config + def get_paths(self) -> Iterator[Path]: + for source_path in self.sources: + yield from utils.list_paths( + source_path, self.include, self.exclude + ) + + def get_requirements(self) -> Iterator[Path]: + yield from Path(".").glob("requirements*.txt") + class Config: @@ -100,3 +110,14 @@ def parse_toml(self) -> DefaultConfig: sources = config.get("sources", self.default_config.sources) config["sources"] = [Path(path) for path in sources] return self.default_config._replace(**config) + + @classmethod + def get_config(cls, args: argparse.Namespace) -> DefaultConfig: + config = ( + Config(args.config).parse() + if args.config and args.config.name in CONFIG_FILES + else cls.default_config + ) + config = config.merge(**vars(args)) + + return config diff --git a/unimport/constants.py b/unimport/constants.py index 03082244..233dd32d 100644 --- a/unimport/constants.py +++ b/unimport/constants.py @@ -11,7 +11,7 @@ DESCRIPTION = ( "A linter, formatter for finding and removing unused import statements." ) -VERSION = "0.9.2" +VERSION = "0.9.4" __all__ = [ "BUILTINS", diff --git a/unimport/main.py b/unimport/main.py index ae0b7ae2..7545e228 100644 --- a/unimport/main.py +++ b/unimport/main.py @@ -1,20 +1,18 @@ import argparse import sys -from pathlib import Path from typing import Optional, Sequence, Set -from unimport import color +from unimport import color, commands from unimport import constants as C from unimport import emoji, utils from unimport.analyzer import Analyzer -from unimport.config import CONFIG_FILES, Config, DefaultConfig +from unimport.commands import options +from unimport.config import Config from unimport.refactor import refactor_string -from unimport.statement import Import, ImportFrom +from unimport.statement import Import __all__ = ["main"] -default_config = DefaultConfig() - def main(argv: Optional[Sequence[str]] = None) -> int: parser = argparse.ArgumentParser( @@ -23,118 +21,37 @@ def main(argv: Optional[Sequence[str]] = None) -> int: epilog=f"Get rid of all unused imports {emoji.PARTYING_FACE}", ) exclusive_group = parser.add_mutually_exclusive_group(required=False) - parser.add_argument( - "sources", - default=default_config.sources, - nargs="*", - help="Files and folders to find the unused imports.", - action="store", - type=Path, - ) - parser.add_argument( - "--check", - action="store_true", - help="Prints which file the unused imports are in.", - default=default_config.check, - ) - 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=default_config.include, - type=str, - ) - parser.add_argument( - "--exclude", - help="File exclude pattern.", - metavar="exclude", - action="store", - default=default_config.exclude, - type=str, - ) - parser.add_argument( - "--gitignore", - action="store_true", - help="Exclude .gitignore patterns. if present.", - default=default_config.gitignore, - ) - parser.add_argument( - "--ignore-init", - action="store_true", - help="Ignore the __init__.py file.", - default=default_config.ignore_init, - ) - parser.add_argument( - "--include-star-import", - action="store_true", - help="Include star imports during scanning and refactor.", - default=default_config.include_star_import, - ) - parser.add_argument( - "-d", - "--diff", - action="store_true", - help="Prints a diff of all the changes unimport would make to a file.", - default=default_config.diff, - ) - exclusive_group.add_argument( - "-r", - "--remove", - action="store_true", - help="Remove unused imports automatically.", - default=default_config.remove, - ) - exclusive_group.add_argument( - "-p", - "--permission", - action="store_true", - help="Refactor permission after see diff.", - default=default_config.permission, - ) - parser.add_argument( - "--requirements", - action="store_true", - help="Include requirements.txt file, You can use it with all other arguments", - default=default_config.requirements, - ) - parser.add_argument( - "-v", - "--version", - action="version", - version=f"Unimport {C.VERSION}", - help="Prints version of unimport", - ) + + options.add_sources_option(parser) + options.add_check_option(parser) + options.add_config_option(parser) + color.add_color_option(parser) + options.add_include_option(parser) + options.add_exclude_option(parser) + options.add_gitignore_option(parser) + options.add_ignore_init_option(parser) + options.add_include_star_import_option(parser) + options.add_diff_option(parser) + options.add_remove_option(exclusive_group) + options.add_permission_option(exclusive_group) + options.add_requirements_option(parser) + options.add_version_option(parser) + argv = argv if argv is not None else sys.argv[1:] args = parser.parse_args(argv) - config = ( - Config(args.config).parse() - if args.config and args.config.name in CONFIG_FILES - else default_config - ) - config = config.merge(**vars(args)) + config = Config.get_config(args) + unused_modules = set() used_packages: Set[str] = set() - for source_path in config.sources: - for py_path in utils.list_paths( - source_path, config.include, config.exclude + + for path in config.get_paths(): + source, encoding, newline = utils.read(path) + + with Analyzer( + source=source, + path=path, + include_star_import=config.include_star_import, ): - source, encoding, newline = utils.read(py_path) - analyzer = Analyzer( - source=source, - path=py_path, - include_star_import=config.include_star_import, - ) - analyzer.traverse() unused_imports = list( Import.get_unused_imports(config.include_star_import) ) @@ -143,108 +60,70 @@ def main(argv: Optional[Sequence[str]] = None) -> int: utils.get_used_packages(Import.imports, unused_imports) ) if config.check: - for imp in unused_imports: - if ( - isinstance(imp, ImportFrom) - and imp.star - and imp.suggestions - ): - context = ( - color.paint(f"from {imp.name} import *", color.RED) - + " -> " - + color.paint( - f"from {imp.name} import {', '.join(imp.suggestions)}", - color.GREEN, - ) - ) - else: - context = color.paint(imp.name, color.YELLOW) - print( - context - + " at " - + color.paint(py_path.as_posix(), color.GREEN) - + ":" - + color.paint(str(imp.lineno), color.GREEN) - ) + commands.check(path, unused_imports, args.color) if any((config.diff, config.remove)): refactor_result = refactor_string( source=source, unused_imports=unused_imports, ) - if config.diff: - diff = utils.diff( - source=source, - refactor_result=refactor_result, - fromfile=py_path, - ) - exists_diff = bool(diff) - if exists_diff: - print(color.difference(diff)) - if config.permission and exists_diff: - action = input( - f"Apply suggested changes to '{color.paint(str(py_path), color.YELLOW)}' [Y/n/q] ? >" - ).lower() - if action == "q": - return 1 - elif utils.actiontobool(action): - config = config._replace(remove=True) - if config.remove and source != refactor_result: - with open( - py_path, mode="w", encoding=encoding, newline=newline - ) as py_file: - py_file.write(refactor_result) - print( - f"Refactoring '{color.paint(str(py_path), color.GREEN)}'" - ) - analyzer.clear() + if config.diff: + exists_diff = commands.diff(path, source, refactor_result) + if config.permission and exists_diff: + commands.permission( + path, encoding, newline, refactor_result, args.color + ) + if config.remove and source != refactor_result: + commands.remove( + path, encoding, newline, refactor_result, args.color + ) + if not unused_modules and config.check: print( color.paint( f"{emoji.STAR} Congratulations there is no unused import in your project. {emoji.STAR}", color.GREEN, + args.color, ) ) if config.requirements: - for requirements in Path(".").glob("requirements*.txt"): - source = requirements.read_text() + for path in config.get_requirements(): + source = path.read_text() copy_source = source.splitlines().copy() + for index, requirement in enumerate(source.splitlines()): module_name = utils.package_name_from_metadata( requirement.split("==")[0] ) if module_name is None: - print(color.paint(requirement + " not found", color.RED)) + print( + color.paint( + requirement + " not found", color.RED, args.color + ) + ) continue + if module_name not in used_packages: copy_source.remove(requirement) + if config.check: - print( - f"{color.paint(requirement, color.CYAN)} at " - f"{color.paint(requirements.as_posix(), color.CYAN)}:{color.paint(str(index + 1), color.CYAN)}" + commands.requirements_check( + path, index, requirement, args.color ) + refactor_result = "\n".join(copy_source) if config.diff: - diff = utils.diff( - source=source, - refactor_result=refactor_result, - fromfile=requirements, - ) - exists_diff = bool(diff) - if exists_diff: - print(color.difference(diff)) - if config.permission and exists_diff: - action = input( - f"Apply suggested changes to '{color.paint(requirements.as_posix(), color.CYAN)}' [Y/n/q] ? >" - ).lower() - if action == "q": - return 1 - if utils.actiontobool(action): - config = config._replace(remove=True) - if config.remove: - requirements.write_text(refactor_result) - print( - f"Refactoring '{color.paint(requirements.as_posix(), color.CYAN)}'" + exists_diff = commands.requirements_diff( + path, source, refactor_result ) + if config.permission and exists_diff: + commands.requirements_permission( + path, refactor_result, args.color + ) + if config.remove and source != refactor_result: + commands.requirements_remove( + path, refactor_result, args.color + ) + if unused_modules: return 1 else: @@ -252,4 +131,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int: if __name__ == "__main__": - sys.exit(main()) + raise SystemExit(main())