[svn r62835] Add ANSI colouring to the Win32 console.
This gives a nice display for py.test, and during pypy translation. the "markup" function should not be used any more. --HG-- branch : trunk
This commit is contained in:
parent
ab3f409b4e
commit
4be27f5078
|
@ -275,7 +275,7 @@ class ReprEntry(Repr):
|
|||
self.reprfuncargs.toterminal(tw)
|
||||
for line in self.lines:
|
||||
red = line.startswith("E ")
|
||||
tw.line(tw.markup(bold=True, red=red, text=line))
|
||||
tw.line(line, bold=True, red=red)
|
||||
if self.reprlocals:
|
||||
#tw.sep(self.localssep, "Locals")
|
||||
tw.line("")
|
||||
|
|
|
@ -14,6 +14,57 @@ def _getdimensions():
|
|||
height,width = struct.unpack( "hhhh", call ) [:2]
|
||||
return height, width
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# ctypes access to the Windows console
|
||||
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
STD_ERROR_HANDLE = -12
|
||||
FOREGROUND_BLUE = 0x0001 # text color contains blue.
|
||||
FOREGROUND_GREEN = 0x0002 # text color contains green.
|
||||
FOREGROUND_RED = 0x0004 # text color contains red.
|
||||
FOREGROUND_WHITE = 0x0007
|
||||
FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
|
||||
BACKGROUND_BLUE = 0x0010 # background color contains blue.
|
||||
BACKGROUND_GREEN = 0x0020 # background color contains green.
|
||||
BACKGROUND_RED = 0x0040 # background color contains red.
|
||||
BACKGROUND_WHITE = 0x0007
|
||||
BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
|
||||
|
||||
def GetStdHandle(kind):
|
||||
import ctypes
|
||||
return ctypes.windll.kernel32.GetStdHandle(kind)
|
||||
|
||||
def SetConsoleTextAttribute(handle, attr):
|
||||
import ctypes
|
||||
ctypes.windll.kernel32.SetConsoleTextAttribute(
|
||||
handle, attr)
|
||||
|
||||
def _getdimensions():
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
SHORT = ctypes.c_short
|
||||
class COORD(ctypes.Structure):
|
||||
_fields_ = [('X', SHORT),
|
||||
('Y', SHORT)]
|
||||
class SMALL_RECT(ctypes.Structure):
|
||||
_fields_ = [('Left', SHORT),
|
||||
('Top', SHORT),
|
||||
('Right', SHORT),
|
||||
('Bottom', SHORT)]
|
||||
class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
|
||||
_fields_ = [('dwSize', COORD),
|
||||
('dwCursorPosition', COORD),
|
||||
('wAttributes', wintypes.WORD),
|
||||
('srWindow', SMALL_RECT),
|
||||
('dwMaximumWindowSize', COORD)]
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
info = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
|
||||
handle, ctypes.byref(info))
|
||||
return info.dwSize.Y, info.dwSize.X
|
||||
|
||||
def get_terminal_width():
|
||||
try:
|
||||
height, width = _getdimensions()
|
||||
|
@ -31,15 +82,46 @@ def ansi_print(text, esc, file=None, newline=True, flush=False):
|
|||
if file is None:
|
||||
file = sys.stderr
|
||||
text = text.rstrip()
|
||||
if esc and sys.platform != "win32" and file.isatty():
|
||||
if not isinstance(esc, tuple):
|
||||
if esc and not isinstance(esc, tuple):
|
||||
esc = (esc,)
|
||||
if esc and sys.platform != "win32" and file.isatty():
|
||||
text = (''.join(['\x1b[%sm' % cod for cod in esc]) +
|
||||
text +
|
||||
'\x1b[0m') # ANSI color code "reset"
|
||||
if newline:
|
||||
text += '\n'
|
||||
|
||||
if esc and sys.platform == "win32" and file.isatty():
|
||||
if 1 in esc:
|
||||
bold = True
|
||||
esc = tuple(x for x in esc if x != 1)
|
||||
else:
|
||||
bold = False
|
||||
esctable = {() : FOREGROUND_WHITE, # normal
|
||||
(31,): FOREGROUND_RED, # red
|
||||
(32,): FOREGROUND_GREEN, # green
|
||||
(33,): FOREGROUND_GREEN|FOREGROUND_RED, # yellow
|
||||
(34,): FOREGROUND_BLUE, # blue
|
||||
(35,): FOREGROUND_BLUE|FOREGROUND_RED, # purple
|
||||
(36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
|
||||
(37,): FOREGROUND_WHITE, # white
|
||||
(39,): FOREGROUND_WHITE, # reset
|
||||
}
|
||||
attr = esctable.get(esc, FOREGROUND_WHITE)
|
||||
if bold:
|
||||
attr |= FOREGROUND_INTENSITY
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
STD_ERROR_HANDLE = -12
|
||||
if file is sys.stderr:
|
||||
handle = GetStdHandle(STD_ERROR_HANDLE)
|
||||
else:
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
SetConsoleTextAttribute(handle, attr)
|
||||
file.write(text)
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
|
||||
else:
|
||||
file.write(text)
|
||||
|
||||
if flush:
|
||||
file.flush()
|
||||
|
||||
|
@ -60,8 +142,7 @@ class TerminalWriter(object):
|
|||
file = WriteFile(file)
|
||||
self._file = file
|
||||
self.fullwidth = get_terminal_width()
|
||||
self.hasmarkup = sys.platform != "win32" and \
|
||||
hasattr(file, 'isatty') and file.isatty()
|
||||
self.hasmarkup = hasattr(file, 'isatty') and file.isatty()
|
||||
|
||||
def _escaped(self, text, esc):
|
||||
if esc and self.hasmarkup:
|
||||
|
@ -119,6 +200,81 @@ class TerminalWriter(object):
|
|||
self._file.write('\n')
|
||||
self._file.flush()
|
||||
|
||||
|
||||
class Win32ConsoleWriter(object):
|
||||
|
||||
def __init__(self, file=None, stringio=False):
|
||||
if file is None:
|
||||
if stringio:
|
||||
self.stringio = file = py.std.cStringIO.StringIO()
|
||||
else:
|
||||
file = py.std.sys.stdout
|
||||
elif callable(file):
|
||||
file = WriteFile(file)
|
||||
self._file = file
|
||||
self.fullwidth = get_terminal_width()
|
||||
self.hasmarkup = hasattr(file, 'isatty') and file.isatty()
|
||||
|
||||
def sep(self, sepchar, title=None, fullwidth=None, **kw):
|
||||
if fullwidth is None:
|
||||
fullwidth = self.fullwidth
|
||||
# On a Windows console, writing in the last column
|
||||
# causes a line feed.
|
||||
fullwidth -= 1
|
||||
# the goal is to have the line be as long as possible
|
||||
# under the condition that len(line) <= fullwidth
|
||||
if title is not None:
|
||||
# we want 2 + 2*len(fill) + len(title) <= fullwidth
|
||||
# i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth
|
||||
# 2*len(sepchar)*N <= fullwidth - len(title) - 2
|
||||
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
|
||||
N = (fullwidth - len(title) - 2) // (2*len(sepchar))
|
||||
fill = sepchar * N
|
||||
line = "%s %s %s" % (fill, title, fill)
|
||||
else:
|
||||
# we want len(sepchar)*N <= fullwidth
|
||||
# i.e. N <= fullwidth // len(sepchar)
|
||||
line = sepchar * (fullwidth // len(sepchar))
|
||||
# in some situations there is room for an extra sepchar at the right,
|
||||
# in particular if we consider that with a sepchar like "_ " the
|
||||
# trailing space is not important at the end of the line
|
||||
if len(line) + len(sepchar.rstrip()) <= fullwidth:
|
||||
line += sepchar.rstrip()
|
||||
|
||||
self.line(line, **kw)
|
||||
|
||||
def write(self, s, **kw):
|
||||
if s:
|
||||
s = str(s)
|
||||
if self.hasmarkup:
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
|
||||
if self.hasmarkup and kw:
|
||||
attr = 0
|
||||
if kw.pop('bold', False):
|
||||
attr |= FOREGROUND_INTENSITY
|
||||
|
||||
if kw.pop('red', False):
|
||||
attr |= FOREGROUND_RED
|
||||
elif kw.pop('blue', False):
|
||||
attr |= FOREGROUND_BLUE
|
||||
elif kw.pop('green', False):
|
||||
attr |= FOREGROUND_GREEN
|
||||
else:
|
||||
attr |= FOREGROUND_WHITE
|
||||
|
||||
SetConsoleTextAttribute(handle, attr)
|
||||
self._file.write(s)
|
||||
self._file.flush()
|
||||
if self.hasmarkup:
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
|
||||
|
||||
def line(self, s='', **kw):
|
||||
self.write(s + '\n', **kw)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
TerminalWriter = Win32ConsoleWriter
|
||||
|
||||
class WriteFile(object):
|
||||
def __init__(self, writemethod):
|
||||
self.write = writemethod
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import py
|
||||
import os
|
||||
import os, sys
|
||||
from py.__.io import terminalwriter
|
||||
|
||||
def skip_win32():
|
||||
if sys.platform == 'win32':
|
||||
py.test.skip('Not relevant on win32')
|
||||
|
||||
def test_terminalwriter_computes_width():
|
||||
py.magic.patch(terminalwriter, 'get_terminal_width', lambda: 42)
|
||||
try:
|
||||
|
@ -36,6 +40,7 @@ class BaseTests:
|
|||
tw.sep("-", fullwidth=60)
|
||||
l = self.getlines()
|
||||
assert len(l) == 1
|
||||
skip_win32()
|
||||
assert l[0] == "-" * 60 + "\n"
|
||||
|
||||
def test_sep_with_title(self):
|
||||
|
@ -43,14 +48,17 @@ class BaseTests:
|
|||
tw.sep("-", "hello", fullwidth=60)
|
||||
l = self.getlines()
|
||||
assert len(l) == 1
|
||||
skip_win32()
|
||||
assert l[0] == "-" * 26 + " hello " + "-" * 27 + "\n"
|
||||
|
||||
def test__escaped(self):
|
||||
skip_win32()
|
||||
tw = self.getwriter()
|
||||
text2 = tw._escaped("hello", (31))
|
||||
assert text2.find("hello") != -1
|
||||
|
||||
def test_markup(self):
|
||||
skip_win32()
|
||||
tw = self.getwriter()
|
||||
for bold in (True, False):
|
||||
for color in ("red", "green"):
|
||||
|
@ -65,6 +73,7 @@ class BaseTests:
|
|||
tw.line("x", bold=True)
|
||||
tw.write("x\n", red=True)
|
||||
l = self.getlines()
|
||||
skip_win32()
|
||||
assert len(l[0]) > 2, l
|
||||
assert len(l[1]) > 2, l
|
||||
|
||||
|
|
|
@ -40,13 +40,13 @@ class TerminalReporter:
|
|||
self.currentfspath = fspath
|
||||
self._tw.write(res)
|
||||
|
||||
def write_ensure_prefix(self, prefix, extra=""):
|
||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
||||
if self.currentfspath != prefix:
|
||||
self._tw.line()
|
||||
self.currentfspath = prefix
|
||||
self._tw.write(prefix)
|
||||
if extra:
|
||||
self._tw.write(extra)
|
||||
self._tw.write(extra, **kwargs)
|
||||
self.currentfspath = -2
|
||||
|
||||
def ensure_newline(self):
|
||||
|
@ -77,13 +77,13 @@ class TerminalReporter:
|
|||
|
||||
def getoutcomeword(self, event):
|
||||
if event.passed:
|
||||
return self._tw.markup("PASS", green=True)
|
||||
return "PASS", dict(green=True)
|
||||
elif event.failed:
|
||||
return self._tw.markup("FAIL", red=True)
|
||||
return "FAIL", dict(red=True)
|
||||
elif event.skipped:
|
||||
return "SKIP"
|
||||
else:
|
||||
return self._tw.markup("???", red=True)
|
||||
return "???", dict(red=True)
|
||||
|
||||
def pyevent_internalerror(self, event):
|
||||
for line in str(event.repr).split("\n"):
|
||||
|
@ -139,13 +139,17 @@ class TerminalReporter:
|
|||
def pyevent_itemtestreport(self, event):
|
||||
fspath = event.colitem.fspath
|
||||
cat, letter, word = self.getcategoryletterword(event)
|
||||
if isinstance(word, tuple):
|
||||
word, markup = word
|
||||
else:
|
||||
markup = {}
|
||||
self.stats.setdefault(cat, []).append(event)
|
||||
if not self.config.option.verbose:
|
||||
self.write_fspath_result(fspath, letter)
|
||||
else:
|
||||
info = event.colitem.repr_metainfo()
|
||||
line = info.verboseline(basedir=self.curdir) + " "
|
||||
self.write_ensure_prefix(line, word)
|
||||
self.write_ensure_prefix(line, word, **markup)
|
||||
|
||||
def pyevent_collectionreport(self, event):
|
||||
if not event.passed:
|
||||
|
|
Loading…
Reference in New Issue