Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recreate support .gitignore PR. #102

Merged
merged 2 commits into from
Sep 14, 2020
Merged

Recreate support .gitignore PR. #102

merged 2 commits into from
Sep 14, 2020

Conversation

hadialqattan
Copy link
Contributor

No description provided.

This was referenced Sep 14, 2020
@hakancelikdev hakancelikdev linked an issue Sep 14, 2020 that may be closed by this pull request
@hadialqattan
Copy link
Contributor Author

@hakancelik96 I've applied your suggestions to this PR!

Copy link
Owner

@hakancelikdev hakancelikdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi again I did some experimentation in my local and made your codes as suitable as possible, can you follow these suggestions I will leave my diff to you.

--- a/.isort.cfg
+++ b/.isort.cfg
@@ -4,4 +4,4 @@ include_trailing_comma=True
 force_grid_wrap=0
 use_parentheses=True
 line_length=79
-known_third_party = libcst,pathspec,semantic_version,setuptools
+known_third_party = libcst,semantic_version,setuptools
diff --git a/tests/test_session.py b/tests/test_session.py
index 35244e5..c54c185 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -2,8 +2,6 @@ import tempfile
 import unittest
 from pathlib import Path

-from pathspec import PathSpec
-
 from unimport.session import Session


@@ -16,7 +14,7 @@ class TestSession(unittest.TestCase):

     def test_list_paths_and_read(self):
         for path in [Path("tests"), Path("tests/test_config.py")]:
-            for p in self.session.list_paths(path, PathSpec("")):
+            for p in self.session.list_paths(path):
                 self.assertTrue(str(p).endswith(".py"))

     def temp_refactor(self, source: str, expected: str, apply: bool = False):
diff --git a/unimport/__main__.py b/unimport/__main__.py
index ddd1e6b..99ab544 100644
--- a/unimport/__main__.py
+++ b/unimport/__main__.py
@@ -7,9 +7,11 @@ from pathlib import Path
 from typing import List, Optional, Tuple, Union

 try:
-    from pathspec import PathSpec
-except ModuleNotFoundError:
-    PathSpec = None
+    from pathspec.patterns.gitwildmatch import GitWildMatchPattern
+
+    HAS_PATHSPEC = True
+except ImportError:
+    HAS_PATHSPEC = False

 import unimport.constants as C
 from unimport.color import Color
@@ -69,15 +71,17 @@ def show(
         )


-def get_gitignore() -> PathSpec:
-    """Return a PathSpec matching gitignore content."""
-    if not PathSpec:
-        return None
-    lines: List[str] = []
+def get_exclude_list_from_gitignore() -> List[str]:
+    """Converts .gitignore patterns to regex and return this exclude regex
+    list."""
     path: Path = Path(".gitignore")
+    gitignore_regex: List[str] = []
     if path.is_file():
-        lines = tokenize.open(path).readlines()
-    return PathSpec.from_lines("gitwildmatch", lines)
+        for line in tokenize.open(path).readlines():
+            regex = GitWildMatchPattern.pattern_to_regex(line)[0]
+            if regex:
+                gitignore_regex.append(regex)
+    return gitignore_regex


 def main(argv: Optional[List[str]] = None) -> int:
@@ -192,17 +196,15 @@ def main(argv: Optional[List[str]] = None) -> int:
         exclude_list.append(namespace.exclude)
     if hasattr(session.config, "exclude"):
         exclude_list.append(session.config.exclude)  # type: ignore
-    if namespace.gitignore:
-        gitignore = get_gitignore()
-    else:
-        gitignore = None
+    if HAS_PATHSPEC and (
+        namespace.gitignore or (hasattr(session.config, "gitignore") and session.config.gitignore)  # type: ignore
+    ):
+        exclude_list.extend(get_exclude_list_from_gitignore())
     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, gitignore, include, exclude
-        ):
+        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:
diff --git a/unimport/config.py b/unimport/config.py
index cfa1463..64eff52 100644
--- a/unimport/config.py
+++ b/unimport/config.py
@@ -1,6 +1,6 @@
 import configparser
-from ast import literal_eval
 from pathlib import Path
+from typing import Union

 try:
     import toml
@@ -36,11 +36,14 @@ class Config:
         parser = configparser.ConfigParser(allow_no_value=True)
         parser.read(self.config_file)
         if parser.has_section(self.section):
+            get_value: Union[str, bool]
             for attr in self.attrs:
                 get_value = parser.get(self.section, attr)
-                if attr == "gitignore":
-                    get_value = literal_eval(get_value)
                 if get_value:
+                    if get_value == "True":
+                        get_value = True
+                    elif get_value == "False":
+                        get_value = False
                     setattr(self, attr, get_value)

     def parse_toml(self) -> None:
diff --git a/unimport/session.py b/unimport/session.py
index 5289d0b..2f95e32 100644
--- a/unimport/session.py
+++ b/unimport/session.py
@@ -4,11 +4,6 @@ import tokenize
 from pathlib import Path
 from typing import Iterable, Iterator, Optional, Tuple

