parent
f71db23824
commit
489d00ed8f
125
README.md
125
README.md
@ -30,6 +30,7 @@ possible.
|
|||||||
|
|
||||||
*Contents:* **[Installation and usage](#installation-and-usage)** |
|
*Contents:* **[Installation and usage](#installation-and-usage)** |
|
||||||
**[The *Black* code style](#the-black-code-style)** |
|
**[The *Black* code style](#the-black-code-style)** |
|
||||||
|
**[pyproject.toml](#pyproject.toml)** |
|
||||||
**[Editor integration](#editor-integration)** |
|
**[Editor integration](#editor-integration)** |
|
||||||
**[Version control integration](#version-control-integration)** |
|
**[Version control integration](#version-control-integration)** |
|
||||||
**[Ignoring unmodified files](#ignoring-unmodified-files)** |
|
**[Ignoring unmodified files](#ignoring-unmodified-files)** |
|
||||||
@ -103,6 +104,7 @@ Options:
|
|||||||
that were not changed or were ignored due to
|
that were not changed or were ignored due to
|
||||||
--exclude=.
|
--exclude=.
|
||||||
--version Show the version and exit.
|
--version Show the version and exit.
|
||||||
|
--config PATH Read configuration from PATH.
|
||||||
--help Show this message and exit.
|
--help Show this message and exit.
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -487,6 +489,98 @@ a future version of the formatter:
|
|||||||
* use `float` instead of `Union[int, float]`.
|
* 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
|
## Editor integration
|
||||||
|
|
||||||
### Emacs
|
### Emacs
|
||||||
@ -632,16 +726,18 @@ repos:
|
|||||||
rev: stable
|
rev: stable
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--line-length=88, --safe]
|
|
||||||
language_version: python3.6
|
language_version: python3.6
|
||||||
```
|
```
|
||||||
Then run `pre-commit install` and you're ready to go.
|
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
|
Avoid using `args` in the hook. Instead, store necessary configuration
|
||||||
the line length if you really need to. If you're already using Python
|
in `pyproject.toml` so that editors and command-line usage of Black all
|
||||||
3.7, switch the `language_version` accordingly. Finally, `stable` is a tag
|
behave consistently for your project. See *Black*'s own `pyproject.toml`
|
||||||
that is pinned to the latest release on PyPI. If you'd rather run on
|
for an example.
|
||||||
master, this is also an option.
|
|
||||||
|
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
|
## Ignoring unmodified files
|
||||||
@ -714,6 +810,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
|
|||||||
|
|
||||||
### 18.6b2
|
### 18.6b2
|
||||||
|
|
||||||
|
* added `--config` (#65)
|
||||||
|
|
||||||
* fixed improper unmodified file caching when `-S` was used
|
* fixed improper unmodified file caching when `-S` was used
|
||||||
|
|
||||||
|
|
||||||
@ -1039,18 +1137,3 @@ Multiple contributions by:
|
|||||||
* [Stavros Korokithakis](mailto:hi@stavros.io)
|
* [Stavros Korokithakis](mailto:hi@stavros.io)
|
||||||
* [Sunil Kapil](mailto:snlkapil@gmail.com)
|
* [Sunil Kapil](mailto:snlkapil@gmail.com)
|
||||||
* [Vishwas B Sharma](mailto:sharma.vishwas88@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 concurrent.futures import Executor, ProcessPoolExecutor
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, Flag
|
from enum import Enum, Flag
|
||||||
from functools import partial, wraps
|
from functools import lru_cache, partial, wraps
|
||||||
import io
|
import io
|
||||||
import keyword
|
import keyword
|
||||||
import logging
|
import logging
|
||||||
@ -38,6 +38,7 @@
|
|||||||
from appdirs import user_cache_dir
|
from appdirs import user_cache_dir
|
||||||
from attr import dataclass, Factory
|
from attr import dataclass, Factory
|
||||||
import click
|
import click
|
||||||
|
import toml
|
||||||
|
|
||||||
# lib2to3 fork
|
# lib2to3 fork
|
||||||
from blib2to3.pytree import Node, Leaf, type_repr
|
from blib2to3.pytree import Node, Leaf, type_repr
|
||||||
@ -156,6 +157,40 @@ def from_configuration(
|
|||||||
return mode
|
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.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"-l",
|
"-l",
|
||||||
@ -257,6 +292,16 @@ def from_configuration(
|
|||||||
type=click.Path(
|
type=click.Path(
|
||||||
exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
|
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
|
@click.pass_context
|
||||||
def main(
|
def main(
|
||||||
@ -272,26 +317,29 @@ def main(
|
|||||||
verbose: bool,
|
verbose: bool,
|
||||||
include: str,
|
include: str,
|
||||||
exclude: str,
|
exclude: str,
|
||||||
src: List[str],
|
src: Tuple[str],
|
||||||
|
config: Optional[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""The uncompromising code formatter."""
|
"""The uncompromising code formatter."""
|
||||||
write_back = WriteBack.from_configuration(check=check, diff=diff)
|
write_back = WriteBack.from_configuration(check=check, diff=diff)
|
||||||
mode = FileMode.from_configuration(
|
mode = FileMode.from_configuration(
|
||||||
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization
|
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization
|
||||||
)
|
)
|
||||||
report = Report(check=check, quiet=quiet, verbose=verbose)
|
if config and verbose:
|
||||||
sources: Set[Path] = set()
|
out(f"Using configuration from {config}.", bold=False, fg="blue")
|
||||||
try:
|
try:
|
||||||
include_regex = re.compile(include)
|
include_regex = re_compile_maybe_verbose(include)
|
||||||
except re.error:
|
except re.error:
|
||||||
err(f"Invalid regular expression for include given: {include!r}")
|
err(f"Invalid regular expression for include given: {include!r}")
|
||||||
ctx.exit(2)
|
ctx.exit(2)
|
||||||
try:
|
try:
|
||||||
exclude_regex = re.compile(exclude)
|
exclude_regex = re_compile_maybe_verbose(exclude)
|
||||||
except re.error:
|
except re.error:
|
||||||
err(f"Invalid regular expression for exclude given: {exclude!r}")
|
err(f"Invalid regular expression for exclude given: {exclude!r}")
|
||||||
ctx.exit(2)
|
ctx.exit(2)
|
||||||
|
report = Report(check=check, quiet=quiet, verbose=verbose)
|
||||||
root = find_project_root(src)
|
root = find_project_root(src)
|
||||||
|
sources: Set[Path] = set()
|
||||||
for s in src:
|
for s in src:
|
||||||
p = Path(s)
|
p = Path(s)
|
||||||
if p.is_dir():
|
if p.is_dir():
|
||||||
@ -307,9 +355,8 @@ def main(
|
|||||||
if verbose or not quiet:
|
if verbose or not quiet:
|
||||||
out("No paths given. Nothing to do 😴")
|
out("No paths given. Nothing to do 😴")
|
||||||
ctx.exit(0)
|
ctx.exit(0)
|
||||||
return
|
|
||||||
|
|
||||||
elif len(sources) == 1:
|
if len(sources) == 1:
|
||||||
reformat_one(
|
reformat_one(
|
||||||
src=sources.pop(),
|
src=sources.pop(),
|
||||||
line_length=line_length,
|
line_length=line_length,
|
||||||
@ -2894,7 +2941,7 @@ def gen_python_files_in_dir(
|
|||||||
normalized_path += "/"
|
normalized_path += "/"
|
||||||
exclude_match = exclude.search(normalized_path)
|
exclude_match = exclude.search(normalized_path)
|
||||||
if exclude_match and exclude_match.group(0):
|
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
|
continue
|
||||||
|
|
||||||
if child.is_dir():
|
if child.is_dir():
|
||||||
@ -2906,7 +2953,8 @@ def gen_python_files_in_dir(
|
|||||||
yield child
|
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.
|
"""Return a directory containing .git, .hg, or pyproject.toml.
|
||||||
|
|
||||||
That directory can be one of the directories passed in `srcs` or their
|
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))
|
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]]:
|
def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]:
|
||||||
"""Like `reversed(enumerate(sequence))` if that were possible."""
|
"""Like `reversed(enumerate(sequence))` if that were possible."""
|
||||||
index = len(sequence) - 1
|
index = len(sequence) - 1
|
||||||
|
@ -48,10 +48,12 @@ Contents
|
|||||||
|
|
||||||
installation_and_usage
|
installation_and_usage
|
||||||
the_black_code_style
|
the_black_code_style
|
||||||
|
pyproject_toml
|
||||||
editor_integration
|
editor_integration
|
||||||
version_control_integration
|
version_control_integration
|
||||||
ignoring_unmodified_files
|
ignoring_unmodified_files
|
||||||
contributing
|
contributing
|
||||||
|
show_your_style
|
||||||
change_log
|
change_log
|
||||||
reference/reference_summary
|
reference/reference_summary
|
||||||
authors
|
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")
|
source, _ = read_data("expression.py")
|
||||||
expected, _ = read_data("expression.diff")
|
expected, _ = read_data("expression.diff")
|
||||||
|
config = THIS_DIR / "data" / "empty_pyproject.toml"
|
||||||
stderrbuf = BytesIO()
|
stderrbuf = BytesIO()
|
||||||
result = BlackRunner(stderrbuf).invoke(
|
args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
|
||||||
black.main, ["-", "--fast", f"--line-length={ll}", "--diff"], input=source
|
result = BlackRunner(stderrbuf).invoke(black.main, args, input=source)
|
||||||
)
|
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
actual = diff_header.sub("[Deterministic header]", result.output)
|
actual = diff_header.sub("[Deterministic header]", result.output)
|
||||||
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
||||||
|
Loading…
Reference in New Issue
Block a user