From 8b0fdb3f4fb95e3a5ab68e8323e2fdb777bc344b Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 18 Jan 2021 18:40:16 -0800 Subject: [PATCH] fix colors in log on Windows no longer require click, require colorama for Windows --- CHANGES.rst | 3 +++ setup.cfg | 3 +++ src/werkzeug/_internal.py | 16 ++++++++++++- src/werkzeug/serving.py | 47 ++++++++++++++++++++++++++------------- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4b4e1d8af..b254f7ed0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -124,6 +124,9 @@ Unreleased addresses, instead of ``0.0.0.0`` or ``::``. It also warns about not running the development server in production in this case. :issue:`1964` +- Colors in the development server log are displayed if Colorama is + installed on Windows. For all platforms, style support no longer + requires Click. :issue:`1832` Version 1.0.2 diff --git a/setup.cfg b/setup.cfg index d770a98b8..b098d0be5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -94,6 +94,9 @@ warn_redundant_casts = True warn_unused_configs = True warn_unused_ignores = True +[mypy-colorama.*] +ignore_missing_imports = True + [mypy-cryptography.*] ignore_missing_imports = True diff --git a/src/werkzeug/_internal.py b/src/werkzeug/_internal.py index 513af334d..6af247b44 100644 --- a/src/werkzeug/_internal.py +++ b/src/werkzeug/_internal.py @@ -183,6 +183,20 @@ def _has_level_handler(logger: logging.Logger) -> bool: return False +class _ColorStreamHandler(logging.StreamHandler): + """On Windows, wrap stream with Colorama for ANSI style support.""" + + def __init__(self): + try: + import colorama + except ImportError: + stream = None + else: + stream = colorama.AnsiToWin32(sys.stderr) + + super().__init__(stream) + + def _log(type: str, message: str, *args, **kwargs) -> None: """Log a message to the 'werkzeug' logger. @@ -200,7 +214,7 @@ def _log(type: str, message: str, *args, **kwargs) -> None: _logger.setLevel(logging.INFO) if not _has_level_handler(_logger): - _logger.addHandler(logging.StreamHandler()) + _logger.addHandler(_ColorStreamHandler()) getattr(_logger, type)(message.rstrip(), *args, **kwargs) diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py index d9ca185e9..dd6922a60 100644 --- a/src/werkzeug/serving.py +++ b/src/werkzeug/serving.py @@ -42,10 +42,13 @@ def __getattr__(self, name): ssl = _SslDummy() # type: ignore -try: - import click -except ImportError: - click = None # type: ignore +_log_add_style = True + +if os.name == "nt": + try: + __import__("colorama") + except ImportError: + _log_add_style = False can_fork = hasattr(os, "fork") @@ -392,23 +395,21 @@ def log_request(self, code: t.Union[int, str] = "-", size: t.Union[int, str] = " code = str(code) - if click: - color = click.style - + if _log_add_style: if code[0] == "1": # 1xx - Informational - msg = color(msg, bold=True) - elif code[0] == "2": # 2xx - Success - msg = color(msg) + msg = _ansi_style(msg, "bold") + elif code == "200": # 2xx - Success + pass elif code == "304": # 304 - Resource Not Modified - msg = color(msg, fg="cyan") + msg = _ansi_style(msg, "cyan") elif code[0] == "3": # 3xx - Redirection - msg = color(msg, fg="green") + msg = _ansi_style(msg, "green") elif code == "404": # 404 - Resource Not Found - msg = color(msg, fg="yellow") + msg = _ansi_style(msg, "yellow") elif code[0] == "4": # 4xx - Client Error - msg = color(msg, fg="red", bold=True) + msg = _ansi_style(msg, "bold", "red") else: # 5xx, or any other response - msg = color(msg, fg="magenta", bold=True) + msg = _ansi_style(msg, "bold", "magenta") self.log("info", '"%s" %s %s', msg, code, size) @@ -426,6 +427,22 @@ def log(self, type: str, message: str, *args) -> None: ) +def _ansi_style(value, *styles): + codes = { + "bold": 1, + "red": 31, + "green": 32, + "yellow": 33, + "magenta": 35, + "cyan": 36, + } + + for style in styles: + value = f"\x1b[{codes[style]}m{value}" + + return f"{value}\x1b[0m" + + def generate_adhoc_ssl_pair( cn: t.Optional[str] = None, ) -> t.Tuple["Certificate", "RSAPrivateKeyWithSerialization"]: