Path: blob/master/thirdparty/colorama/ansitowin32.py
2992 views
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.1import re2import sys3import os45from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style6from .winterm import WinTerm, WinColor, WinStyle7from .win32 import windll, winapi_test8910winterm = None11if windll is not None:12winterm = WinTerm()131415def is_stream_closed(stream):16return not hasattr(stream, 'closed') or stream.closed171819def is_a_tty(stream):20return hasattr(stream, 'isatty') and stream.isatty()212223class StreamWrapper(object):24'''25Wraps a stream (such as stdout), acting as a transparent proxy for all26attribute access apart from method 'write()', which is delegated to our27Converter instance.28'''29def __init__(self, wrapped, converter):30# double-underscore everything to prevent clashes with names of31# attributes on the wrapped stream object.32self.__wrapped = wrapped33self.__convertor = converter3435def __getattr__(self, name):36return getattr(self.__wrapped, name)3738def write(self, text):39self.__convertor.write(text)404142class AnsiToWin32(object):43'''44Implements a 'write()' method which, on Windows, will strip ANSI character45sequences from the text, and if outputting to a tty, will convert them into46win32 function calls.47'''48ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer49ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command (Note: https://github.com/tartley/colorama/issues/247)5051def __init__(self, wrapped, convert=None, strip=None, autoreset=False):52# The wrapped stream (normally sys.stdout or sys.stderr)53self.wrapped = wrapped5455# should we reset colors to defaults after every .write()56self.autoreset = autoreset5758# create the proxy wrapping our output stream59self.stream = StreamWrapper(wrapped, self)6061on_windows = os.name == 'nt'62# We test if the WinAPI works, because even if we are on Windows63# we may be using a terminal that doesn't support the WinAPI64# (e.g. Cygwin Terminal). In this case it's up to the terminal65# to support the ANSI codes.66conversion_supported = on_windows and winapi_test()6768# should we strip ANSI sequences from our output?69if strip is None:70strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))71self.strip = strip7273# should we should convert ANSI sequences into win32 calls?74if convert is None:75convert = conversion_supported and not is_stream_closed(wrapped) and is_a_tty(wrapped)76self.convert = convert7778# dict of ansi codes to win32 functions and parameters79self.win32_calls = self.get_win32_calls()8081# are we wrapping stderr?82self.on_stderr = self.wrapped is sys.stderr8384def should_wrap(self):85'''86True if this class is actually needed. If false, then the output87stream will not be affected, nor will win32 calls be issued, so88wrapping stdout is not actually required. This will generally be89False on non-Windows platforms, unless optional functionality like90autoreset has been requested using kwargs to init()91'''92return self.convert or self.strip or self.autoreset9394def get_win32_calls(self):95if self.convert and winterm:96return {97AnsiStyle.RESET_ALL: (winterm.reset_all, ),98AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),99AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),100AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),101AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),102AnsiFore.RED: (winterm.fore, WinColor.RED),103AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),104AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),105AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),106AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),107AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),108AnsiFore.WHITE: (winterm.fore, WinColor.GREY),109AnsiFore.RESET: (winterm.fore, ),110AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),111AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),112AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),113AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),114AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),115AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),116AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),117AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),118AnsiBack.BLACK: (winterm.back, WinColor.BLACK),119AnsiBack.RED: (winterm.back, WinColor.RED),120AnsiBack.GREEN: (winterm.back, WinColor.GREEN),121AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),122AnsiBack.BLUE: (winterm.back, WinColor.BLUE),123AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),124AnsiBack.CYAN: (winterm.back, WinColor.CYAN),125AnsiBack.WHITE: (winterm.back, WinColor.GREY),126AnsiBack.RESET: (winterm.back, ),127AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),128AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),129AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),130AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),131AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),132AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),133AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),134AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),135}136return dict()137138def write(self, text):139if self.strip or self.convert:140self.write_and_convert(text)141else:142self.wrapped.write(text)143self.wrapped.flush()144if self.autoreset:145self.reset_all()146147148def reset_all(self):149if self.convert:150self.call_win32('m', (0,))151elif not self.strip and not is_stream_closed(self.wrapped):152self.wrapped.write(Style.RESET_ALL)153154155def write_and_convert(self, text):156'''157Write the given text to our wrapped stream, stripping any ANSI158sequences from the text, and optionally converting them into win32159calls.160'''161cursor = 0162text = self.convert_osc(text)163for match in self.ANSI_CSI_RE.finditer(text):164start, end = match.span()165self.write_plain_text(text, cursor, start)166self.convert_ansi(*match.groups())167cursor = end168self.write_plain_text(text, cursor, len(text))169170171def write_plain_text(self, text, start, end):172if start < end:173self._write(text[start:end])174self.wrapped.flush()175176# Reference: https://github.com/robotframework/robotframework/commit/828c67695d85519e4435c556c43ed1b00985df05177# Workaround for Windows 10 console bug:178# https://github.com/robotframework/robotframework/issues/2709179def _write(self, text, retry=5):180try:181self.wrapped.write(text)182except IOError as err:183if not (err.errno == 0 and retry > 0):184raise185self._write(text, retry-1)186except UnicodeError:187self.wrapped.write('?')188189def convert_ansi(self, paramstring, command):190if self.convert:191params = self.extract_params(command, paramstring)192self.call_win32(command, params)193194195def extract_params(self, command, paramstring):196if command in 'Hf':197params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))198while len(params) < 2:199# defaults:200params = params + (1,)201else:202params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)203if len(params) == 0:204# defaults:205if command in 'JKm':206params = (0,)207elif command in 'ABCD':208params = (1,)209210return params211212213def call_win32(self, command, params):214if command == 'm':215for param in params:216if param in self.win32_calls:217func_args = self.win32_calls[param]218func = func_args[0]219args = func_args[1:]220kwargs = dict(on_stderr=self.on_stderr)221func(*args, **kwargs)222elif command in 'J':223winterm.erase_screen(params[0], on_stderr=self.on_stderr)224elif command in 'K':225winterm.erase_line(params[0], on_stderr=self.on_stderr)226elif command in 'Hf': # cursor position - absolute227winterm.set_cursor_position(params, on_stderr=self.on_stderr)228elif command in 'ABCD': # cursor position - relative229n = params[0]230# A - up, B - down, C - forward, D - back231x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]232winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)233234235def convert_osc(self, text):236for match in self.ANSI_OSC_RE.finditer(text):237start, end = match.span()238text = text[:start] + text[end:]239paramstring, command = match.groups()240if command in '\x07': # \x07 = BEL241params = paramstring.split(";")242# 0 - change title and icon (we will only change title)243# 1 - change icon (we don't support this)244# 2 - change title245# if params[0] in '02':246# winterm.set_title(params[1])247return text248249250