diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index be559867c..aeb5c110d 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -1,15 +1,16 @@ -""" +"""Helper functions for writing to terminals and files.""" +import sys +import os +import unicodedata -Helper functions for writing to terminals and files. - -""" - - -import sys, os, unicodedata import py +from py.builtin import text, bytes + +# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. + py3k = sys.version_info[0] >= 3 py33 = sys.version_info >= (3, 3) -from py.builtin import text, bytes + win32_and_ctypes = False colorama = None @@ -19,6 +20,7 @@ if sys.platform == "win32": except ImportError: try: import ctypes + win32_and_ctypes = True except ImportError: pass @@ -27,10 +29,14 @@ if sys.platform == "win32": def _getdimensions(): if py33: import shutil + size = shutil.get_terminal_size() return size.lines, size.columns else: - import termios, fcntl, struct + import termios + import fcntl + import struct + call = fcntl.ioctl(1, termios.TIOCGWINSZ, "\000" * 8) height, width = struct.unpack("hhhh", call)[:2] return height, width @@ -50,27 +56,28 @@ def get_terminal_width(): # FALLBACK: # * some exception happened # * or this is emacs terminal which reports (0,0) - width = int(os.environ.get('COLUMNS', 80)) + width = int(os.environ.get("COLUMNS", 80)) # XXX the windows getdimensions may be bogus, let's sanify a bit if width < 40: width = 80 return width + terminal_width = get_terminal_width() char_width = { - 'A': 1, # "Ambiguous" - 'F': 2, # Fullwidth - 'H': 1, # Halfwidth - 'N': 1, # Neutral - 'Na': 1, # Narrow - 'W': 2, # Wide + "A": 1, # "Ambiguous" + "F": 2, # Fullwidth + "H": 1, # Halfwidth + "N": 1, # Neutral + "Na": 1, # Narrow + "W": 2, # Wide } def get_line_width(text): - text = unicodedata.normalize('NFC', text) + text = unicodedata.normalize("NFC", text) return sum(char_width.get(unicodedata.east_asian_width(c), 1) for c in text) @@ -82,11 +89,11 @@ def ansi_print(text, esc, file=None, newline=True, flush=False): 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" + text = ( + "".join(["\x1b[%sm" % cod for cod in esc]) + text + "\x1b[0m" + ) # ANSI color code "reset" if newline: - text += '\n' + text += "\n" if esc and win32_and_ctypes and file.isatty(): if 1 in esc: @@ -94,16 +101,17 @@ def ansi_print(text, esc, file=None, newline=True, flush=False): 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 - } + 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 @@ -114,7 +122,7 @@ def ansi_print(text, esc, file=None, newline=True, flush=False): else: handle = GetStdHandle(STD_OUTPUT_HANDLE) oldcolors = GetConsoleInfo(handle).wAttributes - attr |= (oldcolors & 0x0f0) + attr |= oldcolors & 0x0F0 SetConsoleTextAttribute(handle, attr) while len(text) > 32768: file.write(text[:32768]) @@ -128,21 +136,43 @@ def ansi_print(text, esc, file=None, newline=True, flush=False): if flush: file.flush() + def should_do_markup(file): - if os.environ.get('PY_COLORS') == '1': + if os.environ.get("PY_COLORS") == "1": return True - if os.environ.get('PY_COLORS') == '0': + if os.environ.get("PY_COLORS") == "0": return False - return hasattr(file, 'isatty') and file.isatty() \ - and os.environ.get('TERM') != 'dumb' \ - and not (sys.platform.startswith('java') and os._name == 'nt') + return ( + hasattr(file, "isatty") + and file.isatty() + and os.environ.get("TERM") != "dumb" + and not (sys.platform.startswith("java") and os._name == "nt") + ) + class TerminalWriter(object): - _esctable = dict(black=30, red=31, green=32, yellow=33, - blue=34, purple=35, cyan=36, white=37, - Black=40, Red=41, Green=42, Yellow=43, - Blue=44, Purple=45, Cyan=46, White=47, - bold=1, light=2, blink=5, invert=7) + _esctable = dict( + black=30, + red=31, + green=32, + yellow=33, + blue=34, + purple=35, + cyan=36, + white=37, + Black=40, + Red=41, + Green=42, + Yellow=43, + Blue=44, + Purple=45, + Cyan=46, + White=47, + bold=1, + light=2, + blink=5, + invert=7, + ) # XXX deprecate stringio argument def __init__(self, file=None, stringio=False, encoding=None): @@ -152,11 +182,12 @@ class TerminalWriter(object): else: from sys import stdout as file elif py.builtin.callable(file) and not ( - hasattr(file, "write") and hasattr(file, "flush")): + hasattr(file, "write") and hasattr(file, "flush") + ): file = WriteFile(file, encoding=encoding) if hasattr(file, "isatty") and file.isatty() and colorama: file = colorama.AnsiToWin32(file).stream - self.encoding = encoding or getattr(file, 'encoding', "utf-8") + self.encoding = encoding or getattr(file, "encoding", "utf-8") self._file = file self.hasmarkup = should_do_markup(file) self._lastlen = 0 @@ -165,7 +196,7 @@ class TerminalWriter(object): @property def fullwidth(self): - if hasattr(self, '_terminal_width'): + if hasattr(self, "_terminal_width"): return self._terminal_width return get_terminal_width() @@ -198,15 +229,14 @@ class TerminalWriter(object): def _escaped(self, text, esc): if esc and self.hasmarkup: - text = (''.join(['\x1b[%sm' % cod for cod in esc]) + - text +'\x1b[0m') + text = "".join(["\x1b[%sm" % cod for cod in esc]) + text + "\x1b[0m" return text def markup(self, text, **kw): esc = [] for name in kw: if name not in self._esctable: - raise ValueError("unknown markup: %r" %(name,)) + raise ValueError("unknown markup: %r" % (name,)) if kw[name]: esc.append(self._esctable[name]) return self._escaped(text, tuple(esc)) @@ -227,7 +257,7 @@ class TerminalWriter(object): # 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 = max((fullwidth - len(title) - 2) // (2*len(sepchar)), 1) + N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) fill = sepchar * N line = "%s %s %s" % (fill, title, fill) else: @@ -256,10 +286,10 @@ class TerminalWriter(object): write_out(self._file, markupmsg) def _update_chars_on_current_line(self, text_or_bytes): - newline = b'\n' if isinstance(text_or_bytes, bytes) else '\n' + newline = b"\n" if isinstance(text_or_bytes, bytes) else "\n" current_line = text_or_bytes.rsplit(newline, 1)[-1] if isinstance(current_line, bytes): - current_line = current_line.decode('utf-8', errors='replace') + current_line = current_line.decode("utf-8", errors="replace") if newline in text_or_bytes: self._chars_on_current_line = len(current_line) self._width_of_current_line = get_line_width(current_line) @@ -267,17 +297,17 @@ class TerminalWriter(object): self._chars_on_current_line += len(current_line) self._width_of_current_line += get_line_width(current_line) - def line(self, s='', **kw): + def line(self, s="", **kw): self.write(s, **kw) self._checkfill(s) - self.write('\n') + self.write("\n") def reline(self, line, **kw): if not self.hasmarkup: raise ValueError("cannot use rewrite-line without terminal") self.write(line, **kw) self._checkfill(line) - self.write('\r') + self.write("\r") self._lastlen = len(line) def _checkfill(self, line): @@ -285,6 +315,7 @@ class TerminalWriter(object): if diff2last > 0: self.write(" " * diff2last) + class Win32ConsoleWriter(TerminalWriter): def write(self, msg, **kw): if msg: @@ -299,17 +330,17 @@ class Win32ConsoleWriter(TerminalWriter): oldcolors = GetConsoleInfo(handle).wAttributes default_bg = oldcolors & 0x00F0 attr = default_bg - if kw.pop('bold', False): + if kw.pop("bold", False): attr |= FOREGROUND_INTENSITY - if kw.pop('red', False): + if kw.pop("red", False): attr |= FOREGROUND_RED - elif kw.pop('blue', False): + elif kw.pop("blue", False): attr |= FOREGROUND_BLUE - elif kw.pop('green', False): + elif kw.pop("green", False): attr |= FOREGROUND_GREEN - elif kw.pop('yellow', False): - attr |= FOREGROUND_GREEN|FOREGROUND_RED + elif kw.pop("yellow", False): + attr |= FOREGROUND_GREEN | FOREGROUND_RED else: attr |= oldcolors & 0x0007 @@ -318,6 +349,7 @@ class Win32ConsoleWriter(TerminalWriter): if oldcolors: SetConsoleTextAttribute(handle, oldcolors) + class WriteFile(object): def __init__(self, writemethod, encoding=None): self.encoding = encoding @@ -339,39 +371,46 @@ if win32_and_ctypes: # ctypes access to the Windows console STD_OUTPUT_HANDLE = -11 - STD_ERROR_HANDLE = -12 - FOREGROUND_BLACK = 0x0000 # black text - 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_BLACK = 0x0000 # background color black - BACKGROUND_BLUE = 0x0010 # background color contains blue. - BACKGROUND_GREEN = 0x0020 # background color contains green. - BACKGROUND_RED = 0x0040 # background color contains red. - BACKGROUND_WHITE = 0x0070 - BACKGROUND_INTENSITY = 0x0080 # background color is intensified. + STD_ERROR_HANDLE = -12 + FOREGROUND_BLACK = 0x0000 # black text + 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_BLACK = 0x0000 # background color black + BACKGROUND_BLUE = 0x0010 # background color contains blue. + BACKGROUND_GREEN = 0x0020 # background color contains green. + BACKGROUND_RED = 0x0040 # background color contains red. + BACKGROUND_WHITE = 0x0070 + BACKGROUND_INTENSITY = 0x0080 # background color is intensified. SHORT = ctypes.c_short + class COORD(ctypes.Structure): - _fields_ = [('X', SHORT), - ('Y', SHORT)] + _fields_ = [("X", SHORT), ("Y", SHORT)] + class SMALL_RECT(ctypes.Structure): - _fields_ = [('Left', SHORT), - ('Top', SHORT), - ('Right', SHORT), - ('Bottom', SHORT)] + _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)] + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] _GetStdHandle = ctypes.windll.kernel32.GetStdHandle _GetStdHandle.argtypes = [wintypes.DWORD] _GetStdHandle.restype = wintypes.HANDLE + def GetStdHandle(kind): return _GetStdHandle(kind) @@ -379,11 +418,13 @@ if win32_and_ctypes: SetConsoleTextAttribute.argtypes = [wintypes.HANDLE, wintypes.WORD] SetConsoleTextAttribute.restype = wintypes.BOOL - _GetConsoleScreenBufferInfo = \ - ctypes.windll.kernel32.GetConsoleScreenBufferInfo - _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, - ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)] + _GetConsoleScreenBufferInfo = ctypes.windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + def GetConsoleInfo(handle): info = CONSOLE_SCREEN_BUFFER_INFO() _GetConsoleScreenBufferInfo(handle, ctypes.byref(info)) @@ -396,6 +437,7 @@ if win32_and_ctypes: # and the ending \n causes an empty line to display. return info.dwSize.Y, info.dwSize.X - 1 + def write_out(fil, msg): # XXX sometimes "msg" is of type bytes, sometimes text which # complicates the situation. Should we try to enforce unicode?