Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Flag Options #2259

Merged
merged 16 commits into from Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.md
Expand Up @@ -68,6 +68,7 @@ Multiple contributions by:
- Gustavo Camargo
- hauntsaninja
- [Hadi Alqattan](mailto:alqattanhadizaki@gmail.com)
- [Hassan Abouelela](mailto:hassan@hassanamr.com)
- [Heaford](mailto:dan@heaford.com)
- [Hugo Barrera](mailto::hugo@barrera.io)
- Hugo van Kemenade
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -9,6 +9,7 @@
- Respect `.gitignore` files in all levels, not only `root/.gitignore` file (apply
`.gitignore` rules like `git` does) (#2225)
- Restored compatibility with Click 8.0 on Python 3.6 when LANG=C used (#2227)
- Fixed option usage when using the `--code` flag (#2259)
- Add extra uvloop install + import support if in python env (#2258)

### _Blackd_
Expand All @@ -26,6 +27,7 @@
- Fix typos discovered by codespell (#2228)
- Fix Vim plugin installation instructions. (#2235)
- Add new Frequently Asked Questions page (#2247)
- Removed safety checks warning for the `--code` option (#2259)
- Fix encoding + symlink issues preventing proper build on Windows (#2262)

## 21.5b1
Expand Down
7 changes: 0 additions & 7 deletions docs/usage_and_configuration/the_basics.md
Expand Up @@ -68,13 +68,6 @@ $ black --code "print ( 'hello, world' )"
print("hello, world")
```

```{warning}
--check, --diff, and --safe / --fast have no effect when using -c / --code. Safety
checks normally turned on by default that verify _Black_'s output are disabled as well.
This is a bug which we intend to fix eventually. More details can be found in this [bug
report](https://github.com/psf/black/issues/2104).
```

### Writeback and reporting

By default _Black_ reformats the files given and/or found in place. Sometimes you need
Expand Down
115 changes: 80 additions & 35 deletions src/black/__init__.py
Expand Up @@ -384,47 +384,56 @@ def main(
)
if config and verbose:
out(f"Using configuration from {config}.", bold=False, fg="blue")
if code is not None:
print(format_str(code, mode=mode))
ctx.exit(0)
report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
sources = get_sources(
ctx=ctx,
src=src,
quiet=quiet,
verbose=verbose,
include=include,
exclude=exclude,
extend_exclude=extend_exclude,
force_exclude=force_exclude,
report=report,
stdin_filename=stdin_filename,
)

path_empty(
sources,
"No Python files are present to be formatted. Nothing to do 😴",
quiet,
verbose,
ctx,
)
report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)

if len(sources) == 1:
reformat_one(
src=sources.pop(),
fast=fast,
write_back=write_back,
mode=mode,
report=report,
if code is not None:
reformat_code(
content=code, fast=fast, write_back=write_back, mode=mode, report=report
)
else:
reformat_many(
sources=sources, fast=fast, write_back=write_back, mode=mode, report=report
sources = get_sources(
ctx=ctx,
src=src,
quiet=quiet,
verbose=verbose,
include=include,
exclude=exclude,
extend_exclude=extend_exclude,
force_exclude=force_exclude,
report=report,
stdin_filename=stdin_filename,
)

path_empty(
sources,
"No Python files are present to be formatted. Nothing to do 😴",
quiet,
verbose,
ctx,
)

if len(sources) == 1:
reformat_one(
src=sources.pop(),
fast=fast,
write_back=write_back,
mode=mode,
report=report,
)
else:
reformat_many(
sources=sources,
fast=fast,
write_back=write_back,
mode=mode,
report=report,
)

if verbose or not quiet:
out("Oh no! 💥 💔 💥" if report.return_code else "All done! ✨ 🍰 ✨")
click.secho(str(report), err=True)
if code is None:
click.secho(str(report), err=True)
ctx.exit(report.return_code)


Expand Down Expand Up @@ -511,6 +520,29 @@ def path_empty(
ctx.exit(0)


def reformat_code(
content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
) -> None:
"""
Reformat and print out `content` without spawning child processes.
Similar to `reformat_one`, but for string content.

`fast`, `write_back`, and `mode` options are passed to
:func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
"""
try:
changed = Changed.NO
if format_stdin_to_stdout(
content=content, fast=fast, write_back=write_back, mode=mode
):
changed = Changed.YES
report.done(content, changed)
HassanAbouelela marked this conversation as resolved.
Show resolved Hide resolved
except Exception as exc:
if report.verbose:
traceback.print_exc()
report.failed(content, str(exc))


def reformat_one(
src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
) -> None:
Expand Down Expand Up @@ -719,16 +751,27 @@ def format_file_in_place(


def format_stdin_to_stdout(
fast: bool, *, write_back: WriteBack = WriteBack.NO, mode: Mode
fast: bool,
*,
content: Optional[str] = None,
write_back: WriteBack = WriteBack.NO,
mode: Mode,
) -> bool:
"""Format file on stdin. Return True if changed.

If content is None, it's read from sys.stdin.

If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
write a diff to stdout. The `mode` argument is passed to
:func:`format_file_contents`.
"""
then = datetime.utcnow()
src, encoding, newline = decode_bytes(sys.stdin.buffer.read())

if content is None:
src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
else:
src, encoding, newline = content, "utf-8", ""

dst = src
try:
dst = format_file_contents(src, fast=fast, mode=mode)
Expand All @@ -742,6 +785,8 @@ def format_stdin_to_stdout(
sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
)
if write_back == WriteBack.YES:
# Make sure there's a newline after the content
dst += "" if dst[-1] == "\n" else "\n"
f.write(dst)
elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
now = datetime.utcnow()
Expand Down
5 changes: 3 additions & 2 deletions src/black/report.py
Expand Up @@ -4,6 +4,7 @@
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import typing
HassanAbouelela marked this conversation as resolved.
Show resolved Hide resolved

from click import style

Expand All @@ -28,7 +29,7 @@ class Report:
same_count: int = 0
failure_count: int = 0

def done(self, src: Path, changed: Changed) -> None:
def done(self, src: typing.Union[Path, str], changed: Changed) -> None:
"""Increment the counter for successful reformatting. Write out a message."""
if changed is Changed.YES:
reformatted = "would reformat" if self.check or self.diff else "reformatted"
Expand All @@ -44,7 +45,7 @@ def done(self, src: Path, changed: Changed) -> None:
out(msg, bold=False)
self.same_count += 1

def failed(self, src: Path, message: str) -> None:
def failed(self, src: typing.Union[Path, str], message: str) -> None:
"""Increment the counter for failed reformatting. Write out a message."""
err(f"error: cannot format {src}: {message}")
self.failure_count += 1
Expand Down
100 changes: 100 additions & 0 deletions tests/test_black.py
Expand Up @@ -34,6 +34,7 @@
from black import Feature, TargetVersion
from black.cache import get_cache_file
from black.debug import DebugVisitor
from black.output import diff, color_diff
from black.report import Report
import black.files

Expand Down Expand Up @@ -63,6 +64,9 @@
T = TypeVar("T")
R = TypeVar("R")

# Match the time output in a diff, but nothing else
DIFF_TIME = re.compile(r"\t[\d-:+\. ]+")


@contextmanager
def cache_dir(exists: bool = True) -> Iterator[Path]:
Expand Down Expand Up @@ -2069,6 +2073,102 @@ def test_docstring_reformat_for_py27(self) -> None:
actual = result.output
self.assertFormatEqual(actual, expected)

@staticmethod
def compare_results(
result: click.testing.Result, expected_value: str, expected_exit_code: int
) -> None:
"""Helper method to test the value and exit code of a click Result."""
assert (
result.output == expected_value
), "The output did not match the expected value."
assert result.exit_code == expected_exit_code, "The exit code is incorrect."
HassanAbouelela marked this conversation as resolved.
Show resolved Hide resolved

def test_code_option(self) -> None:
"""Test the code option with no changes."""
code = 'print("Hello world")\n'
args = ["--quiet", "--code", code]
result = CliRunner().invoke(black.main, args)

self.compare_results(result, code, 0)

def test_code_option_changed(self) -> None:
"""Test the code option when changes are required."""
code = "print('hello world')"
formatted = black.format_str(code, mode=DEFAULT_MODE)

args = ["--quiet", "--code", code]
result = CliRunner().invoke(black.main, args)

self.compare_results(result, formatted, 0)

def test_code_option_check(self) -> None:
"""Test the code option when check is passed."""
args = ["--check", "--quiet", "--code", 'print("Hello world")\n']
result = CliRunner().invoke(black.main, args)
self.compare_results(result, "", 0)

def test_code_option_check_changed(self) -> None:
"""Test the code option when changes are required, and check is passed."""
args = ["--check", "--quiet", "--code", "print('hello world')"]
result = CliRunner().invoke(black.main, args)
self.compare_results(result, "", 1)

def test_code_option_diff(self) -> None:
"""Test the code option when diff is passed."""
code = "print('hello world')"
formatted = black.format_str(code, mode=DEFAULT_MODE)
result_diff = diff(code, formatted, "STDIN", "STDOUT")

args = ["--diff", "--quiet", "--code", code]
result = CliRunner().invoke(black.main, args)

# Remove time from diff
output = DIFF_TIME.sub("", result.output)

assert output == result_diff, "The output did not match the expected value."
assert result.exit_code == 0, "The exit code is incorrect."

def test_code_option_color_diff(self) -> None:
"""Test the code option when color and diff are passed."""
code = "print('hello world')"
formatted = black.format_str(code, mode=DEFAULT_MODE)

result_diff = diff(code, formatted, "STDIN", "STDOUT")
result_diff = color_diff(result_diff)

args = ["--diff", "--quiet", "--color", "--code", code]
result = CliRunner().invoke(black.main, args)

# Remove time from diff
output = DIFF_TIME.sub("", result.output)

assert output == result_diff, "The output did not match the expected value."
assert result.exit_code == 0, "The exit code is incorrect."

def test_code_option_safe(self) -> None:
"""Test that the code option throws an error when the sanity checks fail."""
# Patch black.assert_equivalent to ensure the sanity checks fail
with patch.object(black, "assert_equivalent", side_effect=AssertionError):
code = 'print("Hello world")'
error_msg = f"{code}\nerror: cannot format {code}: \n"

args = ["--safe", "--quiet", "--code", code]
result = CliRunner().invoke(black.main, args)

self.compare_results(result, error_msg, 123)

def test_code_option_fast(self) -> None:
"""Test that the code option ignores errors when the sanity checks fail."""
# Patch black.assert_equivalent to ensure the sanity checks fail
with patch.object(black, "assert_equivalent", side_effect=AssertionError):
code = 'print("Hello world")'
formatted = black.format_str(code, mode=DEFAULT_MODE)

args = ["--fast", "--quiet", "--code", code]
result = CliRunner().invoke(black.main, args)

self.compare_results(result, formatted, 0)


with open(black.__file__, "r", encoding="utf-8") as _bf:
black_source_lines = _bf.readlines()
Expand Down