From da78c6f1b6c477acd8a0b511045ddebbb430cedb Mon Sep 17 00:00:00 2001 From: "maruel@chromium.org" Date: Sun, 23 Oct 2011 00:13:58 +0000 Subject: [PATCH] Reapply r106708 "Include initial use of colorama" isatty() wasn't defined on Windows, causing it to crash. R=dpranke@chromium.org BUG= TEST=manually gclient sync on windows Review URL: http://codereview.chromium.org/8371006 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@106865 0039d316-1c4b-4281-b951-d872f2087c98 --- gclient.py | 15 +- pylintrc | 2 +- third_party/colorama/LICENSE.txt | 33 ++++ third_party/colorama/README.chromium | 10 + third_party/colorama/README.txt | 264 +++++++++++++++++++++++++++ third_party/colorama/__init__.py | 6 + third_party/colorama/ansi.py | 49 +++++ third_party/colorama/ansitowin32.py | 189 +++++++++++++++++++ third_party/colorama/initialise.py | 55 ++++++ third_party/colorama/win32.py | 109 +++++++++++ third_party/colorama/winterm.py | 120 ++++++++++++ 11 files changed, 848 insertions(+), 4 deletions(-) create mode 100644 third_party/colorama/LICENSE.txt create mode 100644 third_party/colorama/README.chromium create mode 100644 third_party/colorama/README.txt create mode 100644 third_party/colorama/__init__.py create mode 100644 third_party/colorama/ansi.py create mode 100644 third_party/colorama/ansitowin32.py create mode 100644 third_party/colorama/initialise.py create mode 100644 third_party/colorama/win32.py create mode 100644 third_party/colorama/winterm.py diff --git a/gclient.py b/gclient.py index 8a848cf20..e29eaa80c 100644 --- a/gclient.py +++ b/gclient.py @@ -69,6 +69,9 @@ import gclient_scm import gclient_utils from third_party.repo.progress import Progress import subprocess2 +from third_party import colorama +# Import shortcut. +from third_party.colorama import Fore def attr(attribute, data): @@ -1483,6 +1486,7 @@ def Main(argv): if sys.hexversion < 0x02050000: print >> sys.stderr, ( '\nYour python version is unsupported, please upgrade.\n') + colorama.init() try: # Make stdout auto-flush so buildbot doesn't kill us during lengthy # operations. Python as a strong tendency to buffer sys.stdout. @@ -1492,9 +1496,14 @@ def Main(argv): # Do it late so all commands are listed. # Unused variable 'usage' # pylint: disable=W0612 - CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([ - ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip()) - for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')])) + def to_str(fn): + return ( + ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) + + ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip()) + cmds = ( + to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD') + ) + CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds) parser = Parser() if argv: command = Command(argv[0]) diff --git a/pylintrc b/pylintrc index 1c7f47ee8..e2faa0451 100644 --- a/pylintrc +++ b/pylintrc @@ -123,7 +123,7 @@ ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). -ignored-classes=SQLObject +ignored-classes=SQLObject,AnsiCodes # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. diff --git a/third_party/colorama/LICENSE.txt b/third_party/colorama/LICENSE.txt new file mode 100644 index 000000000..b7464472e --- /dev/null +++ b/third_party/colorama/LICENSE.txt @@ -0,0 +1,33 @@ +Copyright (c) 2010 Jonathan Hartley + +Released under the New BSD license (reproduced below), or alternatively you may +use this software under any OSI approved open source license such as those at +http://opensource.org/licenses/alphabetical + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name(s) of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/third_party/colorama/README.chromium b/third_party/colorama/README.chromium new file mode 100644 index 000000000..3297d2ed9 --- /dev/null +++ b/third_party/colorama/README.chromium @@ -0,0 +1,10 @@ +Name: colorama +URL: http://code.google.com/p/colorama +Version: 2.3 + c25659277b30 +Revision: c25659277b30 + +Description: +Provides a simple cross-platform API to print colored terminal text from Python +applications. + +LICENSE.txt is the license file copied from upstream. diff --git a/third_party/colorama/README.txt b/third_party/colorama/README.txt new file mode 100644 index 000000000..33467f76e --- /dev/null +++ b/third_party/colorama/README.txt @@ -0,0 +1,264 @@ +Download and docs: + http://pypi.python.org/pypi/colorama +Development: + http://code.google.com/p/colorama + +Description +=========== + +Makes ANSI escape character sequences, for producing colored terminal text and +cursor positioning, work under MS Windows. + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too. It also provides some shortcuts to help generate ANSI sequences, +and works fine in conjunction with any other ANSI sequence generation library, +such as Termcolor (http://pypi.python.org/pypi/termcolor.) + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.init()``. + +Demo scripts in the source code repository prints some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: http://colorama.googlecode.com/hg/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: http://colorama.googlecode.com/hg/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screengrabs show that Colorama on Windows does not support ANSI 'dim +text': it looks the same as 'normal text'. + + +Dependencies +============ + +None, other than Python. Tested on Python 2.5.5, 2.6.5, 2.7, 3.1.2, and 3.2 + + +Usage +===== + +Initialisation +-------------- + +Applications should initialise Colorama using:: + + from colorama import init + init() + +If you are on Windows, the call to ``init()`` will start filtering ANSI escape +sequences out of any text sent to stdout or stderr, and will replace them with +equivalent Win32 calls. + +Calling ``init()`` has no effect on other platforms (unless you request other +optional functionality, see keyword args below.) The intention is that +applications can call ``init()`` unconditionally on all platforms, after which +ANSI output should just work. + +To stop using colorama before your program exits, simply call ``deinit()``. +This will restore stdout and stderr to their original values, so that Colorama +is disabled. To start using Colorama again, call ``reinit()``, which wraps +stdout and stderr again, but is cheaper to call than doing ``init()`` all over +again. + + +Colored Output +-------------- + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences:: + + from colorama import Fore, Back, Style + print Fore.RED + 'some red text' + print Back.GREEN + and with a green background' + print Style.DIM + 'and in dim text' + print + Fore.RESET + Back.RESET + Style.RESET_ALL + print 'back to normal now' + +or simply by manually printing ANSI sequences from your own code:: + + print '/033[31m' + 'some red text' + print '/033[30m' # and reset to default color + +or Colorama can be used happily in conjunction with existing ANSI libraries +such as Termcolor:: + + from colorama import init + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + init() + + # then use Termcolor for all colored text output + print colored('Hello, World!', 'green', 'on_red') + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +Style.RESET_ALL resets foreground, background and brightness. Colorama will +perform this reset automatically on program exit. + + +Cursor Positioning +------------------ + +ANSI codes to reposition the cursor are supported. See demos/demo06.py for +an example of how to generate them. + + +Init Keyword Args +----------------- + +``init()`` accepts some kwargs to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that:: + + from colorama import init + init(autoreset=True) + print Fore.RED + 'some red text' + print 'automatically back to default color again' + +init(strip=None): + Pass ``True`` or ``False`` to override whether ansi codes should be + stripped from the output. The default behaviour is to strip if on Windows. + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ansi codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the .write() method to do their work. If + this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if autoreset or + strip or convert are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly:: + + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + print >>stream, Fore.BLUE + 'blue text on stderr' + + +Status & Known Problems +======================= + +I've personally only tested it on WinXP (CMD, Console2) and Ubuntu +(gnome-terminal, xterm), although it sounds like others are using it on other +platforms too. + +See outstanding issues and wishlist at: +http://code.google.com/p/colorama/issues/list + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd *love* to hear about it on that issues list. + + +Recognised ANSI Sequences +========================= + +ANSI sequences generally take the form: + + ESC [ ; ... + +Where is an integer, and is a single letter. Zero or more +params are passed to a . If no params are passed, it is generally +synonymous with passing a single zero. No spaces exist in the sequence, they +have just been inserted here to make it easy to read. + +The only ANSI sequences that colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ x;y H # position cursor at x,y + + # clear the screen + ESC [ mode J # clear the screen. Only mode 2 (clear entire screen) + # is supported. It should be easy to add other modes, + # let me know if that would be useful. + +Multiple numeric params to the 'm' command can be combined into a single +sequence, eg:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised nor stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the issues on +google code. + + +Development +=========== + +Running tests requires: + +- Michael Foord's 'mock' module to be installed. +- Tests are written using the 2010 era updates to 'unittest', and require to + be run either using Python2.7 or greater, or else to have Michael Foord's + 'unittest2' module installed. + +unittest2 test discovery doesn't work for colorama, so I use 'nose':: + + nosetests -s + +The -s is required because 'nosetests' otherwise applies a proxy of its own to +stdout, which confuses the unit tests. + + +Thanks +====== +Daniel Griffith for multiple fabulous patches. +Oscar Lesta for valuable fix to stop ANSI chars being sent to non-tty output. +Roger Binns, for many suggestions, valuable feedback, & bug reports. +Tim Golden for thought and much appreciated feedback on the initial idea. + diff --git a/third_party/colorama/__init__.py b/third_party/colorama/__init__.py new file mode 100644 index 000000000..c5d780ae7 --- /dev/null +++ b/third_party/colorama/__init__.py @@ -0,0 +1,6 @@ +from .initialise import init, deinit, reinit +from .ansi import Fore, Back, Style +from .ansitowin32 import AnsiToWin32 + +VERSION = '0.2.4' + diff --git a/third_party/colorama/ansi.py b/third_party/colorama/ansi.py new file mode 100644 index 000000000..7b818e19e --- /dev/null +++ b/third_party/colorama/ansi.py @@ -0,0 +1,49 @@ +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' + +def code_to_chars(code): + return CSI + str(code) + 'm' + +class AnsiCodes(object): + def __init__(self, codes): + for name in dir(codes): + if not name.startswith('_'): + value = getattr(codes, name) + setattr(self, name, code_to_chars(value)) + +class AnsiFore: + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + +class AnsiBack: + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + +class AnsiStyle: + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiCodes( AnsiFore ) +Back = AnsiCodes( AnsiBack ) +Style = AnsiCodes( AnsiStyle ) + diff --git a/third_party/colorama/ansitowin32.py b/third_party/colorama/ansitowin32.py new file mode 100644 index 000000000..48503fe41 --- /dev/null +++ b/third_party/colorama/ansitowin32.py @@ -0,0 +1,189 @@ + +import re +import sys + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .winterm import WinTerm, WinColor, WinStyle +from .win32 import windll + + +if windll is not None: + winterm = WinTerm() + + +def is_a_tty(stream): + return hasattr(stream, 'isatty') and stream.isatty() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def write(self, text): + self.__convertor.write(text) + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = sys.platform.startswith('win') + + # should we strip ANSI sequences from our output? + if strip is None: + strip = on_windows + self.strip = strip + + # should we should convert ANSI sequences into win32 calls? + if convert is None: + convert = on_windows and is_a_tty(wrapped) + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + } + + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif is_a_tty(self.wrapped): + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + for match in self.ANSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(paramstring) + self.call_win32(command, params) + + + def extract_params(self, paramstring): + def split(paramstring): + for p in paramstring.split(';'): + if p != '': + yield int(p) + return tuple(split(paramstring)) + + + def call_win32(self, command, params): + if params == []: + params = [0] + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in ('H', 'f'): # set cursor position + func = winterm.set_cursor_position + func(params, on_stderr=self.on_stderr) + elif command in ('J'): + func = winterm.erase_data + func(params, on_stderr=self.on_stderr) + elif command == 'A': + if params == () or params == None: + num_rows = 1 + else: + num_rows = params[0] + func = winterm.cursor_up + func(num_rows, on_stderr=self.on_stderr) + diff --git a/third_party/colorama/initialise.py b/third_party/colorama/initialise.py new file mode 100644 index 000000000..e54f8a8d0 --- /dev/null +++ b/third_party/colorama/initialise.py @@ -0,0 +1,55 @@ +import atexit +import sys + +from .ansitowin32 import AnsiToWin32 + + +orig_stdout = sys.stdout +orig_stderr = sys.stderr + +wrapped_stdout = sys.stdout +wrapped_stderr = sys.stderr + +atexit_done = False + + +def reset_all(): + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + +def reinit(): + sys.stdout = wrapped_stdout + sys.stderr = wrapped_stdout + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream + + diff --git a/third_party/colorama/win32.py b/third_party/colorama/win32.py new file mode 100644 index 000000000..591176131 --- /dev/null +++ b/third_party/colorama/win32.py @@ -0,0 +1,109 @@ + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + from ctypes import windll +except ImportError: + windll = None + SetConsoleTextAttribute = lambda *_: None +else: + from ctypes import ( + byref, Structure, c_char, c_short, c_uint32, c_ushort + ) + + handles = { + STDOUT: windll.kernel32.GetStdHandle(STDOUT), + STDERR: windll.kernel32.GetStdHandle(STDERR), + } + + SHORT = c_short + WORD = c_ushort + DWORD = c_uint32 + TCHAR = c_char + + class COORD(Structure): + """struct in wincon.h""" + _fields_ = [ + ('X', SHORT), + ('Y', SHORT), + ] + + class SMALL_RECT(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("Left", SHORT), + ("Top", SHORT), + ("Right", SHORT), + ("Bottom", SHORT), + ] + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = handles[stream_id] + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = windll.kernel32.GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + + def SetConsoleTextAttribute(stream_id, attrs): + handle = handles[stream_id] + return windll.kernel32.SetConsoleTextAttribute(handle, attrs) + + + def SetConsoleCursorPosition(stream_id, position): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = handles[stream_id] + return windll.kernel32.SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = handles[stream_id] + char = TCHAR(char) + length = DWORD(length) + num_written = DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = windll.kernel32.FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = handles[stream_id] + attribute = WORD(attr) + length = DWORD(length) + num_written = DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return windll.kernel32.FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + diff --git a/third_party/colorama/winterm.py b/third_party/colorama/winterm.py new file mode 100644 index 000000000..ae22c4658 --- /dev/null +++ b/third_party/colorama/winterm.py @@ -0,0 +1,120 @@ + +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + + def get_attrs(self): + return self._fore + self._back * 16 + self._style + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & WinStyle.BRIGHT + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + + def fore(self, fore=None, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + #I'm not currently tracking the position, so there is no default. + #position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_up(self, num_rows=0, on_stderr=False): + if num_rows == 0: + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y - num_rows, position.X) + self.set_cursor_position(adjusted_position, on_stderr) + + def erase_data(self, mode=0, on_stderr=False): + # 0 (or None) should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) + # + # At the moment, I only support mode 2. From looking at the API, it + # should be possible to calculate a different number of bytes to clear, + # and to do so relative to the cursor position. + if mode[0] not in (2,): + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + # here's where we'll home the cursor + coord_screen = win32.COORD(0,0) + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + dw_con_size = csbi.dwSize.X * csbi.dwSize.Y + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ord(' '), dw_con_size, coord_screen) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); + # put the cursor at (0, 0) + win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))