mirror of
https://github.com/PyCQA/flake8.git
synced 2026-03-29 18:46:52 +00:00
add --color option
This commit is contained in:
parent
05cae7e046
commit
848003cc05
10 changed files with 181 additions and 2 deletions
|
|
@ -40,6 +40,8 @@ Index of Options
|
|||
|
||||
- :option:`flake8 --quiet`
|
||||
|
||||
- :option:`flake8 --color`
|
||||
|
||||
- :option:`flake8 --count`
|
||||
|
||||
- :option:`flake8 --diff`
|
||||
|
|
@ -181,6 +183,35 @@ Options and their Descriptions
|
|||
|
||||
quiet = 1
|
||||
|
||||
.. option:: --color
|
||||
|
||||
:ref:`Go back to index <top>`
|
||||
|
||||
Whether to use color in output. Defaults to ``auto``.
|
||||
|
||||
Possible options are ``auto``, ``always``, and ``never``.
|
||||
|
||||
This **can** be specified in config files.
|
||||
|
||||
When color is enabled, the following substitutions are enabled:
|
||||
|
||||
- ``%(bold)s``
|
||||
- ``%(black)s``
|
||||
- ``%(red)s``
|
||||
- ``%(green)s``
|
||||
- ``%(yellow)s``
|
||||
- ``%(blue)s``
|
||||
- ``%(magenta)s``
|
||||
- ``%(cyan)s``
|
||||
- ``%(white)s``
|
||||
- ``%(reset)s``
|
||||
|
||||
Example config file usage:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
color = never
|
||||
|
||||
|
||||
.. option:: --count
|
||||
|
||||
|
|
|
|||
59
src/flake8/formatting/_windows_color.py
Normal file
59
src/flake8/formatting/_windows_color.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""ctypes hackery to enable color processing on windows.
|
||||
|
||||
See: https://github.com/pre-commit/pre-commit/blob/cb40e96/pre_commit/color.py
|
||||
"""
|
||||
import sys
|
||||
|
||||
if sys.platform == "win32": # pragma: no cover (windows)
|
||||
|
||||
def _enable() -> None:
|
||||
from ctypes import POINTER
|
||||
from ctypes import windll
|
||||
from ctypes import WinError
|
||||
from ctypes import WINFUNCTYPE
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import HANDLE
|
||||
|
||||
STD_ERROR_HANDLE = -12
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
||||
|
||||
def bool_errcheck(result, func, args):
|
||||
if not result:
|
||||
raise WinError()
|
||||
return args
|
||||
|
||||
GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(
|
||||
("GetStdHandle", windll.kernel32),
|
||||
((1, "nStdHandle"),),
|
||||
)
|
||||
|
||||
GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(
|
||||
("GetConsoleMode", windll.kernel32),
|
||||
((1, "hConsoleHandle"), (2, "lpMode")),
|
||||
)
|
||||
GetConsoleMode.errcheck = bool_errcheck
|
||||
|
||||
SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)(
|
||||
("SetConsoleMode", windll.kernel32),
|
||||
((1, "hConsoleHandle"), (1, "dwMode")),
|
||||
)
|
||||
SetConsoleMode.errcheck = bool_errcheck
|
||||
|
||||
# As of Windows 10, the Windows console supports (some) ANSI escape
|
||||
# sequences, but it needs to be enabled using `SetConsoleMode` first.
|
||||
#
|
||||
# More info on the escape sequences supported:
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
|
||||
stderr = GetStdHandle(STD_ERROR_HANDLE)
|
||||
flags = GetConsoleMode(stderr)
|
||||
SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
|
||||
try:
|
||||
_enable()
|
||||
except OSError:
|
||||
terminal_supports_color = False
|
||||
else:
|
||||
terminal_supports_color = True
|
||||
else: # pragma: win32 no cover
|
||||
terminal_supports_color = True
|
||||
|
|
@ -8,6 +8,8 @@ from typing import Optional
|
|||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from flake8.formatting import _windows_color
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from flake8.statistics import Statistics
|
||||
from flake8.style_guide import Violation
|
||||
|
|
@ -51,6 +53,11 @@ class BaseFormatter:
|
|||
self.filename = options.output_file
|
||||
self.output_fd: Optional[IO[str]] = None
|
||||
self.newline = "\n"
|
||||
self.color = options.color == "always" or (
|
||||
options.color == "auto"
|
||||
and sys.stdout.isatty()
|
||||
and _windows_color.terminal_supports_color
|
||||
)
|
||||
self.after_init()
|
||||
|
||||
def after_init(self) -> None:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,20 @@ from flake8.formatting import base
|
|||
if TYPE_CHECKING:
|
||||
from flake8.style_guide import Violation
|
||||
|
||||
COLORS = {
|
||||
"bold": "\033[1m",
|
||||
"black": "\033[30m",
|
||||
"red": "\033[31m",
|
||||
"green": "\033[32m",
|
||||
"yellow": "\033[33m",
|
||||
"blue": "\033[34m",
|
||||
"magenta": "\033[35m",
|
||||
"cyan": "\033[36m",
|
||||
"white": "\033[37m",
|
||||
"reset": "\033[m",
|
||||
}
|
||||
COLORS_OFF = {k: "" for k in COLORS}
|
||||
|
||||
|
||||
class SimpleFormatter(base.BaseFormatter):
|
||||
"""Simple abstraction for Default and Pylint formatter commonality.
|
||||
|
|
@ -39,6 +53,7 @@ class SimpleFormatter(base.BaseFormatter):
|
|||
"path": error.filename,
|
||||
"row": error.line_number,
|
||||
"col": error.column_number,
|
||||
**(COLORS if self.color else COLORS_OFF),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -49,7 +64,11 @@ class Default(SimpleFormatter):
|
|||
format string.
|
||||
"""
|
||||
|
||||
error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s"
|
||||
error_format = (
|
||||
"%(bold)s%(path)s%(reset)s"
|
||||
"%(cyan)s:%(reset)s%(row)d%(cyan)s:%(reset)s%(col)d%(cyan)s:%(reset)s "
|
||||
"%(bold)s%(red)s%(code)s%(reset)s %(text)s"
|
||||
)
|
||||
|
||||
def after_init(self) -> None:
|
||||
"""Check for a custom format string."""
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ def register_default_options(option_manager):
|
|||
The default options include:
|
||||
|
||||
- ``-q``/``--quiet``
|
||||
- ``--color``
|
||||
- ``--count``
|
||||
- ``--diff``
|
||||
- ``--exclude``
|
||||
|
|
@ -118,7 +119,6 @@ def register_default_options(option_manager):
|
|||
"""
|
||||
add_option = option_manager.add_option
|
||||
|
||||
# pep8 options
|
||||
add_option(
|
||||
"-q",
|
||||
"--quiet",
|
||||
|
|
@ -128,6 +128,13 @@ def register_default_options(option_manager):
|
|||
help="Report only file names, or nothing. This option is repeatable.",
|
||||
)
|
||||
|
||||
add_option(
|
||||
"--color",
|
||||
choices=("auto", "always", "never"),
|
||||
default="auto",
|
||||
help="Whether to use color in output. Defaults to `%(default)s`.",
|
||||
)
|
||||
|
||||
add_option(
|
||||
"--count",
|
||||
action="store_true",
|
||||
|
|
|
|||
|
|
@ -269,6 +269,12 @@ class PluginManager: # pylint: disable=too-few-public-methods
|
|||
"flake8>=3.7 (which implements per-file-ignores itself)."
|
||||
)
|
||||
continue
|
||||
elif entry_point.name == "flake8-colors":
|
||||
LOG.warning(
|
||||
"flake8-colors plugin is incompatible with "
|
||||
"flake8>=4.1 (which implements colors itself)."
|
||||
)
|
||||
continue
|
||||
self._load_plugin_from_entrypoint(entry_point)
|
||||
|
||||
def _load_plugin_from_entrypoint(self, entry_point, local=False):
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
"""Tests for the BaseFormatter object."""
|
||||
import argparse
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from flake8 import style_guide
|
||||
from flake8.formatting import _windows_color
|
||||
from flake8.formatting import base
|
||||
|
||||
|
||||
def options(**kwargs):
|
||||
"""Create an argparse.Namespace instance."""
|
||||
kwargs.setdefault("color", "auto")
|
||||
kwargs.setdefault("output_file", None)
|
||||
kwargs.setdefault("tee", False)
|
||||
return argparse.Namespace(**kwargs)
|
||||
|
|
@ -136,6 +139,49 @@ def test_write_produces_stdout(capsys):
|
|||
assert capsys.readouterr().out == f"{line}\n{source}\n"
|
||||
|
||||
|
||||
def test_color_always_is_true():
|
||||
"""Verify that color='always' sets it to True."""
|
||||
formatter = base.BaseFormatter(options(color="always"))
|
||||
assert formatter.color is True
|
||||
|
||||
|
||||
def _mock_isatty(val):
|
||||
attrs = {"isatty.return_value": val}
|
||||
return mock.patch.object(sys, "stdout", **attrs)
|
||||
|
||||
|
||||
def _mock_windows_color(val):
|
||||
return mock.patch.object(_windows_color, "terminal_supports_color", val)
|
||||
|
||||
|
||||
def test_color_auto_is_true_for_tty():
|
||||
"""Verify that color='auto' sets it to True for a tty."""
|
||||
with _mock_isatty(True), _mock_windows_color(True):
|
||||
formatter = base.BaseFormatter(options(color="auto"))
|
||||
assert formatter.color is True
|
||||
|
||||
|
||||
def test_color_auto_is_false_without_tty():
|
||||
"""Verify that color='auto' sets it to False without a tty."""
|
||||
with _mock_isatty(False), _mock_windows_color(True):
|
||||
formatter = base.BaseFormatter(options(color="auto"))
|
||||
assert formatter.color is False
|
||||
|
||||
|
||||
def test_color_auto_is_false_if_not_supported_on_windows():
|
||||
"""Verify that color='auto' is False if not supported on windows."""
|
||||
with _mock_isatty(True), _mock_windows_color(False):
|
||||
formatter = base.BaseFormatter(options(color="auto"))
|
||||
assert formatter.color is False
|
||||
|
||||
|
||||
def test_color_never_is_false():
|
||||
"""Verify that color='never' sets it to False despite a tty."""
|
||||
with _mock_isatty(True), _mock_windows_color(True):
|
||||
formatter = base.BaseFormatter(options(color="never"))
|
||||
assert formatter.color is False
|
||||
|
||||
|
||||
class AfterInitFormatter(base.BaseFormatter):
|
||||
"""Subclass for testing after_init."""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from flake8.formatting import default
|
|||
|
||||
def options(**kwargs):
|
||||
"""Create an argparse.Namespace instance."""
|
||||
kwargs.setdefault("color", "auto")
|
||||
kwargs.setdefault("output_file", None)
|
||||
kwargs.setdefault("tee", False)
|
||||
return argparse.Namespace(**kwargs)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from flake8.formatting import default
|
|||
|
||||
def options(**kwargs):
|
||||
"""Create an argparse.Namespace instance."""
|
||||
kwargs.setdefault("color", "auto")
|
||||
kwargs.setdefault("output_file", None)
|
||||
kwargs.setdefault("tee", False)
|
||||
return argparse.Namespace(**kwargs)
|
||||
|
|
|
|||
2
tox.ini
2
tox.ini
|
|
@ -127,6 +127,8 @@ commands =
|
|||
# Once Flake8 3.0 is released and in a good state, we can use both and it will
|
||||
# work well \o/
|
||||
ignore = D203, W503, E203, N818
|
||||
per-file-ignores =
|
||||
src/flake8/formatting/_windows_color.py: N806
|
||||
exclude =
|
||||
.tox,
|
||||
.git,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue