initial commit

This commit is contained in:
klein panic
2024-09-29 01:45:51 -04:00
commit 34102c9fb5
2986 changed files with 634375 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
"""
A thin, practical wrapper around terminal capabilities in Python.
http://pypi.python.org/pypi/blessed
"""
# std imports
import sys as _sys
import platform as _platform
# isort: off
if _platform.system() == 'Windows':
from blessed.win_terminal import Terminal
else:
from blessed.terminal import Terminal # type: ignore
if (3, 0, 0) <= _sys.version_info[:3] < (3, 2, 3):
# Good till 3.2.10
# Python 3.x < 3.2.3 has a bug in which tparm() erroneously takes a string.
raise ImportError('Blessed needs Python 3.2.3 or greater for Python 3 '
'support due to http://bugs.python.org/issue10570.')
__all__ = ('Terminal',)
__version__ = "1.20.0"

View File

@@ -0,0 +1,168 @@
"""Terminal capability builder patterns."""
# std imports
import re
from collections import OrderedDict
__all__ = (
'CAPABILITY_DATABASE',
'CAPABILITIES_RAW_MIXIN',
'CAPABILITIES_ADDITIVES',
'CAPABILITIES_CAUSE_MOVEMENT',
)
CAPABILITY_DATABASE = OrderedDict((
('bell', ('bel', {})),
('carriage_return', ('cr', {})),
('change_scroll_region', ('csr', {'nparams': 2})),
('clear_all_tabs', ('tbc', {})),
('clear_screen', ('clear', {})),
('clr_bol', ('el1', {})),
('clr_eol', ('el', {})),
('clr_eos', ('clear_eos', {})),
('column_address', ('hpa', {'nparams': 1})),
('cursor_address', ('cup', {'nparams': 2, 'match_grouped': True})),
('cursor_down', ('cud1', {})),
('cursor_home', ('home', {})),
('cursor_invisible', ('civis', {})),
('cursor_left', ('cub1', {})),
('cursor_normal', ('cnorm', {})),
('cursor_report', ('u6', {'nparams': 2, 'match_grouped': True})),
('cursor_right', ('cuf1', {})),
('cursor_up', ('cuu1', {})),
('cursor_visible', ('cvvis', {})),
('delete_character', ('dch1', {})),
('delete_line', ('dl1', {})),
('enter_blink_mode', ('blink', {})),
('enter_bold_mode', ('bold', {})),
('enter_dim_mode', ('dim', {})),
('enter_fullscreen', ('smcup', {})),
('enter_standout_mode', ('standout', {})),
('enter_superscript_mode', ('superscript', {})),
('enter_susimpleript_mode', ('susimpleript', {})),
('enter_underline_mode', ('underline', {})),
('erase_chars', ('ech', {'nparams': 1})),
('exit_alt_charset_mode', ('rmacs', {})),
('exit_am_mode', ('rmam', {})),
('exit_attribute_mode', ('sgr0', {})),
('exit_ca_mode', ('rmcup', {})),
('exit_fullscreen', ('rmcup', {})),
('exit_insert_mode', ('rmir', {})),
('exit_standout_mode', ('rmso', {})),
('exit_underline_mode', ('rmul', {})),
('flash_hook', ('hook', {})),
('flash_screen', ('flash', {})),
('insert_line', ('il1', {})),
('keypad_local', ('rmkx', {})),
('keypad_xmit', ('smkx', {})),
('meta_off', ('rmm', {})),
('meta_on', ('smm', {})),
('orig_pair', ('op', {})),
('parm_down_cursor', ('cud', {'nparams': 1})),
('parm_left_cursor', ('cub', {'nparams': 1, 'match_grouped': True})),
('parm_dch', ('dch', {'nparams': 1})),
('parm_delete_line', ('dl', {'nparams': 1})),
('parm_ich', ('ich', {'nparams': 1})),
('parm_index', ('indn', {'nparams': 1})),
('parm_insert_line', ('il', {'nparams': 1})),
('parm_right_cursor', ('cuf', {'nparams': 1, 'match_grouped': True})),
('parm_rindex', ('rin', {'nparams': 1})),
('parm_up_cursor', ('cuu', {'nparams': 1})),
('print_screen', ('mc0', {})),
('prtr_off', ('mc4', {})),
('prtr_on', ('mc5', {})),
('reset_1string', ('r1', {})),
('reset_2string', ('r2', {})),
('reset_3string', ('r3', {})),
('restore_cursor', ('rc', {})),
('row_address', ('vpa', {'nparams': 1})),
('save_cursor', ('sc', {})),
('scroll_forward', ('ind', {})),
('scroll_reverse', ('rev', {})),
('set0_des_seq', ('s0ds', {})),
('set1_des_seq', ('s1ds', {})),
('set2_des_seq', ('s2ds', {})),
('set3_des_seq', ('s3ds', {})),
# this 'color' is deceiving, but often matching, and a better match
# than set_a_attributes1 or set_a_foreground.
('color', ('_foreground_color', {'nparams': 1, 'match_any': True,
'numeric': 1})),
('set_a_foreground', ('color', {'nparams': 1, 'match_any': True,
'numeric': 1})),
('set_a_background', ('on_color', {'nparams': 1, 'match_any': True,
'numeric': 1})),
('set_tab', ('hts', {})),
('tab', ('ht', {})),
('italic', ('sitm', {})),
('no_italic', ('sitm', {})),
))
CAPABILITIES_RAW_MIXIN = {
'bell': re.escape('\a'),
'carriage_return': re.escape('\r'),
'cursor_left': re.escape('\b'),
'cursor_report': re.escape('\x1b') + r'\[(\d+)\;(\d+)R',
'cursor_right': re.escape('\x1b') + r'\[C',
'exit_attribute_mode': re.escape('\x1b') + r'\[m',
'parm_left_cursor': re.escape('\x1b') + r'\[(\d+)D',
'parm_right_cursor': re.escape('\x1b') + r'\[(\d+)C',
'restore_cursor': re.escape(r'\x1b\[u'),
'save_cursor': re.escape(r'\x1b\[s'),
'scroll_forward': re.escape('\n'),
'set0_des_seq': re.escape('\x1b(B'),
'tab': re.escape('\t'),
}
_ANY_NOTESC = '[^' + re.escape('\x1b') + ']*'
CAPABILITIES_ADDITIVES = {
'link': ('link',
re.escape('\x1b') + r'\]8;' + _ANY_NOTESC + ';' +
_ANY_NOTESC + re.escape('\x1b') + '\\\\'),
'color256': ('color', re.escape('\x1b') + r'\[38;5;\d+m'),
'on_color256': ('on_color', re.escape('\x1b') + r'\[48;5;\d+m'),
'color_rgb': ('color_rgb', re.escape('\x1b') + r'\[38;2;\d+;\d+;\d+m'),
'on_color_rgb': ('on_color_rgb', re.escape('\x1b') + r'\[48;2;\d+;\d+;\d+m'),
'shift_in': ('', re.escape('\x0f')),
'shift_out': ('', re.escape('\x0e')),
# sgr(...) outputs strangely, use the basic ANSI/EMCA-48 codes here.
'set_a_attributes1': (
'sgr', re.escape('\x1b') + r'\[\d+m'),
'set_a_attributes2': (
'sgr', re.escape('\x1b') + r'\[\d+\;\d+m'),
'set_a_attributes3': (
'sgr', re.escape('\x1b') + r'\[\d+\;\d+\;\d+m'),
'set_a_attributes4': (
'sgr', re.escape('\x1b') + r'\[\d+\;\d+\;\d+\;\d+m'),
# this helps where xterm's sgr0 includes set0_des_seq, we'd
# rather like to also match this immediate substring.
'sgr0': ('sgr0', re.escape('\x1b') + r'\[m'),
'backspace': ('', re.escape('\b')),
'ascii_tab': ('', re.escape('\t')),
'clr_eol': ('', re.escape('\x1b[K')),
'clr_eol0': ('', re.escape('\x1b[0K')),
'clr_bol': ('', re.escape('\x1b[1K')),
'clr_eosK': ('', re.escape('\x1b[2K')),
}
CAPABILITIES_CAUSE_MOVEMENT = (
'ascii_tab',
'backspace',
'carriage_return',
'clear_screen',
'column_address',
'cursor_address',
'cursor_down',
'cursor_home',
'cursor_left',
'cursor_right',
'cursor_up',
'enter_fullscreen',
'exit_fullscreen',
'parm_down_cursor',
'parm_left_cursor',
'parm_right_cursor',
'parm_up_cursor',
'restore_cursor',
'row_address',
'scroll_forward',
'tab',
)

View File

@@ -0,0 +1,7 @@
# std imports
from typing import Any, Dict, Tuple, OrderedDict
CAPABILITY_DATABASE: OrderedDict[str, Tuple[str, Dict[str, Any]]]
CAPABILITIES_RAW_MIXIN: Dict[str, str]
CAPABILITIES_ADDITIVES: Dict[str, Tuple[str, str]]
CAPABILITIES_CAUSE_MOVEMENT: Tuple[str, ...]

View File

@@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
"""
Sub-module providing color functions.
References,
- https://en.wikipedia.org/wiki/Color_difference
- http://www.easyrgb.com/en/math.php
- Measuring Colour by R.W.G. Hunt and M.R. Pointer
"""
# std imports
from math import cos, exp, sin, sqrt, atan2
# isort: off
try:
from functools import lru_cache
except ImportError:
# lru_cache was added in Python 3.2
from backports.functools_lru_cache import lru_cache
def rgb_to_xyz(red, green, blue):
"""
Convert standard RGB color to XYZ color.
:arg int red: RGB value of Red.
:arg int green: RGB value of Green.
:arg int blue: RGB value of Blue.
:returns: Tuple (X, Y, Z) representing XYZ color
:rtype: tuple
D65/2° standard illuminant
"""
rgb = []
for val in red, green, blue:
val /= 255.0
if val > 0.04045:
val = pow((val + 0.055) / 1.055, 2.4)
else:
val /= 12.92
val *= 100
rgb.append(val)
red, green, blue = rgb # pylint: disable=unbalanced-tuple-unpacking
x_val = red * 0.4124 + green * 0.3576 + blue * 0.1805
y_val = red * 0.2126 + green * 0.7152 + blue * 0.0722
z_val = red * 0.0193 + green * 0.1192 + blue * 0.9505
return x_val, y_val, z_val
def xyz_to_lab(x_val, y_val, z_val):
"""
Convert XYZ color to CIE-Lab color.
:arg float x_val: XYZ value of X.
:arg float y_val: XYZ value of Y.
:arg float z_val: XYZ value of Z.
:returns: Tuple (L, a, b) representing CIE-Lab color
:rtype: tuple
D65/2° standard illuminant
"""
xyz = []
for val, ref in (x_val, 95.047), (y_val, 100.0), (z_val, 108.883):
val /= ref
val = pow(val, 1 / 3.0) if val > 0.008856 else 7.787 * val + 16 / 116.0
xyz.append(val)
x_val, y_val, z_val = xyz # pylint: disable=unbalanced-tuple-unpacking
cie_l = 116 * y_val - 16
cie_a = 500 * (x_val - y_val)
cie_b = 200 * (y_val - z_val)
return cie_l, cie_a, cie_b
@lru_cache(maxsize=256)
def rgb_to_lab(red, green, blue):
"""
Convert RGB color to CIE-Lab color.
:arg int red: RGB value of Red.
:arg int green: RGB value of Green.
:arg int blue: RGB value of Blue.
:returns: Tuple (L, a, b) representing CIE-Lab color
:rtype: tuple
D65/2° standard illuminant
"""
return xyz_to_lab(*rgb_to_xyz(red, green, blue))
def dist_rgb(rgb1, rgb2):
"""
Determine distance between two rgb colors.
:arg tuple rgb1: RGB color definition
:arg tuple rgb2: RGB color definition
:returns: Square of the distance between provided colors
:rtype: float
This works by treating RGB colors as coordinates in three dimensional
space and finding the closest point within the configured color range
using the formula::
d^2 = (r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2
For efficiency, the square of the distance is returned
which is sufficient for comparisons
"""
return sum(pow(rgb1[idx] - rgb2[idx], 2) for idx in (0, 1, 2))
def dist_rgb_weighted(rgb1, rgb2):
"""
Determine the weighted distance between two rgb colors.
:arg tuple rgb1: RGB color definition
:arg tuple rgb2: RGB color definition
:returns: Square of the distance between provided colors
:rtype: float
Similar to a standard distance formula, the values are weighted
to approximate human perception of color differences
For efficiency, the square of the distance is returned
which is sufficient for comparisons
"""
red_mean = (rgb1[0] + rgb2[0]) / 2.0
return ((2 + red_mean / 256) * pow(rgb1[0] - rgb2[0], 2) +
4 * pow(rgb1[1] - rgb2[1], 2) +
(2 + (255 - red_mean) / 256) * pow(rgb1[2] - rgb2[2], 2))
def dist_cie76(rgb1, rgb2):
"""
Determine distance between two rgb colors using the CIE94 algorithm.
:arg tuple rgb1: RGB color definition
:arg tuple rgb2: RGB color definition
:returns: Square of the distance between provided colors
:rtype: float
For efficiency, the square of the distance is returned
which is sufficient for comparisons
"""
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
return pow(l_1 - l_2, 2) + pow(a_1 - a_2, 2) + pow(b_1 - b_2, 2)
def dist_cie94(rgb1, rgb2):
# pylint: disable=too-many-locals
"""
Determine distance between two rgb colors using the CIE94 algorithm.
:arg tuple rgb1: RGB color definition
:arg tuple rgb2: RGB color definition
:returns: Square of the distance between provided colors
:rtype: float
For efficiency, the square of the distance is returned
which is sufficient for comparisons
"""
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
s_l = k_l = k_c = k_h = 1
k_1 = 0.045
k_2 = 0.015
delta_l = l_1 - l_2
delta_a = a_1 - a_2
delta_b = b_1 - b_2
c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
delta_c = c_1 - c_2
delta_h = sqrt(delta_a ** 2 + delta_b ** 2 + delta_c ** 2)
s_c = 1 + k_1 * c_1
s_h = 1 + k_2 * c_1
return ((delta_l / (k_l * s_l)) ** 2 + # pylint: disable=superfluous-parens
(delta_c / (k_c * s_c)) ** 2 +
(delta_h / (k_h * s_h)) ** 2)
def dist_cie2000(rgb1, rgb2):
# pylint: disable=too-many-locals
"""
Determine distance between two rgb colors using the CIE2000 algorithm.
:arg tuple rgb1: RGB color definition
:arg tuple rgb2: RGB color definition
:returns: Square of the distance between provided colors
:rtype: float
For efficiency, the square of the distance is returned
which is sufficient for comparisons
"""
s_l = k_l = k_c = k_h = 1
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
delta_l = l_2 - l_1
l_mean = (l_1 + l_2) / 2
c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
c_mean = (c_1 + c_2) / 2
delta_c = c_1 - c_2
g_x = sqrt(c_mean ** 7 / (c_mean ** 7 + 25 ** 7))
h_1 = atan2(b_1, a_1 + (a_1 / 2) * (1 - g_x)) % 360
h_2 = atan2(b_2, a_2 + (a_2 / 2) * (1 - g_x)) % 360
if 0 in (c_1, c_2):
delta_h_prime = 0
h_mean = h_1 + h_2
else:
delta_h_prime = h_2 - h_1
if abs(delta_h_prime) <= 180:
h_mean = (h_1 + h_2) / 2
else:
if h_2 <= h_1:
delta_h_prime += 360
else:
delta_h_prime -= 360
h_mean = (h_1 + h_2 + 360) / 2 if h_1 + h_2 < 360 else (h_1 + h_2 - 360) / 2
delta_h = 2 * sqrt(c_1 * c_2) * sin(delta_h_prime / 2)
t_x = (1 -
0.17 * cos(h_mean - 30) +
0.24 * cos(2 * h_mean) +
0.32 * cos(3 * h_mean + 6) -
0.20 * cos(4 * h_mean - 63))
s_l = 1 + (0.015 * (l_mean - 50) ** 2) / sqrt(20 + (l_mean - 50) ** 2)
s_c = 1 + 0.045 * c_mean
s_h = 1 + 0.015 * c_mean * t_x
r_t = -2 * g_x * sin(abs(60 * exp(-1 * abs((delta_h - 275) / 25) ** 2)))
delta_l = delta_l / (k_l * s_l)
delta_c = delta_c / (k_c * s_c)
delta_h = delta_h / (k_h * s_h)
return delta_l ** 2 + delta_c ** 2 + delta_h ** 2 + r_t * delta_c * delta_h
COLOR_DISTANCE_ALGORITHMS = {'rgb': dist_rgb,
'rgb-weighted': dist_rgb_weighted,
'cie76': dist_cie76,
'cie94': dist_cie94,
'cie2000': dist_cie2000}

View File

@@ -0,0 +1,17 @@
# std imports
from typing import Dict, Tuple, Callable
_RGB = Tuple[int, int, int]
def rgb_to_xyz(red: int, green: int, blue: int) -> Tuple[float, float, float]: ...
def xyz_to_lab(
x_val: float, y_val: float, z_val: float
) -> Tuple[float, float, float]: ...
def rgb_to_lab(red: int, green: int, blue: int) -> Tuple[float, float, float]: ...
def dist_rgb(rgb1: _RGB, rgb2: _RGB) -> float: ...
def dist_rgb_weighted(rgb1: _RGB, rgb2: _RGB) -> float: ...
def dist_cie76(rgb1: _RGB, rgb2: _RGB) -> float: ...
def dist_cie94(rgb1: _RGB, rgb2: _RGB) -> float: ...
def dist_cie2000(rgb1: _RGB, rgb2: _RGB) -> float: ...
COLOR_DISTANCE_ALGORITHMS: Dict[str, Callable[[_RGB, _RGB], float]]

View File

@@ -0,0 +1,973 @@
"""
Color reference data.
References,
- https://github.com/freedesktop/xorg-rgb/blob/master/rgb.txt
- https://github.com/ThomasDickey/xterm-snapshots/blob/master/256colres.h
- https://github.com/ThomasDickey/xterm-snapshots/blob/master/XTerm-col.ad
- https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
- https://gist.github.com/XVilka/8346728
- https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
- http://jdebp.uk/Softwares/nosh/guide/TerminalCapabilities.html
"""
# std imports
import collections
__all__ = (
'CGA_COLORS',
'RGBColor',
'RGB_256TABLE',
'X11_COLORNAMES_TO_RGB',
)
CGA_COLORS = {'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'}
class RGBColor(collections.namedtuple("RGBColor", ["red", "green", "blue"])):
"""Named tuple for an RGB color definition."""
def __str__(self):
return '#{0:02x}{1:02x}{2:02x}'.format(*self)
#: X11 Color names to (XTerm-defined) RGB values from xorg-rgb/rgb.txt
X11_COLORNAMES_TO_RGB = {
'aliceblue': RGBColor(240, 248, 255),
'antiquewhite': RGBColor(250, 235, 215),
'antiquewhite1': RGBColor(255, 239, 219),
'antiquewhite2': RGBColor(238, 223, 204),
'antiquewhite3': RGBColor(205, 192, 176),
'antiquewhite4': RGBColor(139, 131, 120),
'aqua': RGBColor(0, 255, 255),
'aquamarine': RGBColor(127, 255, 212),
'aquamarine1': RGBColor(127, 255, 212),
'aquamarine2': RGBColor(118, 238, 198),
'aquamarine3': RGBColor(102, 205, 170),
'aquamarine4': RGBColor(69, 139, 116),
'azure': RGBColor(240, 255, 255),
'azure1': RGBColor(240, 255, 255),
'azure2': RGBColor(224, 238, 238),
'azure3': RGBColor(193, 205, 205),
'azure4': RGBColor(131, 139, 139),
'beige': RGBColor(245, 245, 220),
'bisque': RGBColor(255, 228, 196),
'bisque1': RGBColor(255, 228, 196),
'bisque2': RGBColor(238, 213, 183),
'bisque3': RGBColor(205, 183, 158),
'bisque4': RGBColor(139, 125, 107),
'black': RGBColor(0, 0, 0),
'blanchedalmond': RGBColor(255, 235, 205),
'blue': RGBColor(0, 0, 255),
'blue1': RGBColor(0, 0, 255),
'blue2': RGBColor(0, 0, 238),
'blue3': RGBColor(0, 0, 205),
'blue4': RGBColor(0, 0, 139),
'blueviolet': RGBColor(138, 43, 226),
'brown': RGBColor(165, 42, 42),
'brown1': RGBColor(255, 64, 64),
'brown2': RGBColor(238, 59, 59),
'brown3': RGBColor(205, 51, 51),
'brown4': RGBColor(139, 35, 35),
'burlywood': RGBColor(222, 184, 135),
'burlywood1': RGBColor(255, 211, 155),
'burlywood2': RGBColor(238, 197, 145),
'burlywood3': RGBColor(205, 170, 125),
'burlywood4': RGBColor(139, 115, 85),
'cadetblue': RGBColor(95, 158, 160),
'cadetblue1': RGBColor(152, 245, 255),
'cadetblue2': RGBColor(142, 229, 238),
'cadetblue3': RGBColor(122, 197, 205),
'cadetblue4': RGBColor(83, 134, 139),
'chartreuse': RGBColor(127, 255, 0),
'chartreuse1': RGBColor(127, 255, 0),
'chartreuse2': RGBColor(118, 238, 0),
'chartreuse3': RGBColor(102, 205, 0),
'chartreuse4': RGBColor(69, 139, 0),
'chocolate': RGBColor(210, 105, 30),
'chocolate1': RGBColor(255, 127, 36),
'chocolate2': RGBColor(238, 118, 33),
'chocolate3': RGBColor(205, 102, 29),
'chocolate4': RGBColor(139, 69, 19),
'coral': RGBColor(255, 127, 80),
'coral1': RGBColor(255, 114, 86),
'coral2': RGBColor(238, 106, 80),
'coral3': RGBColor(205, 91, 69),
'coral4': RGBColor(139, 62, 47),
'cornflowerblue': RGBColor(100, 149, 237),
'cornsilk': RGBColor(255, 248, 220),
'cornsilk1': RGBColor(255, 248, 220),
'cornsilk2': RGBColor(238, 232, 205),
'cornsilk3': RGBColor(205, 200, 177),
'cornsilk4': RGBColor(139, 136, 120),
'crimson': RGBColor(220, 20, 60),
'cyan': RGBColor(0, 255, 255),
'cyan1': RGBColor(0, 255, 255),
'cyan2': RGBColor(0, 238, 238),
'cyan3': RGBColor(0, 205, 205),
'cyan4': RGBColor(0, 139, 139),
'darkblue': RGBColor(0, 0, 139),
'darkcyan': RGBColor(0, 139, 139),
'darkgoldenrod': RGBColor(184, 134, 11),
'darkgoldenrod1': RGBColor(255, 185, 15),
'darkgoldenrod2': RGBColor(238, 173, 14),
'darkgoldenrod3': RGBColor(205, 149, 12),
'darkgoldenrod4': RGBColor(139, 101, 8),
'darkgray': RGBColor(169, 169, 169),
'darkgreen': RGBColor(0, 100, 0),
'darkgrey': RGBColor(169, 169, 169),
'darkkhaki': RGBColor(189, 183, 107),
'darkmagenta': RGBColor(139, 0, 139),
'darkolivegreen': RGBColor(85, 107, 47),
'darkolivegreen1': RGBColor(202, 255, 112),
'darkolivegreen2': RGBColor(188, 238, 104),
'darkolivegreen3': RGBColor(162, 205, 90),
'darkolivegreen4': RGBColor(110, 139, 61),
'darkorange': RGBColor(255, 140, 0),
'darkorange1': RGBColor(255, 127, 0),
'darkorange2': RGBColor(238, 118, 0),
'darkorange3': RGBColor(205, 102, 0),
'darkorange4': RGBColor(139, 69, 0),
'darkorchid': RGBColor(153, 50, 204),
'darkorchid1': RGBColor(191, 62, 255),
'darkorchid2': RGBColor(178, 58, 238),
'darkorchid3': RGBColor(154, 50, 205),
'darkorchid4': RGBColor(104, 34, 139),
'darkred': RGBColor(139, 0, 0),
'darksalmon': RGBColor(233, 150, 122),
'darkseagreen': RGBColor(143, 188, 143),
'darkseagreen1': RGBColor(193, 255, 193),
'darkseagreen2': RGBColor(180, 238, 180),
'darkseagreen3': RGBColor(155, 205, 155),
'darkseagreen4': RGBColor(105, 139, 105),
'darkslateblue': RGBColor(72, 61, 139),
'darkslategray': RGBColor(47, 79, 79),
'darkslategray1': RGBColor(151, 255, 255),
'darkslategray2': RGBColor(141, 238, 238),
'darkslategray3': RGBColor(121, 205, 205),
'darkslategray4': RGBColor(82, 139, 139),
'darkslategrey': RGBColor(47, 79, 79),
'darkturquoise': RGBColor(0, 206, 209),
'darkviolet': RGBColor(148, 0, 211),
'deeppink': RGBColor(255, 20, 147),
'deeppink1': RGBColor(255, 20, 147),
'deeppink2': RGBColor(238, 18, 137),
'deeppink3': RGBColor(205, 16, 118),
'deeppink4': RGBColor(139, 10, 80),
'deepskyblue': RGBColor(0, 191, 255),
'deepskyblue1': RGBColor(0, 191, 255),
'deepskyblue2': RGBColor(0, 178, 238),
'deepskyblue3': RGBColor(0, 154, 205),
'deepskyblue4': RGBColor(0, 104, 139),
'dimgray': RGBColor(105, 105, 105),
'dimgrey': RGBColor(105, 105, 105),
'dodgerblue': RGBColor(30, 144, 255),
'dodgerblue1': RGBColor(30, 144, 255),
'dodgerblue2': RGBColor(28, 134, 238),
'dodgerblue3': RGBColor(24, 116, 205),
'dodgerblue4': RGBColor(16, 78, 139),
'firebrick': RGBColor(178, 34, 34),
'firebrick1': RGBColor(255, 48, 48),
'firebrick2': RGBColor(238, 44, 44),
'firebrick3': RGBColor(205, 38, 38),
'firebrick4': RGBColor(139, 26, 26),
'floralwhite': RGBColor(255, 250, 240),
'forestgreen': RGBColor(34, 139, 34),
'fuchsia': RGBColor(255, 0, 255),
'gainsboro': RGBColor(220, 220, 220),
'ghostwhite': RGBColor(248, 248, 255),
'gold': RGBColor(255, 215, 0),
'gold1': RGBColor(255, 215, 0),
'gold2': RGBColor(238, 201, 0),
'gold3': RGBColor(205, 173, 0),
'gold4': RGBColor(139, 117, 0),
'goldenrod': RGBColor(218, 165, 32),
'goldenrod1': RGBColor(255, 193, 37),
'goldenrod2': RGBColor(238, 180, 34),
'goldenrod3': RGBColor(205, 155, 29),
'goldenrod4': RGBColor(139, 105, 20),
'gray': RGBColor(190, 190, 190),
'gray0': RGBColor(0, 0, 0),
'gray1': RGBColor(3, 3, 3),
'gray10': RGBColor(26, 26, 26),
'gray100': RGBColor(255, 255, 255),
'gray11': RGBColor(28, 28, 28),
'gray12': RGBColor(31, 31, 31),
'gray13': RGBColor(33, 33, 33),
'gray14': RGBColor(36, 36, 36),
'gray15': RGBColor(38, 38, 38),
'gray16': RGBColor(41, 41, 41),
'gray17': RGBColor(43, 43, 43),
'gray18': RGBColor(46, 46, 46),
'gray19': RGBColor(48, 48, 48),
'gray2': RGBColor(5, 5, 5),
'gray20': RGBColor(51, 51, 51),
'gray21': RGBColor(54, 54, 54),
'gray22': RGBColor(56, 56, 56),
'gray23': RGBColor(59, 59, 59),
'gray24': RGBColor(61, 61, 61),
'gray25': RGBColor(64, 64, 64),
'gray26': RGBColor(66, 66, 66),
'gray27': RGBColor(69, 69, 69),
'gray28': RGBColor(71, 71, 71),
'gray29': RGBColor(74, 74, 74),
'gray3': RGBColor(8, 8, 8),
'gray30': RGBColor(77, 77, 77),
'gray31': RGBColor(79, 79, 79),
'gray32': RGBColor(82, 82, 82),
'gray33': RGBColor(84, 84, 84),
'gray34': RGBColor(87, 87, 87),
'gray35': RGBColor(89, 89, 89),
'gray36': RGBColor(92, 92, 92),
'gray37': RGBColor(94, 94, 94),
'gray38': RGBColor(97, 97, 97),
'gray39': RGBColor(99, 99, 99),
'gray4': RGBColor(10, 10, 10),
'gray40': RGBColor(102, 102, 102),
'gray41': RGBColor(105, 105, 105),
'gray42': RGBColor(107, 107, 107),
'gray43': RGBColor(110, 110, 110),
'gray44': RGBColor(112, 112, 112),
'gray45': RGBColor(115, 115, 115),
'gray46': RGBColor(117, 117, 117),
'gray47': RGBColor(120, 120, 120),
'gray48': RGBColor(122, 122, 122),
'gray49': RGBColor(125, 125, 125),
'gray5': RGBColor(13, 13, 13),
'gray50': RGBColor(127, 127, 127),
'gray51': RGBColor(130, 130, 130),
'gray52': RGBColor(133, 133, 133),
'gray53': RGBColor(135, 135, 135),
'gray54': RGBColor(138, 138, 138),
'gray55': RGBColor(140, 140, 140),
'gray56': RGBColor(143, 143, 143),
'gray57': RGBColor(145, 145, 145),
'gray58': RGBColor(148, 148, 148),
'gray59': RGBColor(150, 150, 150),
'gray6': RGBColor(15, 15, 15),
'gray60': RGBColor(153, 153, 153),
'gray61': RGBColor(156, 156, 156),
'gray62': RGBColor(158, 158, 158),
'gray63': RGBColor(161, 161, 161),
'gray64': RGBColor(163, 163, 163),
'gray65': RGBColor(166, 166, 166),
'gray66': RGBColor(168, 168, 168),
'gray67': RGBColor(171, 171, 171),
'gray68': RGBColor(173, 173, 173),
'gray69': RGBColor(176, 176, 176),
'gray7': RGBColor(18, 18, 18),
'gray70': RGBColor(179, 179, 179),
'gray71': RGBColor(181, 181, 181),
'gray72': RGBColor(184, 184, 184),
'gray73': RGBColor(186, 186, 186),
'gray74': RGBColor(189, 189, 189),
'gray75': RGBColor(191, 191, 191),
'gray76': RGBColor(194, 194, 194),
'gray77': RGBColor(196, 196, 196),
'gray78': RGBColor(199, 199, 199),
'gray79': RGBColor(201, 201, 201),
'gray8': RGBColor(20, 20, 20),
'gray80': RGBColor(204, 204, 204),
'gray81': RGBColor(207, 207, 207),
'gray82': RGBColor(209, 209, 209),
'gray83': RGBColor(212, 212, 212),
'gray84': RGBColor(214, 214, 214),
'gray85': RGBColor(217, 217, 217),
'gray86': RGBColor(219, 219, 219),
'gray87': RGBColor(222, 222, 222),
'gray88': RGBColor(224, 224, 224),
'gray89': RGBColor(227, 227, 227),
'gray9': RGBColor(23, 23, 23),
'gray90': RGBColor(229, 229, 229),
'gray91': RGBColor(232, 232, 232),
'gray92': RGBColor(235, 235, 235),
'gray93': RGBColor(237, 237, 237),
'gray94': RGBColor(240, 240, 240),
'gray95': RGBColor(242, 242, 242),
'gray96': RGBColor(245, 245, 245),
'gray97': RGBColor(247, 247, 247),
'gray98': RGBColor(250, 250, 250),
'gray99': RGBColor(252, 252, 252),
'green': RGBColor(0, 255, 0),
'green1': RGBColor(0, 255, 0),
'green2': RGBColor(0, 238, 0),
'green3': RGBColor(0, 205, 0),
'green4': RGBColor(0, 139, 0),
'greenyellow': RGBColor(173, 255, 47),
'grey': RGBColor(190, 190, 190),
'grey0': RGBColor(0, 0, 0),
'grey1': RGBColor(3, 3, 3),
'grey10': RGBColor(26, 26, 26),
'grey100': RGBColor(255, 255, 255),
'grey11': RGBColor(28, 28, 28),
'grey12': RGBColor(31, 31, 31),
'grey13': RGBColor(33, 33, 33),
'grey14': RGBColor(36, 36, 36),
'grey15': RGBColor(38, 38, 38),
'grey16': RGBColor(41, 41, 41),
'grey17': RGBColor(43, 43, 43),
'grey18': RGBColor(46, 46, 46),
'grey19': RGBColor(48, 48, 48),
'grey2': RGBColor(5, 5, 5),
'grey20': RGBColor(51, 51, 51),
'grey21': RGBColor(54, 54, 54),
'grey22': RGBColor(56, 56, 56),
'grey23': RGBColor(59, 59, 59),
'grey24': RGBColor(61, 61, 61),
'grey25': RGBColor(64, 64, 64),
'grey26': RGBColor(66, 66, 66),
'grey27': RGBColor(69, 69, 69),
'grey28': RGBColor(71, 71, 71),
'grey29': RGBColor(74, 74, 74),
'grey3': RGBColor(8, 8, 8),
'grey30': RGBColor(77, 77, 77),
'grey31': RGBColor(79, 79, 79),
'grey32': RGBColor(82, 82, 82),
'grey33': RGBColor(84, 84, 84),
'grey34': RGBColor(87, 87, 87),
'grey35': RGBColor(89, 89, 89),
'grey36': RGBColor(92, 92, 92),
'grey37': RGBColor(94, 94, 94),
'grey38': RGBColor(97, 97, 97),
'grey39': RGBColor(99, 99, 99),
'grey4': RGBColor(10, 10, 10),
'grey40': RGBColor(102, 102, 102),
'grey41': RGBColor(105, 105, 105),
'grey42': RGBColor(107, 107, 107),
'grey43': RGBColor(110, 110, 110),
'grey44': RGBColor(112, 112, 112),
'grey45': RGBColor(115, 115, 115),
'grey46': RGBColor(117, 117, 117),
'grey47': RGBColor(120, 120, 120),
'grey48': RGBColor(122, 122, 122),
'grey49': RGBColor(125, 125, 125),
'grey5': RGBColor(13, 13, 13),
'grey50': RGBColor(127, 127, 127),
'grey51': RGBColor(130, 130, 130),
'grey52': RGBColor(133, 133, 133),
'grey53': RGBColor(135, 135, 135),
'grey54': RGBColor(138, 138, 138),
'grey55': RGBColor(140, 140, 140),
'grey56': RGBColor(143, 143, 143),
'grey57': RGBColor(145, 145, 145),
'grey58': RGBColor(148, 148, 148),
'grey59': RGBColor(150, 150, 150),
'grey6': RGBColor(15, 15, 15),
'grey60': RGBColor(153, 153, 153),
'grey61': RGBColor(156, 156, 156),
'grey62': RGBColor(158, 158, 158),
'grey63': RGBColor(161, 161, 161),
'grey64': RGBColor(163, 163, 163),
'grey65': RGBColor(166, 166, 166),
'grey66': RGBColor(168, 168, 168),
'grey67': RGBColor(171, 171, 171),
'grey68': RGBColor(173, 173, 173),
'grey69': RGBColor(176, 176, 176),
'grey7': RGBColor(18, 18, 18),
'grey70': RGBColor(179, 179, 179),
'grey71': RGBColor(181, 181, 181),
'grey72': RGBColor(184, 184, 184),
'grey73': RGBColor(186, 186, 186),
'grey74': RGBColor(189, 189, 189),
'grey75': RGBColor(191, 191, 191),
'grey76': RGBColor(194, 194, 194),
'grey77': RGBColor(196, 196, 196),
'grey78': RGBColor(199, 199, 199),
'grey79': RGBColor(201, 201, 201),
'grey8': RGBColor(20, 20, 20),
'grey80': RGBColor(204, 204, 204),
'grey81': RGBColor(207, 207, 207),
'grey82': RGBColor(209, 209, 209),
'grey83': RGBColor(212, 212, 212),
'grey84': RGBColor(214, 214, 214),
'grey85': RGBColor(217, 217, 217),
'grey86': RGBColor(219, 219, 219),
'grey87': RGBColor(222, 222, 222),
'grey88': RGBColor(224, 224, 224),
'grey89': RGBColor(227, 227, 227),
'grey9': RGBColor(23, 23, 23),
'grey90': RGBColor(229, 229, 229),
'grey91': RGBColor(232, 232, 232),
'grey92': RGBColor(235, 235, 235),
'grey93': RGBColor(237, 237, 237),
'grey94': RGBColor(240, 240, 240),
'grey95': RGBColor(242, 242, 242),
'grey96': RGBColor(245, 245, 245),
'grey97': RGBColor(247, 247, 247),
'grey98': RGBColor(250, 250, 250),
'grey99': RGBColor(252, 252, 252),
'honeydew': RGBColor(240, 255, 240),
'honeydew1': RGBColor(240, 255, 240),
'honeydew2': RGBColor(224, 238, 224),
'honeydew3': RGBColor(193, 205, 193),
'honeydew4': RGBColor(131, 139, 131),
'hotpink': RGBColor(255, 105, 180),
'hotpink1': RGBColor(255, 110, 180),
'hotpink2': RGBColor(238, 106, 167),
'hotpink3': RGBColor(205, 96, 144),
'hotpink4': RGBColor(139, 58, 98),
'indianred': RGBColor(205, 92, 92),
'indianred1': RGBColor(255, 106, 106),
'indianred2': RGBColor(238, 99, 99),
'indianred3': RGBColor(205, 85, 85),
'indianred4': RGBColor(139, 58, 58),
'indigo': RGBColor(75, 0, 130),
'ivory': RGBColor(255, 255, 240),
'ivory1': RGBColor(255, 255, 240),
'ivory2': RGBColor(238, 238, 224),
'ivory3': RGBColor(205, 205, 193),
'ivory4': RGBColor(139, 139, 131),
'khaki': RGBColor(240, 230, 140),
'khaki1': RGBColor(255, 246, 143),
'khaki2': RGBColor(238, 230, 133),
'khaki3': RGBColor(205, 198, 115),
'khaki4': RGBColor(139, 134, 78),
'lavender': RGBColor(230, 230, 250),
'lavenderblush': RGBColor(255, 240, 245),
'lavenderblush1': RGBColor(255, 240, 245),
'lavenderblush2': RGBColor(238, 224, 229),
'lavenderblush3': RGBColor(205, 193, 197),
'lavenderblush4': RGBColor(139, 131, 134),
'lawngreen': RGBColor(124, 252, 0),
'lemonchiffon': RGBColor(255, 250, 205),
'lemonchiffon1': RGBColor(255, 250, 205),
'lemonchiffon2': RGBColor(238, 233, 191),
'lemonchiffon3': RGBColor(205, 201, 165),
'lemonchiffon4': RGBColor(139, 137, 112),
'lightblue': RGBColor(173, 216, 230),
'lightblue1': RGBColor(191, 239, 255),
'lightblue2': RGBColor(178, 223, 238),
'lightblue3': RGBColor(154, 192, 205),
'lightblue4': RGBColor(104, 131, 139),
'lightcoral': RGBColor(240, 128, 128),
'lightcyan': RGBColor(224, 255, 255),
'lightcyan1': RGBColor(224, 255, 255),
'lightcyan2': RGBColor(209, 238, 238),
'lightcyan3': RGBColor(180, 205, 205),
'lightcyan4': RGBColor(122, 139, 139),
'lightgoldenrod': RGBColor(238, 221, 130),
'lightgoldenrod1': RGBColor(255, 236, 139),
'lightgoldenrod2': RGBColor(238, 220, 130),
'lightgoldenrod3': RGBColor(205, 190, 112),
'lightgoldenrod4': RGBColor(139, 129, 76),
'lightgoldenrodyellow': RGBColor(250, 250, 210),
'lightgray': RGBColor(211, 211, 211),
'lightgreen': RGBColor(144, 238, 144),
'lightgrey': RGBColor(211, 211, 211),
'lightpink': RGBColor(255, 182, 193),
'lightpink1': RGBColor(255, 174, 185),
'lightpink2': RGBColor(238, 162, 173),
'lightpink3': RGBColor(205, 140, 149),
'lightpink4': RGBColor(139, 95, 101),
'lightsalmon': RGBColor(255, 160, 122),
'lightsalmon1': RGBColor(255, 160, 122),
'lightsalmon2': RGBColor(238, 149, 114),
'lightsalmon3': RGBColor(205, 129, 98),
'lightsalmon4': RGBColor(139, 87, 66),
'lightseagreen': RGBColor(32, 178, 170),
'lightskyblue': RGBColor(135, 206, 250),
'lightskyblue1': RGBColor(176, 226, 255),
'lightskyblue2': RGBColor(164, 211, 238),
'lightskyblue3': RGBColor(141, 182, 205),
'lightskyblue4': RGBColor(96, 123, 139),
'lightslateblue': RGBColor(132, 112, 255),
'lightslategray': RGBColor(119, 136, 153),
'lightslategrey': RGBColor(119, 136, 153),
'lightsteelblue': RGBColor(176, 196, 222),
'lightsteelblue1': RGBColor(202, 225, 255),
'lightsteelblue2': RGBColor(188, 210, 238),
'lightsteelblue3': RGBColor(162, 181, 205),
'lightsteelblue4': RGBColor(110, 123, 139),
'lightyellow': RGBColor(255, 255, 224),
'lightyellow1': RGBColor(255, 255, 224),
'lightyellow2': RGBColor(238, 238, 209),
'lightyellow3': RGBColor(205, 205, 180),
'lightyellow4': RGBColor(139, 139, 122),
'lime': RGBColor(0, 255, 0),
'limegreen': RGBColor(50, 205, 50),
'linen': RGBColor(250, 240, 230),
'magenta': RGBColor(255, 0, 255),
'magenta1': RGBColor(255, 0, 255),
'magenta2': RGBColor(238, 0, 238),
'magenta3': RGBColor(205, 0, 205),
'magenta4': RGBColor(139, 0, 139),
'maroon': RGBColor(176, 48, 96),
'maroon1': RGBColor(255, 52, 179),
'maroon2': RGBColor(238, 48, 167),
'maroon3': RGBColor(205, 41, 144),
'maroon4': RGBColor(139, 28, 98),
'mediumaquamarine': RGBColor(102, 205, 170),
'mediumblue': RGBColor(0, 0, 205),
'mediumorchid': RGBColor(186, 85, 211),
'mediumorchid1': RGBColor(224, 102, 255),
'mediumorchid2': RGBColor(209, 95, 238),
'mediumorchid3': RGBColor(180, 82, 205),
'mediumorchid4': RGBColor(122, 55, 139),
'mediumpurple': RGBColor(147, 112, 219),
'mediumpurple1': RGBColor(171, 130, 255),
'mediumpurple2': RGBColor(159, 121, 238),
'mediumpurple3': RGBColor(137, 104, 205),
'mediumpurple4': RGBColor(93, 71, 139),
'mediumseagreen': RGBColor(60, 179, 113),
'mediumslateblue': RGBColor(123, 104, 238),
'mediumspringgreen': RGBColor(0, 250, 154),
'mediumturquoise': RGBColor(72, 209, 204),
'mediumvioletred': RGBColor(199, 21, 133),
'midnightblue': RGBColor(25, 25, 112),
'mintcream': RGBColor(245, 255, 250),
'mistyrose': RGBColor(255, 228, 225),
'mistyrose1': RGBColor(255, 228, 225),
'mistyrose2': RGBColor(238, 213, 210),
'mistyrose3': RGBColor(205, 183, 181),
'mistyrose4': RGBColor(139, 125, 123),
'moccasin': RGBColor(255, 228, 181),
'navajowhite': RGBColor(255, 222, 173),
'navajowhite1': RGBColor(255, 222, 173),
'navajowhite2': RGBColor(238, 207, 161),
'navajowhite3': RGBColor(205, 179, 139),
'navajowhite4': RGBColor(139, 121, 94),
'navy': RGBColor(0, 0, 128),
'navyblue': RGBColor(0, 0, 128),
'oldlace': RGBColor(253, 245, 230),
'olive': RGBColor(128, 128, 0),
'olivedrab': RGBColor(107, 142, 35),
'olivedrab1': RGBColor(192, 255, 62),
'olivedrab2': RGBColor(179, 238, 58),
'olivedrab3': RGBColor(154, 205, 50),
'olivedrab4': RGBColor(105, 139, 34),
'orange': RGBColor(255, 165, 0),
'orange1': RGBColor(255, 165, 0),
'orange2': RGBColor(238, 154, 0),
'orange3': RGBColor(205, 133, 0),
'orange4': RGBColor(139, 90, 0),
'orangered': RGBColor(255, 69, 0),
'orangered1': RGBColor(255, 69, 0),
'orangered2': RGBColor(238, 64, 0),
'orangered3': RGBColor(205, 55, 0),
'orangered4': RGBColor(139, 37, 0),
'orchid': RGBColor(218, 112, 214),
'orchid1': RGBColor(255, 131, 250),
'orchid2': RGBColor(238, 122, 233),
'orchid3': RGBColor(205, 105, 201),
'orchid4': RGBColor(139, 71, 137),
'palegoldenrod': RGBColor(238, 232, 170),
'palegreen': RGBColor(152, 251, 152),
'palegreen1': RGBColor(154, 255, 154),
'palegreen2': RGBColor(144, 238, 144),
'palegreen3': RGBColor(124, 205, 124),
'palegreen4': RGBColor(84, 139, 84),
'paleturquoise': RGBColor(175, 238, 238),
'paleturquoise1': RGBColor(187, 255, 255),
'paleturquoise2': RGBColor(174, 238, 238),
'paleturquoise3': RGBColor(150, 205, 205),
'paleturquoise4': RGBColor(102, 139, 139),
'palevioletred': RGBColor(219, 112, 147),
'palevioletred1': RGBColor(255, 130, 171),
'palevioletred2': RGBColor(238, 121, 159),
'palevioletred3': RGBColor(205, 104, 137),
'palevioletred4': RGBColor(139, 71, 93),
'papayawhip': RGBColor(255, 239, 213),
'peachpuff': RGBColor(255, 218, 185),
'peachpuff1': RGBColor(255, 218, 185),
'peachpuff2': RGBColor(238, 203, 173),
'peachpuff3': RGBColor(205, 175, 149),
'peachpuff4': RGBColor(139, 119, 101),
'peru': RGBColor(205, 133, 63),
'pink': RGBColor(255, 192, 203),
'pink1': RGBColor(255, 181, 197),
'pink2': RGBColor(238, 169, 184),
'pink3': RGBColor(205, 145, 158),
'pink4': RGBColor(139, 99, 108),
'plum': RGBColor(221, 160, 221),
'plum1': RGBColor(255, 187, 255),
'plum2': RGBColor(238, 174, 238),
'plum3': RGBColor(205, 150, 205),
'plum4': RGBColor(139, 102, 139),
'powderblue': RGBColor(176, 224, 230),
'purple': RGBColor(160, 32, 240),
'purple1': RGBColor(155, 48, 255),
'purple2': RGBColor(145, 44, 238),
'purple3': RGBColor(125, 38, 205),
'purple4': RGBColor(85, 26, 139),
'rebeccapurple': RGBColor(102, 51, 153),
'red': RGBColor(255, 0, 0),
'red1': RGBColor(255, 0, 0),
'red2': RGBColor(238, 0, 0),
'red3': RGBColor(205, 0, 0),
'red4': RGBColor(139, 0, 0),
'rosybrown': RGBColor(188, 143, 143),
'rosybrown1': RGBColor(255, 193, 193),
'rosybrown2': RGBColor(238, 180, 180),
'rosybrown3': RGBColor(205, 155, 155),
'rosybrown4': RGBColor(139, 105, 105),
'royalblue': RGBColor(65, 105, 225),
'royalblue1': RGBColor(72, 118, 255),
'royalblue2': RGBColor(67, 110, 238),
'royalblue3': RGBColor(58, 95, 205),
'royalblue4': RGBColor(39, 64, 139),
'saddlebrown': RGBColor(139, 69, 19),
'salmon': RGBColor(250, 128, 114),
'salmon1': RGBColor(255, 140, 105),
'salmon2': RGBColor(238, 130, 98),
'salmon3': RGBColor(205, 112, 84),
'salmon4': RGBColor(139, 76, 57),
'sandybrown': RGBColor(244, 164, 96),
'seagreen': RGBColor(46, 139, 87),
'seagreen1': RGBColor(84, 255, 159),
'seagreen2': RGBColor(78, 238, 148),
'seagreen3': RGBColor(67, 205, 128),
'seagreen4': RGBColor(46, 139, 87),
'seashell': RGBColor(255, 245, 238),
'seashell1': RGBColor(255, 245, 238),
'seashell2': RGBColor(238, 229, 222),
'seashell3': RGBColor(205, 197, 191),
'seashell4': RGBColor(139, 134, 130),
'sienna': RGBColor(160, 82, 45),
'sienna1': RGBColor(255, 130, 71),
'sienna2': RGBColor(238, 121, 66),
'sienna3': RGBColor(205, 104, 57),
'sienna4': RGBColor(139, 71, 38),
'silver': RGBColor(192, 192, 192),
'skyblue': RGBColor(135, 206, 235),
'skyblue1': RGBColor(135, 206, 255),
'skyblue2': RGBColor(126, 192, 238),
'skyblue3': RGBColor(108, 166, 205),
'skyblue4': RGBColor(74, 112, 139),
'slateblue': RGBColor(106, 90, 205),
'slateblue1': RGBColor(131, 111, 255),
'slateblue2': RGBColor(122, 103, 238),
'slateblue3': RGBColor(105, 89, 205),
'slateblue4': RGBColor(71, 60, 139),
'slategray': RGBColor(112, 128, 144),
'slategray1': RGBColor(198, 226, 255),
'slategray2': RGBColor(185, 211, 238),
'slategray3': RGBColor(159, 182, 205),
'slategray4': RGBColor(108, 123, 139),
'slategrey': RGBColor(112, 128, 144),
'snow': RGBColor(255, 250, 250),
'snow1': RGBColor(255, 250, 250),
'snow2': RGBColor(238, 233, 233),
'snow3': RGBColor(205, 201, 201),
'snow4': RGBColor(139, 137, 137),
'springgreen': RGBColor(0, 255, 127),
'springgreen1': RGBColor(0, 255, 127),
'springgreen2': RGBColor(0, 238, 118),
'springgreen3': RGBColor(0, 205, 102),
'springgreen4': RGBColor(0, 139, 69),
'steelblue': RGBColor(70, 130, 180),
'steelblue1': RGBColor(99, 184, 255),
'steelblue2': RGBColor(92, 172, 238),
'steelblue3': RGBColor(79, 148, 205),
'steelblue4': RGBColor(54, 100, 139),
'tan': RGBColor(210, 180, 140),
'tan1': RGBColor(255, 165, 79),
'tan2': RGBColor(238, 154, 73),
'tan3': RGBColor(205, 133, 63),
'tan4': RGBColor(139, 90, 43),
'teal': RGBColor(0, 128, 128),
'thistle': RGBColor(216, 191, 216),
'thistle1': RGBColor(255, 225, 255),
'thistle2': RGBColor(238, 210, 238),
'thistle3': RGBColor(205, 181, 205),
'thistle4': RGBColor(139, 123, 139),
'tomato': RGBColor(255, 99, 71),
'tomato1': RGBColor(255, 99, 71),
'tomato2': RGBColor(238, 92, 66),
'tomato3': RGBColor(205, 79, 57),
'tomato4': RGBColor(139, 54, 38),
'turquoise': RGBColor(64, 224, 208),
'turquoise1': RGBColor(0, 245, 255),
'turquoise2': RGBColor(0, 229, 238),
'turquoise3': RGBColor(0, 197, 205),
'turquoise4': RGBColor(0, 134, 139),
'violet': RGBColor(238, 130, 238),
'violetred': RGBColor(208, 32, 144),
'violetred1': RGBColor(255, 62, 150),
'violetred2': RGBColor(238, 58, 140),
'violetred3': RGBColor(205, 50, 120),
'violetred4': RGBColor(139, 34, 82),
'webgray': RGBColor(128, 128, 128),
'webgreen': RGBColor(0, 128, 0),
'webgrey': RGBColor(128, 128, 128),
'webmaroon': RGBColor(128, 0, 0),
'webpurple': RGBColor(128, 0, 128),
'wheat': RGBColor(245, 222, 179),
'wheat1': RGBColor(255, 231, 186),
'wheat2': RGBColor(238, 216, 174),
'wheat3': RGBColor(205, 186, 150),
'wheat4': RGBColor(139, 126, 102),
'white': RGBColor(255, 255, 255),
'whitesmoke': RGBColor(245, 245, 245),
'x11gray': RGBColor(190, 190, 190),
'x11green': RGBColor(0, 255, 0),
'x11grey': RGBColor(190, 190, 190),
'x11maroon': RGBColor(176, 48, 96),
'x11purple': RGBColor(160, 32, 240),
'yellow': RGBColor(255, 255, 0),
'yellow1': RGBColor(255, 255, 0),
'yellow2': RGBColor(238, 238, 0),
'yellow3': RGBColor(205, 205, 0),
'yellow4': RGBColor(139, 139, 0),
'yellowgreen': RGBColor(154, 205, 50)
}
#: Curses color indices of 8, 16, and 256-color terminals
RGB_256TABLE = (
RGBColor(0, 0, 0),
RGBColor(205, 0, 0),
RGBColor(0, 205, 0),
RGBColor(205, 205, 0),
RGBColor(0, 0, 238),
RGBColor(205, 0, 205),
RGBColor(0, 205, 205),
RGBColor(229, 229, 229),
RGBColor(127, 127, 127),
RGBColor(255, 0, 0),
RGBColor(0, 255, 0),
RGBColor(255, 255, 0),
RGBColor(92, 92, 255),
RGBColor(255, 0, 255),
RGBColor(0, 255, 255),
RGBColor(255, 255, 255),
RGBColor(0, 0, 0),
RGBColor(0, 0, 95),
RGBColor(0, 0, 135),
RGBColor(0, 0, 175),
RGBColor(0, 0, 215),
RGBColor(0, 0, 255),
RGBColor(0, 95, 0),
RGBColor(0, 95, 95),
RGBColor(0, 95, 135),
RGBColor(0, 95, 175),
RGBColor(0, 95, 215),
RGBColor(0, 95, 255),
RGBColor(0, 135, 0),
RGBColor(0, 135, 95),
RGBColor(0, 135, 135),
RGBColor(0, 135, 175),
RGBColor(0, 135, 215),
RGBColor(0, 135, 255),
RGBColor(0, 175, 0),
RGBColor(0, 175, 95),
RGBColor(0, 175, 135),
RGBColor(0, 175, 175),
RGBColor(0, 175, 215),
RGBColor(0, 175, 255),
RGBColor(0, 215, 0),
RGBColor(0, 215, 95),
RGBColor(0, 215, 135),
RGBColor(0, 215, 175),
RGBColor(0, 215, 215),
RGBColor(0, 215, 255),
RGBColor(0, 255, 0),
RGBColor(0, 255, 95),
RGBColor(0, 255, 135),
RGBColor(0, 255, 175),
RGBColor(0, 255, 215),
RGBColor(0, 255, 255),
RGBColor(95, 0, 0),
RGBColor(95, 0, 95),
RGBColor(95, 0, 135),
RGBColor(95, 0, 175),
RGBColor(95, 0, 215),
RGBColor(95, 0, 255),
RGBColor(95, 95, 0),
RGBColor(95, 95, 95),
RGBColor(95, 95, 135),
RGBColor(95, 95, 175),
RGBColor(95, 95, 215),
RGBColor(95, 95, 255),
RGBColor(95, 135, 0),
RGBColor(95, 135, 95),
RGBColor(95, 135, 135),
RGBColor(95, 135, 175),
RGBColor(95, 135, 215),
RGBColor(95, 135, 255),
RGBColor(95, 175, 0),
RGBColor(95, 175, 95),
RGBColor(95, 175, 135),
RGBColor(95, 175, 175),
RGBColor(95, 175, 215),
RGBColor(95, 175, 255),
RGBColor(95, 215, 0),
RGBColor(95, 215, 95),
RGBColor(95, 215, 135),
RGBColor(95, 215, 175),
RGBColor(95, 215, 215),
RGBColor(95, 215, 255),
RGBColor(95, 255, 0),
RGBColor(95, 255, 95),
RGBColor(95, 255, 135),
RGBColor(95, 255, 175),
RGBColor(95, 255, 215),
RGBColor(95, 255, 255),
RGBColor(135, 0, 0),
RGBColor(135, 0, 95),
RGBColor(135, 0, 135),
RGBColor(135, 0, 175),
RGBColor(135, 0, 215),
RGBColor(135, 0, 255),
RGBColor(135, 95, 0),
RGBColor(135, 95, 95),
RGBColor(135, 95, 135),
RGBColor(135, 95, 175),
RGBColor(135, 95, 215),
RGBColor(135, 95, 255),
RGBColor(135, 135, 0),
RGBColor(135, 135, 95),
RGBColor(135, 135, 135),
RGBColor(135, 135, 175),
RGBColor(135, 135, 215),
RGBColor(135, 135, 255),
RGBColor(135, 175, 0),
RGBColor(135, 175, 95),
RGBColor(135, 175, 135),
RGBColor(135, 175, 175),
RGBColor(135, 175, 215),
RGBColor(135, 175, 255),
RGBColor(135, 215, 0),
RGBColor(135, 215, 95),
RGBColor(135, 215, 135),
RGBColor(135, 215, 175),
RGBColor(135, 215, 215),
RGBColor(135, 215, 255),
RGBColor(135, 255, 0),
RGBColor(135, 255, 95),
RGBColor(135, 255, 135),
RGBColor(135, 255, 175),
RGBColor(135, 255, 215),
RGBColor(135, 255, 255),
RGBColor(175, 0, 0),
RGBColor(175, 0, 95),
RGBColor(175, 0, 135),
RGBColor(175, 0, 175),
RGBColor(175, 0, 215),
RGBColor(175, 0, 255),
RGBColor(175, 95, 0),
RGBColor(175, 95, 95),
RGBColor(175, 95, 135),
RGBColor(175, 95, 175),
RGBColor(175, 95, 215),
RGBColor(175, 95, 255),
RGBColor(175, 135, 0),
RGBColor(175, 135, 95),
RGBColor(175, 135, 135),
RGBColor(175, 135, 175),
RGBColor(175, 135, 215),
RGBColor(175, 135, 255),
RGBColor(175, 175, 0),
RGBColor(175, 175, 95),
RGBColor(175, 175, 135),
RGBColor(175, 175, 175),
RGBColor(175, 175, 215),
RGBColor(175, 175, 255),
RGBColor(175, 215, 0),
RGBColor(175, 215, 95),
RGBColor(175, 215, 135),
RGBColor(175, 215, 175),
RGBColor(175, 215, 215),
RGBColor(175, 215, 255),
RGBColor(175, 255, 0),
RGBColor(175, 255, 95),
RGBColor(175, 255, 135),
RGBColor(175, 255, 175),
RGBColor(175, 255, 215),
RGBColor(175, 255, 255),
RGBColor(215, 0, 0),
RGBColor(215, 0, 95),
RGBColor(215, 0, 135),
RGBColor(215, 0, 175),
RGBColor(215, 0, 215),
RGBColor(215, 0, 255),
RGBColor(215, 95, 0),
RGBColor(215, 95, 95),
RGBColor(215, 95, 135),
RGBColor(215, 95, 175),
RGBColor(215, 95, 215),
RGBColor(215, 95, 255),
RGBColor(215, 135, 0),
RGBColor(215, 135, 95),
RGBColor(215, 135, 135),
RGBColor(215, 135, 175),
RGBColor(215, 135, 215),
RGBColor(215, 135, 255),
RGBColor(215, 175, 0),
RGBColor(215, 175, 95),
RGBColor(215, 175, 135),
RGBColor(215, 175, 175),
RGBColor(215, 175, 215),
RGBColor(215, 175, 255),
RGBColor(215, 215, 0),
RGBColor(215, 215, 95),
RGBColor(215, 215, 135),
RGBColor(215, 215, 175),
RGBColor(215, 215, 215),
RGBColor(215, 215, 255),
RGBColor(215, 255, 0),
RGBColor(215, 255, 95),
RGBColor(215, 255, 135),
RGBColor(215, 255, 175),
RGBColor(215, 255, 215),
RGBColor(215, 255, 255),
RGBColor(255, 0, 0),
RGBColor(255, 0, 135),
RGBColor(255, 0, 95),
RGBColor(255, 0, 175),
RGBColor(255, 0, 215),
RGBColor(255, 0, 255),
RGBColor(255, 95, 0),
RGBColor(255, 95, 95),
RGBColor(255, 95, 135),
RGBColor(255, 95, 175),
RGBColor(255, 95, 215),
RGBColor(255, 95, 255),
RGBColor(255, 135, 0),
RGBColor(255, 135, 95),
RGBColor(255, 135, 135),
RGBColor(255, 135, 175),
RGBColor(255, 135, 215),
RGBColor(255, 135, 255),
RGBColor(255, 175, 0),
RGBColor(255, 175, 95),
RGBColor(255, 175, 135),
RGBColor(255, 175, 175),
RGBColor(255, 175, 215),
RGBColor(255, 175, 255),
RGBColor(255, 215, 0),
RGBColor(255, 215, 95),
RGBColor(255, 215, 135),
RGBColor(255, 215, 175),
RGBColor(255, 215, 215),
RGBColor(255, 215, 255),
RGBColor(255, 255, 0),
RGBColor(255, 255, 95),
RGBColor(255, 255, 135),
RGBColor(255, 255, 175),
RGBColor(255, 255, 215),
RGBColor(255, 255, 255),
RGBColor(8, 8, 8),
RGBColor(18, 18, 18),
RGBColor(28, 28, 28),
RGBColor(38, 38, 38),
RGBColor(48, 48, 48),
RGBColor(58, 58, 58),
RGBColor(68, 68, 68),
RGBColor(78, 78, 78),
RGBColor(88, 88, 88),
RGBColor(98, 98, 98),
RGBColor(108, 108, 108),
RGBColor(118, 118, 118),
RGBColor(128, 128, 128),
RGBColor(138, 138, 138),
RGBColor(148, 148, 148),
RGBColor(158, 158, 158),
RGBColor(168, 168, 168),
RGBColor(178, 178, 178),
RGBColor(188, 188, 188),
RGBColor(198, 198, 198),
RGBColor(208, 208, 208),
RGBColor(218, 218, 218),
RGBColor(228, 228, 228),
RGBColor(238, 238, 238),
)

