[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