hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_bu
ild|buck-out|build|dist)/]
+ --extend-exclude TEXT Like --exclude, but adds additional files
+ and directories on top of the excluded
+ ones (useful if you simply want to add to
+ the default).
+
--force-exclude TEXT Like --exclude, but files and directories
matching this regex will be excluded even
when they are passed explicitly as
arguments.
+
--stdin-filename TEXT The name of the file when passing it through
stdin. Useful to make sure Black will
respect --force-exclude option on some
-v, --verbose Also emit messages to stderr about files
that were not changed or were ignored due to
- --exclude=.
+ exclusion patterns.
--version Show the version and exit.
--config FILE Read configuration from FILE path.
_Black_ is able to read project-specific default values for its command line options
from a `pyproject.toml` file. This is especially useful for specifying custom
-`--include` and `--exclude` patterns for your project.
+`--include` and `--exclude`/`--extend-exclude` patterns for your project.
**Pro-tip**: If you're asking yourself "Do I need to configure anything?" the answer is
"No". _Black_ is all about sensible defaults.
line-length = 88
target-version = ['py37']
include = '\.pyi?$'
-exclude = '''
+extend-exclude = '''
# A regex preceded with ^/ will apply only to files and directories
# in the root of the project.
-^/(
- (
- \.eggs # exclude a few common directories in the
- | \.git # root of the project
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
- )/
- | foo.py # also separately exclude a file named foo.py in
- # the root of the project
-)
+^/foo.py # exclude a file named foo.py in the root of the project (in addition to the defaults)
'''
```
- [Joseph Larson](mailto:larson.joseph@gmail.com)
- [Josh Bode](mailto:joshbode@fastmail.com)
- [Josh Holland](mailto:anowlcalledjosh@gmail.com)
+- [Joshua Cannon](mailto:joshdcannon@gmail.com)
- [José Padilla](mailto:jpadilla@webapplicate.com)
- [Juan Luis Cano Rodríguez](mailto:hello@juanlu.space)
- [kaiix](mailto:kvn.hou@gmail.com)
- use lowercase hex strings (#1692)
+- added `--extend-exclude` argument (#1571)
+
#### _Packaging_
- Self-contained native _Black_ binaries are now provided for releases via GitHub
when they are passed explicitly as
arguments.
+ --extend-exclude TEXT Like --exclude, but adds additional files
+ and directories on top of the excluded
+ ones. (useful if you simply want to add to
+ the default)
+
--stdin-filename TEXT The name of the file when passing it through
stdin. Useful to make sure Black will
respect --force-exclude option on some
-v, --verbose Also emit messages to stderr about files
that were not changed or were ignored due to
- --exclude=.
+ exclusion patterns.
--version Show the version and exit.
--config FILE Read configuration from FILE path.
_Black_ is able to read project-specific default values for its command line options
from a `pyproject.toml` file. This is especially useful for specifying custom
-`--include` and `--exclude` patterns for your project.
+`--include` and `--exclude`/`--force-exclude`/`--extend-exclude` patterns for your
+project.
**Pro-tip**: If you're asking yourself "Do I need to configure anything?" the answer is
"No". _Black_ is all about sensible defaults.
line-length = 88
target-version = ['py37']
include = '\.pyi?$'
-exclude = '''
+extend-exclude = '''
# A regex preceded with ^/ will apply only to files and directories
# in the root of the project.
-^/(
- (
- \.eggs # exclude a few common directories in the
- | \.git # root of the project
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
- )/
- | foo.py # also separately exclude a file named foo.py in
- # the root of the project
-)
+^/foo.py # exclude a file named foo.py in the root of the project (in addition to the defaults)
'''
```
line-length = 88
target-version = ['py36', 'py37', 'py38']
include = '\.pyi?$'
-exclude = '''
+extend-exclude = '''
/(
- \.eggs
- | \.git
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
-
# The following are specific to Black, you probably don't want those.
| blib2to3
| tests/data
),
show_default=True,
)
+@click.option(
+ "--extend-exclude",
+ type=str,
+ help=(
+ "Like --exclude, but adds additional files and directories on top of the"
+ " excluded ones. (Useful if you simply want to add to the default)"
+ ),
+)
@click.option(
"--force-exclude",
type=str,
is_flag=True,
help=(
"Also emit messages to stderr about files that were not changed or were ignored"
- " due to --exclude=."
+ " due to exclusion patterns."
),
)
@click.version_option(version=__version__)
verbose: bool,
include: str,
exclude: str,
+ extend_exclude: Optional[str],
force_exclude: Optional[str],
stdin_filename: Optional[str],
src: Tuple[str, ...],
verbose=verbose,
include=include,
exclude=exclude,
+ extend_exclude=extend_exclude,
force_exclude=force_exclude,
report=report,
stdin_filename=stdin_filename,
ctx.exit(report.return_code)
+def test_regex(
+ ctx: click.Context,
+ regex_name: str,
+ regex: Optional[str],
+) -> Optional[Pattern]:
+ try:
+ return re_compile_maybe_verbose(regex) if regex is not None else None
+ except re.error:
+ err(f"Invalid regular expression for {regex_name} given: {regex!r}")
+ ctx.exit(2)
+
+
def get_sources(
*,
ctx: click.Context,
verbose: bool,
include: str,
exclude: str,
+ extend_exclude: Optional[str],
force_exclude: Optional[str],
report: "Report",
stdin_filename: Optional[str],
) -> Set[Path]:
"""Compute the set of files to be formatted."""
- try:
- include_regex = re_compile_maybe_verbose(include)
- except re.error:
- err(f"Invalid regular expression for include given: {include!r}")
- ctx.exit(2)
- try:
- exclude_regex = re_compile_maybe_verbose(exclude)
- except re.error:
- err(f"Invalid regular expression for exclude given: {exclude!r}")
- ctx.exit(2)
- try:
- force_exclude_regex = (
- re_compile_maybe_verbose(force_exclude) if force_exclude else None
- )
- except re.error:
- err(f"Invalid regular expression for force_exclude given: {force_exclude!r}")
- ctx.exit(2)
+
+ include_regex = test_regex(ctx, "include", include)
+ exclude_regex = test_regex(ctx, "exclude", exclude)
+ assert exclude_regex is not None
+ extend_exclude_regex = test_regex(ctx, "extend_exclude", extend_exclude)
+ force_exclude_regex = test_regex(ctx, "force_exclude", force_exclude)
root = find_project_root(src)
sources: Set[Path] = set()
root,
include_regex,
exclude_regex,
+ extend_exclude_regex,
force_exclude_regex,
report,
gitignore,
return normalized_path
+def path_is_excluded(
+ normalized_path: str,
+ pattern: Optional[Pattern[str]],
+) -> bool:
+ match = pattern.search(normalized_path) if pattern else None
+ return bool(match and match.group(0))
+
+
def gen_python_files(
paths: Iterable[Path],
root: Path,
include: Optional[Pattern[str]],
exclude: Pattern[str],
+ extend_exclude: Optional[Pattern[str]],
force_exclude: Optional[Pattern[str]],
report: "Report",
gitignore: PathSpec,
) -> Iterator[Path]:
"""Generate all files under `path` whose paths are not excluded by the
- `exclude_regex` or `force_exclude` regexes, but are included by the `include` regex.
+ `exclude_regex`, `extend_exclude`, or `force_exclude` regexes,
+ but are included by the `include` regex.
Symbolic links pointing outside of the `root` directory are ignored.
report.path_ignored(child, "matches the .gitignore file content")
continue
- # Then ignore with `--exclude` and `--force-exclude` options.
+ # Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options.
normalized_path = "/" + normalized_path
if child.is_dir():
normalized_path += "/"
- exclude_match = exclude.search(normalized_path) if exclude else None
- if exclude_match and exclude_match.group(0):
+ if path_is_excluded(normalized_path, exclude):
report.path_ignored(child, "matches the --exclude regular expression")
continue
- force_exclude_match = (
- force_exclude.search(normalized_path) if force_exclude else None
- )
- if force_exclude_match and force_exclude_match.group(0):
+ if path_is_excluded(normalized_path, extend_exclude):
+ report.path_ignored(
+ child, "matches the --extend-exclude regular expression"
+ )
+ continue
+
+ if path_is_excluded(normalized_path, force_exclude):
report.path_ignored(child, "matches the --force-exclude regular expression")
continue
root,
include,
exclude,
+ extend_exclude,
force_exclude,
report,
gitignore,
this_abs = THIS_DIR.resolve()
sources.extend(
black.gen_python_files(
- path.iterdir(), this_abs, include, exclude, None, report, gitignore
+ path.iterdir(),
+ this_abs,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))
verbose=False,
include=include,
exclude=exclude,
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=None,
verbose=False,
include=include,
exclude=exclude,
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=None,
verbose=False,
include=include,
exclude=exclude,
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=stdin_filename,
verbose=False,
include=include,
exclude=exclude,
+ extend_exclude=None,
+ force_exclude=None,
+ report=report,
+ stdin_filename=stdin_filename,
+ )
+ )
+ self.assertEqual(sorted(expected), sorted(sources))
+
+ @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
+ def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
+ # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
+ # file being passed directly. This is the same as
+ # test_exclude_for_issue_1572
+ path = THIS_DIR / "data" / "include_exclude_tests"
+ include = ""
+ extend_exclude = r"/exclude/|a\.py"
+ src = "-"
+ report = black.Report()
+ stdin_filename = str(path / "b/exclude/a.py")
+ expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
+ sources = list(
+ black.get_sources(
+ ctx=FakeContext(),
+ src=(src,),
+ quiet=True,
+ verbose=False,
+ include=include,
+ exclude="",
+ extend_exclude=extend_exclude,
force_exclude=None,
report=report,
stdin_filename=stdin_filename,
verbose=False,
include=include,
exclude="",
+ extend_exclude=None,
force_exclude=force_exclude,
report=report,
stdin_filename=stdin_filename,
this_abs = THIS_DIR.resolve()
sources.extend(
black.gen_python_files(
- path.iterdir(), this_abs, include, exclude, None, report, gitignore
+ path.iterdir(),
+ this_abs,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))
empty,
re.compile(black.DEFAULT_EXCLUDES),
None,
+ None,
report,
gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))
- def test_empty_exclude(self) -> None:
+ def test_extend_exclude(self) -> None:
path = THIS_DIR / "data" / "include_exclude_tests"
report = black.Report()
gitignore = PathSpec.from_lines("gitwildmatch", [])
- empty = re.compile(r"")
sources: List[Path] = []
expected = [
- Path(path / "b/dont_exclude/a.py"),
- Path(path / "b/dont_exclude/a.pyi"),
Path(path / "b/exclude/a.py"),
- Path(path / "b/exclude/a.pyi"),
- Path(path / "b/.definitely_exclude/a.py"),
- Path(path / "b/.definitely_exclude/a.pyi"),
+ Path(path / "b/dont_exclude/a.py"),
]
this_abs = THIS_DIR.resolve()
sources.extend(
path.iterdir(),
this_abs,
re.compile(black.DEFAULT_INCLUDES),
- empty,
+ re.compile(r"\.pyi$"),
+ re.compile(r"\.definitely_exclude"),
None,
report,
gitignore,
)
self.assertEqual(sorted(expected), sorted(sources))
- def test_invalid_include_exclude(self) -> None:
- for option in ["--include", "--exclude"]:
+ def test_invalid_cli_regex(self) -> None:
+ for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
def test_preserves_line_endings(self) -> None:
try:
list(
black.gen_python_files(
- path.iterdir(), root, include, exclude, None, report, gitignore
+ path.iterdir(),
+ root,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
except ValueError as ve:
with self.assertRaises(ValueError):
list(
black.gen_python_files(
- path.iterdir(), root, include, exclude, None, report, gitignore
+ path.iterdir(),
+ root,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
path.iterdir.assert_called()