1
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
6
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
7
from .winterm import WinTerm, WinColor, WinStyle
8
from .win32 import windll, winapi_test
12
if windll is not None:
16
def is_stream_closed(stream):
17
return not hasattr(stream, 'closed') or stream.closed
21
return hasattr(stream, 'isatty') and stream.isatty()
24
class StreamWrapper(object):
26
Wraps a stream (such as stdout), acting as a transparent proxy for all
27
attribute access apart from method 'write()', which is delegated to our
30
def __init__(self, wrapped, converter):
31
# double-underscore everything to prevent clashes with names of
32
# attributes on the wrapped stream object.
33
self.__wrapped = wrapped
34
self.__convertor = converter
36
def __getattr__(self, name):
37
return getattr(self.__wrapped, name)
39
def write(self, text):
40
self.__convertor.write(text)
43
class AnsiToWin32(object):
45
Implements a 'write()' method which, on Windows, will strip ANSI character
46
sequences from the text, and if outputting to a tty, will convert them into
49
ANSI_CSI_RE = re.compile('\001?\033\[((?:\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
50
ANSI_OSC_RE = re.compile('\001?\033\]((?:.|;)*?)(\x07)\002?') # Operating System Command
52
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
53
# The wrapped stream (normally sys.stdout or sys.stderr)
54
self.wrapped = wrapped
56
# should we reset colors to defaults after every .write()
57
self.autoreset = autoreset
59
# create the proxy wrapping our output stream
60
self.stream = StreamWrapper(wrapped, self)
62
on_windows = os.name == 'nt'
63
# We test if the WinAPI works, because even if we are on Windows
64
# we may be using a terminal that doesn't support the WinAPI
65
# (e.g. Cygwin Terminal). In this case it's up to the terminal
66
# to support the ANSI codes.
67
conversion_supported = on_windows and winapi_test()
69
# should we strip ANSI sequences from our output?
71
strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))
74
# should we should convert ANSI sequences into win32 calls?
76
convert = conversion_supported and not is_stream_closed(wrapped) and is_a_tty(wrapped)
77
self.convert = convert
79
# dict of ansi codes to win32 functions and parameters
80
self.win32_calls = self.get_win32_calls()
82
# are we wrapping stderr?
83
self.on_stderr = self.wrapped is sys.stderr
85
def should_wrap(self):
87
True if this class is actually needed. If false, then the output
88
stream will not be affected, nor will win32 calls be issued, so
89
wrapping stdout is not actually required. This will generally be
90
False on non-Windows platforms, unless optional functionality like
91
autoreset has been requested using kwargs to init()
93
return self.convert or self.strip or self.autoreset
95
def get_win32_calls(self):
96
if self.convert and winterm:
98
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
99
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
100
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
101
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
102
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
103
AnsiFore.RED: (winterm.fore, WinColor.RED),
104
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
105
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
106
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
107
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
108
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
109
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
110
AnsiFore.RESET: (winterm.fore, ),
111
AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
112
AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
113
AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
114
AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
115
AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
116
AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
117
AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
118
AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
119
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
120
AnsiBack.RED: (winterm.back, WinColor.RED),
121
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
122
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
123
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
124
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
125
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
126
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
127
AnsiBack.RESET: (winterm.back, ),
128
AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
129
AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
130
AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
131
AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
132
AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
133
AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
134
AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
135
AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
139
def write(self, text):
140
if self.strip or self.convert:
141
self.write_and_convert(text)
143
self.wrapped.write(text)
151
self.call_win32('m', (0,))
152
elif not self.strip and not is_stream_closed(self.wrapped):
153
self.wrapped.write(Style.RESET_ALL)
156
def write_and_convert(self, text):
158
Write the given text to our wrapped stream, stripping any ANSI
159
sequences from the text, and optionally converting them into win32
163
text = self.convert_osc(text)
164
for match in self.ANSI_CSI_RE.finditer(text):
165
start, end = match.span()
166
self.write_plain_text(text, cursor, start)
167
self.convert_ansi(*match.groups())
169
self.write_plain_text(text, cursor, len(text))
172
def write_plain_text(self, text, start, end):
174
self.wrapped.write(text[start:end])
178
def convert_ansi(self, paramstring, command):
180
params = self.extract_params(command, paramstring)
181
self.call_win32(command, params)
184
def extract_params(self, command, paramstring):
186
params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
187
while len(params) < 2:
189
params = params + (1,)
191
params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
196
elif command in 'ABCD':
202
def call_win32(self, command, params):
205
if param in self.win32_calls:
206
func_args = self.win32_calls[param]
209
kwargs = dict(on_stderr=self.on_stderr)
210
func(*args, **kwargs)
212
winterm.erase_screen(params[0], on_stderr=self.on_stderr)
214
winterm.erase_line(params[0], on_stderr=self.on_stderr)
215
elif command in 'Hf': # cursor position - absolute
216
winterm.set_cursor_position(params, on_stderr=self.on_stderr)
217
elif command in 'ABCD': # cursor position - relative
219
# A - up, B - down, C - forward, D - back
220
x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
221
winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
224
def convert_osc(self, text):
225
for match in self.ANSI_OSC_RE.finditer(text):
226
start, end = match.span()
227
text = text[:start] + text[end:]
228
paramstring, command = match.groups()
229
if command in '\x07': # \x07 = BEL
230
params = paramstring.split(";")
231
# 0 - change title and icon (we will only change title)
232
# 1 - change icon (we don't support this)
234
if params[0] in '02':
235
winterm.set_title(params[1])