eggs/py-1.4.0-py2.6.egg/py/_io/terminalwriter.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 """
       
     2 
       
     3 Helper functions for writing to terminals and files.
       
     4 
       
     5 """
       
     6 
       
     7 
       
     8 import sys, os
       
     9 import py
       
    10 
       
    11 win32_and_ctypes = False
       
    12 if sys.platform == "win32":
       
    13     try:
       
    14         import ctypes
       
    15         win32_and_ctypes = True
       
    16     except ImportError:
       
    17         pass
       
    18 
       
    19 def _getdimensions():
       
    20     import termios,fcntl,struct
       
    21     call = fcntl.ioctl(1,termios.TIOCGWINSZ,"\000"*8)
       
    22     height,width = struct.unpack( "hhhh", call ) [:2]
       
    23     return height, width
       
    24 
       
    25 
       
    26 def get_terminal_width():
       
    27     try:
       
    28         height, width = _getdimensions()
       
    29     except py.builtin._sysex:
       
    30         raise
       
    31     except:
       
    32         # FALLBACK
       
    33         width = int(os.environ.get('COLUMNS', 80))
       
    34     else:
       
    35         # XXX the windows getdimensions may be bogus, let's sanify a bit
       
    36         if width < 40:
       
    37             width = 80
       
    38     return width
       
    39 
       
    40 terminal_width = get_terminal_width()
       
    41 
       
    42 # XXX unify with _escaped func below
       
    43 def ansi_print(text, esc, file=None, newline=True, flush=False):
       
    44     if file is None:
       
    45         file = sys.stderr
       
    46     text = text.rstrip()
       
    47     if esc and not isinstance(esc, tuple):
       
    48         esc = (esc,)
       
    49     if esc and sys.platform != "win32" and file.isatty():
       
    50         text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
       
    51                 text +
       
    52                 '\x1b[0m')     # ANSI color code "reset"
       
    53     if newline:
       
    54         text += '\n'
       
    55 
       
    56     if esc and win32_and_ctypes and file.isatty():
       
    57         if 1 in esc:
       
    58             bold = True
       
    59             esc = tuple([x for x in esc if x != 1])
       
    60         else:
       
    61             bold = False
       
    62         esctable = {()   : FOREGROUND_WHITE,                 # normal
       
    63                     (31,): FOREGROUND_RED,                   # red
       
    64                     (32,): FOREGROUND_GREEN,                 # green
       
    65                     (33,): FOREGROUND_GREEN|FOREGROUND_RED,  # yellow
       
    66                     (34,): FOREGROUND_BLUE,                  # blue
       
    67                     (35,): FOREGROUND_BLUE|FOREGROUND_RED,   # purple
       
    68                     (36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
       
    69                     (37,): FOREGROUND_WHITE,                 # white
       
    70                     (39,): FOREGROUND_WHITE,                 # reset
       
    71                     }
       
    72         attr = esctable.get(esc, FOREGROUND_WHITE)
       
    73         if bold:
       
    74             attr |= FOREGROUND_INTENSITY
       
    75         STD_OUTPUT_HANDLE = -11
       
    76         STD_ERROR_HANDLE = -12
       
    77         if file is sys.stderr:
       
    78             handle = GetStdHandle(STD_ERROR_HANDLE)
       
    79         else:
       
    80             handle = GetStdHandle(STD_OUTPUT_HANDLE)
       
    81         oldcolors = GetConsoleInfo(handle).wAttributes
       
    82         attr |= (oldcolors & 0x0f0)
       
    83         SetConsoleTextAttribute(handle, attr)
       
    84         file.write(text)
       
    85         SetConsoleTextAttribute(handle, oldcolors)
       
    86     else:
       
    87         file.write(text)
       
    88 
       
    89     if flush:
       
    90         file.flush()
       
    91 
       
    92 def should_do_markup(file):
       
    93     return hasattr(file, 'isatty') and file.isatty() \
       
    94            and os.environ.get('TERM') != 'dumb' \
       
    95            and not (sys.platform.startswith('java') and os._name == 'nt')
       
    96 
       
    97 class TerminalWriter(object):
       
    98     _esctable = dict(black=30, red=31, green=32, yellow=33,
       
    99                      blue=34, purple=35, cyan=36, white=37,
       
   100                      Black=40, Red=41, Green=42, Yellow=43,
       
   101                      Blue=44, Purple=45, Cyan=46, White=47,
       
   102                      bold=1, light=2, blink=5, invert=7)
       
   103 
       
   104     # XXX deprecate stringio argument
       
   105     def __init__(self, file=None, stringio=False, encoding=None):
       
   106         if file is None:
       
   107             if stringio:
       
   108                 self.stringio = file = py.io.TextIO()
       
   109             else:
       
   110                 file = py.std.sys.stdout
       
   111                 if hasattr(file, 'encoding'):
       
   112                     encoding = file.encoding
       
   113         elif hasattr(file, '__call__'):
       
   114             file = WriteFile(file, encoding=encoding)
       
   115         self.encoding = encoding
       
   116         self._file = file
       
   117         self.fullwidth = get_terminal_width()
       
   118         self.hasmarkup = should_do_markup(file)
       
   119 
       
   120     def _escaped(self, text, esc):
       
   121         if esc and self.hasmarkup:
       
   122             text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
       
   123                 text +'\x1b[0m')
       
   124         return text
       
   125 
       
   126     def markup(self, text, **kw):
       
   127         esc = []
       
   128         for name in kw:
       
   129             if name not in self._esctable:
       
   130                 raise ValueError("unknown markup: %r" %(name,))
       
   131             if kw[name]:
       
   132                 esc.append(self._esctable[name])
       
   133         return self._escaped(text, tuple(esc))
       
   134 
       
   135     def sep(self, sepchar, title=None, fullwidth=None, **kw):
       
   136         if fullwidth is None:
       
   137             fullwidth = self.fullwidth
       
   138         # the goal is to have the line be as long as possible
       
   139         # under the condition that len(line) <= fullwidth
       
   140         if title is not None:
       
   141             # we want 2 + 2*len(fill) + len(title) <= fullwidth
       
   142             # i.e.    2 + 2*len(sepchar)*N + len(title) <= fullwidth
       
   143             #         2*len(sepchar)*N <= fullwidth - len(title) - 2
       
   144             #         N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
       
   145             N = (fullwidth - len(title) - 2) // (2*len(sepchar))
       
   146             fill = sepchar * N
       
   147             line = "%s %s %s" % (fill, title, fill)
       
   148         else:
       
   149             # we want len(sepchar)*N <= fullwidth
       
   150             # i.e.    N <= fullwidth // len(sepchar)
       
   151             line = sepchar * (fullwidth // len(sepchar))
       
   152         # in some situations there is room for an extra sepchar at the right,
       
   153         # in particular if we consider that with a sepchar like "_ " the
       
   154         # trailing space is not important at the end of the line
       
   155         if len(line) + len(sepchar.rstrip()) <= fullwidth:
       
   156             line += sepchar.rstrip()
       
   157 
       
   158         self.line(line, **kw)
       
   159 
       
   160     def write(self, s, **kw):
       
   161         if s:
       
   162             if not isinstance(self._file, WriteFile):
       
   163                 s = self._getbytestring(s)
       
   164                 if self.hasmarkup and kw:
       
   165                     s = self.markup(s, **kw)
       
   166             self._file.write(s)
       
   167             self._file.flush()
       
   168 
       
   169     def _getbytestring(self, s):
       
   170         # XXX review this and the whole logic
       
   171         if self.encoding and sys.version_info[0] < 3 and isinstance(s, unicode):
       
   172             return s.encode(self.encoding)
       
   173         elif not isinstance(s, str):
       
   174             try:
       
   175                 return str(s)
       
   176             except UnicodeEncodeError:
       
   177                 return "<print-error '%s' object>" % type(s).__name__
       
   178         return s
       
   179 
       
   180     def line(self, s='', **kw):
       
   181         self.write(s, **kw)
       
   182         self.write('\n')
       
   183 
       
   184 class Win32ConsoleWriter(TerminalWriter):
       
   185     def write(self, s, **kw):
       
   186         if s:
       
   187             oldcolors = None
       
   188             if self.hasmarkup and kw:
       
   189                 handle = GetStdHandle(STD_OUTPUT_HANDLE)
       
   190                 oldcolors = GetConsoleInfo(handle).wAttributes
       
   191                 default_bg = oldcolors & 0x00F0
       
   192                 attr = default_bg
       
   193                 if kw.pop('bold', False):
       
   194                     attr |= FOREGROUND_INTENSITY
       
   195 
       
   196                 if kw.pop('red', False):
       
   197                     attr |= FOREGROUND_RED
       
   198                 elif kw.pop('blue', False):
       
   199                     attr |= FOREGROUND_BLUE
       
   200                 elif kw.pop('green', False):
       
   201                     attr |= FOREGROUND_GREEN
       
   202                 else:
       
   203                     attr |= FOREGROUND_BLACK # (oldcolors & 0x0007)
       
   204 
       
   205                 SetConsoleTextAttribute(handle, attr)
       
   206             if not isinstance(self._file, WriteFile):
       
   207                 s = self._getbytestring(s)
       
   208             self._file.write(s)
       
   209             self._file.flush()
       
   210             if oldcolors:
       
   211                 SetConsoleTextAttribute(handle, oldcolors)
       
   212 
       
   213     def line(self, s="", **kw):
       
   214         self.write(s+"\n", **kw)
       
   215 
       
   216 class WriteFile(object):
       
   217     def __init__(self, writemethod, encoding=None):
       
   218         self.encoding = encoding
       
   219         self._writemethod = writemethod
       
   220 
       
   221     def write(self, data):
       
   222         if self.encoding:
       
   223             data = data.encode(self.encoding)
       
   224         self._writemethod(data)
       
   225 
       
   226     def flush(self):
       
   227         return
       
   228 
       
   229 
       
   230 if win32_and_ctypes:
       
   231     TerminalWriter = Win32ConsoleWriter
       
   232     import ctypes
       
   233     from ctypes import wintypes
       
   234 
       
   235     # ctypes access to the Windows console
       
   236     STD_OUTPUT_HANDLE = -11
       
   237     STD_ERROR_HANDLE  = -12
       
   238     FOREGROUND_BLACK     = 0x0000 # black text
       
   239     FOREGROUND_BLUE      = 0x0001 # text color contains blue.
       
   240     FOREGROUND_GREEN     = 0x0002 # text color contains green.
       
   241     FOREGROUND_RED       = 0x0004 # text color contains red.
       
   242     FOREGROUND_WHITE     = 0x0007
       
   243     FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
       
   244     BACKGROUND_BLACK     = 0x0000 # background color black
       
   245     BACKGROUND_BLUE      = 0x0010 # background color contains blue.
       
   246     BACKGROUND_GREEN     = 0x0020 # background color contains green.
       
   247     BACKGROUND_RED       = 0x0040 # background color contains red.
       
   248     BACKGROUND_WHITE     = 0x0070
       
   249     BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
       
   250 
       
   251     SHORT = ctypes.c_short
       
   252     class COORD(ctypes.Structure):
       
   253         _fields_ = [('X', SHORT),
       
   254                     ('Y', SHORT)]
       
   255     class SMALL_RECT(ctypes.Structure):
       
   256         _fields_ = [('Left', SHORT),
       
   257                     ('Top', SHORT),
       
   258                     ('Right', SHORT),
       
   259                     ('Bottom', SHORT)]
       
   260     class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
       
   261         _fields_ = [('dwSize', COORD),
       
   262                     ('dwCursorPosition', COORD),
       
   263                     ('wAttributes', wintypes.WORD),
       
   264                     ('srWindow', SMALL_RECT),
       
   265                     ('dwMaximumWindowSize', COORD)]
       
   266 
       
   267     def GetStdHandle(kind):
       
   268         return ctypes.windll.kernel32.GetStdHandle(kind)
       
   269 
       
   270     SetConsoleTextAttribute = \
       
   271         ctypes.windll.kernel32.SetConsoleTextAttribute
       
   272 
       
   273     def GetConsoleInfo(handle):
       
   274         info = CONSOLE_SCREEN_BUFFER_INFO()
       
   275         ctypes.windll.kernel32.GetConsoleScreenBufferInfo(\
       
   276             handle, ctypes.byref(info))
       
   277         return info
       
   278 
       
   279     def _getdimensions():
       
   280         handle = GetStdHandle(STD_OUTPUT_HANDLE)
       
   281         info = GetConsoleInfo(handle)
       
   282         # Substract one from the width, otherwise the cursor wraps
       
   283         # and the ending \n causes an empty line to display.
       
   284         return info.dwSize.Y, info.dwSize.X - 1
       
   285