Robot Framework
highlighting.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 # Windows highlighting code adapted from color_console.py. It is copyright
17 # Andre Burgaud, licensed under the MIT License, and available here:
18 # http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/
19 
20 from contextlib import contextmanager
21 import errno
22 import os
23 import sys
24 try:
25  from ctypes import windll, Structure, c_short, c_ushort, byref
26 except ImportError: # Not on Windows
27  windll = None
28 
29 from robot.errors import DataError
30 from robot.utils import console_encode, isatty, WINDOWS
31 
32 
34 
35  def __init__(self, stream, colors='AUTO'):
36  self.streamstream = stream
37  self._highlighter_highlighter = self._get_highlighter_get_highlighter(stream, colors)
38 
39  def _get_highlighter(self, stream, colors):
40  options = {'AUTO': Highlighter if isatty(stream) else NoHighlighting,
41  'ON': Highlighter,
42  'OFF': NoHighlighting,
43  'ANSI': AnsiHighlighter}
44  try:
45  highlighter = options[colors.upper()]
46  except KeyError:
47  raise DataError("Invalid console color value '%s'. Available "
48  "'AUTO', 'ON', 'OFF' and 'ANSI'." % colors)
49  return highlighter(stream)
50 
51  def write(self, text, flush=True):
52  self._write_write(console_encode(text, stream=self.streamstream))
53  if flush:
54  self.flushflush()
55 
56  def _write(self, text, retry=5):
57  # Workaround for Windows 10 console bug:
58  # https://github.com/robotframework/robotframework/issues/2709
59  try:
60  with self._suppress_broken_pipe_error_suppress_broken_pipe_error_suppress_broken_pipe_error:
61  self.streamstream.write(text)
62  except IOError as err:
63  if not (WINDOWS and err.errno == 0 and retry > 0):
64  raise
65  self._write_write(text, retry-1)
66 
67  @property
68  @contextmanager
69  _suppress_broken_pipe_error = property
70 
72  try:
73  yield
74  except IOError as err:
75  if err.errno not in (errno.EPIPE, errno.EINVAL, errno.EBADF):
76  raise
77 
78  def flush(self):
79  with self._suppress_broken_pipe_error_suppress_broken_pipe_error_suppress_broken_pipe_error:
80  self.streamstream.flush()
81 
82  def highlight(self, text, status=None, flush=True):
83  if self._must_flush_before_and_after_highlighting_must_flush_before_and_after_highlighting():
84  self.flushflush()
85  flush = True
86  with self._highlighting_highlighting(status or text):
87  self.writewrite(text, flush)
88 
90  # Must flush on Windows before and after highlighting to make sure set
91  # console colors only affect the actual highlighted text. Problems
92  # only encountered with Python 3, but better to be safe than sorry.
93  return WINDOWS and not isinstance(self._highlighter_highlighter, NoHighlighting)
94 
95  def error(self, message, level):
96  self.writewrite('[ ', flush=False)
97  self.highlighthighlight(level, flush=False)
98  self.writewrite(' ] %s\n' % message)
99 
100  @contextmanager
101  def _highlighting(self, status):
102  highlighter = self._highlighter_highlighter
103  start = {'PASS': highlighter.green,
104  'FAIL': highlighter.red,
105  'ERROR': highlighter.red,
106  'WARN': highlighter.yellow,
107  'SKIP': highlighter.yellow}[status]
108  start()
109  try:
110  yield
111  finally:
112  highlighter.reset()
113 
114 
115 def Highlighter(stream):
116  if os.sep == '/':
117  return AnsiHighlighter(stream)
118  return DosHighlighter(stream) if windll else NoHighlighting(stream)
119 
120 
122 
125  _ANSI_GREEN = '\033[32m'
126 
129  _ANSI_RED = '\033[31m'
130 
133  _ANSI_YELLOW = '\033[33m'
134 
137  _ANSI_RESET = '\033[0m'
138 
139  def __init__(self, stream):
140  self._stream_stream = stream
141 
142  def green(self):
143  self._set_color_set_color(self._ANSI_GREEN_ANSI_GREEN)
144 
145  def red(self):
146  self._set_color_set_color(self._ANSI_RED_ANSI_RED)
147 
148  def yellow(self):
149  self._set_color_set_color(self._ANSI_YELLOW_ANSI_YELLOW)
150 
151  def reset(self):
152  self._set_color_set_color(self._ANSI_RESET_ANSI_RESET)
153 
154  def _set_color(self, color):
155  self._stream_stream.write(color)
156 
157 
159 
160  def _set_color(self, color):
161  pass
162 
163 
164 class DosHighlighter:
165 
168  _FOREGROUND_GREEN = 0x2
169 
172  _FOREGROUND_RED = 0x4
173 
176  _FOREGROUND_YELLOW = 0x6
177 
180  _FOREGROUND_GREY = 0x7
181 
184  _FOREGROUND_INTENSITY = 0x8
185 
188  _BACKGROUND_MASK = 0xF0
189 
192  _STDOUT_HANDLE = -11
193 
196  _STDERR_HANDLE = -12
197 
198  def __init__(self, stream):
199  self._handle_handle = self._get_std_handle_get_std_handle(stream)
200  self._orig_colors_orig_colors = self._get_colors_get_colors()
201  self._background_background = self._orig_colors_orig_colors & self._BACKGROUND_MASK_BACKGROUND_MASK
202 
203  def green(self):
204  self._set_foreground_colors_set_foreground_colors(self._FOREGROUND_GREEN_FOREGROUND_GREEN)
205 
206  def red(self):
207  self._set_foreground_colors_set_foreground_colors(self._FOREGROUND_RED_FOREGROUND_RED)
208 
209  def yellow(self):
210  self._set_foreground_colors_set_foreground_colors(self._FOREGROUND_YELLOW_FOREGROUND_YELLOW)
211 
212  def reset(self):
213  self._set_colors_set_colors(self._orig_colors_orig_colors)
214 
215  def _get_std_handle(self, stream):
216  handle = self._STDOUT_HANDLE_STDOUT_HANDLE \
217  if stream is sys.__stdout__ else self._STDERR_HANDLE_STDERR_HANDLE
218  return windll.kernel32.GetStdHandle(handle)
219 
220  def _get_colors(self):
222  ok = windll.kernel32.GetConsoleScreenBufferInfo(self._handle_handle, byref(csbi))
223  if not ok: # Call failed, return default console colors (gray on black)
224  return self._FOREGROUND_GREY_FOREGROUND_GREY
225  return csbi.wAttributes
226 
227  def _set_foreground_colors(self, colors):
228  self._set_colors_set_colors(colors | self._FOREGROUND_INTENSITY_FOREGROUND_INTENSITY | self._background_background)
229 
230  def _set_colors(self, colors):
231  windll.kernel32.SetConsoleTextAttribute(self._handle_handle, colors)
232 
233 
234 if windll:
235 
236  class _COORD(Structure):
237 
240  _fields_ = [("X", c_short),
241  ("Y", c_short)]
242 
243  class _SMALL_RECT(Structure):
244 
247  _fields_ = [("Left", c_short),
248  ("Top", c_short),
249  ("Right", c_short),
250  ("Bottom", c_short)]
251 
252  class _CONSOLE_SCREEN_BUFFER_INFO(Structure):
253 
256  _fields_ = [("dwSize", _COORD),
257  ("dwCursorPosition", _COORD),
258  ("wAttributes", c_ushort),
259  ("srWindow", _SMALL_RECT),
260  ("dwMaximumWindowSize", _COORD)]
def highlight(self, text, status=None, flush=True)
Definition: highlighting.py:82
def __init__(self, stream, colors='AUTO')
Definition: highlighting.py:35
def write(msg, level='INFO', html=False)
Writes the message to the log file using the given level.
Definition: logger.py:84
def console_encode(string, encoding=None, errors='replace', stream=sys.__stdout__, force=False)
Encodes the given string so that it can be used in the console.
Definition: encoding.py:61
def isatty(stream)
Definition: misc.py:116