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

Added --include and --exclude cli options #281

Merged
merged 5 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ Options:
**kwargs. [default: per-file auto-detection]
-S, --skip-string-normalization
Don't normalize string quotes or prefixes.
--include TEXT A regular expression that matches files and
directories that should be included on
recursive searches. [default: \.pyi?$]
--exclude TEXT A regular expression that matches files and
directories that should be excluded on
recursive searches. [default: build\/|buck-o
ut\/|dist\/|_build\/|.git\/|.hg\/|.mypy_cach
e\/|.tox\/|.venv\/]
--version Show the version and exit.
--help Show this message and exit.
```
Expand Down Expand Up @@ -698,6 +706,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).

### 18.6b0

* added `--include` and `--exclude` (#270)

* added `--skip-string-normalization` (#118)


Expand Down
72 changes: 51 additions & 21 deletions black.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@

__version__ = "18.5b1"
DEFAULT_LINE_LENGTH = 88
DEFAULT_EXCLUDES = (
r"build/|buck-out/|dist/|_build/|\.git/|\.hg/|\.mypy_cache/|\.tox/|\.venv/"
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a problem to solve here. In the current model, "not_a_build/" is also matching. It would be more robust to prepend all those directory names with slashes. However, if the user specified a relative directory, we might not have a leading slash always. If they said black ., it's fine. If they said black *, it's not.

So up to you how we solve this but it will have to be addressed before we land this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I think prepending the directories with slashes would be the best route. I'll play around with it and see if I can come up with an elegant way of doing this.

)
DEFAULT_INCLUDES = r"\.pyi?$"
CACHE_DIR = Path(user_cache_dir("black", version=__version__))


Expand Down Expand Up @@ -189,6 +193,26 @@ class FileMode(Flag):
is_flag=True,
help="Don't normalize string quotes or prefixes.",
)
@click.option(
"--include",
type=str,
default=DEFAULT_INCLUDES,
help=(
"A regular expression that matches files and directories that should be "
"included on recursive searches."
),
show_default=True,
)
@click.option(
"--exclude",
type=str,
default=DEFAULT_EXCLUDES,
help=(
"A regular expression that matches files and directories that should be "
"excluded on recursive searches."
),
show_default=True,
)
@click.version_option(version=__version__)
@click.argument(
"src",
Expand All @@ -208,14 +232,28 @@ def main(
py36: bool,
skip_string_normalization: bool,
quiet: bool,
include: str,
exclude: str,
src: List[str],
) -> None:
"""The uncompromising code formatter."""
sources: List[Path] = []
for s in src:
p = Path(s)
if p.is_dir():
sources.extend(gen_python_files_in_dir(p))
try:
include_regex = re.compile(include)
except Exception as exc:
raise SyntaxError(
"Invalid regular expression for include given: {}".format(include)
)
try:
exclude_regex = re.compile(exclude)
except Exception as esc:
raise SyntaxError(
"Invalid regular expression for exclude given: {}".format(exclude)
)
sources.extend(gen_python_files_in_dir(p, include_regex, exclude_regex))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, just noticed this now.

  1. Why are we compiling the regex again for every entry in src?
  2. Could we use a better exception type than Exception?
  3. Use an f-string for the exception message?
  4. Use the !r modifier so that the error is less likely to be confusing.
  5. We actually don't want to raise an exception but rather just use err() followed by ctx.exit(2) (1 is already used by --check and 123 is internal error). It's a tool, we don't want to trouble people with unnecessary tracebacks.

elif p.is_file():
# if a file was explicitly given, we don't care about its extension
sources.append(p)
Expand Down Expand Up @@ -2750,32 +2788,24 @@ def get_future_imports(node: Node) -> Set[str]:
return imports


PYTHON_EXTENSIONS = {".py", ".pyi"}
BLACKLISTED_DIRECTORIES = {
"build",
"buck-out",
"dist",
"_build",
".git",
".hg",
".mypy_cache",
".tox",
".venv",
}


def gen_python_files_in_dir(path: Path) -> Iterator[Path]:
"""Generate all files under `path` which aren't under BLACKLISTED_DIRECTORIES
and have one of the PYTHON_EXTENSIONS.
def gen_python_files_in_dir(
path: Path, include: Pattern[str], exclude: Pattern[str]
) -> Iterator[Path]:
"""Generate all files under `path` whose paths are not excluded by the
`exclude` regex, but are included by the `include` regex.
"""
for child in path.iterdir():
if child.is_dir():
if child.name in BLACKLISTED_DIRECTORIES:
if exclude.search(str(child) + "/"):
continue

yield from gen_python_files_in_dir(child)
yield from gen_python_files_in_dir(child, include, exclude)

elif child.is_file() and child.suffix in PYTHON_EXTENSIONS:
elif (
child.is_file()
and include.search(str(child))
and not exclude.search(str(child))
):
yield child


Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
13 changes: 13 additions & 0 deletions tests/test_black.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any, List, Tuple, Iterator
import unittest
from unittest.mock import patch
import re

from click import unstyle
from click.testing import CliRunner
Expand Down Expand Up @@ -851,6 +852,18 @@ def test_pipe_force_py36(self) -> None:
actual = result.output
self.assertFormatEqual(actual, expected)

def test_include_exclude(self) -> None:
path = THIS_DIR / "include_exclude_tests"
include = re.compile(r"\.pyi?$")
exclude = re.compile(r"exclude/|\.definitely_exclude/")
sources: List[Path] = []
expected = [
Path(THIS_DIR / "include_exclude_tests/b/c/a.py"),
Path(THIS_DIR / "include_exclude_tests/b/c/a.pyi"),
]
sources.extend(black.gen_python_files_in_dir(path, include, exclude))
self.assertEqual(sorted(expected), sorted(sources))


if __name__ == "__main__":
unittest.main()