# 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 os
import sys
try:
from ctypes import windll, Structure, c_short, c_ushort, byref
except ImportError: # Not on Windows or using Jython
windll = None
from robot.errors import DataError
from robot.utils import console_encode, isatty, WINDOWS
[docs]class HighlightingStream(object):
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:
self.stream.write(text)
except IOError as err:
if not (WINDOWS and err.errno == 0 and retry > 0):
raise
self._write(text, retry-1)
[docs] def flush(self):
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}[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(object):
_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(object):
_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)]