View File

@@ -0,0 +1,12 @@
# std imports
from typing import Set, Dict, Tuple, NamedTuple
CGA_COLORS: Set[str]
class RGBColor(NamedTuple):
red: int
green: int
blue: int
X11_COLORNAMES_TO_RGB: Dict[str, RGBColor]
RGB_256TABLE: Tuple[RGBColor, ...]

View File

@@ -0,0 +1,496 @@
"""Sub-module providing sequence-formatting functions."""
# std imports
import platform
# 3rd party
import six
# local
from blessed.colorspace import CGA_COLORS, X11_COLORNAMES_TO_RGB
# isort: off
# curses
if platform.system() == 'Windows':
import jinxed as curses # pylint: disable=import-error
else:
import curses
def _make_colors():
"""
Return set of valid colors and their derivatives.
:rtype: set
:returns: Color names with prefixes
"""
colors = set()
# basic CGA foreground color, background, high intensity, and bold
# background ('iCE colors' in my day).
for cga_color in CGA_COLORS:
colors.add(cga_color)
colors.add('on_' + cga_color)
colors.add('bright_' + cga_color)
colors.add('on_bright_' + cga_color)
# foreground and background VGA color
for vga_color in X11_COLORNAMES_TO_RGB:
colors.add(vga_color)
colors.add('on_' + vga_color)
return colors
#: Valid colors and their background (on), bright, and bright-background
#: derivatives.
COLORS = _make_colors()
#: Attributes that may be compounded with colors, by underscore, such as
#: 'reverse_indigo'.
COMPOUNDABLES = set('bold underline reverse blink italic standout'.split())
class ParameterizingString(six.text_type):
r"""
A Unicode string which can be called as a parameterizing termcap.
For example::
>>> from blessed import Terminal
>>> term = Terminal()
>>> color = ParameterizingString(term.color, term.normal, 'color')
>>> color(9)('color #9')
u'\x1b[91mcolor #9\x1b(B\x1b[m'
"""
def __new__(cls, cap, normal=u'', name=u'<not specified>'):
# pylint: disable = missing-return-doc, missing-return-type-doc
"""
Class constructor accepting 3 positional arguments.
:arg str cap: parameterized string suitable for curses.tparm()
:arg str normal: terminating sequence for this capability (optional).
:arg str name: name of this terminal capability (optional).
"""
new = six.text_type.__new__(cls, cap)
new._normal = normal
new._name = name
return new
def __call__(self, *args):
"""
Returning :class:`FormattingString` instance for given parameters.
Return evaluated terminal capability (self), receiving arguments
``*args``, followed by the terminating sequence (self.normal) into
a :class:`FormattingString` capable of being called.
:raises TypeError: Mismatch between capability and arguments
:raises curses.error: :func:`curses.tparm` raised an exception
:rtype: :class:`FormattingString` or :class:`NullCallableString`
:returns: Callable string for given parameters
"""
try:
# Re-encode the cap, because tparm() takes a bytestring in Python
# 3. However, appear to be a plain Unicode string otherwise so
# concats work.
attr = curses.tparm(self.encode('latin1'), *args).decode('latin1')
return FormattingString(attr, self._normal)
except TypeError as err:
# If the first non-int (i.e. incorrect) arg was a string, suggest
# something intelligent:
if args and isinstance(args[0], six.string_types):
raise TypeError(
"Unknown terminal capability, %r, or, TypeError "
"for arguments %r: %s" % (self._name, args, err))
# Somebody passed a non-string; I don't feel confident
# guessing what they were trying to do.
raise
except curses.error as err:
# ignore 'tparm() returned NULL', you won't get any styling,
# even if does_styling is True. This happens on win32 platforms
# with http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses installed
if "tparm() returned NULL" not in six.text_type(err):
raise
return NullCallableString()
class ParameterizingProxyString(six.text_type):
r"""
A Unicode string which can be called to proxy missing termcap entries.
This class supports the function :func:`get_proxy_string`, and mirrors
the behavior of :class:`ParameterizingString`, except that instead of
a capability name, receives a format string, and callable to filter the
given positional ``*args`` of :meth:`ParameterizingProxyString.__call__`
into a terminal sequence.
For example::
>>> from blessed import Terminal
>>> term = Terminal('screen')
>>> hpa = ParameterizingString(term.hpa, term.normal, 'hpa')
>>> hpa(9)
u''
>>> fmt = u'\x1b[{0}G'
>>> fmt_arg = lambda *arg: (arg[0] + 1,)
>>> hpa = ParameterizingProxyString((fmt, fmt_arg), term.normal, 'hpa')
>>> hpa(9)
u'\x1b[10G'
"""
def __new__(cls, fmt_pair, normal=u'', name=u'<not specified>'):
# pylint: disable = missing-return-doc, missing-return-type-doc
"""
Class constructor accepting 4 positional arguments.
:arg tuple fmt_pair: Two element tuple containing:
- format string suitable for displaying terminal sequences
- callable suitable for receiving __call__ arguments for formatting string
:arg str normal: terminating sequence for this capability (optional).
:arg str name: name of this terminal capability (optional).
"""
assert isinstance(fmt_pair, tuple), fmt_pair
assert callable(fmt_pair[1]), fmt_pair[1]
new = six.text_type.__new__(cls, fmt_pair[0])
new._fmt_args = fmt_pair[1]
new._normal = normal
new._name = name
return new
def __call__(self, *args):
"""
Returning :class:`FormattingString` instance for given parameters.
Arguments are determined by the capability. For example, ``hpa``
(move_x) receives only a single integer, whereas ``cup`` (move)
receives two integers. See documentation in terminfo(5) for the
given capability.
:rtype: FormattingString
:returns: Callable string for given parameters
"""
return FormattingString(self.format(*self._fmt_args(*args)),
self._normal)
class FormattingString(six.text_type):
r"""
A Unicode string which doubles as a callable.
This is used for terminal attributes, so that it may be used both
directly, or as a callable. When used directly, it simply emits
the given terminal sequence. When used as a callable, it wraps the
given (string) argument with the 2nd argument used by the class
constructor::
>>> from blessed import Terminal
>>> term = Terminal()
>>> style = FormattingString(term.bright_blue, term.normal)
>>> print(repr(style))
u'\x1b[94m'
>>> style('Big Blue')
u'\x1b[94mBig Blue\x1b(B\x1b[m'
"""
def __new__(cls, sequence, normal=u''):
# pylint: disable = missing-return-doc, missing-return-type-doc
"""
Class constructor accepting 2 positional arguments.
:arg str sequence: terminal attribute sequence.
:arg str normal: terminating sequence for this attribute (optional).
"""
new = six.text_type.__new__(cls, sequence)
new._normal = normal
return new
def __call__(self, *args):
"""
Return ``text`` joined by ``sequence`` and ``normal``.
:raises TypeError: Not a string type
:rtype: str
:returns: Arguments wrapped in sequence and normal
"""
# Jim Allman brings us this convenience of allowing existing
# unicode strings to be joined as a call parameter to a formatting
# string result, allowing nestation:
#
# >>> t.red('This is ', t.bold('extremely'), ' dangerous!')
for idx, ucs_part in enumerate(args):
if not isinstance(ucs_part, six.string_types):
expected_types = ', '.join(_type.__name__ for _type in six.string_types)
raise TypeError(
"TypeError for FormattingString argument, "
"%r, at position %s: expected type %s, "
"got %s" % (ucs_part, idx, expected_types,
type(ucs_part).__name__))
postfix = u''
if self and self._normal:
postfix = self._normal
_refresh = self._normal + self
args = [_refresh.join(ucs_part.split(self._normal))
for ucs_part in args]
return self + u''.join(args) + postfix
class FormattingOtherString(six.text_type):
r"""
A Unicode string which doubles as a callable for another sequence when called.
This is used for the :meth:`~.Terminal.move_up`, ``down``, ``left``, and ``right()``
family of functions::
>>> from blessed import Terminal
>>> term = Terminal()
>>> move_right = FormattingOtherString(term.cuf1, term.cuf)
>>> print(repr(move_right))
u'\x1b[C'
>>> print(repr(move_right(666)))
u'\x1b[666C'
>>> print(repr(move_right()))
u'\x1b[C'
"""
def __new__(cls, direct, target):
# pylint: disable = missing-return-doc, missing-return-type-doc
"""
Class constructor accepting 2 positional arguments.
:arg str direct: capability name for direct formatting, eg ``('x' + term.right)``.
:arg str target: capability name for callable, eg ``('x' + term.right(99))``.
"""
new = six.text_type.__new__(cls, direct)
new._callable = target
return new
def __getnewargs__(self):
# return arguments used for the __new__ method upon unpickling.
return six.text_type.__new__(six.text_type, self), self._callable
def __call__(self, *args):
"""Return ``text`` by ``target``."""
return self._callable(*args) if args else self
class NullCallableString(six.text_type):
"""
A dummy callable Unicode alternative to :class:`FormattingString`.
This is used for colors on terminals that do not support colors, it is just a basic form of
unicode that may also act as a callable.
"""
def __new__(cls):
"""Class constructor."""
return six.text_type.__new__(cls, u'')
def __call__(self, *args):
"""
Allow empty string to be callable, returning given string, if any.
When called with an int as the first arg, return an empty Unicode. An
int is a good hint that I am a :class:`ParameterizingString`, as there
are only about half a dozen string-returning capabilities listed in
terminfo(5) which accept non-int arguments, they are seldom used.
When called with a non-int as the first arg (no no args at all), return
the first arg, acting in place of :class:`FormattingString` without
any attributes.
"""
if not args or isinstance(args[0], int):
# As a NullCallableString, even when provided with a parameter,
# such as t.color(5), we must also still be callable, fe:
#
# >>> t.color(5)('shmoo')
#
# is actually simplified result of NullCallable()() on terminals
# without color support, so turtles all the way down: we return
# another instance.
return NullCallableString()
return u''.join(args)
def get_proxy_string(term, attr):
"""
Proxy and return callable string for proxied attributes.
:arg Terminal term: :class:`~.Terminal` instance.
:arg str attr: terminal capability name that may be proxied.
:rtype: None or :class:`ParameterizingProxyString`.
:returns: :class:`ParameterizingProxyString` for some attributes
of some terminal types that support it, where the terminfo(5)
database would otherwise come up empty, such as ``move_x``
attribute for ``term.kind`` of ``screen``. Otherwise, None.
"""
# normalize 'screen-256color', or 'ansi.sys' to its basic names
term_kind = next(iter(_kind for _kind in ('screen', 'ansi',)
if term.kind.startswith(_kind)), term)
_proxy_table = { # pragma: no cover
'screen': {
# proxy move_x/move_y for 'screen' terminal type, used by tmux(1).
'hpa': ParameterizingProxyString(
(u'\x1b[{0}G', lambda *arg: (arg[0] + 1,)), term.normal, attr),
'vpa': ParameterizingProxyString(
(u'\x1b[{0}d', lambda *arg: (arg[0] + 1,)), term.normal, attr),
},
'ansi': {
# proxy show/hide cursor for 'ansi' terminal type. There is some
# demand for a richly working ANSI terminal type for some reason.
'civis': ParameterizingProxyString(
(u'\x1b[?25l', lambda *arg: ()), term.normal, attr),
'cnorm': ParameterizingProxyString(
(u'\x1b[?25h', lambda *arg: ()), term.normal, attr),
'hpa': ParameterizingProxyString(
(u'\x1b[{0}G', lambda *arg: (arg[0] + 1,)), term.normal, attr),
'vpa': ParameterizingProxyString(
(u'\x1b[{0}d', lambda *arg: (arg[0] + 1,)), term.normal, attr),
'sc': '\x1b[s',
'rc': '\x1b[u',
}
}
return _proxy_table.get(term_kind, {}).get(attr, None)
def split_compound(compound):
"""
Split compound formating string into segments.
>>> split_compound('bold_underline_bright_blue_on_red')
['bold', 'underline', 'bright_blue', 'on_red']
:arg str compound: a string that may contain compounds, separated by
underline (``_``).
:rtype: list
:returns: List of formating string segments
"""
merged_segs = []
# These occur only as prefixes, so they can always be merged:
mergeable_prefixes = ['on', 'bright', 'on_bright']
for segment in compound.split('_'):
if merged_segs and merged_segs[-1] in mergeable_prefixes:
merged_segs[-1] += '_' + segment
else:
merged_segs.append(segment)
return merged_segs
def resolve_capability(term, attr):
"""
Resolve a raw terminal capability using :func:`tigetstr`.
:arg Terminal term: :class:`~.Terminal` instance.
:arg str attr: terminal capability name.
:returns: string of the given terminal capability named by ``attr``,
which may be empty (u'') if not found or not supported by the
given :attr:`~.Terminal.kind`.
:rtype: str
"""
if not term.does_styling:
return u''
val = curses.tigetstr(term._sugar.get(attr, attr)) # pylint: disable=protected-access
# Decode sequences as latin1, as they are always 8-bit bytes, so when
# b'\xff' is returned, this is decoded as u'\xff'.
return u'' if val is None else val.decode('latin1')
def resolve_color(term, color):
"""
Resolve a simple color name to a callable capability.
This function supports :func:`resolve_attribute`.
:arg Terminal term: :class:`~.Terminal` instance.
:arg str color: any string found in set :const:`COLORS`.
:returns: a string class instance which emits the terminal sequence
for the given color, and may be used as a callable to wrap the
given string with such sequence.
:returns: :class:`NullCallableString` when
:attr:`~.Terminal.number_of_colors` is 0,
otherwise :class:`FormattingString`.
:rtype: :class:`NullCallableString` or :class:`FormattingString`
"""
# pylint: disable=protected-access
if term.number_of_colors == 0:
return NullCallableString()
# fg/bg capabilities terminals that support 0-256+ colors.
vga_color_cap = (term._background_color if 'on_' in color else
term._foreground_color)
base_color = color.rsplit('_', 1)[-1]
if base_color in CGA_COLORS:
# curses constants go up to only 7, so add an offset to get at the
# bright colors at 8-15:
offset = 8 if 'bright_' in color else 0
base_color = color.rsplit('_', 1)[-1]
attr = 'COLOR_%s' % (base_color.upper(),)
fmt_attr = vga_color_cap(getattr(curses, attr) + offset)
return FormattingString(fmt_attr, term.normal)
assert base_color in X11_COLORNAMES_TO_RGB, (
'color not known', base_color)
rgb = X11_COLORNAMES_TO_RGB[base_color]
# downconvert X11 colors to CGA, EGA, or VGA color spaces
if term.number_of_colors <= 256:
fmt_attr = vga_color_cap(term.rgb_downconvert(*rgb))
return FormattingString(fmt_attr, term.normal)
# Modern 24-bit color terminals are written pretty basically. The
# foreground and background sequences are:
# - ^[38;2;<r>;<g>;<b>m
# - ^[48;2;<r>;<g>;<b>m
fgbg_seq = ('48' if 'on_' in color else '38')
assert term.number_of_colors == 1 << 24
fmt_attr = u'\x1b[' + fgbg_seq + ';2;{0};{1};{2}m'
return FormattingString(fmt_attr.format(*rgb), term.normal)
def resolve_attribute(term, attr):
"""
Resolve a terminal attribute name into a capability class.
:arg Terminal term: :class:`~.Terminal` instance.
:arg str attr: Sugary, ordinary, or compound formatted terminal
capability, such as "red_on_white", "normal", "red", or
"bold_on_black".
:returns: a string class instance which emits the terminal sequence
for the given terminal capability, or may be used as a callable to
wrap the given string with such sequence.
:returns: :class:`NullCallableString` when
:attr:`~.Terminal.number_of_colors` is 0,
otherwise :class:`FormattingString`.
:rtype: :class:`NullCallableString` or :class:`FormattingString`
"""
if attr in COLORS:
return resolve_color(term, attr)
# A direct compoundable, such as `bold' or `on_red'.
if attr in COMPOUNDABLES:
sequence = resolve_capability(term, attr)
return FormattingString(sequence, term.normal)
# Given `bold_on_red', resolve to ('bold', 'on_red'), RECURSIVE
# call for each compounding section, joined and returned as
# a completed completed FormattingString.
formatters = split_compound(attr)
if all((fmt in COLORS or fmt in COMPOUNDABLES) for fmt in formatters):
resolution = (resolve_attribute(term, fmt) for fmt in formatters)
return FormattingString(u''.join(resolution), term.normal)
# otherwise, this is our end-game: given a sequence such as 'csr'
# (change scrolling region), return a ParameterizingString instance,
# that when called, performs and returns the final string after curses
# capability lookup is performed.
tparm_capseq = resolve_capability(term, attr)
if not tparm_capseq:
# and, for special terminals, such as 'screen', provide a Proxy
# ParameterizingString for attributes they do not claim to support,
# but actually do! (such as 'hpa' and 'vpa').
proxy = get_proxy_string(term,
term._sugar.get(attr, attr)) # pylint: disable=protected-access
if proxy is not None:
return proxy
return ParameterizingString(tparm_capseq, term.normal, attr)

