Repository: psf/black

Issue:
extend-exclude with an array is a footgun
I accidentally had the following configuration in a project:

```
[tool.black]
extend-exclude = [
    "backendapp/migrations"
]
```

If i then run `black . -vvv`, it tells me e.g.

```
manage.py ignored: matches the --extend-exclude regular expression
```

which is really confusing because `backendapp/migrations` is neither a regex nor should it match `manage.py`.

The solution is `extend-exclude = "backendapp/migrations"`, which i only realized when looking into other projects' pyproject.toml files.

**Describe the solution you'd like**

I would be nice if black would fail or at least warn when the configuration uses the wrong datatype. Additionally, there aren't any examples of `extend-exclude` in pyproject.toml in the docs, the only usage in the repo is black's own pyproject.toml

**Describe alternatives you've considered**

<!-- A clear and concise description of any
alternative solutions or features you've considered. -->

n/a

**Additional context**

<!-- Add any other context or screenshots about the issue
here. -->

Doesn't honor extend-exclude for nested file
**Describe the bug**

Given the following configuration in `pyproject.toml`
```
[tool.black]
line-length = 120
extend-exclude = [
    '^(?:([^\/]+\/)+(?:test_.*\.py|.*_test\.py))$',
    '.github',
    '.mypy_cache',
    '.pytest_cache',
    'venv'
]
```

Black fails with the error:

```
Error: Invalid value for '--extend-exclude': Not a valid regular expression: unbalanced parenthesis at position 17
```

But this is a valid regex that's supposed to match any files starting or ending in `test` or files in a directory named `test`. For example, the following path matches the `test` directory:
myapp/folder/test/test_something.py

**To Reproduce**

I guess use the regex in a unit test that parses it?

**Expected behavior**

It should work. It would be even better if Black could use the `extend-exclude` list from flake8 or mypy, so that I don't have repeat it. The [documentation](https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html) only shows the `max-line-length` property. 

**Environment**

- Black's version: 23.3.0
- OS: macOS Ventura 13.4.1, M1 chip
- Python version: (CPython) 3.11.3



Discussion:
Let's do it! (unless we want to support globs - #3559 but that seems unlikely).  
Oh wow yeah, I just shot myself in the foot. Thanks.
```toml
extend-exclude = [
  "Externals/", # Don't format vendored code
]
```
@Avasam According to [this comment](https://github.com/psf/black/issues/3754#issuecomment-1610797901), `extend-exclude` is a str, not a list. I'm curious how you got it working as a list.
> @Avasam According to [this comment](https://github.com/psf/black/issues/3754#issuecomment-1610797901), `extend-exclude` is a str, not a list. I'm curious how you got it working as a list.

I didn't, the point of this issue is that arrays don't work. My example fails.
The extend-exclude config is meant to be a str, not a list. The error message here could obviously be much better.
@hauntsaninja Thanks, let me try that. Do you've any comment on the other thing I mentioned above?

> It would be even better if Black could use the extend-exclude list from flake8 or mypy
> The extend-exclude config is meant to be a str
```
extend-exclude = """
/
^(?:(?:[^/]+/)+(?:test_.*\\.py|.*_test\\.py))$
| .github
/
"""
```
Nah, this doesn't work too. No error, but the file `test_something.py` gets formatted. Perhaps related to https://github.com/psf/black/issues/3617?

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -157,6 +157,16 @@ def read_pyproject_toml(
             "target-version", "Config key target-version must be a list"
         )
 
+    exclude = config.get("exclude")
+    if exclude is not None and not isinstance(exclude, str):
+        raise click.BadOptionUsage("exclude", "Config key exclude must be a string")
+
+    extend_exclude = config.get("extend_exclude")
+    if extend_exclude is not None and not isinstance(extend_exclude, str):
+        raise click.BadOptionUsage(
+            "extend-exclude", "Config key extend-exclude must be a string"
+        )
+
     default_map: Dict[str, Any] = {}
     if ctx.default_map:
         default_map.update(ctx.default_map)


Test patch: