Source code for robot.output.console.highlighting

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

# Windows highlighting code adapted from color_console.py. It is copyright
# Andre Burgaud, licensed under the MIT License, and available here:
# http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/

from contextlib import contextmanager
import errno
import os
import sys
try:
    from ctypes import windll, Structure, c_short, c_ushort, byref
except ImportError:  # Not on Windows
    windll = None

from robot.errors import DataError
from robot.utils import console_encode, isatty, WINDOWS


[docs] class HighlightingStream: def __init__(self, stream, colors='AUTO'): self.stream = stream self._highlighter = self._get_highlighter(stream, colors) def _get_highlighter(self, stream, colors): options = {'AUTO': Highlighter if isatty(stream) else NoHighlighting, 'ON': Highlighter, 'OFF': NoHighlighting, 'ANSI': AnsiHighlighter} try: highlighter = options[colors.upper()] except KeyError: raise DataError("Invalid console color value '%s'. Available " "'AUTO', 'ON', 'OFF' and 'ANSI'." % colors) return highlighter(stream)
[docs] def write(self, text, flush=True): self._write(console_encode(text, stream=self.stream)) if flush: self.flush()
def _write(self, text, retry=5): # Workaround for Windows 10 console bug: # https://github.com/robotframework/robotframework/issues/2709 try: with self._suppress_broken_pipe_error: self.stream.write(text) except IOError as err: if not (WINDOWS and err.errno == 0 and retry > 0): raise self._write(text, retry-1) @property @contextmanager def _suppress_broken_pipe_error(self): try: yield except IOError as err: if err.errno not in (errno.EPIPE, errno.EINVAL, errno.EBADF): raise
[docs] def flush(self): with self._suppress_broken_pipe_error: self.stream.flush()
[docs] def highlight(self, text, status=None, flush=True): if self._must_flush_before_and_after_highlighting(): self.flush() flush = True with self._highlighting(status or text): self.write(text, flush)
def _must_flush_before_and_after_highlighting(self): # Must flush on Windows before and after highlighting to make sure set # console colors only affect the actual highlighted text. Problems # only encountered with Python 3, but better to be safe than sorry. return WINDOWS and not isinstance(self._highlighter, NoHighlighting)
[docs] def error(self, message, level): self.write('[ ', flush=False) self.highlight(level, flush=False) self.write(' ] %s\n' % message)
@contextmanager def _highlighting(self, status): highlighter = self._highlighter start = {'PASS': highlighter.green, 'FAIL': highlighter.red, 'ERROR': highlighter.red, 'WARN': highlighter.yellow, 'SKIP': highlighter.yellow}[status] start() try: yield finally: highlighter.reset()
[docs] def Highlighter(stream): if os.sep == '/': return AnsiHighlighter(stream) return DosHighlighter(stream) if windll else NoHighlighting(stream)
[docs] class AnsiHighlighter: _ANSI_GREEN = '\033[32m' _ANSI_RED = '\033[31m' _ANSI_YELLOW = '\033[33m' _ANSI_RESET = '\033[0m' def __init__(self, stream): self._stream = stream
[docs] def green(self): self._set_color(self._ANSI_GREEN)
[docs] def red(self): self._set_color(self._ANSI_RED)
[docs] def yellow(self): self._set_color(self._ANSI_YELLOW)
[docs] def reset(self): self._set_color(self._ANSI_RESET)
def _set_color(self, color): self._stream.write(color)
[docs] class NoHighlighting(AnsiHighlighter): def _set_color(self, color): pass
[docs] class DosHighlighter: _FOREGROUND_GREEN = 0x2 _FOREGROUND_RED = 0x4 _FOREGROUND_YELLOW = 0x6 _FOREGROUND_GREY = 0x7 _FOREGROUND_INTENSITY = 0x8 _BACKGROUND_MASK = 0xF0 _STDOUT_HANDLE = -11 _STDERR_HANDLE = -12 def __init__(self, stream): self._handle = self._get_std_handle(stream) self._orig_colors = self._get_colors() self._background = self._orig_colors & self._BACKGROUND_MASK
[docs] def green(self): self._set_foreground_colors(self._FOREGROUND_GREEN)
[docs] def red(self): self._set_foreground_colors(self._FOREGROUND_RED)
[docs] def yellow(self): self._set_foreground_colors(self._FOREGROUND_YELLOW)
[docs] def reset(self): self._set_colors(self._orig_colors)
def _get_std_handle(self, stream): handle = self._STDOUT_HANDLE \ if stream is sys.__stdout__ else self._STDERR_HANDLE return windll.kernel32.GetStdHandle(handle) def _get_colors(self): csbi = _CONSOLE_SCREEN_BUFFER_INFO() ok = windll.kernel32.GetConsoleScreenBufferInfo(self._handle, byref(csbi)) if not ok: # Call failed, return default console colors (gray on black) return self._FOREGROUND_GREY return csbi.wAttributes def _set_foreground_colors(self, colors): self._set_colors(colors | self._FOREGROUND_INTENSITY | self._background) def _set_colors(self, colors): windll.kernel32.SetConsoleTextAttribute(self._handle, colors)
if windll: class _COORD(Structure): _fields_ = [("X", c_short), ("Y", c_short)] class _SMALL_RECT(Structure): _fields_ = [("Left", c_short), ("Top", c_short), ("Right", c_short), ("Bottom", c_short)] class _CONSOLE_SCREEN_BUFFER_INFO(Structure): _fields_ = [("dwSize", _COORD), ("dwCursorPosition", _COORD), ("wAttributes", c_ushort), ("srWindow", _SMALL_RECT), ("dwMaximumWindowSize", _COORD)]