View File

@@ -0,0 +1,70 @@
# std imports
from typing import (Any,
Set,
List,
Type,
Tuple,
Union,
TypeVar,
Callable,
NoReturn,
Optional,
overload)
# local
from .terminal import Terminal
COLORS: Set[str]
COMPOUNDABLES: Set[str]
_T = TypeVar("_T")
class ParameterizingString(str):
def __new__(cls: Type[_T], cap: str, normal: str = ..., name: str = ...) -> _T: ...
@overload
def __call__(
self, *args: int
) -> Union["FormattingString", "NullCallableString"]: ...
@overload
def __call__(self, *args: str) -> NoReturn: ...
class ParameterizingProxyString(str):
def __new__(
cls: Type[_T],
fmt_pair: Tuple[str, Callable[..., Tuple[object, ...]]],
normal: str = ...,
name: str = ...,
) -> _T: ...
def __call__(self, *args: Any) -> "FormattingString": ...
class FormattingString(str):
def __new__(cls: Type[_T], sequence: str, normal: str = ...) -> _T: ...
@overload
def __call__(self, *args: int) -> NoReturn: ...
@overload
def __call__(self, *args: str) -> str: ...
class FormattingOtherString(str):
def __new__(
cls: Type[_T], direct: ParameterizingString, target: ParameterizingString = ...
) -> _T: ...
def __call__(self, *args: Union[int, str]) -> str: ...
class NullCallableString(str):
def __new__(cls: Type[_T]) -> _T: ...
@overload
def __call__(self, *args: int) -> "NullCallableString": ...
@overload
def __call__(self, *args: str) -> str: ...
def get_proxy_string(
term: Terminal, attr: str
) -> Optional[ParameterizingProxyString]: ...
def split_compound(compound: str) -> List[str]: ...
def resolve_capability(term: Terminal, attr: str) -> str: ...
def resolve_color(
term: Terminal, color: str
) -> Union[NullCallableString, FormattingString]: ...
def resolve_attribute(
term: Terminal, attr: str
) -> Union[ParameterizingString, FormattingString]: ...

