]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Add --extend-exclude parameter (#2005)
authorJoshua Cannon <joshua.cannon@ni.com>
Mon, 1 Mar 2021 22:07:36 +0000 (16:07 -0600)
committerGitHub <noreply@github.com>
Mon, 1 Mar 2021 22:07:36 +0000 (14:07 -0800)
Look ma! I contribute to open source!

Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com>
README.md
docs/change_log.md
docs/installation_and_usage.md
docs/pyproject_toml.md
pyproject.toml
src/black/__init__.py
tests/test_black.py

index 178f763c32d3fd5c1cc864f7e36441326e1dd811..5f8b52cb823eb129e00027c6c9d42eeaeaa979bb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -135,11 +135,17 @@ Options:
                                   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
@@ -151,7 +157,7 @@ Options:
 
   -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.
@@ -263,7 +269,7 @@ above. What seems like a bug might be intended behaviour.
 
 _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.
@@ -313,25 +319,10 @@ expressions by Black. Use `[ ]` to denote a significant space character.
 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)
 '''
 ```
 
@@ -616,6 +607,7 @@ Multiple contributions by:
 - [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)
index 066be76c06cbee5ee16c7b0dd73e8bfbb6cdb2ae..01c27553fca404f6063a4bfda065cd090468aff3 100644 (file)
@@ -28,6 +28,8 @@
 
 - use lowercase hex strings (#1692)
 
+- added `--extend-exclude` argument (#1571)
+
 #### _Packaging_
 
 - Self-contained native _Black_ binaries are now provided for releases via GitHub
index ee45c934da80b7d403ddeac5c339c3de9a0a261c..bb554eb674462f29badbe898f66a54de9c8d8999 100644 (file)
@@ -95,6 +95,11 @@ Options:
                                   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
@@ -106,7 +111,7 @@ Options:
 
   -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.
index 453f533bf961fbf321d24ea0dd6daad349431c5d..9acc4c03d7c3dfa19bfed555f7b8d05734f3c76b 100644 (file)
@@ -4,7 +4,8 @@
 
 _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.
@@ -54,25 +55,10 @@ expressions by Black. Use `[ ]` to denote a significant space character.
 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)
 '''
 ```
 
index 9d4da0bf6927ac7991041559338a12f71ac47664..7f632f2839d8f3d7ed0dea8926d2051f15fe9c1d 100644 (file)
@@ -9,19 +9,8 @@
 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
index a1d16d9bdeb222f9d5d2a811c52f3671323cfe6e..e21e2af5bd1394ca0441d1ccf133a0d68f6c0515 100644 (file)
@@ -461,6 +461,14 @@ def target_version_option_callback(
     ),
     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,
@@ -493,7 +501,7 @@ def target_version_option_callback(
     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__)
@@ -537,6 +545,7 @@ def main(
     verbose: bool,
     include: str,
     exclude: str,
+    extend_exclude: Optional[str],
     force_exclude: Optional[str],
     stdin_filename: Optional[str],
     src: Tuple[str, ...],
@@ -570,6 +579,7 @@ def main(
         verbose=verbose,
         include=include,
         exclude=exclude,
+        extend_exclude=extend_exclude,
         force_exclude=force_exclude,
         report=report,
         stdin_filename=stdin_filename,
@@ -602,6 +612,18 @@ def main(
     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,
@@ -610,28 +632,18 @@ def get_sources(
     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()
@@ -672,6 +684,7 @@ def get_sources(
                     root,
                     include_regex,
                     exclude_regex,
+                    extend_exclude_regex,
                     force_exclude_regex,
                     report,
                     gitignore,
@@ -6112,17 +6125,27 @@ def normalize_path_maybe_ignore(
     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.
 
@@ -6139,20 +6162,22 @@ def gen_python_files(
             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
 
@@ -6162,6 +6187,7 @@ def gen_python_files(
                 root,
                 include,
                 exclude,
+                extend_exclude,
                 force_exclude,
                 report,
                 gitignore,
index 9c3cc64387b1d91bdc3238024a7e6d94d67aaa5c..ba1869aa4c541064f65abeacec545099e4e28028 100644 (file)
@@ -1346,7 +1346,14 @@ class BlackTestCase(BlackBaseTestCase):
         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))
@@ -1370,6 +1377,7 @@ class BlackTestCase(BlackBaseTestCase):
                 verbose=False,
                 include=include,
                 exclude=exclude,
+                extend_exclude=None,
                 force_exclude=None,
                 report=report,
                 stdin_filename=None,
@@ -1392,6 +1400,7 @@ class BlackTestCase(BlackBaseTestCase):
                 verbose=False,
                 include=include,
                 exclude=exclude,
+                extend_exclude=None,
                 force_exclude=None,
                 report=report,
                 stdin_filename=None,
@@ -1415,6 +1424,7 @@ class BlackTestCase(BlackBaseTestCase):
                 verbose=False,
                 include=include,
                 exclude=exclude,
+                extend_exclude=None,
                 force_exclude=None,
                 report=report,
                 stdin_filename=stdin_filename,
@@ -1442,6 +1452,35 @@ class BlackTestCase(BlackBaseTestCase):
                 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,
@@ -1467,6 +1506,7 @@ class BlackTestCase(BlackBaseTestCase):
                 verbose=False,
                 include=include,
                 exclude="",
+                extend_exclude=None,
                 force_exclude=force_exclude,
                 report=report,
                 stdin_filename=stdin_filename,
@@ -1551,7 +1591,14 @@ class BlackTestCase(BlackBaseTestCase):
         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))
@@ -1581,25 +1628,21 @@ class BlackTestCase(BlackBaseTestCase):
                 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(
@@ -1607,7 +1650,8 @@ class BlackTestCase(BlackBaseTestCase):
                 path.iterdir(),
                 this_abs,
                 re.compile(black.DEFAULT_INCLUDES),
-                empty,
+                re.compile(r"\.pyi$"),
+                re.compile(r"\.definitely_exclude"),
                 None,
                 report,
                 gitignore,
@@ -1615,8 +1659,8 @@ class BlackTestCase(BlackBaseTestCase):
         )
         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:
@@ -1665,7 +1709,14 @@ class BlackTestCase(BlackBaseTestCase):
         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:
@@ -1679,7 +1730,14 @@ class BlackTestCase(BlackBaseTestCase):
         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()