terminalwriter: auto-format

This commit is contained in:
Ran Benita 2020-04-29 15:00:58 +03:00
parent 276405a039
commit 3014d9a3f7
1 changed files with 130 additions and 88 deletions

View File

@ -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?