Skip to content

Commit

Permalink
Console Argument (#17)
Browse files Browse the repository at this point in the history
* write name change as remove & permission add &  check add console improved & _list_paths params name fix
* readme update & add warning in console
* Failed to scan warning add
* star import bug fix & wrong variable fix
* #r368288064 resolve
* #r368287971
  • Loading branch information
hakancelikdev authored and isidentical committed Jan 19, 2020
1 parent 8c9b122 commit e4db14d
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 81 deletions.
79 changes: 37 additions & 42 deletions README.md
Expand Up @@ -7,75 +7,70 @@

[![MIT License](https://img.shields.io/github/license/hakancelik96/unimport.svg)](https://github.com/hakancelik96/unimport/blob/master/LICENSE) [![releases](https://img.shields.io/github/release/hakancelik96/unimport.svg)](https://github.com/hakancelik96/unimport/releases) [![last-commit](https://img.shields.io/github/last-commit/hakancelik96/unimport.svg)](https://github.com/hakancelik96/unimport/commits/master) [![style](https://img.shields.io/badge/style-black-black)](https://github.com/psf/black) [![style](https://img.shields.io/badge/style-isort-lightgrey)](https://github.com/timothycrosley/isort) [![style](https://img.shields.io/badge/style-unimport-green)](https://github.com/hakancelik96/unimport) [![](https://img.shields.io/github/contributors/hakancelik96/unimport)](https://github.com/hakancelik96/unimport/graphs/contributors) [![](https://pepy.tech/badge/unimport)](https://pepy.tech/badge/unimport) [![codecov](https://codecov.io/gh/hakancelik96/unimport/branch/master/graph/badge.svg)](https://codecov.io/gh/hakancelik96/unimport) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/unimport)

### 🚀 Installation and Usage 🚀
### Installation and Usage
## Installation
Unimport can be installed by running `pip install unimport`. It requires Python 3.5.0+ to run.
Unimport can be installed by running `pip install unimport`. It requires Python 3.6.0+ to run.

## Usage

unimport {source_file_or_directory} or write direct unimport to current path scan

## Command line options
You can list many options by running unimport --help

**Please insert this badge into your project**

`[![](https://img.shields.io/badge/style-unimport-green)](https://github.com/hakancelik96/unimport)`
```
usage: __main__.py [-h] [-c PATH] [-r | -p] [-d] [--check] [-v]
[sources [sources ...]]
Detect or remove unused Python imports.
positional arguments:
sources files and folders to find the unused imports.
optional arguments:
-h, --help show this help message and exit
-c PATH, --config PATH
read configuration from PATH.
-r, --remove remove unused imports automatically.
-p, --permission Refactor permission after see diff.
-d, --diff Prints a diff of all the changes unimport would make
to a file.
--check Prints which file the unused imports are in.
-v, --version Prints version of unimport
```

[![](https://img.shields.io/badge/style-unimport-green)](https://github.com/hakancelik96/unimport)

## Configuring Unimport
To configure unimport for a single user create a ~/.unimport.cfg and type the names of folders that you do not want scanning.
**Please insert this badge into your project**

**blablabla/.unimport.cfg**
> regex
`[![](https://img.shields.io/badge/style-unimport-red)](https://github.com/hakancelik96/unimport)`

```ini
[folders]
.*(some_folder_name_to_ignore)
[![](https://img.shields.io/badge/style-unimport-red)](https://github.com/hakancelik96/unimport)

[files]
.*(some_file_name_to_ignore)
```

**Example; mydjango-project/.unimport.cfg**
## Configuring Unimport
It's possible to configure **unimport** from `pyproject.toml` or `setup.cfg` files if you have.

```ini
[folders]
.*(migrations)
[files]
.*(__init__.py)
```
Use `exclude` config name to configure glob patterns for exluding files and folders.

Also, it's possible to configure **unimport** from `pyproject.toml` or `setup.cfg` files if you have. For example:
For example:

**pyproject.toml**

```ini
[tool.unimport]
folders = [
'.*(migrations)',
]
files = [
'.*(__init__.py)',
'.*(settings.py)',
exclude = [
'./[0-9].*',
'tests'
]
```

**setup.cfg**

```ini
[unimport]
folders = .*(migrations)
files = .*(__init__.py)
.*(settings.py)
exclude = ./[0-9].*
tests
```

If you have multiple configuration files on your project, it considers just the first file that found in order of priority:

1. .unimport.cfg
2. pyproject.toml (if `toml` is not installed, it ignores this rule.)
3. setup.cfg
4. Default settings

## Author / Social

👤 **Hakan Çelik** 👤
Expand All @@ -89,7 +84,7 @@ If you have multiple configuration files on your project, it considers just the
<tr>
<td align="center">
<a href="https://radity.com/?ref=unimport">
<img src="https://raw.githubusercontent.com/hakancelik96/unimport/master/images/clients/radity.jpg" width="75px;" alt="radity.com"/>
<img src="https://raw.githubusercontent.com/hakancelik96/unimport/master/images/clients/radity.jpg" width="160px;" alt="radity.com"/>
<br/>
<sub>
<b>Radity</b>
Expand Down
4 changes: 3 additions & 1 deletion rare_cases/__future__.py → tests/future_action.py
@@ -1,3 +1,5 @@
# this cases are ignoring by unimport

from __future__ import (
absolute_import, division, print_function, unicode_literals
)
)
5 changes: 5 additions & 0 deletions tests/samples/future_acpected.py
@@ -0,0 +1,5 @@
# this cases are ignoring by unimport

from __future__ import (
absolute_import, division, print_function, unicode_literals
)
15 changes: 15 additions & 0 deletions tests/samples/star_action.py
@@ -0,0 +1,15 @@
from typing import (
Callable,
Iterable,
Iterator,
List,
Optional,
Text,
Tuple,
Pattern,
Union,
cast,
)

from blib2to3.pgen2.token import *
from blib2to3.pgen2.grammar import Grammar
2 changes: 2 additions & 0 deletions tests/samples/star_expected.py
@@ -0,0 +1,2 @@

from blib2to3.pgen2.token import *
60 changes: 34 additions & 26 deletions unimport/__main__.py
Expand Up @@ -2,12 +2,13 @@
import pathlib
import sys

from unimport.session import Session
from unimport import __version__
from unimport.session import Session

parser = argparse.ArgumentParser(
description="Detect or remove unused Python imports."
)
exclusive_group = parser.add_mutually_exclusive_group(required=False)
parser.add_argument(
"sources",
default=".",
Expand All @@ -22,22 +23,34 @@
metavar="PATH",
type=pathlib.Path,
)
parser.add_argument(
"-w",
"--write",
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(
"-d",
"--diff",
action="store_true",
help="Prints a diff of all the changes unimport would make to a file.",
)
parser.add_argument(
"--check",
action="store_true",
help="Prints which file the unused imports are in.",
)
parser.add_argument(
"-v",
"--version",
action="store_true",
action="version",
version=f"Unimport {__version__}",
help="Prints version of unimport",
)

Expand All @@ -50,34 +63,29 @@ def print_if_exists(sequence):

def main(argv=None):
namespace = parser.parse_args(argv)
any_namespace = any([value for key, value in vars(namespace).items()][1: ])
if namespace.permission and not namespace.diff:
namespace.diff = True
session = Session(config_file=namespace.config)
sources = []
for source in namespace.sources:
sources.extend(session._list_paths(source, "**/*.py"))

if namespace.diff and namespace.write:
for source in sources:
print_if_exists(tuple(session.diff_file(source)))
session.refactor_file(source, apply=True)
elif namespace.diff:
for source in sources:
diff = tuple(session.diff_file(source))
if print_if_exists(diff):
for source_path in namespace.sources:
sources.extend(session._list_paths(source_path, "**/*.py"))
for source_path in sources:
if not any_namespace or namespace.check:
print_if_exists(tuple(session.scan_file(source_path)))
if namespace.diff:
exists_diff = print_if_exists(tuple(session.diff_file(source_path)))
if namespace.permission and exists_diff:
action = input(
f"Apply suggested changes to '{source}' [y/n/q] ? > "
f"Apply suggested changes to '{source_path}' [y/n/q] ? > "
)
if action == "q":
break
elif action == "y":
session.refactor_file(source, apply=True)
elif namespace.write:
for source in sources:
session.refactor_file(source, apply=True)
elif namespace.version:
print(f"Unimport {__version__}")
else:
for source in sources:
print_if_exists(tuple(session.scan_file(source)))
namespace.remove = True
if namespace.remove:
session.refactor_file(source_path, apply=True)


if __name__ == "__main__":
main(sys.argv[1:])
4 changes: 1 addition & 3 deletions unimport/config.py
Expand Up @@ -49,16 +49,14 @@


class Config:
exclude = set()

def __init__(self, config_file=None):
self.exclude = DEFAULT_EXCLUDES.copy()
self.config_file = config_file
self.config_path, self.section = self.find_config()
if self.config_path is not None:
self.parse()

self.exclude.update(DEFAULT_EXCLUDES)

@staticmethod
def is_available_to_parse(config_path):
if config_path.suffix == ".toml" and HAS_TOML is False:
Expand Down
4 changes: 2 additions & 2 deletions unimport/refactor.py
Expand Up @@ -8,7 +8,7 @@ def traverse_imports(names):
pending = [names]
while pending:
node = pending.pop()
if node.type == token.NAME:
if node.type in {token.NAME, token.STAR}:
yield node.value
elif node.type == syms.dotted_name:
yield "".join([ch.value for ch in node.children])
Expand Down Expand Up @@ -102,7 +102,7 @@ class RefactorTool(RefactoringTool):
def __init__(self):
self._fixer = RefactorImports()
self._fixers = [self._fixer]
super().__init__(None, options = {"print_function": True})
super().__init__(None, options={"print_function": True})
del self.grammar.keywords["exec"]

def get_fixers(self):
Expand Down
17 changes: 10 additions & 7 deletions unimport/session.py
Expand Up @@ -19,8 +19,8 @@ def _read(self, path):
with tokenize.open(path) as stream:
source = stream.read()
encoding = stream.encoding
except OSError:
print(f"Can't read {file_path}")
except OSError as exc:
print(f"{exc} Can't read")
return "", "utf-8"
else:
return source, encoding
Expand All @@ -42,20 +42,24 @@ def _is_excluded(path):
if not _is_excluded(path):
yield path

def scan(self, source):
yield from self.scanner.iter_imports(source)
def scan(self, source, filename=None):
try:
yield from self.scanner.iter_imports(source)
except SyntaxError as exc:
exc.filename = filename
print("Failed to scan", exc)
yield from []

def scan_file(self, path):
source, _ = self._read(path)
for imports in self.scan(source=source):
for imports in self.scan(source, path):
imports.update(path=path)
yield imports

def scan_directory(self, path, recursive=False):
pattern = "*.py"
if recursive:
pattern = f"**/{pattern}"

for path in self._list_paths(path, pattern):
yield from self.scan_file(path)

Expand All @@ -70,7 +74,6 @@ def refactor_file(self, path, apply=False):
result = self.refactor(source)
except ParseError as exc:
raise ValueError(f"Invalid python file {path}.") from exc

if apply:
path.write_text(result, encoding=encoding)
else:
Expand Down

0 comments on commit e4db14d

Please sign in to comment.