-try:
-    from pathspec import PathSpec
-except ModuleNotFoundError:
-    pass
-
 from unimport.color import Color
 from unimport.config import CONFIG_FILES, Config
 from unimport.refactor import refactor_string
@@ -51,7 +46,6 @@ class Session:
     def list_paths(
         self,
         start: Path,
-        gitignore: Optional[PathSpec],
         include: Optional[str] = None,
         exclude: Optional[str] = None,
     ) -> Iterator[Path]:
@@ -64,17 +58,11 @@ class Session:
             file_names = start.glob(self.GLOB_PATTERN)
         else:
             file_names = [start]
-
-        def matcher(filename: Path) -> bool:
-            return (
-                bool(include_regex.search(str(filename)))
-                and not bool(exclude_regex.search(str(filename)))
-                and not (
-                    gitignore.match_file(str(filename)) if gitignore else False
-                )
-            )
-
-        yield from filter(matcher, file_names)
+        yield from filter(
+            lambda filename: include_regex.search(str(filename))
+            and not exclude_regex.search(str(filename)),
+            file_names,
+        )

     def refactor(self, source: str) -> str:
         self.scanner.scan(source)

@hakancelikdev
Copy link
Owner

Updated review

@hakancelikdev
Copy link
Owner

Sorry but I found some issues so I updated it again.

@hadialqattan
Copy link
Contributor Author

Sorry but I found some issues so I updated it again.

No worries!

@hadialqattan
Copy link
Contributor Author

Please wait before merging, I'll reread changes.

@hakancelikdev
Copy link
Owner

Difference between pathspec output and fnmatch.translate output

pathspec

['^(?:.+/)?__pycache__/.*$', '^(?:.+/)?[^/]*\\.py[cod](?:/.*)?$', '^(?:.+/)?[^/]*\\$py\\.class(?:/.*)?$', '^(?:.+/)?[^/]*\\.so(?:/.*)?$', '^(?:.+/)?\\.Python(?:/.*)?$', '^(?:.+/)?build/.*$', '^(?:.+/)?develop\\-eggs/.*$', '^(?:.+/)?dist/.*$', '^(?:.+/)?downloads/.*$', '^(?:.+/)?eggs/.*$', '^(?:.+/)?\\.eggs/.*$', '^(?:.+/)?lib/.*$', '^(?:.+/)?lib64/.*$', '^(?:.+/)?parts/.*$', '^(?:.+/)?sdist/.*$', '^(?:.+/)?var/.*$', '^(?:.+/)?wheels/.*$', '^(?:.+/)?[^/]*\\.egg\\-info/.*$', '^(?:.+/)?\\.installed\\.cfg(?:/.*)?$', '^(?:.+/)?[^/]*\\.egg(?:/.*)?$', '^(?:.+/)?MANIFEST(?:/.*)?$', '^(?:.+/)?[^/]*\\.manifest(?:/.*)?$', '^(?:.+/)?[^/]*\\.spec(?:/.*)?$', '^(?:.+/)?pip\\-log\\.txt(?:/.*)?$', '^(?:.+/)?pip\\-delete\\-this\\-directory\\.txt(?:/.*)?$', '^(?:.+/)?htmlcov/.*$', '^(?:.+/)?\\.tox/.*$', '^(?:.+/)?\\.coverage(?:/.*)?$', '^(?:.+/)?\\.coverage\\.[^/]*(?:/.*)?$', '^(?:.+/)?\\.cache(?:/.*)?$', '^(?:.+/)?nosetests\\.xml(?:/.*)?$', '^(?:.+/)?coverage\\.xml(?:/.*)?$', '^(?:.+/)?[^/]*\\.cover(?:/.*)?$', '^(?:.+/)?\\.hypothesis/.*$', '^(?:.+/)?\\.pytest_cache/.*$', '^(?:.+/)?[^/]*\\.mo(?:/.*)?$', '^(?:.+/)?[^/]*\\.pot(?:/.*)?$', '^(?:.+/)?[^/]*\\.log(?:/.*)?$', '^(?:.+/)?local_settings\\.py(?:/.*)?$', '^(?:.+/)?db\\.sqlite3(?:/.*)?$', '^(?:.+/)?instance/.*$', '^(?:.+/)?\\.webassets\\-cache(?:/.*)?$', '^(?:.+/)?\\.scrapy(?:/.*)?$', '^docs/_build/.*$', '^(?:.+/)?target/.*$', '^(?:.+/)?\\.ipynb_checkpoints(?:/.*)?$', '^(?:.+/)?\\.python\\-version(?:/.*)?$', '^(?:.+/)?celerybeat\\-schedule(?:/.*)?$', '^(?:.+/)?[^/]*\\.sage\\.py(?:/.*)?$', '^(?:.+/)?\\.env[^/]*/.*$', '^(?:.+/)?\\.venv(?:/.*)?$', '^(?:.+/)?env[^/]*/.*$', '^(?:.+/)?venv/.*$', '^(?:.+/)?ENV/.*$', '^(?:.+/)?env\\.bak/.*$', '^(?:.+/)?venv\\.bak/.*$', '^(?:.+/)?\\.spyderproject(?:/.*)?$', '^(?:.+/)?\\.spyproject(?:/.*)?$', '^(?:.+/)?\\.ropeproject(?:/.*)?$', '^site(?:/.*)?$', '^(?:.+/)?\\.mypy_cache/.*$', '^(?:.+/)?monkeytype\\.sqlite3(?:/.*)?$']