View File

@@ -0,0 +1,451 @@
"""Sub-module providing 'keyboard awareness'."""
# std imports
import re
import time
import platform
from collections import OrderedDict
# 3rd party
import six
# isort: off
# curses
if platform.system() == 'Windows':
# pylint: disable=import-error
import jinxed as curses
from jinxed.has_key import _capability_names as capability_names
else:
import curses
from curses.has_key import _capability_names as capability_names
class Keystroke(six.text_type):
"""
A unicode-derived class for describing a single keystroke.
A class instance describes a single keystroke received on input,
which may contain multiple characters as a multibyte sequence,
which is indicated by properties :attr:`is_sequence` returning
``True``.
When the string is a known sequence, :attr:`code` matches terminal
class attributes for comparison, such as ``term.KEY_LEFT``.
The string-name of the sequence, such as ``u'KEY_LEFT'`` is accessed
by property :attr:`name`, and is used by the :meth:`__repr__` method
to display a human-readable form of the Keystroke this class
instance represents. It may otherwise by joined, split, or evaluated
just as as any other unicode string.
"""
def __new__(cls, ucs='', code=None, name=None):
"""Class constructor."""
new = six.text_type.__new__(cls, ucs)
new._name = name
new._code = code
return new
@property
def is_sequence(self):
"""Whether the value represents a multibyte sequence (bool)."""
return self._code is not None
def __repr__(self):
"""Docstring overwritten."""
return (six.text_type.__repr__(self) if self._name is None else
self._name)
__repr__.__doc__ = six.text_type.__doc__
@property
def name(self):
"""String-name of key sequence, such as ``u'KEY_LEFT'`` (str)."""
return self._name
@property
def code(self):
"""Integer keycode value of multibyte sequence (int)."""
return self._code
def get_curses_keycodes():
"""
Return mapping of curses key-names paired by their keycode integer value.
:rtype: dict
:returns: Dictionary of (name, code) pairs for curses keyboard constant
values and their mnemonic name. Such as code ``260``, with the value of
its key-name identity, ``u'KEY_LEFT'``.
"""
_keynames = [attr for attr in dir(curses)
if attr.startswith('KEY_')]
return {keyname: getattr(curses, keyname) for keyname in _keynames}
def get_keyboard_codes():
"""
Return mapping of keycode integer values paired by their curses key-name.
:rtype: dict
:returns: Dictionary of (code, name) pairs for curses keyboard constant
values and their mnemonic name. Such as key ``260``, with the value of
its identity, ``u'KEY_LEFT'``.
These keys are derived from the attributes by the same of the curses module,
with the following exceptions:
* ``KEY_DELETE`` in place of ``KEY_DC``
* ``KEY_INSERT`` in place of ``KEY_IC``
* ``KEY_PGUP`` in place of ``KEY_PPAGE``
* ``KEY_PGDOWN`` in place of ``KEY_NPAGE``
* ``KEY_ESCAPE`` in place of ``KEY_EXIT``
* ``KEY_SUP`` in place of ``KEY_SR``
* ``KEY_SDOWN`` in place of ``KEY_SF``
This function is the inverse of :func:`get_curses_keycodes`. With the
given override "mixins" listed above, the keycode for the delete key will
map to our imaginary ``KEY_DELETE`` mnemonic, effectively erasing the
phrase ``KEY_DC`` from our code vocabulary for anyone that wishes to use
the return value to determine the key-name by keycode.
"""
keycodes = OrderedDict(get_curses_keycodes())
keycodes.update(CURSES_KEYCODE_OVERRIDE_MIXIN)
# merge _CURSES_KEYCODE_ADDINS added to our module space
keycodes.update(
(name, value) for name, value in globals().copy().items() if name.startswith('KEY_')
)
# invert dictionary (key, values) => (values, key), preferring the
# last-most inserted value ('KEY_DELETE' over 'KEY_DC').
return dict(zip(keycodes.values(), keycodes.keys()))
def _alternative_left_right(term):
r"""
Determine and return mapping of left and right arrow keys sequences.
:arg blessed.Terminal term: :class:`~.Terminal` instance.
:rtype: dict
:returns: Dictionary of sequences ``term._cuf1``, and ``term._cub1``,
valued as ``KEY_RIGHT``, ``KEY_LEFT`` (when appropriate).
This function supports :func:`get_terminal_sequences` to discover
the preferred input sequence for the left and right application keys.
It is necessary to check the value of these sequences to ensure we do not
use ``u' '`` and ``u'\b'`` for ``KEY_RIGHT`` and ``KEY_LEFT``,
preferring their true application key sequence, instead.
"""
# pylint: disable=protected-access
keymap = {}
if term._cuf1 and term._cuf1 != u' ':
keymap[term._cuf1] = curses.KEY_RIGHT
if term._cub1 and term._cub1 != u'\b':
keymap[term._cub1] = curses.KEY_LEFT
return keymap
def get_keyboard_sequences(term):
r"""
Return mapping of keyboard sequences paired by keycodes.
:arg blessed.Terminal term: :class:`~.Terminal` instance.
:returns: mapping of keyboard unicode sequences paired by keycodes
as integer. This is used as the argument ``mapper`` to
the supporting function :func:`resolve_sequence`.
:rtype: OrderedDict
Initialize and return a keyboard map and sequence lookup table,
(sequence, keycode) from :class:`~.Terminal` instance ``term``,
where ``sequence`` is a multibyte input sequence of unicode
characters, such as ``u'\x1b[D'``, and ``keycode`` is an integer
value, matching curses constant such as term.KEY_LEFT.
The return value is an OrderedDict instance, with their keys
sorted longest-first.
"""
# A small gem from curses.has_key that makes this all possible,
# _capability_names: a lookup table of terminal capability names for
# keyboard sequences (fe. kcub1, key_left), keyed by the values of
# constants found beginning with KEY_ in the main curses module
# (such as KEY_LEFT).
#
# latin1 encoding is used so that bytes in 8-bit range of 127-255
# have equivalent chr() and unichr() values, so that the sequence
# of a kermit or avatar terminal, for example, remains unchanged
# in its byte sequence values even when represented by unicode.
#
sequence_map = dict((
(seq.decode('latin1'), val)
for (seq, val) in (
(curses.tigetstr(cap), val)
for (val, cap) in capability_names.items()
) if seq
) if term.does_styling else ())
sequence_map.update(_alternative_left_right(term))
sequence_map.update(DEFAULT_SEQUENCE_MIXIN)
# This is for fast lookup matching of sequences, preferring
# full-length sequence such as ('\x1b[D', KEY_LEFT)
# over simple sequences such as ('\x1b', KEY_EXIT).
return OrderedDict((
(seq, sequence_map[seq]) for seq in sorted(
sequence_map.keys(), key=len, reverse=True)))
def get_leading_prefixes(sequences):
"""
Return a set of proper prefixes for given sequence of strings.
:arg iterable sequences
:rtype: set
:return: Set of all string prefixes
Given an iterable of strings, all textparts leading up to the final
string is returned as a unique set. This function supports the
:meth:`~.Terminal.inkey` method by determining whether the given
input is a sequence that **may** lead to a final matching pattern.
>>> prefixes(['abc', 'abdf', 'e', 'jkl'])
set([u'a', u'ab', u'abd', u'j', u'jk'])
"""
return {seq[:i] for seq in sequences for i in range(1, len(seq))}
def resolve_sequence(text, mapper, codes):
r"""
Return a single :class:`Keystroke` instance for given sequence ``text``.
:arg str text: string of characters received from terminal input stream.
:arg OrderedDict mapper: unicode multibyte sequences, such as ``u'\x1b[D'``
paired by their integer value (260)
:arg dict codes: a :type:`dict` of integer values (such as 260) paired
by their mnemonic name, such as ``'KEY_LEFT'``.
:rtype: Keystroke
:returns: Keystroke instance for the given sequence
The given ``text`` may extend beyond a matching sequence, such as
``u\x1b[Dxxx`` returns a :class:`Keystroke` instance of attribute
:attr:`Keystroke.sequence` valued only ``u\x1b[D``. It is up to
calls to determine that ``xxx`` remains unresolved.
"""
for sequence, code in mapper.items():
if text.startswith(sequence):
return Keystroke(ucs=sequence, code=code, name=codes[code])
return Keystroke(ucs=text and text[0] or u'')
def _time_left(stime, timeout):
"""
Return time remaining since ``stime`` before given ``timeout``.
This function assists determining the value of ``timeout`` for
class method :meth:`~.Terminal.kbhit` and similar functions.
:arg float stime: starting time for measurement
:arg float timeout: timeout period, may be set to None to
indicate no timeout (where None is always returned).
:rtype: float or int
:returns: time remaining as float. If no time is remaining,
then the integer ``0`` is returned.
"""
return max(0, timeout - (time.time() - stime)) if timeout else timeout
def _read_until(term, pattern, timeout):
"""
Convenience read-until-pattern function, supporting :meth:`~.get_location`.
:arg blessed.Terminal term: :class:`~.Terminal` instance.
:arg float timeout: timeout period, may be set to None to indicate no
timeout (where 0 is always returned).
:arg str pattern: target regular expression pattern to seek.
:rtype: tuple
:returns: tuple in form of ``(match, str)``, *match*
may be :class:`re.MatchObject` if pattern is discovered
in input stream before timeout has elapsed, otherwise
None. ``str`` is any remaining text received exclusive
of the matching pattern).
The reason a tuple containing non-matching data is returned, is that the
consumer should push such data back into the input buffer by
:meth:`~.Terminal.ungetch` if any was received.
For example, when a user is performing rapid input keystrokes while its
terminal emulator surreptitiously responds to this in-band sequence, we
must ensure any such keyboard data is well-received by the next call to
term.inkey() without delay.
"""
stime = time.time()
match, buf = None, u''
# first, buffer all pending data. pexpect library provides a
# 'searchwindowsize' attribute that limits this memory region. We're not
# concerned about OOM conditions: only (human) keyboard input and terminal
# response sequences are expected.
while True: # pragma: no branch
# block as long as necessary to ensure at least one character is
# received on input or remaining timeout has elapsed.
ucs = term.inkey(timeout=_time_left(stime, timeout))
# while the keyboard buffer is "hot" (has input), we continue to
# aggregate all awaiting data. We do this to ensure slow I/O
# calls do not unnecessarily give up within the first 'while' loop
# for short timeout periods.
while ucs:
buf += ucs
ucs = term.inkey(timeout=0)
match = re.search(pattern=pattern, string=buf)
if match is not None:
# match
break
if timeout is not None and not _time_left(stime, timeout):
# timeout
break
return match, buf
#: Though we may determine *keynames* and codes for keyboard input that
#: generate multibyte sequences, it is also especially useful to aliases
#: a few basic ASCII characters such as ``KEY_TAB`` instead of ``u'\t'`` for
#: uniformity.
#:
#: Furthermore, many key-names for application keys enabled only by context
#: manager :meth:`~.Terminal.keypad` are surprisingly absent. We inject them
#: here directly into the curses module.
_CURSES_KEYCODE_ADDINS = (
'TAB',
'KP_MULTIPLY',
'KP_ADD',
'KP_SEPARATOR',
'KP_SUBTRACT',
'KP_DECIMAL',
'KP_DIVIDE',
'KP_EQUAL',
'KP_0',
'KP_1',
'KP_2',
'KP_3',
'KP_4',
'KP_5',
'KP_6',
'KP_7',
'KP_8',
'KP_9')
_LASTVAL = max(get_curses_keycodes().values())
for keycode_name in _CURSES_KEYCODE_ADDINS:
_LASTVAL += 1
globals()['KEY_' + keycode_name] = _LASTVAL
#: In a perfect world, terminal emulators would always send exactly what
#: the terminfo(5) capability database plans for them, accordingly by the
#: value of the ``TERM`` name they declare.
#:
#: But this isn't a perfect world. Many vt220-derived terminals, such as
#: those declaring 'xterm', will continue to send vt220 codes instead of
#: their native-declared codes, for backwards-compatibility.
#:
#: This goes for many: rxvt, putty, iTerm.
#:
#: These "mixins" are used for *all* terminals, regardless of their type.
#:
#: Furthermore, curses does not provide sequences sent by the keypad,
#: at least, it does not provide a way to distinguish between keypad 0
#: and numeric 0.
DEFAULT_SEQUENCE_MIXIN = (
# these common control characters (and 127, ctrl+'?') mapped to
# an application key definition.
(six.unichr(10), curses.KEY_ENTER),
(six.unichr(13), curses.KEY_ENTER),
(six.unichr(8), curses.KEY_BACKSPACE),
(six.unichr(9), KEY_TAB), # noqa # pylint: disable=undefined-variable
(six.unichr(27), curses.KEY_EXIT),
(six.unichr(127), curses.KEY_BACKSPACE),
(u"\x1b[A", curses.KEY_UP),
(u"\x1b[B", curses.KEY_DOWN),
(u"\x1b[C", curses.KEY_RIGHT),
(u"\x1b[D", curses.KEY_LEFT),
(u"\x1b[1;2A", curses.KEY_SR),
(u"\x1b[1;2B", curses.KEY_SF),
(u"\x1b[1;2C", curses.KEY_SRIGHT),
(u"\x1b[1;2D", curses.KEY_SLEFT),
(u"\x1b[F", curses.KEY_END),
(u"\x1b[H", curses.KEY_HOME),
# not sure where these are from .. please report
(u"\x1b[K", curses.KEY_END),
(u"\x1b[U", curses.KEY_NPAGE),
(u"\x1b[V", curses.KEY_PPAGE),
# keys sent after term.smkx (keypad_xmit) is emitted, source:
# http://www.xfree86.org/current/ctlseqs.html#PC-Style%20Function%20Keys
# http://fossies.org/linux/rxvt/doc/rxvtRef.html#KeyCodes
#
# keypad, numlock on
(u"\x1bOM", curses.KEY_ENTER), # noqa return
(u"\x1bOj", KEY_KP_MULTIPLY), # noqa * # pylint: disable=undefined-variable
(u"\x1bOk", KEY_KP_ADD), # noqa + # pylint: disable=undefined-variable
(u"\x1bOl", KEY_KP_SEPARATOR), # noqa , # pylint: disable=undefined-variable
(u"\x1bOm", KEY_KP_SUBTRACT), # noqa - # pylint: disable=undefined-variable
(u"\x1bOn", KEY_KP_DECIMAL), # noqa . # pylint: disable=undefined-variable
(u"\x1bOo", KEY_KP_DIVIDE), # noqa / # pylint: disable=undefined-variable
(u"\x1bOX", KEY_KP_EQUAL), # noqa = # pylint: disable=undefined-variable
(u"\x1bOp", KEY_KP_0), # noqa 0 # pylint: disable=undefined-variable
(u"\x1bOq", KEY_KP_1), # noqa 1 # pylint: disable=undefined-variable
(u"\x1bOr", KEY_KP_2), # noqa 2 # pylint: disable=undefined-variable
(u"\x1bOs", KEY_KP_3), # noqa 3 # pylint: disable=undefined-variable
(u"\x1bOt", KEY_KP_4), # noqa 4 # pylint: disable=undefined-variable
(u"\x1bOu", KEY_KP_5), # noqa 5 # pylint: disable=undefined-variable
(u"\x1bOv", KEY_KP_6), # noqa 6 # pylint: disable=undefined-variable
(u"\x1bOw", KEY_KP_7), # noqa 7 # pylint: disable=undefined-variable
(u"\x1bOx", KEY_KP_8), # noqa 8 # pylint: disable=undefined-variable
(u"\x1bOy", KEY_KP_9), # noqa 9 # pylint: disable=undefined-variable
# keypad, numlock off
(u"\x1b[1~", curses.KEY_FIND), # find
(u"\x1b[2~", curses.KEY_IC), # insert (0)
(u"\x1b[3~", curses.KEY_DC), # delete (.), "Execute"
(u"\x1b[4~", curses.KEY_SELECT), # select
(u"\x1b[5~", curses.KEY_PPAGE), # pgup (9)
(u"\x1b[6~", curses.KEY_NPAGE), # pgdown (3)
(u"\x1b[7~", curses.KEY_HOME), # home
(u"\x1b[8~", curses.KEY_END), # end
(u"\x1b[OA", curses.KEY_UP), # up (8)
(u"\x1b[OB", curses.KEY_DOWN), # down (2)
(u"\x1b[OC", curses.KEY_RIGHT), # right (6)
(u"\x1b[OD", curses.KEY_LEFT), # left (4)
(u"\x1b[OF", curses.KEY_END), # end (1)
(u"\x1b[OH", curses.KEY_HOME), # home (7)
# The vt220 placed F1-F4 above the keypad, in place of actual
# F1-F4 were local functions (hold screen, print screen,
# set up, data/talk, break).
(u"\x1bOP", curses.KEY_F1),
(u"\x1bOQ", curses.KEY_F2),
(u"\x1bOR", curses.KEY_F3),
(u"\x1bOS", curses.KEY_F4),
)
#: Override mixins for a few curses constants with easier
#: mnemonics: there may only be a 1:1 mapping when only a
#: keycode (int) is given, where these phrases are preferred.
CURSES_KEYCODE_OVERRIDE_MIXIN = (
('KEY_DELETE', curses.KEY_DC),
('KEY_INSERT', curses.KEY_IC),
('KEY_PGUP', curses.KEY_PPAGE),
('KEY_PGDOWN', curses.KEY_NPAGE),
('KEY_ESCAPE', curses.KEY_EXIT),
('KEY_SUP', curses.KEY_SR),
('KEY_SDOWN', curses.KEY_SF),
('KEY_UP_LEFT', curses.KEY_A1),
('KEY_UP_RIGHT', curses.KEY_A3),
('KEY_CENTER', curses.KEY_B2),
('KEY_BEGIN', curses.KEY_BEG),
)
__all__ = ('Keystroke', 'get_keyboard_codes', 'get_keyboard_sequences',)

