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

Pretty Terminal Output #2787

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ ignore = E203, E266, E501, W503
max-line-length = 80
max-complexity = 18
select = B,C,E,F,W,T4,B9
per-file-ignores=src/black/const.py:C408
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
Jupyter Notebooks (#2744)
- Deprecate `--experimental-string-processing` and move the functionality under
`--preview` (#2789)
- Black now styles your output logs in the CLI using pretty colours ✨ (#2787)

### Packaging

Expand Down
22 changes: 15 additions & 7 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,13 @@
from dataclasses import replace
from mypy_extensions import mypyc_attr

from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
from black.const import STDIN_PLACEHOLDER
from black.const import (
DEFAULT_LINE_LENGTH,
DEFAULT_INCLUDES,
DEFAULT_EXCLUDES,
STDIN_PLACEHOLDER,
LogLevel,
)
from black.nodes import STARS, syms, is_simple_decorator_expression
from black.nodes import is_string_token
from black.lines import Line, EmptyLineTracker
Expand Down Expand Up @@ -438,7 +443,7 @@ def main(
if root:
out(
f"Identified `{root}` as project root containing a {method}.",
fg="blue",
style=LogLevel.info,
)

normalized = [
Expand All @@ -453,14 +458,14 @@ def main(
for _norm, source in normalized
]
)
out(f"Sources to be formatted: {srcs_string}", fg="blue")
out(f"Sources to be formatted: {srcs_string}", style=LogLevel.info)

if config:
config_source = ctx.get_parameter_source("config")
if config_source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP):
out("Using configuration from project root.", fg="blue")
out("Using configuration from project root.", style=LogLevel.info)
else:
out(f"Using configuration in '{config}'.", fg="blue")
out(f"Using configuration in '{config}'.", style=LogLevel.info)

error_msg = "Oh no! 💥 💔 💥"
if required_version and required_version != __version__:
Expand Down Expand Up @@ -548,7 +553,10 @@ def main(
if verbose or not quiet:
if code is None and (verbose or report.change_count or report.failure_count):
out()
out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
out(
error_msg if report.return_code else "All done! ✨ 🍰 ✨",
style=LogLevel.success,
)
if code is None:
click.echo(str(report), err=True)
ctx.exit(report.return_code)
Expand Down
19 changes: 19 additions & 0 deletions src/black/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
from enum import Enum
from typing_extensions import TypedDict

DEFAULT_LINE_LENGTH = 88
DEFAULT_EXCLUDES = r"/(\.direnv|\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950
DEFAULT_INCLUDES = r"(\.pyi?|\.ipynb)$"
STDIN_PLACEHOLDER = "__BLACK_STDIN_FILENAME__"


class Empty(TypedDict):
pass


class LogLevel(Enum):
none: Empty = {}
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved
trace = dict(fg="cyan", dim=True)
debug = dict(fg="green", dim=True)
info = dict(fg="blue")
success = dict(fg="green", bold=True)
warning = dict(fg="yellow")
error = dict(fg="red")
critical = dict(fg="red", bold=True)
notice = dict(fg="magenta", bold=True)
28 changes: 14 additions & 14 deletions src/black/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@
from mypy_extensions import mypyc_attr
import tempfile

from click import echo, style
from click import echo, style as _style
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved

from black.const import LogLevel


@mypyc_attr(patchable=True)
def _out(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
if message is not None:
if "bold" not in styles:
styles["bold"] = True
message = style(message, **styles)
message = _style(message, **styles)
echo(message, nl=nl, err=True)


@mypyc_attr(patchable=True)
def _err(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
if message is not None:
if "fg" not in styles:
styles["fg"] = "red"
message = style(message, **styles)
echo(message, nl=nl, err=True)
def out(
message: Optional[str] = None,
nl: bool = True,
style: LogLevel = LogLevel.none,
**_styles: Any,
) -> None:
styles = style.value.copy()
if style:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this is redundant because .update with an empty dict does nothing. Also discussed before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but I wanted to keep the .update() change explicit hence keeping it in an if block.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong, doesn't it mean we ignore _styles if style == LogLevel.none?

Also, the variable names style, styles, and _styles here are pretty confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I forgot to make that change.

Screenshot from 2022-01-22 10-43-07

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, can't think of a better name for _styles, these are the "styles" which the user explicitly passes as kwargs to overwrite the style defined by the LogLevel.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest style_overrides.

styles.update(_styles)


@mypyc_attr(patchable=True)
def out(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
_out(message, nl=nl, **styles)


def err(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
_err(message, nl=nl, **styles)
return out(message, nl, LogLevel.error, **styles)


def ipynb_diff(a: str, b: str, a_name: str, b_name: str) -> str:
Expand Down
17 changes: 12 additions & 5 deletions src/black/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from click import style

from black import LogLevel
from black.output import out, err


Expand Down Expand Up @@ -37,15 +38,21 @@ def done(self, src: Path, changed: Changed) -> None:
if changed is Changed.YES:
reformatted = "would reformat" if self.check or self.diff else "reformatted"
if self.verbose or not self.quiet:
out(f"{reformatted} {src}")
out(f"{reformatted} {src}", style=LogLevel.trace)
self.change_count += 1
else:
if self.verbose:
if changed is Changed.NO:
msg = f"{src} already well formatted, good job."
msg, style = (
f"{src} already well formatted, good job.",
LogLevel.success,
)
else:
msg = f"{src} wasn't modified on disk since last run."
out(msg, bold=False)
msg, style = (
f"{src} wasn't modified on disk since last run.",
LogLevel.trace,
)
out(msg, style=style, bold=False)
self.same_count += 1

def failed(self, src: Path, message: str) -> None:
Expand All @@ -55,7 +62,7 @@ def failed(self, src: Path, message: str) -> None:

def path_ignored(self, path: Path, message: str) -> None:
if self.verbose:
out(f"{path} ignored: {message}", bold=False)
out(f"{path} ignored: {message}", style=LogLevel.warning)

@property
def return_code(self) -> int:
Expand Down
9 changes: 5 additions & 4 deletions tests/test_black.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ def out(msg: str, **kwargs: Any) -> None:
def err(msg: str, **kwargs: Any) -> None:
err_lines.append(msg)

with patch("black.output._out", out), patch("black.output._err", err):
with patch("black.report.out", out), patch("black.report.err", err):
report.done(Path("f1"), black.Changed.NO)
self.assertEqual(len(out_lines), 1)
self.assertEqual(len(err_lines), 0)
Expand Down Expand Up @@ -545,7 +545,7 @@ def out(msg: str, **kwargs: Any) -> None:
def err(msg: str, **kwargs: Any) -> None:
err_lines.append(msg)

with patch("black.output._out", out), patch("black.output._err", err):
with patch("black.report.out", out), patch("black.report.err", err):
report.done(Path("f1"), black.Changed.NO)
self.assertEqual(len(out_lines), 0)
self.assertEqual(len(err_lines), 0)
Expand Down Expand Up @@ -639,7 +639,7 @@ def out(msg: str, **kwargs: Any) -> None:
def err(msg: str, **kwargs: Any) -> None:
err_lines.append(msg)

with patch("black.output._out", out), patch("black.output._err", err):
with patch("black.report.out", out), patch("black.report.err", err):
report.done(Path("f1"), black.Changed.NO)
self.assertEqual(len(out_lines), 0)
self.assertEqual(len(err_lines), 0)
Expand Down Expand Up @@ -940,7 +940,8 @@ def out(msg: str, **kwargs: Any) -> None:
def err(msg: str, **kwargs: Any) -> None:
err_lines.append(msg)

with patch("black.output._out", out), patch("black.output._err", err):
with patch("tests.util.out", out), patch("tests.util.err", err):
black.output.out("test test")
with self.assertRaises(AssertionError):
self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")

Expand Down