fnmatch

['(?s:__pycache__/\\\n)\\Z', '(?s:.*\\.py[cod]\\\n)\\Z', '(?s:.*\\$py\\.class\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:.*\\.so\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.Python\\\n)\\Z', '(?s:build/\\\n)\\Z', '(?s:develop\\-eggs/\\\n)\\Z', '(?s:dist/\\\n)\\Z', '(?s:downloads/\\\n)\\Z', '(?s:eggs/\\\n)\\Z', '(?s:\\.eggs/\\\n)\\Z', '(?s:lib/\\\n)\\Z', '(?s:lib64/\\\n)\\Z', '(?s:parts/\\\n)\\Z', '(?s:sdist/\\\n)\\Z', '(?s:var/\\\n)\\Z', '(?s:wheels/\\\n)\\Z', '(?s:.*\\.egg\\-info/\\\n)\\Z', '(?s:\\.installed\\.cfg\\\n)\\Z', '(?s:.*\\.egg\\\n)\\Z', '(?s:MANIFEST\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:.*\\.manifest\\\n)\\Z', '(?s:.*\\.spec\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:pip\\-log\\.txt\\\n)\\Z', '(?s:pip\\-delete\\-this\\-directory\\.txt\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:htmlcov/\\\n)\\Z', '(?s:\\.tox/\\\n)\\Z', '(?s:\\.coverage\\\n)\\Z', '(?s:\\.coverage\\..*\\\n)\\Z', '(?s:\\.cache\\\n)\\Z', '(?s:nosetests\\.xml\\\n)\\Z', '(?s:coverage\\.xml\\\n)\\Z', '(?s:.*\\.cover\\\n)\\Z', '(?s:\\.hypothesis/\\\n)\\Z', '(?s:\\.pytest_cache/\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:.*\\.mo\\\n)\\Z', '(?s:.*\\.pot\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:.*\\.log\\\n)\\Z', '(?s:local_settings\\.py\\\n)\\Z', '(?s:db\\.sqlite3\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:instance/\\\n)\\Z', '(?s:\\.webassets\\-cache\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.scrapy\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:docs/_build/\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:target/\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.ipynb_checkpoints\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.python\\-version\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:celerybeat\\-schedule\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:.*\\.sage\\.py\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.env.*/\\\n)\\Z', '(?s:\\.venv\\\n)\\Z', '(?s:env.*/\\\n)\\Z', '(?s:venv/\\\n)\\Z', '(?s:ENV/\\\n)\\Z', '(?s:env\\.bak/\\\n)\\Z', '(?s:venv\\.bak/\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.spyderproject\\\n)\\Z', '(?s:\\.spyproject\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.ropeproject\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:/site\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:\\.mypy_cache/\\\n)\\Z', '(?s:\\\n)\\Z', '(?s:monkeytype\\.sqlite3\\\n)\\Z']

so it does not work as expected, if there is a solution to this I would definitely prefer fnmatch.

@hakancelikdev hakancelikdev merged commit f01eee2 into hakancelikdev:master Sep 14, 2020
@hadialqattan hadialqattan deleted the i18-new branch September 14, 2020 19:20
Copy link
Collaborator

@isidentical isidentical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Even though I am a little bit late, I have to state that, I dont really like adding custom options for things like toml, pathspec etc. We can simply integrate it as a normal dependency, there is no shame on depending to other libraries!

def get_exclude_list_from_gitignore() -> List[str]:
"""Converts .gitignore patterns to regex and return this exclude regex
list."""
path: Path = Path(".gitignore")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need to explicitly type this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it's a useless type annotation!

Copy link
Contributor Author

@hadialqattan hadialqattan Sep 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I'm gonna remove it on my next PR, so thx!

@hadialqattan
Copy link
Contributor Author

hadialqattan commented Sep 14, 2020

Nice work! Even though I am a little bit late, I have to state that, I dont really like adding custom options for things like toml, pathspec etc. We can simply integrate it as a normal dependency, there is no shame on depending to other libraries!

I agree with you, but it seems that @hakancelik96 has policies to lower the number of normal dependencies as low as possible! ;-)

@hakancelikdev
Copy link
Owner

Nice work! Even though I am a little bit late, I have to state that, I dont really like adding custom options for things like toml, pathspec etc. We can simply integrate it as a normal dependency, there is no shame on depending to other libraries!

I agree with you, but it seems that @hakancelik96 has policies to lower the number of normal dependencies as low as possible! ;-)

Actually it was common policy for us, but it seems that @isidentical's ideas have changed.

@isidentical
Copy link
Collaborator

Actually it was common policy for us, but it seems that @isidentical's ideas have changed.

As you may well know, I've recently talked with someone who told me about why using dependencies is not a problem, so yeah, I am now +1 on dependencies.

hakancelikdev pushed a commit that referenced this pull request Jul 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

.gitignore support
3 participants