View File

@@ -0,0 +1,28 @@
# std imports
from typing import Set, Dict, Type, Mapping, TypeVar, Iterable, Optional, OrderedDict
# local
from .terminal import Terminal
_T = TypeVar("_T")
class Keystroke(str):
def __new__(
cls: Type[_T],
ucs: str = ...,
code: Optional[int] = ...,
name: Optional[str] = ...,
) -> _T: ...
@property
def is_sequence(self) -> bool: ...
@property
def name(self) -> Optional[str]: ...
@property
def code(self) -> Optional[int]: ...
def get_keyboard_codes() -> Dict[int, str]: ...
def get_keyboard_sequences(term: Terminal) -> OrderedDict[str, int]: ...
def get_leading_prefixes(sequences: Iterable[str]) -> Set[str]: ...
def resolve_sequence(
text: str, mapper: Mapping[str, int], codes: Mapping[int, str]
) -> Keystroke: ...

View File

@@ -0,0 +1,461 @@
# -*- coding: utf-8 -*-
"""Module providing 'sequence awareness'."""
# std imports
import re
import math
import textwrap
# 3rd party
import six
from wcwidth import wcwidth
# local
from blessed._capabilities import CAPABILITIES_CAUSE_MOVEMENT
__all__ = ('Sequence', 'SequenceTextWrapper', 'iter_parse', 'measure_length')
class Termcap(object):
"""Terminal capability of given variable name and pattern."""
def __init__(self, name, pattern, attribute):
"""
Class initializer.
:arg str name: name describing capability.
:arg str pattern: regular expression string.
:arg str attribute: :class:`~.Terminal` attribute used to build
this terminal capability.
"""
self.name = name
self.pattern = pattern
self.attribute = attribute
self._re_compiled = None
def __repr__(self):
# pylint: disable=redundant-keyword-arg
return '<Termcap {self.name}:{self.pattern!r}>'.format(self=self)
@property
def named_pattern(self):
"""Regular expression pattern for capability with named group."""
# pylint: disable=redundant-keyword-arg
return '(?P<{self.name}>{self.pattern})'.format(self=self)
@property
def re_compiled(self):
"""Compiled regular expression pattern for capability."""
if self._re_compiled is None:
self._re_compiled = re.compile(self.pattern)
return self._re_compiled
@property
def will_move(self):
"""Whether capability causes cursor movement."""
return self.name in CAPABILITIES_CAUSE_MOVEMENT
def horizontal_distance(self, text):
"""
Horizontal carriage adjusted by capability, may be negative.
:rtype: int
:arg str text: for capabilities *parm_left_cursor*,
*parm_right_cursor*, provide the matching sequence
text, its interpreted distance is returned.
:returns: 0 except for matching '
"""
value = {
'cursor_left': -1,
'backspace': -1,
'cursor_right': 1,
'tab': 8,
'ascii_tab': 8,
}.get(self.name)
if value is not None:
return value
unit = {
'parm_left_cursor': -1,
'parm_right_cursor': 1
}.get(self.name)
if unit is not None:
value = int(self.re_compiled.match(text).group(1))
return unit * value
return 0
# pylint: disable=too-many-arguments
@classmethod
def build(cls, name, capability, attribute, nparams=0,
numeric=99, match_grouped=False, match_any=False,
match_optional=False):
r"""
Class factory builder for given capability definition.
:arg str name: Variable name given for this pattern.
:arg str capability: A unicode string representing a terminal
capability to build for. When ``nparams`` is non-zero, it
must be a callable unicode string (such as the result from
``getattr(term, 'bold')``.
:arg str attribute: The terminfo(5) capability name by which this
pattern is known.
:arg int nparams: number of positional arguments for callable.
:arg int numeric: Value to substitute into capability to when generating pattern
:arg bool match_grouped: If the numeric pattern should be
grouped, ``(\d+)`` when ``True``, ``\d+`` default.
:arg bool match_any: When keyword argument ``nparams`` is given,
*any* numeric found in output is suitable for building as
pattern ``(\d+)``. Otherwise, only the first matching value of
range *(numeric - 1)* through *(numeric + 1)* will be replaced by
pattern ``(\d+)`` in builder.
:arg bool match_optional: When ``True``, building of numeric patterns
containing ``(\d+)`` will be built as optional, ``(\d+)?``.
:rtype: blessed.sequences.Termcap
:returns: Terminal capability instance for given capability definition
"""
_numeric_regex = r'\d+'
if match_grouped:
_numeric_regex = r'(\d+)'
if match_optional:
_numeric_regex = r'(\d+)?'
numeric = 99 if numeric is None else numeric
# basic capability attribute, not used as a callable
if nparams == 0:
return cls(name, re.escape(capability), attribute)
# a callable capability accepting numeric argument
_outp = re.escape(capability(*(numeric,) * nparams))
if not match_any:
for num in range(numeric - 1, numeric + 2):
if str(num) in _outp:
pattern = _outp.replace(str(num), _numeric_regex)
return cls(name, pattern, attribute)
if match_grouped:
pattern = re.sub(r'(\d+)', lambda x: _numeric_regex, _outp)
else:
pattern = re.sub(r'\d+', lambda x: _numeric_regex, _outp)
return cls(name, pattern, attribute)
class SequenceTextWrapper(textwrap.TextWrapper):
"""Docstring overridden."""
def __init__(self, width, term, **kwargs):
"""
Class initializer.
This class supports the :meth:`~.Terminal.wrap` method.
"""
self.term = term
textwrap.TextWrapper.__init__(self, width, **kwargs)
def _wrap_chunks(self, chunks):
"""
Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`.
:raises ValueError: ``self.width`` is not a positive integer
:rtype: list
:returns: text chunks adjusted for width
This simply ensures that word boundaries are not broken mid-sequence, as standard python
textwrap would incorrectly determine the length of a string containing sequences, and may
also break consider sequences part of a "word" that may be broken by hyphen (``-``), where
this implementation corrects both.
"""
lines = []
if self.width <= 0 or not isinstance(self.width, int):
raise ValueError(
"invalid width {0!r}({1!r}) (must be integer > 0)"
.format(self.width, type(self.width)))
term = self.term
drop_whitespace = not hasattr(self, 'drop_whitespace'
) or self.drop_whitespace
chunks.reverse()
while chunks:
cur_line = []
cur_len = 0
indent = self.subsequent_indent if lines else self.initial_indent
width = self.width - len(indent)
if drop_whitespace and (
Sequence(chunks[-1], term).strip() == '' and lines):
del chunks[-1]
while chunks:
chunk_len = Sequence(chunks[-1], term).length()
if cur_len + chunk_len > width:
break
cur_line.append(chunks.pop())
cur_len += chunk_len
if chunks and Sequence(chunks[-1], term).length() > width:
self._handle_long_word(chunks, cur_line, cur_len, width)
if drop_whitespace and (
cur_line and Sequence(cur_line[-1], term).strip() == ''):
del cur_line[-1]
if cur_line:
lines.append(indent + u''.join(cur_line))
return lines
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
"""
Sequence-aware :meth:`textwrap.TextWrapper._handle_long_word`.
This simply ensures that word boundaries are not broken mid-sequence, as standard python
textwrap would incorrectly determine the length of a string containing sequences, and may
also break consider sequences part of a "word" that may be broken by hyphen (``-``), where
this implementation corrects both.
"""
# Figure out when indent is larger than the specified width, and make
# sure at least one character is stripped off on every pass
space_left = 1 if width < 1 else width - cur_len
# If we're allowed to break long words, then do so: put as much
# of the next chunk onto the current line as will fit.
if self.break_long_words:
term = self.term
chunk = reversed_chunks[-1]
idx = nxt = 0
for text, _ in iter_parse(term, chunk):
nxt += len(text)
if Sequence(chunk[:nxt], term).length() > space_left:
break
idx = nxt
cur_line.append(chunk[:idx])
reversed_chunks[-1] = chunk[idx:]
# Otherwise, we have to preserve the long word intact. Only add
# it to the current line if there's nothing already there --
# that minimizes how much we violate the width constraint.
elif not cur_line:
cur_line.append(reversed_chunks.pop())
# If we're not allowed to break long words, and there's already
# text on the current line, do nothing. Next time through the
# main loop of _wrap_chunks(), we'll wind up here again, but
# cur_len will be zero, so the next line will be entirely
# devoted to the long word that we can't handle right now.
SequenceTextWrapper.__doc__ = textwrap.TextWrapper.__doc__
class Sequence(six.text_type):
"""
A "sequence-aware" version of the base :class:`str` class.
This unicode-derived class understands the effect of escape sequences
of printable length, allowing a properly implemented :meth:`rjust`,
:meth:`ljust`, :meth:`center`, and :meth:`length`.
"""
def __new__(cls, sequence_text, term):
# pylint: disable = missing-return-doc, missing-return-type-doc
"""
Class constructor.
:arg str sequence_text: A string that may contain sequences.
:arg blessed.Terminal term: :class:`~.Terminal` instance.
"""
new = six.text_type.__new__(cls, sequence_text)
new._term = term
return new
def ljust(self, width, fillchar=u' '):
"""
Return string containing sequences, left-adjusted.
:arg int width: Total width given to left-adjust ``text``. If
unspecified, the width of the attached terminal is used (default).
:arg str fillchar: String for padding right-of ``text``.
:returns: String of ``text``, left-aligned by ``width``.
:rtype: str
"""
rightside = fillchar * int(
(max(0.0, float(width.__index__() - self.length()))) / float(len(fillchar)))
return u''.join((self, rightside))
def rjust(self, width, fillchar=u' '):
"""
Return string containing sequences, right-adjusted.
:arg int width: Total width given to right-adjust ``text``. If
unspecified, the width of the attached terminal is used (default).
:arg str fillchar: String for padding left-of ``text``.
:returns: String of ``text``, right-aligned by ``width``.
:rtype: str
"""
leftside = fillchar * int(
(max(0.0, float(width.__index__() - self.length()))) / float(len(fillchar)))
return u''.join((leftside, self))
def center(self, width, fillchar=u' '):
"""
Return string containing sequences, centered.
:arg int width: Total width given to center ``text``. If
unspecified, the width of the attached terminal is used (default).
:arg str fillchar: String for padding left and right-of ``text``.
:returns: String of ``text``, centered by ``width``.
:rtype: str
"""
split = max(0.0, float(width.__index__()) - self.length()) / 2
leftside = fillchar * int(
(max(0.0, math.floor(split))) / float(len(fillchar)))
rightside = fillchar * int(
(max(0.0, math.ceil(split))) / float(len(fillchar)))
return u''.join((leftside, self, rightside))
def truncate(self, width):
"""
Truncate a string in a sequence-aware manner.
Any printable characters beyond ``width`` are removed, while all
sequences remain in place. Horizontal Sequences are first expanded
by :meth:`padd`.
:arg int width: The printable width to truncate the string to.
:rtype: str
:returns: String truncated to at most ``width`` printable characters.
"""
output = ""
current_width = 0
target_width = width.__index__()
parsed_seq = iter_parse(self._term, self.padd())
# Retain all text until non-cap width reaches desired width
for text, cap in parsed_seq:
if not cap:
# use wcwidth clipped to 0 because it can sometimes return -1
current_width += max(wcwidth(text), 0)
if current_width > target_width:
break
output += text
# Return with remaining caps appended
return output + ''.join(text for text, cap in parsed_seq if cap)
def length(self):
r"""
Return the printable length of string containing sequences.
Strings containing ``term.left`` or ``\b`` will cause "overstrike",
but a length less than 0 is not ever returned. So ``_\b+`` is a
length of 1 (displays as ``+``), but ``\b`` alone is simply a
length of 0.
Some characters may consume more than one cell, mainly those CJK
Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode
as half or full-width characters.
For example:
>>> from blessed import Terminal
>>> from blessed.sequences import Sequence
>>> term = Terminal()
>>> msg = term.clear + term.red(u'コンニチハ')
>>> Sequence(msg, term).length()
10
.. note:: Although accounted for, strings containing sequences such
as ``term.clear`` will not give accurate returns, it is not
considered lengthy (a length of 0).
"""
# because control characters may return -1, "clip" their length to 0.
return sum(max(wcwidth(w_char), 0) for w_char in self.padd(strip=True))
def strip(self, chars=None):
"""
Return string of sequences, leading and trailing whitespace removed.
:arg str chars: Remove characters in chars instead of whitespace.
:rtype: str
:returns: string of sequences with leading and trailing whitespace removed.
"""
return self.strip_seqs().strip(chars)
def lstrip(self, chars=None):
"""
Return string of all sequences and leading whitespace removed.
:arg str chars: Remove characters in chars instead of whitespace.
:rtype: str
:returns: string of sequences with leading removed.
"""
return self.strip_seqs().lstrip(chars)
def rstrip(self, chars=None):
"""
Return string of all sequences and trailing whitespace removed.
:arg str chars: Remove characters in chars instead of whitespace.
:rtype: str
:returns: string of sequences with trailing removed.
"""
return self.strip_seqs().rstrip(chars)
def strip_seqs(self):
"""
Return ``text`` stripped of only its terminal sequences.
:rtype: str
:returns: Text with terminal sequences removed
"""
return self.padd(strip=True)
def padd(self, strip=False):
"""
Return non-destructive horizontal movement as destructive spacing.
:arg bool strip: Strip terminal sequences
:rtype: str
:returns: Text adjusted for horizontal movement
"""
outp = ''
for text, cap in iter_parse(self._term, self):
if not cap:
outp += text
continue
value = cap.horizontal_distance(text)
if value > 0:
outp += ' ' * value
elif value < 0:
outp = outp[:value]
elif not strip:
outp += text
return outp
def iter_parse(term, text):
"""
Generator yields (text, capability) for characters of ``text``.
value for ``capability`` may be ``None``, where ``text`` is
:class:`str` of length 1. Otherwise, ``text`` is a full
matching sequence of given capability.
"""
for match in term._caps_compiled_any.finditer(text): # pylint: disable=protected-access
name = match.lastgroup
value = match.group(name)
if name == 'MISMATCH':
yield (value, None)
else:
yield value, term.caps[name]
def measure_length(text, term):
"""
.. deprecated:: 1.12.0.
:rtype: int
:returns: Length of the first sequence in the string
"""
try:
text, capability = next(iter_parse(term, text))
if capability:
return len(text)
except StopIteration:
return 0
return 0

