parent
f71db23824
commit
489d00ed8f
125
README.md
125
README.md
@ -30,6 +30,7 @@ possible.
|
||||
|
||||
*Contents:* **[Installation and usage](#installation-and-usage)** |
|
||||
**[The *Black* code style](#the-black-code-style)** |
|
||||
**[pyproject.toml](#pyproject.toml)** |
|
||||
**[Editor integration](#editor-integration)** |
|
||||
**[Version control integration](#version-control-integration)** |
|
||||
**[Ignoring unmodified files](#ignoring-unmodified-files)** |
|
||||
@ -103,6 +104,7 @@ Options:
|
||||
that were not changed or were ignored due to
|
||||
--exclude=.
|
||||
--version Show the version and exit.
|
||||
--config PATH Read configuration from PATH.
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
@ -487,6 +489,98 @@ a future version of the formatter:
|
||||
* use `float` instead of `Union[int, float]`.
|
||||
|
||||
|
||||
## pyproject.toml
|
||||
|
||||
*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.
|
||||
|
||||
**Pro-tip**: If you're asking yourself "Do I need to configure anything?"
|
||||
the answer is "No". *Black* is all about sensible defaults.
|
||||
|
||||
|
||||
### What on Earth is a `pyproject.toml` file?
|
||||
|
||||
[PEP 518](https://www.python.org/dev/peps/pep-0518/) defines
|
||||
`pyproject.toml` as a configuration file to store build system
|
||||
requirements for Python projects. With the help of tools
|
||||
like [Poetry](https://poetry.eustace.io/) or
|
||||
[Flit](https://flit.readthedocs.io/en/latest/) it can fully replace the
|
||||
need for `setup.py` and `setup.cfg` files.
|
||||
|
||||
|
||||
### Where *Black* looks for the file
|
||||
|
||||
By default *Black* looks for `pyproject.toml` starting from the common
|
||||
base directory of all files and directories passed on the command line.
|
||||
If it's not there, it looks in parent directories. It stops looking
|
||||
when it finds the file, or a `.git` directory, or a `.hg` directory,
|
||||
or the root of the file system, whichever comes first.
|
||||
|
||||
If you're formatting standard input, *Black* will look for configuration
|
||||
starting from the current working directory.
|
||||
|
||||
You can also explicitly specify the path to a particular file that you
|
||||
want with `--config`. In this situation *Black* will not look for any
|
||||
other file.
|
||||
|
||||
If you're running with `--verbose`, you will see a blue message if
|
||||
a file was found and used.
|
||||
|
||||
|
||||
### Configuration format
|
||||
|
||||
As the file extension suggests, `pyproject.toml` is a [TOML](https://github.com/toml-lang/toml) file. It contains separate
|
||||
sections for different tools. *Black* is using the `[tool.black]`
|
||||
section. The option keys are the same as long names of options on
|
||||
the command line.
|
||||
|
||||
Note that you have to use single-quoted strings in TOML for regular
|
||||
expressions. It's the equivalent of r-strings in Python. Multiline
|
||||
strings are treated as verbose regular expressions by Black. Use `[ ]`
|
||||
to denote a significant space character.
|
||||
|
||||
<details>
|
||||
<summary>Example `pyproject.toml`</summary>
|
||||
|
||||
```toml
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
py36 = true
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
/(
|
||||
\.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
|
||||
)/
|
||||
'''
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Lookup hierarchy
|
||||
|
||||
Command-line options have defaults that you can see in `--help`.
|
||||
A `pyproject.toml` can override those defaults. Finally, options
|
||||
provided by the user on the command line override both.
|
||||
|
||||
*Black* will only ever use one `pyproject.toml` file during an entire
|
||||
run. It doesn't look for multiple files, and doesn't compose
|
||||
configuration from different levels of the file hierarchy.
|
||||
|
||||
|
||||
## Editor integration
|
||||
|
||||
### Emacs
|
||||
@ -632,16 +726,18 @@ repos:
|
||||
rev: stable
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--line-length=88, --safe]
|
||||
language_version: python3.6
|
||||
```
|
||||
Then run `pre-commit install` and you're ready to go.
|
||||
|
||||
`args` in the above config is optional but shows you how you can change
|
||||
the line length if you really need to. If you're already using Python
|
||||
3.7, switch the `language_version` accordingly. Finally, `stable` is a tag
|
||||
that is pinned to the latest release on PyPI. If you'd rather run on
|
||||
master, this is also an option.
|
||||
Avoid using `args` in the hook. Instead, store necessary configuration
|
||||
in `pyproject.toml` so that editors and command-line usage of Black all
|
||||
behave consistently for your project. See *Black*'s own `pyproject.toml`
|
||||
for an example.
|
||||
|
||||
If you're already using Python 3.7, switch the `language_version`
|
||||
accordingly. Finally, `stable` is a tag that is pinned to the latest
|
||||
release on PyPI. If you'd rather run on master, this is also an option.
|
||||
|
||||
|
||||
## Ignoring unmodified files
|
||||
@ -714,6 +810,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
### 18.6b2
|
||||
|
||||
* added `--config` (#65)
|
||||
|
||||
* fixed improper unmodified file caching when `-S` was used
|
||||
|
||||
|
||||
@ -1039,18 +1137,3 @@ Multiple contributions by:
|
||||
* [Stavros Korokithakis](mailto:hi@stavros.io)
|
||||
* [Sunil Kapil](mailto:snlkapil@gmail.com)
|
||||
* [Vishwas B Sharma](mailto:sharma.vishwas88@gmail.com)
|
||||
|
||||
---
|
||||
|
||||
*Contents:*
|
||||
**[Installation and Usage](#installation-and-usage)** |
|
||||
**[The *Black* code style](#the-black-code-style)** |
|
||||
**[Editor integration](#editor-integration)** |
|
||||
**[Version control integration](#version-control-integration)** |
|
||||
**[Ignoring unmodified files](#ignoring-unmodified-files)** |
|
||||
**[Testimonials](#testimonials)** |
|
||||
**[Show your style](#show-your-style)** |
|
||||
**[License](#license)** |
|
||||
**[Contributing](#contributing-to-black)** |
|
||||
**[Change Log](#change-log)** |
|
||||
**[Authors](#authors)**
|
||||
|
78
black.py
78
black.py
@ -3,7 +3,7 @@
|
||||
from concurrent.futures import Executor, ProcessPoolExecutor
|
||||
from datetime import datetime
|
||||
from enum import Enum, Flag
|
||||
from functools import partial, wraps
|
||||
from functools import lru_cache, partial, wraps
|
||||
import io
|
||||
import keyword
|
||||
import logging
|
||||
@ -38,6 +38,7 @@
|
||||
from appdirs import user_cache_dir
|
||||
from attr import dataclass, Factory
|
||||
import click
|
||||
import toml
|
||||
|
||||
# lib2to3 fork
|
||||
from blib2to3.pytree import Node, Leaf, type_repr
|
||||
@ -156,6 +157,40 @@ def from_configuration(
|
||||
return mode
|
||||
|
||||
|
||||
def read_pyproject_toml(
|
||||
ctx: click.Context, param: click.Parameter, value: Union[str, int, bool, None]
|
||||
) -> Optional[str]:
|
||||
"""Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
|
||||
|
||||
Returns the path to a successfully found and read configuration file, None
|
||||
otherwise.
|
||||
"""
|
||||
assert not isinstance(value, (int, bool)), "Invalid parameter type passed"
|
||||
if not value:
|
||||
root = find_project_root(ctx.params.get("src", ()))
|
||||
path = root / "pyproject.toml"
|
||||
if path.is_file():
|
||||
value = str(path)
|
||||
else:
|
||||
return None
|
||||
|
||||
try:
|
||||
pyproject_toml = toml.load(value)
|
||||
config = pyproject_toml.get("tool", {}).get("black", {})
|
||||
except (toml.TomlDecodeError, OSError) as e:
|
||||
raise click.BadOptionUsage(f"Error reading configuration file: {e}", ctx)
|
||||
|
||||
if not config:
|
||||
return None
|
||||
|
||||
if ctx.default_map is None:
|
||||
ctx.default_map = {}
|
||||
ctx.default_map.update( # type: ignore # bad types in .pyi
|
||||
{k.replace("--", "").replace("-", "_"): v for k, v in config.items()}
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"-l",
|
||||
@ -257,6 +292,16 @@ def from_configuration(
|
||||
type=click.Path(
|
||||
exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
|
||||
),
|
||||
is_eager=True,
|
||||
)
|
||||
@click.option(
|
||||
"--config",
|
||||
type=click.Path(
|
||||
exists=False, file_okay=True, dir_okay=False, readable=True, allow_dash=False
|
||||
),
|
||||
is_eager=True,
|
||||
callback=read_pyproject_toml,
|
||||
help="Read configuration from PATH.",
|
||||
)
|
||||
@click.pass_context
|
||||
def main(
|
||||
@ -272,26 +317,29 @@ def main(
|
||||
verbose: bool,
|
||||
include: str,
|
||||
exclude: str,
|
||||
src: List[str],
|
||||
src: Tuple[str],
|
||||
config: Optional[str],
|
||||
) -> None:
|
||||
"""The uncompromising code formatter."""
|
||||
write_back = WriteBack.from_configuration(check=check, diff=diff)
|
||||
mode = FileMode.from_configuration(
|
||||
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization
|
||||
)
|
||||
report = Report(check=check, quiet=quiet, verbose=verbose)
|
||||
sources: Set[Path] = set()
|
||||
if config and verbose:
|
||||
out(f"Using configuration from {config}.", bold=False, fg="blue")
|
||||
try:
|
||||
include_regex = re.compile(include)
|
||||
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(exclude)
|
||||
exclude_regex = re_compile_maybe_verbose(exclude)
|
||||
except re.error:
|
||||
err(f"Invalid regular expression for exclude given: {exclude!r}")
|
||||
ctx.exit(2)
|
||||
report = Report(check=check, quiet=quiet, verbose=verbose)
|
||||
root = find_project_root(src)
|
||||
sources: Set[Path] = set()
|
||||
for s in src:
|
||||
p = Path(s)
|
||||
if p.is_dir():
|
||||
@ -307,9 +355,8 @@ def main(
|
||||
if verbose or not quiet:
|
||||
out("No paths given. Nothing to do 😴")
|
||||
ctx.exit(0)
|
||||
return
|
||||
|
||||
elif len(sources) == 1:
|
||||
if len(sources) == 1:
|
||||
reformat_one(
|
||||
src=sources.pop(),
|
||||
line_length=line_length,
|
||||
@ -2894,7 +2941,7 @@ def gen_python_files_in_dir(
|
||||
normalized_path += "/"
|
||||
exclude_match = exclude.search(normalized_path)
|
||||
if exclude_match and exclude_match.group(0):
|
||||
report.path_ignored(child, f"matches --exclude={exclude.pattern}")
|
||||
report.path_ignored(child, f"matches the --exclude regular expression")
|
||||
continue
|
||||
|
||||
if child.is_dir():
|
||||
@ -2906,7 +2953,8 @@ def gen_python_files_in_dir(
|
||||
yield child
|
||||
|
||||
|
||||
def find_project_root(srcs: List[str]) -> Path:
|
||||
@lru_cache()
|
||||
def find_project_root(srcs: Iterable[str]) -> Path:
|
||||
"""Return a directory containing .git, .hg, or pyproject.toml.
|
||||
|
||||
That directory can be one of the directories passed in `srcs` or their
|
||||
@ -3164,6 +3212,16 @@ def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
|
||||
return regex.sub(replacement, regex.sub(replacement, original))
|
||||
|
||||
|
||||
def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
|
||||
"""Compile a regular expression string in `regex`.
|
||||
|
||||
If it contains newlines, use verbose mode.
|
||||
"""
|
||||
if "\n" in regex:
|
||||
regex = "(?x)" + regex
|
||||
return re.compile(regex)
|
||||
|
||||
|
||||
def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]:
|
||||
"""Like `reversed(enumerate(sequence))` if that were possible."""
|
||||
index = len(sequence) - 1
|
||||
|
@ -48,10 +48,12 @@ Contents
|
||||
|
||||
installation_and_usage
|
||||
the_black_code_style
|
||||
pyproject_toml
|
||||
editor_integration
|
||||
version_control_integration
|
||||
ignoring_unmodified_files
|
||||
contributing
|
||||
show_your_style
|
||||
change_log
|
||||
reference/reference_summary
|
||||
authors
|
||||
|
1
docs/pyproject_toml.md
Symbolic link
1
docs/pyproject_toml.md
Symbolic link
@ -0,0 +1 @@
|
||||
_build/generated/pyproject_toml.md
|
28
pyproject.toml
Normal file
28
pyproject.toml
Normal file
@ -0,0 +1,28 @@
|
||||
# Example configuration for Black.
|
||||
|
||||
# NOTE: you have to use single-quoted strings in TOML for regular expressions.
|
||||
# It's the equivalent of r-strings in Python. Multiline strings are treated as
|
||||
# verbose regular expressions by Black. Use [ ] to denote a significant space
|
||||
# character.
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
py36 = true
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
/(
|
||||
\.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
|
||||
)/
|
||||
'''
|
2
tests/data/empty_pyproject.toml
Normal file
2
tests/data/empty_pyproject.toml
Normal file
@ -0,0 +1,2 @@
|
||||
# Empty pyproject.toml to use with some tests that depend on Python 3.6 autodiscovery
|
||||
# and so on.
|
@ -176,10 +176,10 @@ def test_piping_diff(self) -> None:
|
||||
)
|
||||
source, _ = read_data("expression.py")
|
||||
expected, _ = read_data("expression.diff")
|
||||
config = THIS_DIR / "data" / "empty_pyproject.toml"
|
||||
stderrbuf = BytesIO()
|
||||
result = BlackRunner(stderrbuf).invoke(
|
||||
black.main, ["-", "--fast", f"--line-length={ll}", "--diff"], input=source
|
||||
)
|
||||
args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
|
||||
result = BlackRunner(stderrbuf).invoke(black.main, args, input=source)
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
actual = diff_header.sub("[Deterministic header]", result.output)
|
||||
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
||||
|
Loading…
Reference in New Issue
Block a user