View File

@@ -0,0 +1,55 @@
# std imports
import textwrap
from typing import Any, Type, Tuple, Pattern, TypeVar, Iterator, Optional, SupportsIndex
# local
from .terminal import Terminal
_T = TypeVar("_T")
class Termcap:
name: str = ...
pattern: str = ...
attribute: str = ...
def __init__(self, name: str, pattern: str, attribute: str) -> None: ...
@property
def named_pattern(self) -> str: ...
@property
def re_compiled(self) -> Pattern[str]: ...
@property
def will_move(self) -> bool: ...
def horizontal_distance(self, text: str) -> int: ...
@classmethod
def build(
cls,
name: str,
capability: str,
attribute: str,
nparams: int = ...,
numeric: int = ...,
match_grouped: bool = ...,
match_any: bool = ...,
match_optional: bool = ...,
) -> "Termcap": ...
class SequenceTextWrapper(textwrap.TextWrapper):
term: Terminal = ...
def __init__(self, width: int, term: Terminal, **kwargs: Any) -> None: ...
class Sequence(str):
def __new__(cls: Type[_T], sequence_text: str, term: Terminal) -> _T: ...
def ljust(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
def rjust(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
def center(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
def truncate(self, width: SupportsIndex) -> str: ...
def length(self) -> int: ...
def strip(self, chars: Optional[str] = ...) -> str: ...
def lstrip(self, chars: Optional[str] = ...) -> str: ...
def rstrip(self, chars: Optional[str] = ...) -> str: ...
def strip_seqs(self) -> str: ...
def padd(self, strip: bool = ...) -> str: ...
def iter_parse(
term: Terminal, text: str
) -> Iterator[Tuple[str, Optional[Termcap]]]: ...
def measure_length(text: str, term: Terminal) -> int: ...

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
# std imports
from typing import IO, Any, List, Tuple, Union, Optional, OrderedDict, SupportsIndex, ContextManager
# local
from .keyboard import Keystroke
from .sequences import Termcap
from .formatters import (FormattingString,
NullCallableString,
ParameterizingString,
FormattingOtherString)
HAS_TTY: bool
class Terminal:
caps: OrderedDict[str, Termcap]
errors: List[str] = ...
def __init__(
self,
kind: Optional[str] = ...,
stream: Optional[IO[str]] = ...,
force_styling: bool = ...,
) -> None: ...
def __getattr__(
self, attr: str
) -> Union[NullCallableString, ParameterizingString, FormattingString]: ...
@property
def kind(self) -> str: ...
@property
def does_styling(self) -> bool: ...
@property
def is_a_tty(self) -> bool: ...
@property
def height(self) -> int: ...
@property
def width(self) -> int: ...
@property
def pixel_height(self) -> int: ...
@property
def pixel_width(self) -> int: ...
def location(
self, x: Optional[int] = ..., y: Optional[int] = ...
) -> ContextManager[None]: ...
def get_location(self, timeout: Optional[float] = ...) -> Tuple[int, int]: ...
def get_fgcolor(self, timeout: Optional[float] = ...) -> Tuple[int, int, int]: ...
def get_bgcolor(self, timeout: Optional[float] = ...) -> Tuple[int, int, int]: ...
def fullscreen(self) -> ContextManager[None]: ...
def hidden_cursor(self) -> ContextManager[None]: ...
def move_xy(self, x: int, y: int) -> ParameterizingString: ...
def move_yx(self, y: int, x: int) -> ParameterizingString: ...
@property
def move_left(self) -> FormattingOtherString: ...
@property
def move_right(self) -> FormattingOtherString: ...
@property
def move_up(self) -> FormattingOtherString: ...
@property
def move_down(self) -> FormattingOtherString: ...
@property
def color(self) -> Union[NullCallableString, ParameterizingString]: ...
def color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ...
@property
def on_color(self) -> Union[NullCallableString, ParameterizingString]: ...
def on_color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ...
def formatter(self, value: str) -> Union[NullCallableString, FormattingString]: ...
def rgb_downconvert(self, red: int, green: int, blue: int) -> int: ...
@property
def normal(self) -> str: ...
def link(self, url: str, text: str, url_id: str = ...) -> str: ...
@property
def stream(self) -> IO[str]: ...
@property
def number_of_colors(self) -> int: ...
@number_of_colors.setter
def number_of_colors(self, value: int) -> None: ...
@property
def color_distance_algorithm(self) -> str: ...
@color_distance_algorithm.setter
def color_distance_algorithm(self, value: str) -> None: ...
def ljust(
self, text: str, width: Optional[SupportsIndex] = ..., fillchar: str = ...
) -> str: ...
def rjust(
self, text: str, width: Optional[SupportsIndex] = ..., fillchar: str = ...
) -> str: ...
def center(
self, text: str, width: Optional[SupportsIndex] = ..., fillchar: str = ...
) -> str: ...
def truncate(self, text: str, width: Optional[SupportsIndex] = ...) -> str: ...
def length(self, text: str) -> int: ...
def strip(self, text: str, chars: Optional[str] = ...) -> str: ...
def rstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
def lstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
def strip_seqs(self, text: str) -> str: ...
def split_seqs(self, text: str, maxsplit: int) -> List[str]: ...
def wrap(
self, text: str, width: Optional[int] = ..., **kwargs: Any
) -> List[str]: ...
def getch(self) -> str: ...
def ungetch(self, text: str) -> None: ...
def kbhit(self, timeout: Optional[float] = ...) -> bool: ...
def cbreak(self) -> ContextManager[None]: ...
def raw(self) -> ContextManager[None]: ...
def keypad(self) -> ContextManager[None]: ...
def inkey(
self, timeout: Optional[float] = ..., esc_delay: float = ...
) -> Keystroke: ...
class WINSZ: ...

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
"""Module containing Windows version of :class:`Terminal`."""
from __future__ import absolute_import
# std imports
import time
import msvcrt # pylint: disable=import-error
import contextlib
# 3rd party
from jinxed import win32 # pylint: disable=import-error
# local
from .terminal import WINSZ
from .terminal import Terminal as _Terminal
class Terminal(_Terminal):
"""Windows subclass of :class:`Terminal`."""
def getch(self):
r"""
Read, decode, and return the next byte from the keyboard stream.
:rtype: unicode
:returns: a single unicode character, or ``u''`` if a multi-byte
sequence has not yet been fully received.
For versions of Windows 10.0.10586 and later, the console is expected
to be in ENABLE_VIRTUAL_TERMINAL_INPUT mode and the default method is
called.
For older versions of Windows, msvcrt.getwch() is used. If the received
character is ``\x00`` or ``\xe0``, the next character is
automatically retrieved.
"""
if win32.VTMODE_SUPPORTED:
return super(Terminal, self).getch()
rtn = msvcrt.getwch()
if rtn in ('\x00', '\xe0'):
rtn += msvcrt.getwch()
return rtn
def kbhit(self, timeout=None):
"""
Return whether a keypress has been detected on the keyboard.
This method is used by :meth:`inkey` to determine if a byte may
be read using :meth:`getch` without blocking. This is implemented
by wrapping msvcrt.kbhit() in a timeout.
:arg float timeout: When ``timeout`` is 0, this call is
non-blocking, otherwise blocking indefinitely until keypress
is detected when None (default). When ``timeout`` is a
positive number, returns after ``timeout`` seconds have
elapsed (float).
:rtype: bool
:returns: True if a keypress is awaiting to be read on the keyboard
attached to this terminal.
"""
end = time.time() + (timeout or 0)
while True:
if msvcrt.kbhit():
return True
if timeout is not None and end < time.time():
break
time.sleep(0.01) # Sleep to reduce CPU load
return False
@staticmethod
def _winsize(fd):
"""
Return named tuple describing size of the terminal by ``fd``.
:arg int fd: file descriptor queries for its window size.
:rtype: WINSZ
:returns: named tuple describing size of the terminal
WINSZ is a :class:`collections.namedtuple` instance, whose structure
directly maps to the return value of the :const:`termios.TIOCGWINSZ`
ioctl return value. The return parameters are:
- ``ws_row``: width of terminal by its number of character cells.
- ``ws_col``: height of terminal by its number of character cells.
- ``ws_xpixel``: width of terminal by pixels (not accurate).
- ``ws_ypixel``: height of terminal by pixels (not accurate).
"""
window = win32.get_terminal_size(fd)
return WINSZ(ws_row=window.lines, ws_col=window.columns,
ws_xpixel=0, ws_ypixel=0)
@contextlib.contextmanager
def cbreak(self):
"""
Allow each keystroke to be read immediately after it is pressed.
This is a context manager for ``jinxed.w32.setcbreak()``.
.. note:: You must explicitly print any user input you would like
displayed. If you provide any kind of editing, you must handle
backspace and other line-editing control functions in this mode
as well!
**Normally**, characters received from the keyboard cannot be read
by Python until the *Return* key is pressed. Also known as *cooked* or
*canonical input* mode, it allows the tty driver to provide
line-editing before shuttling the input to your program and is the
(implicit) default terminal mode set by most unix shells before
executing programs.
"""
if self._keyboard_fd is not None:
filehandle = msvcrt.get_osfhandle(self._keyboard_fd)
# Save current terminal mode:
save_mode = win32.get_console_mode(filehandle)
save_line_buffered = self._line_buffered
win32.setcbreak(filehandle)
try:
self._line_buffered = False
yield
finally:
win32.set_console_mode(filehandle, save_mode)
self._line_buffered = save_line_buffered
else:
yield
@contextlib.contextmanager
def raw(self):
"""
A context manager for ``jinxed.w32.setcbreak()``.
Although both :meth:`break` and :meth:`raw` modes allow each keystroke
to be read immediately after it is pressed, Raw mode disables
processing of input and output.
In cbreak mode, special input characters such as ``^C`` are
interpreted by the terminal driver and excluded from the stdin stream.
In raw mode these values are receive by the :meth:`inkey` method.
"""
if self._keyboard_fd is not None:
filehandle = msvcrt.get_osfhandle(self._keyboard_fd)
# Save current terminal mode:
save_mode = win32.get_console_mode(filehandle)
save_line_buffered = self._line_buffered
win32.setraw(filehandle)
try:
self._line_buffered = False
yield
finally:
win32.set_console_mode(filehandle, save_mode)
self._line_buffered = save_line_buffered
else:
yield

View File

@@ -0,0 +1,11 @@
# std imports
from typing import Optional, ContextManager
# local
from .terminal import Terminal as _Terminal
class Terminal(_Terminal):
def getch(self) -> str: ...
def kbhit(self, timeout: Optional[float] = ...) -> bool: ...
def cbreak(self) -> ContextManager[None]: ...
def raw(self) -> ContextManager[None]: ...