pax_global_header 0000666 0000000 0000000 00000000064 14565701425 0014523 g ustar 00root root 0000000 0000000 52 comment=14d5e9b608235260607d77880422a3034317181e
python-blessed-1.20.0/ 0000775 0000000 0000000 00000000000 14565701425 0014543 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/.editorconfig 0000664 0000000 0000000 00000000440 14565701425 0017216 0 ustar 00root root 0000000 0000000 root = true
[*.json]
charset = utf-8
tab_width = 4
indent_size = tab
indent_space = space
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
charset = utf-8
tab_width = 4
indent_size = tab
indent_space = space
trim_trailing_whitespace = true
insert_final_newline = true
python-blessed-1.20.0/.github/ 0000775 0000000 0000000 00000000000 14565701425 0016103 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14565701425 0020140 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/.github/workflows/tests.yml 0000664 0000000 0000000 00000006615 14565701425 0022035 0 ustar 00root root 0000000 0000000 name: Tests
on:
push:
pull_request:
release:
schedule:
# Every Thursday at 1 AM
- cron: '0 1 * * 4'
jobs:
Tests:
continue-on-error: ${{ matrix.optional || false }}
runs-on: ${{ matrix.os }}
name: ${{ matrix.label || matrix.python-version }} ${{ startsWith(matrix.os, 'windows') && '(Windows)' || '' }} ${{ matrix.optional && '[OPTIONAL]' }}
strategy:
fail-fast: false
matrix:
include:
- python-version: '3.11'
label: Linting
os: ubuntu-latest
toxenv: docformatter_check,flake8,flake8_tests,isort_check,mypy,sphinx,pydocstyle,pylint,pylint_tests
- python-version: '3.11'
os: ubuntu-latest
toxenv: py311
test_keyboard: 1
test_raw: 1
- python-version: '3.11'
os: windows-latest
toxenv: py311
- python-version: '3.10'
os: ubuntu-latest
toxenv: py310
test_quick: 1
- python-version: '3.9'
os: ubuntu-latest
toxenv: py39
test_quick: 1
- python-version: '3.8'
os: ubuntu-latest
toxenv: py38
test_quick: 1
- python-version: '3.7'
os: ubuntu-latest
toxenv: py37
test_quick: 1
- python-version: '3.6'
os: ubuntu-20.04
toxenv: py36
test_quick: 1
- python-version: '3.5'
os: ubuntu-20.04
toxenv: py35
test_quick: 1
- python-version: '3.4'
os: ubuntu-18.04
toxenv: py34
test_quick: 1
- python-version: '2.7'
os: ubuntu-20.04
toxenv: py27
test_keyboard: 1
test_raw: 1
- python-version: '2.7'
os: windows-latest
toxenv: py27
- python-version: '3.12-dev'
optional: true
os: ubuntu-latest
toxenv: py312
toxpython: 3.12
test_quick: 1
env:
TOXENV: ${{ matrix.toxenv }}
TEST_QUICK: ${{ matrix.test_quick || 0 }}
TEST_KEYBOARD: ${{ matrix.test_keyboard || 0 }}
TEST_RAW: ${{ matrix.test_raw || 0 }}
TOXPYTHON: python${{ matrix.toxpython || matrix.python-version }}
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
run: pip install tox
- name: Collect terminal information
run: tox -e about
- name: Run tox
run: tox
- name: Upload to Codecov
if: ${{ matrix.label != 'linting' }}
# CodeCov Python Uploader
env:
CODECOV_ENV: TEST_QUICK,TEST_KEYBOARD,TEST_RAW
CODECOV_NAME: ${{ matrix.label || matrix.python-version }} ${{ startsWith(matrix.os, 'windows') && '(Windows)' || '' }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: tox -e codecov
# CodeCov GitHub Actions Uploader
# uses: codecov/codecov-action@v2
# with:
# verbose: true
# name: ${{ matrix.label || matrix.python-version }} ${{ startsWith(matrix.os, 'windows') && '(Windows)' || '' }}
# env_vars: TOXENV,TEST_QUICK,TEST_KEYBOARD,TEST_RAW
python-blessed-1.20.0/.gitignore 0000664 0000000 0000000 00000000375 14565701425 0016540 0 ustar 00root root 0000000 0000000 .coverage
._coverage.*
.coverage.*
coverage.xml
.cache
.tox
*.egg-info
*.egg
*.pyc
results*.xml
build
dist
docs/_build
docs/all_the_colors.txt
docs/all_the_keys.txt
docs/_static/rgb
htmlcov
.coveralls.yml
.DS_Store
.*.sw?
.vscode
.python-version
.idea/
python-blessed-1.20.0/.pylintrc 0000664 0000000 0000000 00000001765 14565701425 0016421 0 ustar 00root root 0000000 0000000 [MASTER]
load-plugins=
pylint.extensions.mccabe,
pylint.extensions.check_elif,
pylint.extensions.docparams,
pylint.extensions.overlapping_exceptions,
pylint.extensions.redefined_variable_type
persistent = no
jobs = 0
unsafe-load-any-extension = yes
[MESSAGES CONTROL]
disable=
fixme,
consider-using-f-string, # Python 2
raise-missing-from, # Python 2
redundant-u-string-prefix, # Python 2
super-with-arguments, # Python 2
useless-object-inheritance # Python 2
[FORMAT]
max-line-length: 100
good-names=ks,fd,_,x,y
[PARAMETER_DOCUMENTATION]
default-docstring-type=sphinx
accept-no-raise-doc=no
accept-no-param-doc=yes
accept-no-return-doc=yes
[DESIGN]
max-args=10
max-attributes=7
max-branches=12
max-complexity=11
max-locals=15
max-module-lines=1300
max-parents=7
max-public-methods=20
max-returns=6
max-statements=50
[SIMILARITIES]
ignore-imports=yes
min-similarity-lines=8
[REPORTS]
reports=no
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
python-blessed-1.20.0/.readthedocs.yml 0000664 0000000 0000000 00000000355 14565701425 0017634 0 ustar 00root root 0000000 0000000 # https://docs.readthedocs.io/en/stable/config-file/v2.html
version: 2
sphinx:
configuration: docs/conf.py
formats: all
python:
version: 3.7
install:
- method: setuptools
path: .
- requirements: docs/requirements.txt
python-blessed-1.20.0/.travis.yml 0000664 0000000 0000000 00000003341 14565701425 0016655 0 ustar 00root root 0000000 0000000 language: python
matrix:
fast_finish: true
include:
- python: 3.8
env: TOXENV=about,pydocstyle,pylint,flake8,flake8_tests,mypy,sphinx COVERAGE_ID=travis-ci
- python: 2.7
env: TOXENV=py27,codecov COVERAGE_ID=travis-ci TEST_KEYBOARD=yes TEST_RAW=yes
- python: 3.4
env: TOXENV=py34,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci
- python: 3.5
env: TOXENV=py35,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci
- python: 3.6
env: TOXENV=py36,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci
- python: 3.7
env: TOXENV=py37,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci
- python: 3.8
env: TOXENV=py38,codecov COVERAGE_ID=travis-ci TEST_RAW=yes
- python: 3.9
env: TOXENV=py39,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci TOXPYTHON=3.9
- python: 3.10-dev
env: TOXENV=py310,codecov TEST_QUICK=1 COVERAGE_ID=travis-ci TOXPYTHON=3.10
- python: 2.7
os: windows
language: shell
before_install:
- choco install python2
- python -m pip install --upgrade pip
- python -m pip install tox
env: PATH=/c/Python27:/c/Python27/Scripts:$PATH TOXPYTHON=2.7 TOXENV=py27,codecov COVERAGE_ID=travis-ci TEST_KEYBOARD=no
- python: 3.8
os: windows
language: shell
before_install:
- choco install python --version 3.8.0
- python -m pip install --upgrade pip
- python -m pip install tox
env: PATH=/c/Python38:/c/Python38/Scripts:$PATH TOXENV=py38,codecov COVERAGE_ID=travis-ci TEST_KEYBOARD=no
allow_failures:
- python: 3.10-dev
install:
- pip install tox
script:
- tox
sudo: false
notifications:
email:
recipients:
- contact@jeffquast.com
on_success: change
on_failure: change
python-blessed-1.20.0/CONTRIBUTING.rst 0000664 0000000 0000000 00000002347 14565701425 0017212 0 ustar 00root root 0000000 0000000 Contributing
============
We welcome contributions via GitHub pull requests:
- `Fork a Repo `_
- `Creating a pull request
`_
Developing
----------
Prepare a developer environment. Then, from the blessed code folder::
pip install --editable .
Any changes made in this project folder are then made available to the python
interpreter as the 'blessed' package from any working directory.
Running Tests
~~~~~~~~~~~~~
Install and run tox
::
pip install --upgrade tox
tox
Py.test is used as the test runner, supporting positional arguments, you may
for example use `looponfailing
`_
with python 3.5, stopping at the first failing test case, and looping
(retrying) after a filesystem save is detected::
tox -epy35 -- -fx
The test runner (``tox``) ensures all code and documentation complies with
standard python style guides, pep8 and pep257, as well as various static
analysis tools.
Test Coverage
~~~~~~~~~~~~~
When you contribute a new feature, make sure it is covered by tests.
Likewise, a bug fix should include a test demonstrating the bug.
python-blessed-1.20.0/LICENSE 0000664 0000000 0000000 00000002073 14565701425 0015552 0 ustar 00root root 0000000 0000000 Copyright (c) 2014 Jeff Quast
Copyright (c) 2011 Erik Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
python-blessed-1.20.0/MANIFEST.in 0000664 0000000 0000000 00000000342 14565701425 0016300 0 ustar 00root root 0000000 0000000 graft docs
prune docs/_build
global-exclude *.py[cod] __pycache__
include LICENSE
include version.json
include *.txt
include mypy.ini
include tox.ini
recursive-include blessed *.pyi py.typed
recursive-include tests *.py *.ans
python-blessed-1.20.0/README.rst 0000777 0000000 0000000 00000000000 14565701425 0021042 2docs/intro.rst ustar 00root root 0000000 0000000 python-blessed-1.20.0/bin/ 0000775 0000000 0000000 00000000000 14565701425 0015313 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/bin/bounce.py 0000775 0000000 0000000 00000001470 14565701425 0017145 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""Classic game of tennis."""
# std imports
from math import floor
# local
from blessed import Terminal
def roundxy(x, y):
return int(floor(x)), int(floor(y))
term = Terminal()
x, y, xs, ys = 2, 2, 0.4, 0.3
with term.cbreak(), term.hidden_cursor():
# clear the screen
print(term.home + term.black_on_olivedrab4 + term.clear)
# loop every 20ms
while term.inkey(timeout=0.02) != 'q':
# erase,
txt_erase = term.move_xy(*roundxy(x, y)) + ' '
# bounce,
if x >= (term.width - 1) or x <= 0:
xs *= -1
if y >= term.height or y <= 0:
ys *= -1
# move,
x, y = x + xs, y + ys
# draw !
txt_ball = term.move_xy(*roundxy(x, y)) + '█'
print(txt_erase + txt_ball, end='', flush=True)
python-blessed-1.20.0/bin/cnn.py 0000664 0000000 0000000 00000002657 14565701425 0016455 0 ustar 00root root 0000000 0000000 """Basic example of hyperlinks -- show CNN news site with clickable URL's."""
# std imports
import random
# 3rd party
import requests
# local
# 3rd-party
from bs4 import BeautifulSoup
# local imports
from blessed import Terminal
def embolden(phrase):
# bold some phrases
return phrase.isdigit() or phrase[:1].isupper()
def make_bold(term, text):
# embolden text
return ' '.join(term.bold(phrase) if embolden(phrase) else phrase
for phrase in text.split(' '))
def whitespace_only(term, line):
# return only left-hand whitespace of `line'.
return line[:term.length(line) - term.length(line.lstrip())]
def find_articles(soup):
return (a_link for a_link in soup.find_all('a') if '/article' in a_link.get('href'))
def main():
term = Terminal()
cnn_url = 'https://lite.cnn.io'
soup = BeautifulSoup(requests.get(cnn_url).content, 'html.parser')
textwrap_kwargs = {
'width': term.width - (term.width // 4),
'initial_indent': ' ' * (term.width // 6) + '* ',
'subsequent_indent': (' ' * (term.width // 6)) + ' ' * 2,
}
for a_href in find_articles(soup):
url_id = random.randrange(0, 1 << 24)
for line in term.wrap(make_bold(term, a_href.text), **textwrap_kwargs):
print(whitespace_only(term, line), end='')
print(term.link(cnn_url + a_href.get('href'), line.lstrip(), url_id))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/colorchart.py 0000664 0000000 0000000 00000005056 14565701425 0020033 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
"""
Utility to show X11 colors in 24-bit and downconverted to 256, 16, and 8 colors.
The time to generate the table is displayed to give an indication of how long each algorithm takes
compared to the others.
"""
# std imports
import sys
import timeit
import colorsys
# local
import blessed
from blessed.color import COLOR_DISTANCE_ALGORITHMS
from blessed.colorspace import X11_COLORNAMES_TO_RGB
def sort_colors():
"""Sort colors by HSV value and remove duplicates."""
colors = {}
for color_name, rgb_color in X11_COLORNAMES_TO_RGB.items():
if rgb_color not in colors:
colors[rgb_color] = color_name
return sorted(colors.items(),
key=lambda rgb: colorsys.rgb_to_hsv(*rgb[0]),
reverse=True)
ALGORITHMS = tuple(sorted(COLOR_DISTANCE_ALGORITHMS))
SORTED_COLORS = sort_colors()
def draw_chart(term):
"""Draw a chart of each color downconverted with selected distance algorithm."""
sys.stdout.write(term.home)
width = term.width
line = ''
line_len = 0
start = timeit.default_timer()
for color in SORTED_COLORS:
chart = ''
for noc in (1 << 24, 256, 16, 8):
term.number_of_colors = noc
chart += getattr(term, color[1])(u'█')
if line_len + 5 > width:
line += '\n'
line_len = 0
line += ' %s' % chart
line_len += 5
elapsed = round((timeit.default_timer() - start) * 1000)
print(line)
left_text = '[] to select, q to quit'
center_text = f'{term.color_distance_algorithm}'
right_text = f'{elapsed:d} ms\n'
sys.stdout.write(term.clear_eos + left_text +
term.center(center_text, term.width -
term.length(left_text) - term.length(right_text)) +
right_text)
def color_chart(term):
"""Main color chart application."""
term = blessed.Terminal()
algo_idx = 0
dirty = True
with term.cbreak(), term.hidden_cursor(), term.fullscreen():
while True:
if dirty:
draw_chart(term)
inp = term.inkey()
dirty = True
if inp in '[]':
algo_idx += 1 if inp == ']' else -1
algo_idx %= len(ALGORITHMS)
term.color_distance_algorithm = ALGORITHMS[algo_idx]
elif inp == '\x0c':
pass
elif inp in 'qQ':
break
else:
dirty = False
if __name__ == '__main__':
color_chart(blessed.Terminal())
python-blessed-1.20.0/bin/detect-multibyte.py 0000775 0000000 0000000 00000005612 14565701425 0021160 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Determines whether the attached terminal supports multibyte encodings.
Problem: A screen drawing application wants to detect whether the terminal
client is capable of rendering utf-8. Some transports, such as a serial link,
often cannot forward their ``LANG`` environment preference, or protocols such
as telnet and rlogin often assume mutual agreement by manual configuration.
We can interactively determine whether the connecting terminal emulator is
rendering in utf8 by making an inquiry of their cursor position:
- request cursor position (p0).
- display multibyte character.
- request cursor position (p1).
If the horizontal distance of (p0, p1) is 1 cell, we know the connecting
client is certainly matching our intended encoding.
As a (tough!) exercise, it may be possible to use this technique to accurately
determine the remote encoding without protocol negotiation using cursor
positioning alone through a complex state decision tree, as demonstrated
by the following diagram:
.. image:: _static/soulburner-ru-family-encodings.jpg
:alt: Cyrillic encodings flowchart
"""
# pylint: disable=invalid-name
# Invalid module name "detect-multibyte"
from __future__ import print_function
# std imports
import sys
import collections
# local
from blessed import Terminal
def get_pos(term):
"""Get cursor position, calling os.exit(2) if not determined."""
# pylint: disable=invalid-name
# Invalid variable name "Position"
Position = collections.namedtuple('Position', ('row', 'column'))
pos = Position(*term.get_location(timeout=5.0))
if -1 in pos:
print('stdin: not a human', file=sys.stderr)
exit(2)
return pos
def main():
"""Program entry point."""
term = Terminal()
# move to bottom of screen, temporarily, where we're likely to do
# the least damage, as we are performing something of a "destructive
# write and erase" onto this screen location.
with term.cbreak(), term.location(y=term.height - 1, x=0):
# store first position
pos0 = get_pos(term)
# display multibyte character
print(u'⦰', end='')
# store second position
pos1 = get_pos(term)
# determine distance
horizontal_distance = pos1.column - pos0.column
multibyte_capable = horizontal_distance == 1
# rubout character(s)
print('\b \b' * horizontal_distance, end='')
# returned to our original starting position,
if not multibyte_capable:
print('multibyte encoding failed, horizontal distance is {0}, '
'expected 1 for unicode point https://codepoints.net/U+29B0'
.format(horizontal_distance), file=sys.stderr)
exit(1)
print('{checkbox} multibyte encoding supported!'
.format(checkbox=term.bold_green(u'✓')))
if __name__ == '__main__':
exit(main())
python-blessed-1.20.0/bin/display-fpathconf.py 0000775 0000000 0000000 00000004112 14565701425 0021301 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""Displays os.fpathconf values related to terminals."""
# pylint: disable=invalid-name
# Invalid module name "display-sighandlers"
from __future__ import print_function
# std imports
import os
import sys
def display_fpathconf():
"""Program entry point."""
if not hasattr(os, "pathconf_names"):
return
disp_values = (
('PC_MAX_CANON', ('Max no. of bytes in a '
'terminal canonical input line.')),
('PC_MAX_INPUT', ('Max no. of bytes for which '
'space is available in a terminal input queue.')),
('PC_PIPE_BUF', ('Max no. of bytes which will '
'be written atomically to a pipe.')),
# to explain in more detail: PC_VDISABLE is the reference character in
# the pairing output for bin/display-terminalinfo.py: if the value
# matches (\xff), then that special control character is disabled, fe:
#
# Index Name Special Character Default Value
# VEOF EOF ^D
# VEOL EOL _POSIX_VDISABLE
#
# irregardless, this value is almost always \xff.
('PC_VDISABLE', 'Terminal character disabling value.')
)
fmt = '{name:<13} {value:<10} {description:<11}'
# column header
print(fmt.format(name='name', value='value', description='description'))
print(fmt.replace('<', '-<').format(name='-', value='-', description='-'))
fd = sys.stdin.fileno()
for name, description in disp_values:
key = os.pathconf_names.get(name, None)
if key is None:
value = 'UNDEF'
else:
try:
value = os.fpathconf(fd, name)
if name == 'PC_VDISABLE':
value = r'\x{0:02x}'.format(value)
except OSError as err:
value = 'OSErrno {0.errno}'.format(err)
print(fmt.format(name=name, value=value, description=description))
print()
if __name__ == '__main__':
display_fpathconf()
python-blessed-1.20.0/bin/display-maxcanon.py 0000775 0000000 0000000 00000005634 14565701425 0021147 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
A tool which uses pexpect to test expected Canonical mode length.
All systems use the value of MAX_CANON which can be found using
fpathconf(3) value PC_MAX_CANON -- with the exception of Linux
and FreeBSD.
Linux, though defining a value of 255, actually honors the value
of 4096 from linux kernel include file tty.h definition
N_TTY_BUF_SIZE.
Linux also does not honor IMAXBEL. termios(3) states, "Linux does not
implement this bit, and acts as if it is always set." Although these
tests ensure it is enabled, this is a non-op for Linux.
FreeBSD supports neither, and instead uses a fraction (1/5) of the tty
speed which is always 9600. Therefor, the maximum limited input line
length is 9600 / 5 = 1920.
In other words, the only way to determine the true MAX_CANON in a
cross-platform manner is through this systems integrated test: the given
system definitions are misleading on some operating systems.
"""
# pylint: disable=invalid-name
# Invalid module name "display-sighandlers"
from __future__ import print_function
# std imports
import os
import sys
def detect_maxcanon():
"""Program entry point."""
import pexpect
bashrc = os.path.join(
# re-use pexpect/replwrap.py's bashrc file,
os.path.dirname(__file__), os.path.pardir, 'pexpect', 'bashrc.sh')
child = pexpect.spawn('bash', ['--rcfile', bashrc],
echo=True, encoding='utf8',
timeout=3)
child.sendline(u'echo -n READY_; echo GO')
child.expect_exact(u'READY_GO')
child.sendline(u'stty icanon imaxbel erase ^H; echo -n retval: $?')
child.expect_exact(u'retval: 0')
child.sendline(u'echo -n GO_; echo AGAIN')
child.expect_exact(u'GO_AGAIN')
child.sendline(u'cat')
child.delaybeforesend = 0
column, blocksize = 0, 64
ch_marker = u'_'
print('auto-detecting MAX_CANON: ', end='')
sys.stdout.flush()
while True:
child.send(ch_marker * blocksize)
result = child.expect([ch_marker * blocksize, u'\a', pexpect.TIMEOUT])
if result == 0:
# entire block fit without emitting bel
column += blocksize
elif result == 1:
# an '\a' was emitted, count the number of ch_markers
# found since last blocksize, determining our MAX_CANON
column += child.before.count(ch_marker)
break
elif result == 3:
print('Undetermined (Timeout) !')
print(('child.before: ', child.before))
print(column)
if __name__ == '__main__':
try:
detect_maxcanon()
except ImportError:
# we'd like to use this with CI -- but until we integrate
# with tox, we can't determine a period in testing when
# the pexpect module has been installed
print('warning: pexpect not in module path, MAX_CANON '
'could not be determined by systems test.',
file=sys.stderr)
python-blessed-1.20.0/bin/display-sighandlers.py 0000775 0000000 0000000 00000002157 14565701425 0021643 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""Displays all signals, their values, and their handlers to stdout."""
# pylint: disable=invalid-name
# Invalid module name "display-sighandlers"
from __future__ import print_function
# std imports
import signal
def main():
"""Program entry point."""
fmt = '{name:<10} {value:<5} {description}'
# header
print(fmt.format(name='name', value='value', description='description'))
print('-' * (33))
for name, value in [(signal_name, getattr(signal, signal_name))
for signal_name in dir(signal)
if signal_name.startswith('SIG')
and not signal_name.startswith('SIG_')]:
try:
handler = signal.getsignal(value)
except ValueError:
# FreeBSD: signal number out of range
handler = 'out of range'
description = {
signal.SIG_IGN: "ignored(SIG_IGN)",
signal.SIG_DFL: "default(SIG_DFL)"
}.get(handler, handler)
print(fmt.format(name=name, value=value, description=description))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/display-terminalinfo.py 0000775 0000000 0000000 00000016353 14565701425 0022032 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""Display known information about our terminal."""
# pylint: disable=invalid-name
# Invalid module name "display-terminalinfo"
from __future__ import print_function
# std imports
import os
import sys
import locale
import platform
BITMAP_IFLAG = {
'IGNBRK': 'ignore BREAK condition',
'BRKINT': 'map BREAK to SIGINTR',
'IGNPAR': 'ignore (discard) parity errors',
'PARMRK': 'mark parity and framing errors',
'INPCK': 'enable checking of parity errors',
'ISTRIP': 'strip 8th bit off chars',
'INLCR': 'map NL into CR',
'IGNCR': 'ignore CR',
'ICRNL': 'map CR to NL (ala CRMOD)',
'IXON': 'enable output flow control',
'IXOFF': 'enable input flow control',
'IXANY': 'any char will restart after stop',
'IMAXBEL': 'ring bell on input queue full',
'IUCLC': 'translate upper case to lower case',
}
BITMAP_OFLAG = {
'OPOST': 'enable following output processing',
'ONLCR': 'map NL to CR-NL (ala CRMOD)',
'OXTABS': 'expand tabs to spaces',
'ONOEOT': 'discard EOT\'s `^D\' on output)',
'OCRNL': 'map CR to NL',
'OLCUC': 'translate lower case to upper case',
'ONOCR': 'No CR output at column 0',
'ONLRET': 'NL performs CR function',
}
BITMAP_CFLAG = {
'CSIZE': 'character size mask',
'CS5': '5 bits (pseudo)',
'CS6': '6 bits',
'CS7': '7 bits',
'CS8': '8 bits',
'CSTOPB': 'send 2 stop bits',
'CREAD': 'enable receiver',
'PARENB': 'parity enable',
'PARODD': 'odd parity, else even',
'HUPCL': 'hang up on last close',
'CLOCAL': 'ignore modem status lines',
'CCTS_OFLOW': 'CTS flow control of output',
'CRTSCTS': 'same as CCTS_OFLOW',
'CRTS_IFLOW': 'RTS flow control of input',
'MDMBUF': 'flow control output via Carrier',
}
BITMAP_LFLAG = {
'ECHOKE': 'visual erase for line kill',
'ECHOE': 'visually erase chars',
'ECHO': 'enable echoing',
'ECHONL': 'echo NL even if ECHO is off',
'ECHOPRT': 'visual erase mode for hardcopy',
'ECHOCTL': 'echo control chars as ^(Char)',
'ISIG': 'enable signals INTR, QUIT, [D]SUSP',
'ICANON': 'canonicalize input lines',
'ALTWERASE': 'use alternate WERASE algorithm',
'IEXTEN': 'enable DISCARD and LNEXT',
'EXTPROC': 'external processing',
'TOSTOP': 'stop background jobs from output',
'FLUSHO': 'output being flushed (state)',
'NOKERNINFO': 'no kernel output from VSTATUS',
'PENDIN': 'XXX retype pending input (state)',
'NOFLSH': 'don\'t flush after interrupt',
}
CTLCHAR_INDEX = {
'VEOF': 'EOF',
'VEOL': 'EOL',
'VEOL2': 'EOL2',
'VERASE': 'ERASE',
'VWERASE': 'WERASE',
'VKILL': 'KILL',
'VREPRINT': 'REPRINT',
'VINTR': 'INTR',
'VQUIT': 'QUIT',
'VSUSP': 'SUSP',
'VDSUSP': 'DSUSP',
'VSTART': 'START',
'VSTOP': 'STOP',
'VLNEXT': 'LNEXT',
'VDISCARD': 'DISCARD',
'VMIN': '---',
'VTIME': '---',
'VSTATUS': 'STATUS',
}
def display_bitmask(kind, bitmap, value):
"""Display all matching bitmask values for ``value`` given ``bitmap``."""
import termios
col1_width = max(map(len, list(bitmap.keys()) + [kind]))
col2_width = 7
fmt = '{name:>{col1_width}} {value:>{col2_width}} {description}'
print(fmt.format(name=kind,
value='Value',
description='Description',
col1_width=col1_width,
col2_width=col2_width))
print('{0} {1} {2}'.format('-' * col1_width,
'-' * col2_width,
'-' * max(map(len, bitmap.values()))))
for flag_name, description in bitmap.items():
try:
bitmask = getattr(termios, flag_name)
bit_val = 'on' if bool(value & bitmask) else 'off'
except AttributeError:
bit_val = 'undef'
print(fmt.format(name=flag_name,
value=bit_val,
description=description,
col1_width=col1_width,
col2_width=col2_width))
print()
def display_ctl_chars(index, ctlc):
"""Display all control character indicies, names, and values."""
import termios
title = 'Special Character'
col1_width = len(title)
col2_width = max(map(len, index.values()))
fmt = '{idx:<{col1_width}} {name:<{col2_width}} {value}'
print('Special line Characters'.center(40).rstrip())
print(fmt.format(idx='Index',
name='Name',
value='Value',
col1_width=col1_width,
col2_width=col2_width))
print('{0} {1} {2}'.format('-' * col1_width,
'-' * col2_width,
'-' * 10))
for index_name, name in index.items():
try:
index = getattr(termios, index_name)
value = ctlc[index]
value = '_POSIX_VDISABLE' if value == b'\xff' else repr(value)
except AttributeError:
value = 'undef'
print(fmt.format(idx=index_name,
name=name,
value=value,
col1_width=col1_width,
col2_width=col2_width))
print()
def display_pathconf(names, getter):
"""Helper displays results of os.pathconf_names values."""
col1_width = max(map(len, names))
fmt = '{name:>{col1_width}} {value}'
print(fmt.format(name='pathconf'.ljust(col1_width), value='value',
col1_width=col1_width))
print('{0} {1}'.format('-' * col1_width, '-' * 27))
for name in names:
try:
value = getter(name)
except OSError as err:
value = 'OSErrno {err.errno}'.format(err=err)
print(fmt.format(name=name, value=value, col1_width=col1_width))
print()
def main():
"""Program entry point."""
if platform.system() == 'Windows':
print('No terminal on windows systems!')
exit(0)
import termios
fd = sys.stdin.fileno()
locale.setlocale(locale.LC_ALL, '')
encoding = locale.getpreferredencoding()
print('os.isatty({0}) => {1}'.format(fd, os.isatty(fd)))
print('locale.getpreferredencoding() => {0}'.format(encoding))
display_pathconf(names=os.pathconf_names,
getter=lambda name: os.fpathconf(fd, name))
try:
(iflag, oflag, cflag, lflag,
_, _, # input / output speed (bps macros)
ctlc) = termios.tcgetattr(fd)
except termios.error as err:
print('stdin is not a typewriter: {0}'.format(err))
else:
display_bitmask(kind=' Input Mode',
bitmap=BITMAP_IFLAG,
value=iflag)
display_bitmask(kind=' Output Mode',
bitmap=BITMAP_OFLAG,
value=oflag)
display_bitmask(kind='Control Mode',
bitmap=BITMAP_CFLAG,
value=cflag)
display_bitmask(kind=' Local Mode',
bitmap=BITMAP_LFLAG,
value=lflag)
display_ctl_chars(index=CTLCHAR_INDEX,
ctlc=ctlc)
print('os.ttyname({0}) => {1}'.format(fd, os.ttyname(fd)))
print('os.ctermid() => {0}'.format(os.ttyname(fd)))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/editor.py 0000775 0000000 0000000 00000020551 14565701425 0017161 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
A Dumb full-screen editor.
This example program makes use of many context manager methods:
:meth:`~.Terminal.hidden_cursor`, :meth:`~.Terminal.raw`,
:meth:`~.Terminal.location`, :meth:`~.Terminal.fullscreen`, and
:meth:`~.Terminal.keypad`.
Early curses work focused namely around writing screen editors, naturally
any serious editor would make liberal use of special modes.
``Ctrl - L``
refresh
``Ctrl - C``
quit
``Ctrl - S``
save
"""
from __future__ import division, print_function
# std imports
import functools
import collections
# local
from blessed import Terminal
# python 2/3 compatibility, provide 'echo' function as an
# alias for "print without newline and flush"
try:
# pylint: disable=invalid-name
# Invalid constant name "echo"
echo = functools.partial(print, end='', flush=True)
echo(u'')
except TypeError:
# TypeError: 'flush' is an invalid keyword argument for this function
import sys
def echo(text):
"""Display ``text`` and flush output."""
sys.stdout.write(u'{}'.format(text))
sys.stdout.flush()
def input_filter(keystroke):
"""
For given keystroke, return whether it should be allowed as input.
This somewhat requires that the interface use special application keys to perform functions, as
alphanumeric input intended for persisting could otherwise be interpreted as a command sequence.
"""
if keystroke.is_sequence:
# Namely, deny multi-byte sequences (such as '\x1b[A'),
return False
if ord(keystroke) < ord(u' '):
# or control characters (such as ^L),
return False
return True
def echo_yx(cursor, text):
"""Move to ``cursor`` and display ``text``."""
echo(cursor.term.move_yx(cursor.y, cursor.x) + text)
Cursor = collections.namedtuple('Cursor', ('y', 'x', 'term'))
def readline(term, width=20):
"""A rudimentary readline implementation."""
text = u''
while True:
inp = term.inkey()
if inp.code == term.KEY_ENTER:
break
elif inp.code == term.KEY_ESCAPE or inp == chr(3):
text = None
break
elif not inp.is_sequence and len(text) < width:
text += inp
echo(inp)
elif inp.code in (term.KEY_BACKSPACE, term.KEY_DELETE):
text = text[:-1]
# https://utcc.utoronto.ca/~cks/space/blog/unix/HowUnixBackspaces
#
# "When you hit backspace, the kernel tty line discipline rubs out
# your previous character by printing (in the simple case)
# Ctrl-H, a space, and then another Ctrl-H."
echo(u'\b \b')
return text
def save(screen, fname):
"""Save screen contents to file."""
if not fname:
return
with open(fname, 'w') as fout:
cur_row = cur_col = 0
for (row, col) in sorted(screen):
char = screen[(row, col)]
while row != cur_row:
cur_row += 1
cur_col = 0
fout.write(u'\n')
while col > cur_col:
cur_col += 1
fout.write(u' ')
fout.write(char)
cur_col += 1
fout.write(u'\n')
def redraw(term, screen, start=None, end=None):
"""Redraw the screen."""
if start is None and end is None:
echo(term.clear)
start, end = (Cursor(y=min(y for (y, x) in screen or [(0, 0)]),
x=min(x for (y, x) in screen or [(0, 0)]),
term=term),
Cursor(y=max(y for (y, x) in screen or [(0, 0)]),
x=max(x for (y, x) in screen or [(0, 0)]),
term=term))
lastcol, lastrow = -1, -1
for row, col in sorted(screen):
if start.y <= row <= end.y and start.x <= col <= end.x:
if col >= term.width or row >= term.height:
# out of bounds
continue
if row != lastrow or col != lastcol + 1:
# use cursor movement
echo_yx(Cursor(row, col, term), screen[row, col])
else:
# just write past last one
echo(screen[row, col])
def main():
"""Program entry point."""
def above(csr, offset):
return Cursor(y=max(0, csr.y - offset),
x=csr.x,
term=csr.term)
def below(csr, offset):
return Cursor(y=min(csr.term.height - 1, csr.y + offset),
x=csr.x,
term=csr.term)
def right_of(csr, offset):
return Cursor(y=csr.y,
x=min(csr.term.width - 1, csr.x + offset),
term=csr.term)
def left_of(csr, offset):
return Cursor(y=csr.y,
x=max(0, csr.x - offset),
term=csr.term)
def home(csr):
return Cursor(y=csr.y,
x=0,
term=csr.term)
def end(csr):
return Cursor(y=csr.y,
x=csr.term.width - 1,
term=csr.term)
def bottom(csr):
return Cursor(y=csr.term.height - 1,
x=csr.x,
term=csr.term)
def center(csr):
return Cursor(csr.term.height // 2,
csr.term.width // 2,
csr.term)
def lookup_move(inp_code, csr):
return {
# arrows, including angled directionals
csr.term.KEY_END: below(left_of(csr, 1), 1),
csr.term.KEY_KP_1: below(left_of(csr, 1), 1),
csr.term.KEY_DOWN: below(csr, 1),
csr.term.KEY_KP_2: below(csr, 1),
csr.term.KEY_PGDOWN: below(right_of(csr, 1), 1),
csr.term.KEY_LR: below(right_of(csr, 1), 1),
csr.term.KEY_KP_3: below(right_of(csr, 1), 1),
csr.term.KEY_LEFT: left_of(csr, 1),
csr.term.KEY_KP_4: left_of(csr, 1),
csr.term.KEY_CENTER: center(csr),
csr.term.KEY_KP_5: center(csr),
csr.term.KEY_RIGHT: right_of(csr, 1),
csr.term.KEY_KP_6: right_of(csr, 1),
csr.term.KEY_HOME: above(left_of(csr, 1), 1),
csr.term.KEY_KP_7: above(left_of(csr, 1), 1),
csr.term.KEY_UP: above(csr, 1),
csr.term.KEY_KP_8: above(csr, 1),
csr.term.KEY_PGUP: above(right_of(csr, 1), 1),
csr.term.KEY_KP_9: above(right_of(csr, 1), 1),
# shift + arrows
csr.term.KEY_SLEFT: left_of(csr, 10),
csr.term.KEY_SRIGHT: right_of(csr, 10),
csr.term.KEY_SDOWN: below(csr, 10),
csr.term.KEY_SUP: above(csr, 10),
# carriage return
csr.term.KEY_ENTER: home(below(csr, 1)),
}.get(inp_code, csr)
term = Terminal()
csr = Cursor(0, 0, term)
screen = {}
with term.hidden_cursor(), \
term.raw(), \
term.location(), \
term.fullscreen(), \
term.keypad():
inp = None
while True:
echo_yx(csr, term.reverse(screen.get((csr.y, csr.x), u' ')))
inp = term.inkey()
if inp == chr(3):
# ^c exits
break
elif inp == chr(19):
# ^s saves
echo_yx(home(bottom(csr)),
term.ljust(term.bold_white(u'Filename: ')))
echo_yx(right_of(home(bottom(csr)), len(u'Filename: ')), u'')
save(screen, readline(term))
echo_yx(home(bottom(csr)), term.clear_eol)
redraw(term=term, screen=screen,
start=home(bottom(csr)),
end=end(bottom(csr)))
continue
elif inp == chr(12):
# ^l refreshes
redraw(term=term, screen=screen)
else:
n_csr = lookup_move(inp.code, csr)
if n_csr != csr:
# erase old cursor,
echo_yx(csr, screen.get((csr.y, csr.x), u' '))
csr = n_csr
elif input_filter(inp):
echo_yx(csr, inp)
screen[(csr.y, csr.x)] = inp.__str__()
n_csr = right_of(csr, 1)
if n_csr == csr:
# wrap around margin
n_csr = home(below(csr, 1))
csr = n_csr
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/generate-keycodes.py 0000664 0000000 0000000 00000003123 14565701425 0021262 0 ustar 00root root 0000000 0000000 # generate keycodes for the tables in docs/keyboard.rst
# std imports
import os
# local
from blessed.keyboard import DEFAULT_SEQUENCE_MIXIN, CURSES_KEYCODE_OVERRIDE_MIXIN
def is_override(key_attr_name, code):
return (code in [val for name, val in CURSES_KEYCODE_OVERRIDE_MIXIN] and
key_attr_name not in [name for name, val in CURSES_KEYCODE_OVERRIDE_MIXIN])
def main():
from blessed import Terminal
term = Terminal()
csv_header = """
.. csv-table:: All Terminal class attribute Keyboard codes, by name
:delim: |
:header: "Name", "Value", "Example Sequence(s)"
"""
fname = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, 'docs', 'all_the_keys.txt'))
with open(fname, 'w') as fout:
print(f"write: {fout.name}")
fout.write(csv_header)
for key_attr_name in sorted([
attr for attr in dir(term) if attr.startswith('KEY_')
]):
# filter away F23-F63 (lol)
if key_attr_name.startswith('KEY_F'):
maybe_digit = key_attr_name[len('KEY_F'):]
if maybe_digit.isdigit() and int(maybe_digit) > 23:
continue
code = getattr(term, key_attr_name)
repr_sequences = [repr(seq) for (seq, value) in DEFAULT_SEQUENCE_MIXIN if value == code]
txt_sequences = ', '.join(repr_sequences).replace('\\', '\\\\')
fout.write(f' {key_attr_name} | {code}')
if txt_sequences:
fout.write(f'| {txt_sequences}')
fout.write('\n')
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/generate-x11-colorchart.py 0000664 0000000 0000000 00000005153 14565701425 0022230 0 ustar 00root root 0000000 0000000 # generate images and tables for inclusion in docs/colors.rst
# std imports
import os
import re
import math
import colorsys
from functools import reduce
# 3rd party
from PIL import Image
# local
from blessed.colorspace import X11_COLORNAMES_TO_RGB
rgb_folder = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, 'docs', '_static', 'rgb'))
color_alias_fmt = """
.. |{color_name}| image:: _static/rgb/{color_name}.png
:width: 48pt
:height: 12pt"""
csv_table = """.. csv-table:: All Terminal colors, by name
:header: "Name", "Image", "R", "G", "B", "H", "S", "V"
:name: Color chart
"""
def sort_colors():
colors = {}
for color_name, rgb_color in X11_COLORNAMES_TO_RGB.items():
if rgb_color in colors:
colors[rgb_color].append(color_name)
else:
colors[rgb_color] = [color_name]
def sortby_hv(rgb_item):
# sort by hue rounded to nearest %,
# then by color name & number
# except shades of grey -- by name & number, only
rgb, name = rgb_item
digit = 0
match = re.match(r'(.*)(\d+)', name[0])
if match is not None:
name = match.group(1)
digit = int(match.group(2))
else:
name = name[0]
hash_name = reduce(int.__mul__, map(ord, name))
hsv = colorsys.rgb_to_hsv(*rgb)
if rgb[0] == rgb[1] == rgb[2]:
return 100, hsv[2], hash_name, digit
return int(math.floor(hsv[0] * 100)), hash_name, digit, hsv[2]
return sorted(colors.items(), key=sortby_hv)
def main():
aliases, csv_rows = '', ''
for rgb, x11_colors in sort_colors():
x11_color = sorted(x11_colors)[0]
fname = os.path.join(rgb_folder, f'{x11_color}.png')
if not os.path.exists(os.path.join(fname)):
img = Image.new('RGB', (1, 1), color=rgb)
img.save(fname)
print(f'write: {fname}')
aliases += color_alias_fmt.format(color_name=x11_color)
hsv = colorsys.rgb_to_hsv(*rgb)
csv_rows += (' '
f'{x11_color}, |{x11_color}|, '
f'{rgb[0]/255:0.1%}, {rgb[1]/255:0.1%}, {rgb[2]/255:0.1%}, '
f'{hsv[0]:0.1%}, {hsv[1]:0.1%}, {hsv[2]/255:0.1%}\n')
output = aliases + '\n\n' + csv_table + '\n' + csv_rows
filepath_txt = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, 'docs',
'all_the_colors.txt'))
with open(filepath_txt, 'w') as fout:
print(f'write: {fout.name}')
fout.write(output.lstrip())
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/keymatrix.py 0000775 0000000 0000000 00000011377 14565701425 0017716 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
A simple "game": hit all application keys to win.
Display all known key capabilities that may match the terminal. As each key is pressed on input, it
is lit up and points are scored.
"""
from __future__ import division, print_function
# std imports
import sys
import functools
# local
from blessed import Terminal
# python 2/3 compatibility, provide 'echo' function as an
# alias for "print without newline and flush"
try:
# pylint: disable=invalid-name
# Invalid constant name "echo"
echo = functools.partial(print, end='', flush=True)
echo(u'')
except TypeError:
# TypeError: 'flush' is an invalid keyword argument for this function
def echo(text):
"""Display ``text`` and flush output."""
sys.stdout.write(u'{}'.format(text))
sys.stdout.flush()
def refresh(term, board, level, score, inps):
"""Refresh the game screen."""
echo(term.home + term.clear)
level_color = level % 7
if level_color == 0:
level_color = 4
bottom = 0
for keycode, attr in board.items():
echo(u''.join((
term.move_yx(attr['row'], attr['column']),
term.color(level_color),
(term.reverse if attr['hit'] else term.bold),
keycode,
term.normal)))
bottom = max(bottom, attr['row'])
echo(term.move_yx(term.height, 0) + 'level: %s score: %s' % (level, score,))
if bottom >= (term.height - 5):
sys.stderr.write(
('\n' * (term.height // 2)) +
term.center(term.red_underline('cheater!')) + '\n')
sys.stderr.write(
term.center("(use a larger screen)") +
('\n' * (term.height // 2)))
sys.exit(1)
echo(term.move_yx(bottom + 1, 0))
echo('Press ^C to exit.')
for row, inp in enumerate(inps[(term.height - (bottom + 3)) * -1:], 1):
echo(term.move_yx(bottom + row + 1, 0))
echo('{0!r}, {1}, {2}'.format(
inp.__str__() if inp.is_sequence else inp,
inp.code,
inp.name))
echo(term.clear_eol)
def build_gameboard(term):
"""Build the gameboard layout."""
column, row = 0, 0
board = {}
spacing = 2
for keycode in sorted(term._keycodes.values()):
if (keycode.startswith('KEY_F')
and keycode[-1].isdigit()
and int(keycode[len('KEY_F'):]) > 24):
continue
if column + len(keycode) + (spacing * 2) >= term.width:
column = 0
row += 1
board[keycode] = {'column': column,
'row': row,
'hit': 0,
}
column += len(keycode) + (spacing * 2)
return board
def add_score(score, pts, level):
"""Add points to score, determine and return new score and level."""
lvl_multiplier = 10
score += pts
if (score % (pts * lvl_multiplier)) == 0:
level += 1
return score, level
def main():
"""Program entry point."""
term = Terminal()
score = level = hit_highbit = hit_unicode = 0
dirty = True
gameboard = build_gameboard(term)
inps = []
with term.raw(), term.keypad(), term.location():
inp = term.inkey(timeout=0)
while inp != chr(3):
if dirty:
refresh(term, gameboard, level, score, inps)
dirty = False
inp = term.inkey(timeout=5.0)
dirty = True
if (inp.is_sequence and
inp.name in gameboard and
gameboard[inp.name]['hit'] == 0):
gameboard[inp.name]['hit'] = 1
score, level = add_score(score, 100, level)
elif inp and not inp.is_sequence and 128 <= ord(inp) <= 255:
hit_highbit += 1
if hit_highbit < 5:
score, level = add_score(score, 100, level)
elif inp and not inp.is_sequence and ord(inp) > 256:
hit_unicode += 1
if hit_unicode < 5:
score, level = add_score(score, 100, level)
inps.append(inp)
with term.cbreak():
echo(term.move_y(term.height))
echo(
u'{term.clear_eol}Your final score was {score} '
u'at level {level}{term.clear_eol}\n'
u'{term.clear_eol}\n'
u'{term.clear_eol}You hit {hit_highbit} '
u' 8-bit characters\n{term.clear_eol}\n'
u'{term.clear_eol}You hit {hit_unicode} '
u' unicode characters.\n{term.clear_eol}\n'
u'{term.clear_eol}press any key\n'.format(
term=term,
score=score, level=level,
hit_highbit=hit_highbit,
hit_unicode=hit_unicode)
)
term.inkey()
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/on_resize.py 0000775 0000000 0000000 00000002562 14565701425 0017672 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
Example application for the 'blessed' Terminal library for python.
Window size changes are caught by the 'on_resize' function using a traditional signal handler.
Meanwhile, blocking keyboard input is displayed to stdout. If a resize event is discovered, an empty
string is returned by term.inkey().
"""
from __future__ import print_function
# std imports
import signal
# local
from blessed import Terminal
def main():
"""Program entry point."""
term = Terminal()
def on_resize(*args):
# pylint: disable=unused-argument
# Unused argument 'args'
# Its generally not a good idea to put blocking functions (such as
# print) within a signal handler -- if another SIGWINCH is received
# while this function blocks, an error will occur.
# In most programs, you'll want to set some kind of 'dirty' flag,
# perhaps by a Semaphore like threading.Event or (thanks to the GIL)
# a simple global variable will suffice.
print('height={t.height}, width={t.width}\r'.format(t=term))
signal.signal(signal.SIGWINCH, on_resize)
# display initial size
on_resize(term)
with term.cbreak():
print("press 'X' to stop.")
inp = None
while inp != 'X':
inp = term.inkey()
print(repr(inp))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/plasma.py 0000775 0000000 0000000 00000007312 14565701425 0017150 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# std imports
import sys
import math
import time
import timeit
import colorsys
import contextlib
# local
import blessed
def scale_255(val): return int(round(val * 255))
def rgb_at_xy(term, x, y, t):
h, w = term.height, term.width
hue = 4.0 + (
math.sin(x / 16.0)
+ math.sin(y / 32.0)
+ math.sin(math.sqrt(
((x - w / 2.0) * (x - w / 2.0) +
(y - h / 2.0) * (y - h / 2.0))
) / 8.0 + t * 3)
) + math.sin(math.sqrt((x * x + y * y)) / 8.0)
saturation = y / h
lightness = x / w
return tuple(map(scale_255, colorsys.hsv_to_rgb(hue / 8.0, saturation, lightness)))
def screen_plasma(term, plasma_fn, t):
result = ''
for y in range(term.height - 1):
for x in range(term.width):
result += term.on_color_rgb(*plasma_fn(term, x, y, t)) + ' '
return result
@contextlib.contextmanager
def elapsed_timer():
"""Timer pattern, from https://stackoverflow.com/a/30024601."""
start = timeit.default_timer()
def elapser():
return timeit.default_timer() - start
# pylint: disable=unnecessary-lambda
yield lambda: elapser()
def show_please_wait(term):
txt_wait = 'please wait ...'
outp = term.move_yx(term.height - 1, 0) + term.clear_eol + term.center(txt_wait)
print(outp, end='')
sys.stdout.flush()
def show_paused(term):
txt_paused = 'paused'
outp = term.move_yx(term.height - 1, int(term.width / 2 - len(txt_paused) / 2))
outp += txt_paused
print(outp, end='')
sys.stdout.flush()
def next_algo(algo, forward):
algos = tuple(sorted(blessed.color.COLOR_DISTANCE_ALGORITHMS))
next_index = algos.index(algo) + (1 if forward else -1)
if next_index == len(algos):
next_index = 0
return algos[next_index]
def next_color(color, forward):
colorspaces = (4, 8, 16, 256, 1 << 24)
next_index = colorspaces.index(color) + (1 if forward else -1)
if next_index == len(colorspaces):
next_index = 0
return colorspaces[next_index]
def status(term, elapsed):
left_txt = (f'{term.number_of_colors} colors - '
f'{term.color_distance_algorithm} - ?: help ')
right_txt = f'fps: {1 / elapsed:2.2f}'
return ('\n' + term.normal +
term.white_on_blue + term.clear_eol + left_txt +
term.rjust(right_txt, term.width - len(left_txt)))
def main(term):
with term.cbreak(), term.hidden_cursor(), term.fullscreen():
pause, dirty = False, True
t = time.time()
while True:
if dirty or not pause:
if not pause:
t = time.time()
with elapsed_timer() as elapsed:
outp = term.home + screen_plasma(term, rgb_at_xy, t)
outp += status(term, elapsed())
print(outp, end='')
sys.stdout.flush()
dirty = False
if pause:
show_paused(term)
inp = term.inkey(timeout=None if pause else 0.01)
if inp == '?':
assert False, "don't panic"
elif inp == '\x0c':
dirty = True
if inp in ('[', ']'):
term.color_distance_algorithm = next_algo(
term.color_distance_algorithm, inp == '[')
show_please_wait(term)
dirty = True
if inp == ' ':
pause = not pause
if inp.code in (term.KEY_TAB, term.KEY_BTAB):
term.number_of_colors = next_color(
term.number_of_colors, inp.code == term.KEY_TAB)
show_please_wait(term)
dirty = True
if __name__ == "__main__":
exit(main(blessed.Terminal()))
python-blessed-1.20.0/bin/progress_bar.py 0000775 0000000 0000000 00000002677 14565701425 0020374 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
Example application for the 'blessed' Terminal library for python.
This isn't a real progress bar, just a sample "animated prompt" of sorts that demonstrates the
separate move_x() and move_yx() capabilities, made mainly to test the `hpa' compatibility for
'screen' terminal type which fails to provide one, but blessed recognizes that it actually does, and
provides a proxy.
"""
from __future__ import print_function
# std imports
import sys
# local
from blessed import Terminal
def main():
"""Program entry point."""
term = Terminal()
assert term.hpa(1) != u'', (
'Terminal does not support hpa (Horizontal position absolute)')
col, offset = 1, 1
with term.cbreak():
inp = None
print("press 'X' to stop.")
sys.stderr.write(term.move_yx(term.height, 0) + u'[')
sys.stderr.write(term.move_x(term.width - 1) + u']' + term.move_x(1))
while inp != 'X':
if col >= (term.width - 2):
offset = -1
elif col <= 1:
offset = 1
sys.stderr.write(term.move_x(col))
if offset == -1:
sys.stderr.write(u'.')
else:
sys.stderr.write(u'=')
col += offset
sys.stderr.write(term.move_x(col))
sys.stderr.write(u'|\b')
sys.stderr.flush()
inp = term.inkey(0.04)
print()
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/resize.py 0000775 0000000 0000000 00000006252 14565701425 0017176 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
Determines and prints COLUMNS and LINES of the attached window width.
A strange problem: programs that perform screen addressing incorrectly
determine the screen margins. Calls to reset(1) do not resolve the
issue.
This may often happen because the transport is incapable of communicating
the terminal size, such as over a serial line. This demonstration program
determines true screen dimensions and produces output suitable for evaluation
by a bourne-like shell::
$ eval `./resize.py`
The following remote login protocols communicate window size:
- ssh: notifies on dedicated session channel, see for example,
``paramiko.ServerInterface.check_channel_window_change_request``.
- telnet: sends window size through NAWS (negotiate about window
size, RFC 1073), see for example,
``telnetlib3.TelnetServer.naws_receive``.
- rlogin: protocol sends only initial window size, and does not notify
about size changes.
This is a simplified version of `resize.c
`_ provided by the
xterm package.
"""
from __future__ import print_function
# std imports
import sys
import collections
# local
from blessed import Terminal
def main():
"""Program entry point."""
# pylint: disable=invalid-name
# Invalid variable name "Position"
Position = collections.namedtuple('Position', ('row', 'column'))
# particularly strange, we use sys.stderr as our output stream device,
# this 'stream' file descriptor is only used for side effects, of which
# this application uses two: the term.location() has an implied write,
# as does get_position().
#
# the reason we chose stderr is to ensure that the terminal emulator
# receives our bytes even when this program is wrapped by shell eval
# `resize.py`; backticks gather stdout but not stderr in this case.
term = Terminal(stream=sys.stderr)
# Move the cursor to the farthest lower-right hand corner that is
# reasonable. Due to word size limitations in older protocols, 999,999
# is our most reasonable and portable edge boundary. Telnet NAWS is just
# two unsigned shorts: ('!HH' in python struct module format).
with term.location(999, 999):
# We're not likely at (999, 999), but a well behaved terminal emulator
# will do its best to accommodate our request, positioning the cursor
# to the farthest lower-right corner. By requesting the current
# position, we may negotiate about the window size directly with the
# terminal emulator connected at the distant end.
pos = Position(*term.get_location(timeout=5.0))
if -1 not in pos:
# true size was determined
lines, columns = pos.row, pos.column
else:
# size could not be determined. Oh well, the built-in blessed
# properties will use termios if available, falling back to
# existing environment values if it has to.
lines, columns = term.height, term.width
print("COLUMNS={columns};\nLINES={lines};\nexport COLUMNS LINES;"
.format(columns=columns, lines=lines))
if __name__ == '__main__':
exit(main())
python-blessed-1.20.0/bin/strip.py 0000775 0000000 0000000 00000000470 14565701425 0017032 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
"""Example scrip that strips input of terminal sequences."""
# std imports
import sys
# local
import blessed
def main():
"""Program entry point."""
term = blessed.Terminal()
for line in sys.stdin:
print(term.strip_seqs(line))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/tprint.py 0000775 0000000 0000000 00000001506 14565701425 0017212 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
A simple cmd-line tool for displaying FormattingString capabilities.
For example::
$ python tprint.py bold A rather bold statement.
"""
# std
from __future__ import print_function
# std imports
import argparse
# local
from blessed import Terminal
def parse_args():
"""Parse sys.argv, returning dict suitable for main()."""
parser = argparse.ArgumentParser(
description='displays argument as specified style')
parser.add_argument('style', type=str, help='style formatter')
parser.add_argument('text', type=str, nargs='+')
return dict(parser.parse_args()._get_kwargs())
def main(style, text):
"""Program entry point."""
term = Terminal()
style = getattr(term, style)
print(style(' '.join(text)))
if __name__ == '__main__':
exit(main(**parse_args()))
python-blessed-1.20.0/bin/worms.py 0000775 0000000 0000000 00000021304 14565701425 0017037 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
Example application for the 'blessed' Terminal library for python.
It is also an experiment in functional programming.
"""
from __future__ import division, print_function
# std imports
from random import randrange
from functools import partial
from collections import namedtuple
# local
from blessed import Terminal
# python 2/3 compatibility, provide 'echo' function as an
# alias for "print without newline and flush"
try:
# pylint: disable=invalid-name
# Invalid constant name "echo"
echo = partial(print, end='', flush=True)
echo(u'')
except TypeError:
# TypeError: 'flush' is an invalid keyword argument for this function
import sys
def echo(text):
"""Python 2 version of print(end='', flush=True)."""
sys.stdout.write(u'{0}'.format(text))
sys.stdout.flush()
# a worm is a list of (y, x) segments Locations
Location = namedtuple('Point', ('y', 'x',))
# a nibble is a (x,y) Location and value
Nibble = namedtuple('Nibble', ('location', 'value'))
# A direction is a bearing, fe.
# y=0, x=-1 = move right
# y=1, x=0 = move down
Direction = namedtuple('Direction', ('y', 'x',))
# these functions return a new Location instance, given
# the direction indicated by their name.
LEFT = (0, -1)
RIGHT = (0, 1)
UP = (-1, 0)
DOWN = (1, 0)
def left_of(segment, term):
"""Return Location left-of given segment."""
# pylint: disable=unused-argument
# Unused argument 'term'
return Location(y=segment.y,
x=max(0, segment.x - 1))
def right_of(segment, term):
"""Return Location right-of given segment."""
return Location(y=segment.y,
x=min(term.width - 1, segment.x + 1))
def above(segment, term):
"""Return Location above given segment."""
# pylint: disable=unused-argument
# Unused argument 'term'
return Location(
y=max(0, segment.y - 1),
x=segment.x)
def below(segment, term):
"""Return Location below given segment."""
return Location(
y=min(term.height - 1, segment.y + 1),
x=segment.x)
def next_bearing(term, inp_code, bearing):
"""
Return direction function for new bearing by inp_code.
If no inp_code matches a bearing direction, return a function for the current bearing.
"""
return {
term.KEY_LEFT: left_of,
term.KEY_RIGHT: right_of,
term.KEY_UP: above,
term.KEY_DOWN: below,
}.get(inp_code,
# direction function given the current bearing
{LEFT: left_of,
RIGHT: right_of,
UP: above,
DOWN: below}[(bearing.y, bearing.x)])
def change_bearing(f_mov, segment, term):
"""Return new bearing given the movement f(x)."""
return Direction(
f_mov(segment, term).y - segment.y,
f_mov(segment, term).x - segment.x)
def bearing_flipped(dir1, dir2):
"""
direction-flipped check.
Return true if dir2 travels in opposite direction of dir1.
"""
return (0, 0) == (dir1.y + dir2.y, dir1.x + dir2.x)
def hit_any(loc, segments):
"""Return True if `loc' matches any (y, x) coordinates within segments."""
# `segments' -- a list composing a worm.
return loc in segments
def hit_vany(locations, segments):
"""Return True if any locations are found within any segments."""
return any(hit_any(loc, segments)
for loc in locations)
def hit(src, dst):
"""Return True if segments are same position (hit detection)."""
return src.x == dst.x and src.y == dst.y
def next_wormlength(nibble, head, worm_length):
"""Return new worm_length if current nibble is hit."""
if hit(head, nibble.location):
return worm_length + nibble.value
return worm_length
def next_speed(nibble, head, speed, modifier):
"""Return new speed if current nibble is hit."""
return speed * modifier if hit(head, nibble.location) else speed
def head_glyph(direction):
"""Return character for worm head depending on horiz/vert orientation."""
return u':' if direction in (left_of, right_of) else u'"'
def next_nibble(term, nibble, head, worm):
"""
Provide the next nibble.
continuously generate a random new nibble so long as the current nibble hits any location of the
worm. Otherwise, return a nibble of the same location and value as provided.
"""
loc, val = nibble.location, nibble.value
while hit_vany([head] + worm, nibble_locations(loc, val)):
loc = Location(x=randrange(1, term.width - 1),
y=randrange(1, term.height - 1))
val = nibble.value + 1
return Nibble(loc, val)
def nibble_locations(nibble_location, nibble_value):
"""Return array of locations for the current "nibble"."""
# generate an array of locations for the current nibble's location
# -- a digit such as '123' may be hit at 3 different (y, x) coordinates.
return [
Location(x=nibble_location.x + offset, y=nibble_location.y)
for offset in range(1 + len('{}'.format(nibble_value)) - 1)
]
def main():
"""Program entry point."""
# pylint: disable=too-many-locals
# Too many local variables (20/15)
term = Terminal()
worm = [Location(x=term.width // 2, y=term.height // 2)]
worm_length = 2
bearing = Direction(*LEFT)
direction = left_of
nibble = Nibble(location=worm[0], value=0)
color_nibble = term.black_on_green
color_worm = term.yellow_reverse
color_head = term.red_reverse
color_bg = term.on_blue
echo(term.move_yx(1, 1))
echo(color_bg(term.clear))
# speed is actually a measure of time; the shorter, the faster.
speed = 0.1
modifier = 0.93
inp = None
echo(term.move_yx(term.height, 0))
with term.hidden_cursor(), term.cbreak(), term.location():
while inp not in (u'q', u'Q'):
# delete the tail of the worm at worm_length
if len(worm) > worm_length:
echo(term.move_yx(*worm.pop(0)))
echo(color_bg(u' '))
# compute head location
head = worm.pop()
# check for hit against self; hitting a wall results in the (y, x)
# location being clipped, -- and death by hitting self (not wall).
if hit_any(head, worm):
break
# get the next nibble, which may be equal to ours unless this
# nibble has been struck by any portion of our worm body.
n_nibble = next_nibble(term, nibble, head, worm)
# get the next worm_length and speed, unless unchanged.
worm_length = next_wormlength(nibble, head, worm_length)
speed = next_speed(nibble, head, speed, modifier)
if n_nibble != nibble:
# erase the old one, careful to redraw the nibble contents
# with a worm color for those portions that overlay.
for (yloc, xloc) in nibble_locations(*nibble):
echo(u''.join((
term.move_yx(yloc, xloc),
(color_worm if (yloc, xloc) == head
else color_bg)(u' '),
term.normal)))
# and draw the new,
echo(term.move_yx(*n_nibble.location) + (
color_nibble('{}'.format(n_nibble.value))))
# display new worm head
echo(term.move_yx(*head) + color_head(head_glyph(direction)))
# and its old head (now, a body piece)
if worm:
echo(term.move_yx(*(worm[-1])))
echo(color_worm(u' '))
echo(term.move_yx(*head))
# wait for keyboard input, which may indicate
# a new direction (up/down/left/right)
inp = term.inkey(timeout=speed)
# discover new direction, given keyboard input and/or bearing.
nxt_direction = next_bearing(term, inp.code, bearing)
# discover new bearing, given new direction compared to prev
nxt_bearing = change_bearing(nxt_direction, head, term)
# disallow new bearing/direction when flipped: running into
# oneself, for example traveling left while traveling right.
if not bearing_flipped(bearing, nxt_bearing):
direction = nxt_direction
bearing = nxt_bearing
# append the prior `head' onto the worm, then
# a new `head' for the given direction.
worm.extend([head, direction(head, term)])
# re-assign new nibble,
nibble = n_nibble
echo(term.normal)
score = (worm_length - 1) * 100
echo(u''.join((term.move_yx(term.height - 1, 1), term.normal)))
echo(u''.join((u'\r\n', u'score: {}'.format(score), u'\r\n')))
if __name__ == '__main__':
main()
python-blessed-1.20.0/bin/x11_colorpicker.py 0000664 0000000 0000000 00000007174 14565701425 0020703 0 ustar 00root root 0000000 0000000 # std imports
import re
import math
import colorsys
from functools import reduce
# local
import blessed
from blessed.colorspace import X11_COLORNAMES_TO_RGB
def sort_colors():
colors = {}
for color_name, rgb_color in X11_COLORNAMES_TO_RGB.items():
if rgb_color in colors:
colors[rgb_color].append(color_name)
else:
colors[rgb_color] = [color_name]
def sortby_hv(rgb_item):
# sort by hue rounded to nearest %,
# then by color name & number
# except shades of grey -- by name & number, only
rgb, name = rgb_item
digit = 0
match = re.match(r'(.*)(\d+)', name[0])
if match is not None:
name = match.group(1)
digit = int(match.group(2))
else:
name = name[0]
hash_name = reduce(int.__mul__, map(ord, name))
hsv = colorsys.rgb_to_hsv(*rgb)
if rgb[0] == rgb[1] == rgb[2]:
return 100, hsv[2], hash_name, digit
return int(math.floor(hsv[0] * 100)), hash_name, digit, hsv[2]
return sorted(colors.items(), key=sortby_hv)
HSV_SORTED_COLORS = sort_colors()
def render(term, idx):
rgb_color, color_names = HSV_SORTED_COLORS[idx]
result = term.home + term.normal + ''.join(
getattr(term, HSV_SORTED_COLORS[i][1][0]) + '◼'
for i in range(len(HSV_SORTED_COLORS))
)
result += term.clear_eos + '\n'
result += getattr(term, 'on_' + color_names[0]) + term.clear_eos + '\n'
result += term.normal + \
term.center(f'{" | ".join(color_names)}: {rgb_color}') + '\n'
result += term.normal + term.center(
f'{term.number_of_colors} colors - '
f'{term.color_distance_algorithm}')
result += term.move_yx(idx // term.width, idx % term.width)
result += term.on_color_rgb(*rgb_color)(' \b')
return result
def next_algo(algo, forward):
algos = tuple(sorted(blessed.color.COLOR_DISTANCE_ALGORITHMS))
next_index = algos.index(algo) + (1 if forward else -1)
if next_index == len(algos):
next_index = 0
return algos[next_index]
def next_color(color, forward):
colorspaces = (4, 8, 16, 256, 1 << 24)
next_index = colorspaces.index(color) + (1 if forward else -1)
if next_index == len(colorspaces):
next_index = 0
return colorspaces[next_index]
def main():
term = blessed.Terminal()
with term.cbreak(), term.hidden_cursor(), term.fullscreen():
idx = len(HSV_SORTED_COLORS) // 2
dirty = True
while True:
if dirty:
outp = render(term, idx)
print(outp, end='', flush=True)
with term.hidden_cursor():
inp = term.inkey()
dirty = True
if inp.code == term.KEY_LEFT or inp == 'h':
idx -= 1
elif inp.code == term.KEY_DOWN or inp == 'j':
idx += term.width
elif inp.code == term.KEY_UP or inp == 'k':
idx -= term.width
elif inp.code == term.KEY_RIGHT or inp == 'l':
idx += 1
elif inp.code in (term.KEY_TAB, term.KEY_BTAB):
term.number_of_colors = next_color(
term.number_of_colors, inp.code == term.KEY_TAB)
elif inp in ('[', ']'):
term.color_distance_algorithm = next_algo(
term.color_distance_algorithm, inp == '[')
elif inp != '\x0c':
dirty = False
while idx < 0:
idx += len(HSV_SORTED_COLORS)
while idx >= len(HSV_SORTED_COLORS):
idx -= len(HSV_SORTED_COLORS)
if __name__ == '__main__':
main()
python-blessed-1.20.0/blessed/ 0000775 0000000 0000000 00000000000 14565701425 0016164 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/blessed/__init__.py 0000664 0000000 0000000 00000001257 14565701425 0020302 0 ustar 00root root 0000000 0000000 """
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"
python-blessed-1.20.0/blessed/_capabilities.py 0000664 0000000 0000000 00000014224 14565701425 0021331 0 ustar 00root root 0000000 0000000 """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',
)
python-blessed-1.20.0/blessed/_capabilities.pyi 0000664 0000000 0000000 00000000411 14565701425 0021473 0 ustar 00root root 0000000 0000000 # 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, ...]
python-blessed-1.20.0/blessed/color.py 0000664 0000000 0000000 00000016516 14565701425 0017665 0 ustar 00root root 0000000 0000000 # -*- 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}
python-blessed-1.20.0/blessed/color.pyi 0000664 0000000 0000000 00000001262 14565701425 0020026 0 ustar 00root root 0000000 0000000 # 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]]
python-blessed-1.20.0/blessed/colorspace.py 0000664 0000000 0000000 00000104761 14565701425 0020701 0 ustar 00root root 0000000 0000000 """
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),
)
python-blessed-1.20.0/blessed/colorspace.pyi 0000664 0000000 0000000 00000000352 14565701425 0021041 0 ustar 00root root 0000000 0000000 # 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, ...]
python-blessed-1.20.0/blessed/formatters.py 0000664 0000000 0000000 00000045701 14565701425 0020733 0 ustar 00root root 0000000 0000000 """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''):
# 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''):
# 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;;;m
# - ^[48;2;;;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)
python-blessed-1.20.0/blessed/formatters.pyi 0000664 0000000 0000000 00000004053 14565701425 0021077 0 ustar 00root root 0000000 0000000 # 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]: ...
python-blessed-1.20.0/blessed/keyboard.py 0000664 0000000 0000000 00000042454 14565701425 0020347 0 ustar 00root root 0000000 0000000 """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',)
python-blessed-1.20.0/blessed/keyboard.pyi 0000664 0000000 0000000 00000001434 14565701425 0020511 0 ustar 00root root 0000000 0000000 # 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: ...
python-blessed-1.20.0/blessed/py.typed 0000664 0000000 0000000 00000000000 14565701425 0017651 0 ustar 00root root 0000000 0000000 python-blessed-1.20.0/blessed/sequences.py 0000664 0000000 0000000 00000041332 14565701425 0020534 0 ustar 00root root 0000000 0000000 # -*- 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 ''.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
python-blessed-1.20.0/blessed/sequences.pyi 0000664 0000000 0000000 00000003474 14565701425 0020712 0 ustar 00root root 0000000 0000000 # 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: ...
python-blessed-1.20.0/blessed/terminal.py 0000664 0000000 0000000 00000171173 14565701425 0020363 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# pylint: disable=too-many-lines
"""Module containing :class:`Terminal`, the primary API entry point."""
# std imports
import os
import re
import sys
import time
import codecs
import locale
import select
import struct
import platform
import warnings
import functools
import contextlib
import collections
# local
from .color import COLOR_DISTANCE_ALGORITHMS
from .keyboard import (_time_left,
_read_until,
resolve_sequence,
get_keyboard_codes,
get_leading_prefixes,
get_keyboard_sequences)
from .sequences import Termcap, Sequence, SequenceTextWrapper
from .colorspace import RGB_256TABLE
from .formatters import (COLORS,
COMPOUNDABLES,
FormattingString,
NullCallableString,
ParameterizingString,
FormattingOtherString,
split_compound,
resolve_attribute,
resolve_capability)
from ._capabilities import CAPABILITY_DATABASE, CAPABILITIES_ADDITIVES, CAPABILITIES_RAW_MIXIN
# isort: off
# Alias py2 exception to py3
if sys.version_info[:2] < (3, 3):
InterruptedError = select.error # pylint: disable=redefined-builtin
HAS_TTY = True
if platform.system() == 'Windows':
IS_WINDOWS = True
import jinxed as curses # pylint: disable=import-error
from jinxed.win32 import get_console_input_encoding # pylint: disable=import-error
else:
IS_WINDOWS = False
import curses
try:
import fcntl
import termios
import tty
except ImportError:
_TTY_METHODS = ('setraw', 'cbreak', 'kbhit', 'height', 'width')
_MSG_NOSUPPORT = (
"One or more of the modules: 'termios', 'fcntl', and 'tty' "
"are not found on your platform '{platform}'. "
"The following methods of Terminal are dummy/no-op "
"unless a deriving class overrides them: {tty_methods}."
.format(platform=platform.system(),
tty_methods=', '.join(_TTY_METHODS)))
warnings.warn(_MSG_NOSUPPORT)
HAS_TTY = False
_CUR_TERM = None # See comments at end of file
class Terminal(object):
"""
An abstraction for color, style, positioning, and input in the terminal.
This keeps the endless calls to ``tigetstr()`` and ``tparm()`` out of your code, acts
intelligently when somebody pipes your output to a non-terminal, and abstracts over the
complexity of unbuffered keyboard input. It uses the terminfo database to remain portable across
terminal types.
"""
# pylint: disable=too-many-instance-attributes,too-many-public-methods
# Too many public methods (28/20)
# Too many instance attributes (12/7)
#: Sugary names for commonly-used capabilities
_sugar = {
'save': 'sc',
'restore': 'rc',
'clear_eol': 'el',
'clear_bol': 'el1',
'clear_eos': 'ed',
'enter_fullscreen': 'smcup',
'exit_fullscreen': 'rmcup',
'move': 'cup',
'move_yx': 'cup',
'move_x': 'hpa',
'move_y': 'vpa',
'hide_cursor': 'civis',
'normal_cursor': 'cnorm',
'reset_colors': 'op',
'normal': 'sgr0',
'reverse': 'rev',
'italic': 'sitm',
'no_italic': 'ritm',
'shadow': 'sshm',
'no_shadow': 'rshm',
'standout': 'smso',
'no_standout': 'rmso',
'subscript': 'ssubm',
'no_subscript': 'rsubm',
'superscript': 'ssupm',
'no_superscript': 'rsupm',
'underline': 'smul',
'no_underline': 'rmul',
'cursor_report': 'u6',
'cursor_request': 'u7',
'terminal_answerback': 'u8',
'terminal_enquire': 'u9',
}
def __init__(self, kind=None, stream=None, force_styling=False):
"""
Initialize the terminal.
:arg str kind: A terminal string as taken by :func:`curses.setupterm`.
Defaults to the value of the ``TERM`` environment variable.
.. note:: Terminals withing a single process must share a common
``kind``. See :obj:`_CUR_TERM`.
:arg file stream: A file-like object representing the Terminal output.
Defaults to the original value of :obj:`sys.__stdout__`, like
:func:`curses.initscr` does.
If ``stream`` is not a tty, empty Unicode strings are returned for
all capability values, so things like piping your program output to
a pipe or file does not emit terminal sequences.
:arg bool force_styling: Whether to force the emission of capabilities
even if :obj:`sys.__stdout__` does not seem to be connected to a
terminal. If you want to force styling to not happen, use
``force_styling=None``.
This comes in handy if users are trying to pipe your output through
something like ``less -r`` or build systems which support decoding
of terminal sequences.
"""
# pylint: disable=global-statement,too-many-branches
global _CUR_TERM
self.errors = ['parameters: kind=%r, stream=%r, force_styling=%r' %
(kind, stream, force_styling)]
self._normal = None # cache normal attr, preventing recursive lookups
# we assume our input stream to be line-buffered until either the
# cbreak of raw context manager methods are entered with an attached tty.
self._line_buffered = True
self._stream = stream
self._keyboard_fd = None
self._init_descriptor = None
self._is_a_tty = False
self.__init__streams()
if IS_WINDOWS and self._init_descriptor is not None:
self._kind = kind or curses.get_term(self._init_descriptor)
else:
self._kind = kind or os.environ.get('TERM', 'dumb') or 'dumb'
self._does_styling = False
if force_styling is None and self.is_a_tty:
self.errors.append('force_styling is None')
elif force_styling or self.is_a_tty:
self._does_styling = True
if self.does_styling:
# Initialize curses (call setupterm), so things like tigetstr() work.
try:
curses.setupterm(self._kind, self._init_descriptor)
except curses.error as err:
msg = 'Failed to setupterm(kind={0!r}): {1}'.format(self._kind, err)
warnings.warn(msg)
self.errors.append(msg)
self._kind = None
self._does_styling = False
else:
if _CUR_TERM is None or self._kind == _CUR_TERM:
_CUR_TERM = self._kind
else:
# termcap 'kind' is immutable in a python process! Once
# initialized by setupterm, it is unsupported by the
# 'curses' module to change the terminal type again. If you
# are a downstream developer and you need this
# functionality, consider sub-processing, instead.
warnings.warn(
'A terminal of kind "%s" has been requested; due to an'
' internal python curses bug, terminal capabilities'
' for a terminal of kind "%s" will continue to be'
' returned for the remainder of this process.' % (
self._kind, _CUR_TERM,))
self.__init__color_capabilities()
self.__init__capabilities()
self.__init__keycodes()
def __init__streams(self):
# pylint: disable=too-complex,too-many-branches
# Agree to disagree !
stream_fd = None
# Default stream is stdout
if self._stream is None:
self._stream = sys.__stdout__
if not hasattr(self._stream, 'fileno'):
self.errors.append('stream has no fileno method')
elif not callable(self._stream.fileno):
self.errors.append('stream.fileno is not callable')
else:
try:
stream_fd = self._stream.fileno()
except ValueError as err:
# The stream is not a file, such as the case of StringIO, or, when it has been
# "detached", such as might be the case of stdout in some test scenarios.
self.errors.append('Unable to determine output stream file descriptor: %s' % err)
else:
self._is_a_tty = os.isatty(stream_fd)
if not self._is_a_tty:
self.errors.append('stream not a TTY')
# Keyboard valid as stdin only when output stream is stdout or stderr and is a tty.
if self._stream in (sys.__stdout__, sys.__stderr__):
try:
self._keyboard_fd = sys.__stdin__.fileno()
except (AttributeError, ValueError) as err:
self.errors.append('Unable to determine input stream file descriptor: %s' % err)
else:
# _keyboard_fd only non-None if both stdin and stdout is a tty.
if not self.is_a_tty:
self.errors.append('Output stream is not a TTY')
self._keyboard_fd = None
elif not os.isatty(self._keyboard_fd):
self.errors.append('Input stream is not a TTY')
self._keyboard_fd = None
else:
self.errors.append('Output stream is not a default stream')
# The descriptor to direct terminal initialization sequences to.
self._init_descriptor = stream_fd
if stream_fd is None:
try:
self._init_descriptor = sys.__stdout__.fileno()
except ValueError as err:
self.errors.append('Unable to determine __stdout__ file descriptor: %s' % err)
def __init__color_capabilities(self):
self._color_distance_algorithm = 'cie2000'
if not self.does_styling:
self.number_of_colors = 0
elif IS_WINDOWS or os.environ.get('COLORTERM') in ('truecolor', '24bit'):
self.number_of_colors = 1 << 24
else:
self.number_of_colors = max(0, curses.tigetnum('colors') or -1)
def __clear_color_capabilities(self):
for cached_color_cap in set(dir(self)) & COLORS:
delattr(self, cached_color_cap)
def __init__capabilities(self):
# important that we lay these in their ordered direction, so that our
# preferred, 'color' over 'set_a_attributes1', for example.
self.caps = collections.OrderedDict()
# some static injected patterns, esp. without named attribute access.
for name, (attribute, pattern) in CAPABILITIES_ADDITIVES.items():
self.caps[name] = Termcap(name, pattern, attribute)
for name, (attribute, kwds) in CAPABILITY_DATABASE.items():
if self.does_styling:
# attempt dynamic lookup
cap = getattr(self, attribute)
if cap:
self.caps[name] = Termcap.build(
name, cap, attribute, **kwds)
continue
# fall-back
pattern = CAPABILITIES_RAW_MIXIN.get(name)
if pattern:
self.caps[name] = Termcap(name, pattern, attribute)
# make a compiled named regular expression table
self.caps_compiled = re.compile(
'|'.join(cap.pattern for name, cap in self.caps.items()))
# for tokenizer, the '.lastgroup' is the primary lookup key for
# 'self.caps', unless 'MISMATCH'; then it is an unmatched character.
self._caps_compiled_any = re.compile('|'.join(
cap.named_pattern for name, cap in self.caps.items()
) + '|(?P.)')
self._caps_unnamed_any = re.compile('|'.join(
'({0})'.format(cap.pattern) for name, cap in self.caps.items()
) + '|(.)')
def __init__keycodes(self):
# Initialize keyboard data determined by capability.
# Build database of int code <=> KEY_NAME.
self._keycodes = get_keyboard_codes()
# Store attributes as: self.KEY_NAME = code.
for key_code, key_name in self._keycodes.items():
setattr(self, key_name, key_code)
# Build database of sequence <=> KEY_NAME.
self._keymap = get_keyboard_sequences(self)
# build set of prefixes of sequences
self._keymap_prefixes = get_leading_prefixes(self._keymap)
# keyboard stream buffer
self._keyboard_buf = collections.deque()
if self._keyboard_fd is not None:
# set input encoding and initialize incremental decoder
if IS_WINDOWS:
self._encoding = get_console_input_encoding() \
or locale.getpreferredencoding() or 'UTF-8'
else:
self._encoding = locale.getpreferredencoding() or 'UTF-8'
try:
self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)()
except LookupError as err:
# encoding is illegal or unsupported, use 'UTF-8'
warnings.warn('LookupError: {0}, defaulting to UTF-8 for keyboard.'.format(err))
self._encoding = 'UTF-8'
self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)()
def __getattr__(self, attr):
r"""
Return a terminal capability as Unicode string.
For example, ``term.bold`` is a unicode string that may be prepended
to text to set the video attribute for bold, which should also be
terminated with the pairing :attr:`normal`. This capability
returns a callable, so you can use ``term.bold("hi")`` which
results in the joining of ``(term.bold, "hi", term.normal)``.
Compound formatters may also be used. For example::
>>> term.bold_blink_red_on_green("merry x-mas!")
For a parameterized capability such as ``move`` (or ``cup``), pass the
parameters as positional arguments::
>>> term.move(line, column)
See the manual page `terminfo(5)
`_ for a
complete list of capabilities and their arguments.
"""
if not self._does_styling:
return NullCallableString()
# Fetch the missing 'attribute' into some kind of curses-resolved
# capability, and cache by attaching to this Terminal class instance.
#
# Note that this will prevent future calls to __getattr__(), but
# that's precisely the idea of the cache!
val = resolve_attribute(self, attr)
setattr(self, attr, val)
return val
@property
def kind(self):
"""
Read-only property: Terminal kind determined on class initialization.
:rtype: str
"""
return self._kind
@property
def does_styling(self):
"""
Read-only property: Whether this class instance may emit sequences.
:rtype: bool
"""
return self._does_styling
@property
def is_a_tty(self):
"""
Read-only property: Whether :attr:`~.stream` is a terminal.
:rtype: bool
"""
return self._is_a_tty
@property
def height(self):
"""
Read-only property: Height of the terminal (in number of lines).
:rtype: int
"""
return self._height_and_width().ws_row
@property
def width(self):
"""
Read-only property: Width of the terminal (in number of columns).
:rtype: int
"""
return self._height_and_width().ws_col
@property
def pixel_height(self):
"""
Read-only property: Height ofthe terminal (in pixels).
:rtype: int
"""
return self._height_and_width().ws_ypixel
@property
def pixel_width(self):
"""
Read-only property: Width of terminal (in pixels).
:rtype: int
"""
return self._height_and_width().ws_xpixel
@staticmethod
def _winsize(fd):
"""
Return named tuple describing size of the terminal by ``fd``.
If the given platform does not have modules :mod:`termios`,
:mod:`fcntl`, or :mod:`tty`, window size of 80 columns by 25
rows is always returned.
:arg int fd: file descriptor queries for its window size.
:raises IOError: the file descriptor ``fd`` is not a terminal.
: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).
"""
if HAS_TTY:
# pylint: disable=protected-access
data = fcntl.ioctl(fd, termios.TIOCGWINSZ, WINSZ._BUF)
return WINSZ(*struct.unpack(WINSZ._FMT, data))
return WINSZ(ws_row=25, ws_col=80, ws_xpixel=0, ws_ypixel=0)
def _height_and_width(self):
"""
Return a tuple of (terminal height, terminal width).
If :attr:`stream` or :obj:`sys.__stdout__` is not a tty or does not
support :func:`fcntl.ioctl` of :const:`termios.TIOCGWINSZ`, a window
size of 80 columns by 25 rows is returned for any values not
represented by environment variables ``LINES`` and ``COLUMNS``, which
is the default text mode of IBM PC compatibles.
:rtype: WINSZ
:returns: Named tuple specifying the terminal size
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``: height of terminal by its number of cell rows.
- ``ws_col``: width of terminal by its number of cell columns.
- ``ws_xpixel``: width of terminal by pixels (not accurate).
- ``ws_ypixel``: height of terminal by pixels (not accurate).
.. note:: the peculiar (height, width, width, height) order, which
matches the return order of TIOCGWINSZ!
"""
for fd in (self._init_descriptor, sys.__stdout__):
try:
if fd is not None:
return self._winsize(fd)
except (IOError, OSError, ValueError, TypeError): # pylint: disable=overlapping-except
pass
return WINSZ(ws_row=int(os.getenv('LINES', '25')),
ws_col=int(os.getenv('COLUMNS', '80')),
ws_xpixel=None,
ws_ypixel=None)
def _query_response(self, query_str, response_re, timeout):
"""
Sends a query string to the terminal and waits for a response.
:arg str query_str: Query string written to output
:arg str response_re: Regular expression matching query response
:arg float timeout: Return after time elapsed in seconds
:return: re.match object for response_re or None if not found
:rtype: re.Match
"""
# Avoid changing user's desired raw or cbreak mode if already entered,
# by entering cbreak mode ourselves. This is necessary to receive user
# input without awaiting a human to press the return key. This mode
# also disables echo, which we should also hide, as our input is an
# sequence that is not meaningful for display as an output sequence.
ctx = None
try:
if self._line_buffered:
ctx = self.cbreak()
ctx.__enter__() # pylint: disable=no-member
# Emit the query sequence,
self.stream.write(query_str)
self.stream.flush()
# Wait for response
match, data = _read_until(term=self,
pattern=response_re,
timeout=timeout)
# Exclude response from subsequent input
if match:
data = data[:match.start()] + data[match.end():]
# re-buffer keyboard data, if any
self.ungetch(data)
finally:
if ctx is not None:
ctx.__exit__(None, None, None) # pylint: disable=no-member
return match
@contextlib.contextmanager
def location(self, x=None, y=None):
"""
Context manager for temporarily moving the cursor.
:arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
:arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
:return: a context manager.
:rtype: Iterator
Move the cursor to a certain position on entry, do any kind of I/O, and upon exit
let you print stuff there, then return the cursor to its original position:
.. code-block:: python
term = Terminal()
with term.location(y=0, x=0):
for row_num in range(term.height-1):
print('Row #{row_num}')
print(term.clear_eol + 'Back to original location.')
Specify ``x`` to move to a certain column, ``y`` to move to a certain
row, both, or neither. If you specify neither, only the saving and
restoration of cursor position will happen. This can be useful if you
simply want to restore your place after doing some manual cursor
movement.
Calls cannot be nested: only one should be entered at a time.
.. note:: The argument order *(x, y)* differs from the return value order *(y, x)*
of :meth:`get_location`, or argument order *(y, x)* of :meth:`move`. This is
for API Compaibility with the blessings library, sorry for the trouble!
"""
# pylint: disable=invalid-name
# Invalid argument name "x"
# Save position and move to the requested column, row, or both:
self.stream.write(self.save)
if x is not None and y is not None:
self.stream.write(self.move(y, x))
elif x is not None:
self.stream.write(self.move_x(x))
elif y is not None:
self.stream.write(self.move_y(y))
try:
self.stream.flush()
yield
finally:
# Restore original cursor position:
self.stream.write(self.restore)
self.stream.flush()
def get_location(self, timeout=None):
r"""
Return tuple (row, column) of cursor position.
:arg float timeout: Return after time elapsed in seconds with value ``(-1, -1)`` indicating
that the remote end did not respond.
:rtype: tuple
:returns: cursor position as tuple in form of ``(y, x)``. When a timeout is specified,
always ensure the return value is checked for ``(-1, -1)``.
The location of the cursor is determined by emitting the ``u7`` terminal capability, or
VT100 `Query Cursor Position
`_
when such capability is undefined, which elicits a response from a reply string described by
capability ``u6``, or again VT100's definition of ``\x1b[%i%d;%dR`` when undefined.
The ``(y, x)`` return value matches the parameter order of the :meth:`move_xy` capability.
The following sequence should cause the cursor to not move at all::
>>> term = Terminal()
>>> term.move_yx(*term.get_location()))
And the following should assert True with a terminal:
>>> term = Terminal()
>>> given_y, given_x = 10, 20
>>> with term.location(y=given_y, x=given_x):
... result_y, result_x = term.get_location()
...
>>> assert given_x == result_x, (given_x, result_x)
>>> assert given_y == result_y, (given_y, result_y)
"""
# Local lines attached by termios and remote login protocols such as
# ssh and telnet both provide a means to determine the window
# dimensions of a connected client, but **no means to determine the
# location of the cursor**.
#
# from https://invisible-island.net/ncurses/terminfo.src.html,
#
# > The System V Release 4 and XPG4 terminfo format defines ten string
# > capabilities for use by applications, .... In this file,
# > we use certain of these capabilities to describe functions which
# > are not covered by terminfo. The mapping is as follows:
# >
# > u9 terminal enquire string (equiv. to ANSI/ECMA-48 DA)
# > u8 terminal answerback description
# > u7 cursor position request (equiv. to VT100/ANSI/ECMA-48 DSR 6)
# > u6 cursor position report (equiv. to ANSI/ECMA-48 CPR)
response_str = getattr(self, self.caps['cursor_report'].attribute) or u'\x1b[%i%d;%dR'
match = self._query_response(
self.u7 or u'\x1b[6n', self.caps['cursor_report'].re_compiled, timeout
)
if match:
# return matching sequence response, the cursor location.
row, col = (int(val) for val in match.groups())
# Per https://invisible-island.net/ncurses/terminfo.src.html
# The cursor position report () string must contain two
# scanf(3)-style %d format elements. The first of these must
# correspond to the Y coordinate and the second to the %d.
# If the string contains the sequence %i, it is taken as an
# instruction to decrement each value after reading it (this is
# the inverse sense from the cup string).
if u'%i' in response_str:
row -= 1
col -= 1
return row, col
# We chose to return an illegal value rather than an exception,
# favoring that users author function filters, such as max(0, y),
# rather than crowbarring such logic into an exception handler.
return -1, -1
def get_fgcolor(self, timeout=None):
"""
Return tuple (r, g, b) of foreground color.
:arg float timeout: Return after time elapsed in seconds with value ``(-1, -1, -1)``
indicating that the remote end did not respond.
:rtype: tuple
:returns: foreground color as tuple in form of ``(r, g, b)``. When a timeout is specified,
always ensure the return value is checked for ``(-1, -1, -1)``.
The foreground color is determined by emitting an `OSC 10 color query
`_.
"""
match = self._query_response(
u'\x1b]10;?\x07',
re.compile(u'\x1b]10;rgb:([0-9a-fA-F]+)/([0-9a-fA-F]+)/([0-9a-fA-F]+)\x07'),
timeout
)
return tuple(int(val, 16) for val in match.groups()) if match else (-1, -1, -1)
def get_bgcolor(self, timeout=None):
"""
Return tuple (r, g, b) of background color.
:arg float timeout: Return after time elapsed in seconds with value ``(-1, -1, -1)``
indicating that the remote end did not respond.
:rtype: tuple
:returns: background color as tuple in form of ``(r, g, b)``. When a timeout is specified,
always ensure the return value is checked for ``(-1, -1, -1)``.
The background color is determined by emitting an `OSC 11 color query
`_.
"""
match = self._query_response(
u'\x1b]11;?\x07',
re.compile(u'\x1b]11;rgb:([0-9a-fA-F]+)/([0-9a-fA-F]+)/([0-9a-fA-F]+)\x07'),
timeout
)
return tuple(int(val, 16) for val in match.groups()) if match else (-1, -1, -1)
@contextlib.contextmanager
def fullscreen(self):
"""
Context manager that switches to secondary screen, restoring on exit.
Under the hood, this switches between the primary screen buffer and
the secondary one. The primary one is saved on entry and restored on
exit. Likewise, the secondary contents are also stable and are
faithfully restored on the next entry::
with term.fullscreen():
main()
.. note:: There is only one primary and one secondary screen buffer.
:meth:`fullscreen` calls cannot be nested, only one should be
entered at a time.
"""
self.stream.write(self.enter_fullscreen)
self.stream.flush()
try:
yield
finally:
self.stream.write(self.exit_fullscreen)
self.stream.flush()
@contextlib.contextmanager
def hidden_cursor(self):
"""
Context manager that hides the cursor, setting visibility on exit.
with term.hidden_cursor():
main()
.. note:: :meth:`hidden_cursor` calls cannot be nested: only one
should be entered at a time.
"""
self.stream.write(self.hide_cursor)
self.stream.flush()
try:
yield
finally:
self.stream.write(self.normal_cursor)
self.stream.flush()
def move_xy(self, x, y):
"""
A callable string that moves the cursor to the given ``(x, y)`` screen coordinates.
:arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
:arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
:rtype: ParameterizingString
:returns: Callable string that moves the cursor to the given coordinates
"""
# this is just a convenience alias to the built-in, but hidden 'move'
# attribute -- we encourage folks to use only (x, y) positional
# arguments, or, if they must use (y, x), then use the 'move_yx'
# alias.
return self.move(y, x)
def move_yx(self, y, x):
"""
A callable string that moves the cursor to the given ``(y, x)`` screen coordinates.
:arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
:arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
:rtype: ParameterizingString
:returns: Callable string that moves the cursor to the given coordinates
"""
return self.move(y, x)
@property
def move_left(self):
"""Move cursor 1 cells to the left, or callable string for n>1 cells."""
return FormattingOtherString(self.cub1, ParameterizingString(self.cub))
@property
def move_right(self):
"""Move cursor 1 or more cells to the right, or callable string for n>1 cells."""
return FormattingOtherString(self.cuf1, ParameterizingString(self.cuf))
@property
def move_up(self):
"""Move cursor 1 or more cells upwards, or callable string for n>1 cells."""
return FormattingOtherString(self.cuu1, ParameterizingString(self.cuu))
@property
def move_down(self):
"""Move cursor 1 or more cells downwards, or callable string for n>1 cells."""
return FormattingOtherString(self.cud1, ParameterizingString(self.cud))
@property
def color(self):
"""
A callable string that sets the foreground color.
:rtype: ParameterizingString
The capability is unparameterized until called and passed a number, at which point it
returns another string which represents a specific color change. This second string can
further be called to color a piece of text and set everything back to normal afterward.
This should not be used directly, but rather a specific color by name or
:meth:`~.Terminal.color_rgb` value.
"""
if self.does_styling:
return ParameterizingString(self._foreground_color, self.normal, 'color')
return NullCallableString()
def color_rgb(self, red, green, blue):
"""
Provides callable formatting string to set foreground color to the specified RGB color.
:arg int red: RGB value of Red.
:arg int green: RGB value of Green.
:arg int blue: RGB value of Blue.
:rtype: FormattingString
:returns: Callable string that sets the foreground color
If the terminal does not support RGB color, the nearest supported
color will be determined using :py:attr:`color_distance_algorithm`.
"""
if self.number_of_colors == 1 << 24:
# "truecolor" 24-bit
fmt_attr = u'\x1b[38;2;{0};{1};{2}m'.format(red, green, blue)
return FormattingString(fmt_attr, self.normal)
# color by approximation to 256 or 16-color terminals
color_idx = self.rgb_downconvert(red, green, blue)
return FormattingString(self._foreground_color(color_idx), self.normal)
@property
def on_color(self):
"""
A callable capability that sets the background color.
:rtype: ParameterizingString
"""
if self.does_styling:
return ParameterizingString(self._background_color, self.normal, 'on_color')
return NullCallableString()
def on_color_rgb(self, red, green, blue):
"""
Provides callable formatting string to set background color to the specified RGB color.
:arg int red: RGB value of Red.
:arg int green: RGB value of Green.
:arg int blue: RGB value of Blue.
:rtype: FormattingString
:returns: Callable string that sets the foreground color
If the terminal does not support RGB color, the nearest supported
color will be determined using :py:attr:`color_distance_algorithm`.
"""
if self.number_of_colors == 1 << 24:
fmt_attr = u'\x1b[48;2;{0};{1};{2}m'.format(red, green, blue)
return FormattingString(fmt_attr, self.normal)
color_idx = self.rgb_downconvert(red, green, blue)
return FormattingString(self._background_color(color_idx), self.normal)
def formatter(self, value):
"""
Provides callable formatting string to set color and other text formatting options.
:arg str value: Sugary, ordinary, or compound formatted terminal capability,
such as "red_on_white", "normal", "red", or "bold_on_black".
:rtype: :class:`FormattingString` or :class:`NullCallableString`
:returns: Callable string that sets color and other text formatting options
Calling ``term.formatter('bold_on_red')`` is equivalent to ``term.bold_on_red``, but a
string that is not a valid text formatter will return a :class:`NullCallableString`.
This is intended to allow validation of text formatters without the possibility of
inadvertently returning another terminal capability.
"""
formatters = split_compound(value)
if all((fmt in COLORS or fmt in COMPOUNDABLES) for fmt in formatters):
return getattr(self, value)
return NullCallableString()
def rgb_downconvert(self, red, green, blue):
"""
Translate an RGB color to a color code of the terminal's color depth.
:arg int red: RGB value of Red (0-255).
:arg int green: RGB value of Green (0-255).
:arg int blue: RGB value of Blue (0-255).
:rtype: int
:returns: Color code of downconverted RGB color
"""
# Though pre-computing all 1 << 24 options is memory-intensive, a pre-computed
# "k-d tree" of 256 (x,y,z) vectors of a colorspace in 3 dimensions, such as a
# cone of HSV, or simply 255x255x255 RGB square, any given rgb value is just a
# nearest-neighbor search of 256 points, which k-d should be much faster by
# sub-dividing / culling search points, rather than our "search all 256 points
# always" approach.
fn_distance = COLOR_DISTANCE_ALGORITHMS[self.color_distance_algorithm]
color_idx = 7
shortest_distance = None
for cmp_depth, cmp_rgb in enumerate(RGB_256TABLE):
cmp_distance = fn_distance(cmp_rgb, (red, green, blue))
if shortest_distance is None or cmp_distance < shortest_distance:
shortest_distance = cmp_distance
color_idx = cmp_depth
if cmp_depth >= self.number_of_colors:
break
return color_idx
@property
def normal(self):
"""
A capability that resets all video attributes.
:rtype: str
``normal`` is an alias for ``sgr0`` or ``exit_attribute_mode``. Any
styling attributes previously applied, such as foreground or
background colors, reverse video, or bold are reset to defaults.
"""
if self._normal:
return self._normal
self._normal = resolve_capability(self, 'normal')
return self._normal
def link(self, url, text, url_id=''):
"""
Display ``text`` that when touched or clicked, navigates to ``url``.
Optional ``url_id`` may be specified, so that non-adjacent cells can reference a single
target, all cells painted with the same "id" will highlight on hover, rather than any
individual one, as described in "Hovering and underlining the id parameter" of gist
https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda.
:param str url: Hyperlink URL.
:param str text: Clickable text.
:param str url_id: Optional 'id'.
:rtype: str
:returns: String of ``text`` as a hyperlink to ``url``.
"""
assert len(url) < 2000, (len(url), url)
if url_id:
assert len(str(url_id)) < 250, (len(str(url_id)), url_id)
params = 'id={0}'.format(url_id)
else:
params = ''
if not self.does_styling:
return text
return ('\x1b]8;{0};{1}\x1b\\{2}'
'\x1b]8;;\x1b\\'.format(params, url, text))
@property
def stream(self):
"""
Read-only property: stream the terminal outputs to.
This is a convenience attribute. It is used internally for implied
writes performed by context managers :meth:`~.hidden_cursor`,
:meth:`~.fullscreen`, :meth:`~.location`, and :meth:`~.keypad`.
"""
return self._stream
@property
def number_of_colors(self):
"""
Number of colors supported by terminal.
Common return values are 0, 8, 16, 256, or 1 << 24.
This may be used to test whether the terminal supports colors,
and at what depth, if that's a concern.
If this property is assigned a value of 88, the value 16 will be saved. This is due to the
the rarity of 88 color support and the inconsistency of behavior between implementations.
Assigning this property to a value other than 0, 4, 8, 16, 88, 256, or 1 << 24 will
raise an :py:exc:`AssertionError`.
"""
return self._number_of_colors
@number_of_colors.setter
def number_of_colors(self, value):
assert value in (0, 4, 8, 16, 88, 256, 1 << 24)
# Because 88 colors is rare and we can't guarantee consistent behavior,
# when 88 colors is detected, it is treated as 16 colors
self._number_of_colors = 16 if value == 88 else value
self.__clear_color_capabilities()
@property
def color_distance_algorithm(self):
"""
Color distance algorithm used by :meth:`rgb_downconvert`.
The slowest, but most accurate, 'cie2000', is default. Other available options are 'rgb',
'rgb-weighted', 'cie76', and 'cie94'.
"""
return self._color_distance_algorithm
@color_distance_algorithm.setter
def color_distance_algorithm(self, value):
assert value in COLOR_DISTANCE_ALGORITHMS
self._color_distance_algorithm = value
self.__clear_color_capabilities()
@property
def _foreground_color(self):
"""
Convenience capability to support :attr:`~.on_color`.
Prefers returning sequence for capability ``setaf``, "Set foreground color to #1, using ANSI
escape". If the given terminal does not support such sequence, fallback to returning
attribute ``setf``, "Set foreground color #1".
"""
return self.setaf or self.setf
@property
def _background_color(self):
"""
Convenience capability to support :attr:`~.on_color`.
Prefers returning sequence for capability ``setab``, "Set background color to #1, using ANSI
escape". If the given terminal does not support such sequence, fallback to returning
attribute ``setb``, "Set background color #1".
"""
return self.setab or self.setb
def ljust(self, text, width=None, fillchar=u' '):
"""
Left-align ``text``, which may contain terminal sequences.
:arg str text: String to be aligned
:arg int width: Total width to fill with aligned text. If
unspecified, the whole width of the terminal is filled.
:arg str fillchar: String for padding the right of ``text``
:rtype: str
:returns: String of ``text``, left-aligned by ``width``.
"""
# Left justification is different from left alignment, but we continue
# the vocabulary error of the str method for polymorphism.
if width is None:
width = self.width
return Sequence(text, self).ljust(width, fillchar)
def rjust(self, text, width=None, fillchar=u' '):
"""
Right-align ``text``, which may contain terminal sequences.
:arg str text: String to be aligned
:arg int width: Total width to fill with aligned text. If
unspecified, the whole width of the terminal is used.
:arg str fillchar: String for padding the left of ``text``
:rtype: str
:returns: String of ``text``, right-aligned by ``width``.
"""
if width is None:
width = self.width
return Sequence(text, self).rjust(width, fillchar)
def center(self, text, width=None, fillchar=u' '):
"""
Center ``text``, which may contain terminal sequences.
:arg str text: String to be centered
:arg int width: Total width in which to center text. If
unspecified, the whole width of the terminal is used.
:arg str fillchar: String for padding the left and right of ``text``
:rtype: str
:returns: String of ``text``, centered by ``width``
"""
if width is None:
width = self.width
return Sequence(text, self).center(width, fillchar)
def truncate(self, text, width=None):
r"""
Truncate ``text`` to maximum ``width`` printable characters, retaining terminal sequences.
:arg str text: Text to truncate
:arg int width: The maximum width to truncate it to
:rtype: str
:returns: ``text`` truncated to at most ``width`` printable characters
>>> term.truncate(u'xyz\x1b[0;3m', 2)
u'xy\x1b[0;3m'
"""
if width is None:
width = self.width
return Sequence(text, self).truncate(width)
def length(self, text):
u"""
Return printable length of a string containing sequences.
:arg str text: String to measure. May contain terminal sequences.
:rtype: int
:returns: The number of terminal character cells the string will occupy
when printed
Wide characters that consume 2 character cells are supported:
>>> term = Terminal()
>>> term.length(term.clear + term.red(u'コンニチハ'))
10
.. note:: Sequences such as 'clear', which is considered as a
"movement sequence" because it would move the cursor to
(y, x)(0, 0), are evaluated as a printable length of
*0*.
"""
return Sequence(text, self).length()
def strip(self, text, chars=None):
r"""
Return ``text`` without sequences and leading or trailing whitespace.
:rtype: str
:returns: Text with leading and trailing whitespace removed
>>> term.strip(u' \x1b[0;3m xyz ')
u'xyz'
"""
return Sequence(text, self).strip(chars)
def rstrip(self, text, chars=None):
r"""
Return ``text`` without terminal sequences or trailing whitespace.
:rtype: str
:returns: Text with terminal sequences and trailing whitespace removed
>>> term.rstrip(u' \x1b[0;3m xyz ')
u' xyz'
"""
return Sequence(text, self).rstrip(chars)
def lstrip(self, text, chars=None):
r"""
Return ``text`` without terminal sequences or leading whitespace.
:rtype: str
:returns: Text with terminal sequences and leading whitespace removed
>>> term.lstrip(u' \x1b[0;3m xyz ')
u'xyz '
"""
return Sequence(text, self).lstrip(chars)
def strip_seqs(self, text):
r"""
Return ``text`` stripped of only its terminal sequences.
:rtype: str
:returns: Text with terminal sequences removed
>>> term.strip_seqs(u'\x1b[0;3mxyz')
u'xyz'
>>> term.strip_seqs(term.cuf(5) + term.red(u'test'))
u' test'
.. note:: Non-destructive sequences that adjust horizontal distance
(such as ``\b`` or ``term.cuf(5)``) are replaced by destructive
space or erasing.
"""
return Sequence(text, self).strip_seqs()
def split_seqs(self, text, maxsplit=0):
r"""
Return ``text`` split by individual character elements and sequences.
:arg str text: String containing sequences
:arg int maxsplit: When maxsplit is nonzero, at most maxsplit splits
occur, and the remainder of the string is returned as the final element
of the list (same meaning is argument for :func:`re.split`).
:rtype: list[str]
:returns: List of sequences and individual characters
>>> term.split_seqs(term.underline(u'xyz'))
['\x1b[4m', 'x', 'y', 'z', '\x1b(B', '\x1b[m']
>>> term.split_seqs(term.underline(u'xyz'), 1)
['\x1b[4m', r'xyz\x1b(B\x1b[m']
"""
pattern = self._caps_unnamed_any
result = []
for idx, match in enumerate(re.finditer(pattern, text)):
result.append(match.group())
if maxsplit and idx == maxsplit:
remaining = text[match.end():]
if remaining:
result[-1] += remaining
break
return result
def wrap(self, text, width=None, **kwargs):
r"""
Text-wrap a string, returning a list of wrapped lines.
:arg str text: Unlike :func:`textwrap.wrap`, ``text`` may contain
terminal sequences, such as colors, bold, or underline. By
default, tabs in ``text`` are expanded by
:func:`string.expandtabs`.
:arg int width: Unlike :func:`textwrap.wrap`, ``width`` will
default to the width of the attached terminal.
:arg \**kwargs: See :py:class:`textwrap.TextWrapper`
:rtype: list
:returns: List of wrapped lines
See :class:`textwrap.TextWrapper` for keyword arguments that can
customize wrapping behaviour.
"""
width = self.width if width is None else width
wrapper = SequenceTextWrapper(width=width, term=self, **kwargs)
lines = []
for line in text.splitlines():
lines.extend(iter(wrapper.wrap(line)) if line.strip() else (u'',))
return lines
def getch(self):
"""
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.
This method name and behavior mimics curses ``getch(void)``, and
it supports :meth:`inkey`, reading only one byte from
the keyboard string at a time. This method should always return
without blocking if called after :meth:`kbhit` has returned True.
Implementors of alternate input stream methods should override
this method.
"""
assert self._keyboard_fd is not None
byte = os.read(self._keyboard_fd, 1)
return self._keyboard_decoder.decode(byte, final=False)
def ungetch(self, text):
"""
Buffer input data to be discovered by next call to :meth:`~.inkey`.
:arg str text: String to be buffered as keyboard input.
"""
self._keyboard_buf.extendleft(text)
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. The standard
implementation simply uses the :func:`select.select` call on stdin.
: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. When input is not a terminal, False is
always returned.
"""
stime = time.time()
ready_r = [None, ]
check_r = [self._keyboard_fd] if self._keyboard_fd is not None else []
while HAS_TTY:
try:
ready_r, _, _ = select.select(check_r, [], [], timeout)
except InterruptedError:
# Beginning with python3.5, IntrruptError is no longer thrown
# https://www.python.org/dev/peps/pep-0475/
#
# For previous versions of python, we take special care to
# retry select on InterruptedError exception, namely to handle
# a custom SIGWINCH handler. When installed, it would cause
# select() to be interrupted with errno 4 (EAGAIN).
#
# Just as in python3.5, it is ignored, and a new timeout value
# is derived from the previous unless timeout becomes negative.
# because the signal handler has blocked beyond timeout, then
# False is returned. Otherwise, when timeout is None, we
# continue to block indefinitely (default).
if timeout is not None:
# subtract time already elapsed,
timeout -= time.time() - stime
if timeout > 0:
continue
# no time remains after handling exception (rare)
ready_r = [] # pragma: no cover
break # pragma: no cover
else:
break
return False if self._keyboard_fd is None else check_r == ready_r
@contextlib.contextmanager
def cbreak(self):
"""
Allow each keystroke to be read immediately after it is pressed.
This is a context manager for :func:`tty.setcbreak`.
This context manager activates 'rare' mode, the opposite of 'cooked'
mode: On entry, :func:`tty.setcbreak` mode is activated disabling
line-buffering of keyboard input and turning off automatic echo of
input as output.
.. 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.
Technically, this context manager sets the :mod:`termios` attributes
of the terminal attached to :obj:`sys.__stdin__`.
.. note:: :func:`tty.setcbreak` sets ``VMIN = 1`` and ``VTIME = 0``,
see http://www.unixwiz.net/techtips/termios-vmin-vtime.html
"""
if HAS_TTY and self._keyboard_fd is not None:
# Save current terminal mode:
save_mode = termios.tcgetattr(self._keyboard_fd)
save_line_buffered = self._line_buffered
tty.setcbreak(self._keyboard_fd, termios.TCSANOW)
try:
self._line_buffered = False
yield
finally:
# Restore prior mode:
termios.tcsetattr(self._keyboard_fd,
termios.TCSAFLUSH,
save_mode)
self._line_buffered = save_line_buffered
else:
yield
@contextlib.contextmanager
def raw(self):
r"""
A context manager for :func:`tty.setraw`.
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`` or ``^S`` are
interpreted by the terminal driver and excluded from the stdin stream.
In raw mode these values are receive by the :meth:`inkey` method.
Because output processing is not done, the newline ``'\n'`` is not
enough, you must also print carriage return to ensure that the cursor
is returned to the first column::
with term.raw():
print("printing in raw mode", end="\r\n")
"""
if HAS_TTY and self._keyboard_fd is not None:
# Save current terminal mode:
save_mode = termios.tcgetattr(self._keyboard_fd)
save_line_buffered = self._line_buffered
tty.setraw(self._keyboard_fd, termios.TCSANOW)
try:
self._line_buffered = False
yield
finally:
# Restore prior mode:
termios.tcsetattr(self._keyboard_fd,
termios.TCSAFLUSH,
save_mode)
self._line_buffered = save_line_buffered
else:
yield
@contextlib.contextmanager
def keypad(self):
r"""
Context manager that enables directional keypad input.
On entrying, this puts the terminal into "keyboard_transmit" mode by
emitting the keypad_xmit (smkx) capability. On exit, it emits
keypad_local (rmkx).
On an IBM-PC keyboard with numeric keypad of terminal-type *xterm*,
with numlock off, the lower-left diagonal key transmits sequence
``\\x1b[F``, translated to :class:`~.Terminal` attribute
``KEY_END``.
However, upon entering :meth:`keypad`, ``\\x1b[OF`` is transmitted,
translating to ``KEY_LL`` (lower-left key), allowing you to determine
diagonal direction keys.
"""
try:
self.stream.write(self.smkx)
self.stream.flush()
yield
finally:
self.stream.write(self.rmkx)
self.stream.flush()
def inkey(self, timeout=None, esc_delay=0.35):
"""
Read and return the next keyboard event within given timeout.
Generally, this should be used inside the :meth:`raw` context manager.
:arg float timeout: Number of seconds to wait for a keystroke before
returning. When ``None`` (default), this method may block
indefinitely.
:arg float esc_delay: To distinguish between the keystroke of
``KEY_ESCAPE``, and sequences beginning with escape, the parameter
``esc_delay`` specifies the amount of time after receiving escape
(``chr(27)``) to seek for the completion of an application key
before returning a :class:`~.Keystroke` instance for
``KEY_ESCAPE``.
:rtype: :class:`~.Keystroke`.
:returns: :class:`~.Keystroke`, which may be empty (``u''``) if
``timeout`` is specified and keystroke is not received.
.. note:: When used without the context manager :meth:`cbreak`, or
:meth:`raw`, :obj:`sys.__stdin__` remains line-buffered, and this
function will block until the return key is pressed!
.. note:: On Windows, a 10 ms sleep is added to the key press detection loop to reduce CPU
load. Due to the behavior of :py:func:`time.sleep` on Windows, this will actually
result in a 15.6 ms delay when using the default `time resolution
`_.
Decreasing the time resolution will reduce this to 10 ms, while increasing it, which
is rarely done, will have a perceptable impact on the behavior.
"""
resolve = functools.partial(resolve_sequence,
mapper=self._keymap,
codes=self._keycodes)
stime = time.time()
# re-buffer previously received keystrokes,
ucs = u''
while self._keyboard_buf:
ucs += self._keyboard_buf.pop()
# receive all immediately available bytes
while self.kbhit(timeout=0):
ucs += self.getch()
# decode keystroke, if any
ks = resolve(text=ucs)
# so long as the most immediately received or buffered keystroke is
# incomplete, (which may be a multibyte encoding), block until until
# one is received.
while not ks and self.kbhit(timeout=_time_left(stime, timeout)):
ucs += self.getch()
ks = resolve(text=ucs)
# handle escape key (KEY_ESCAPE) vs. escape sequence (like those
# that begin with \x1b[ or \x1bO) up to esc_delay when
# received. This is not optimal, but causes least delay when
# "meta sends escape" is used, or when an unsupported sequence is
# sent.
#
# The statement, "ucs in self._keymap_prefixes" has an effect on
# keystrokes such as Alt + Z ("\x1b[z" with metaSendsEscape): because
# no known input sequences begin with such phrasing to allow it to be
# returned more quickly than esc_delay otherwise blocks for.
if ks.code == self.KEY_ESCAPE:
esctime = time.time()
while (ks.code == self.KEY_ESCAPE and
ucs in self._keymap_prefixes and
self.kbhit(timeout=_time_left(esctime, esc_delay))):
ucs += self.getch()
ks = resolve(text=ucs)
# buffer any remaining text received
self.ungetch(ucs[len(ks):])
return ks
class WINSZ(collections.namedtuple('WINSZ', (
'ws_row', 'ws_col', 'ws_xpixel', 'ws_ypixel'))):
"""
Structure represents return value of :const:`termios.TIOCGWINSZ`.
.. py:attribute:: ws_row
rows, in characters
.. py:attribute:: ws_col
columns, in characters
.. py:attribute:: ws_xpixel
horizontal size, pixels
.. py:attribute:: ws_ypixel
vertical size, pixels
"""
#: format of termios structure
_FMT = 'hhhh'
#: buffer of termios structure appropriate for ioctl argument
_BUF = '\x00' * struct.calcsize(_FMT)
#: _CUR_TERM = None
#: From libcurses/doc/ncurses-intro.html (ESR, Thomas Dickey, et. al)::
#:
#: "After the call to setupterm(), the global variable cur_term is set to
#: point to the current structure of terminal capabilities. By calling
#: setupterm() for each terminal, and saving and restoring cur_term, it
#: is possible for a program to use two or more terminals at once."
#:
#: However, if you study Python's ``./Modules/_cursesmodule.c``, you'll find::
#:
#: if (!initialised_setupterm && setupterm(termstr,fd,&err) == ERR) {
#:
#: Python - perhaps wrongly - will not allow for re-initialisation of new
#: terminals through :func:`curses.setupterm`, so the value of cur_term cannot
#: be changed once set: subsequent calls to :func:`curses.setupterm` have no
#: effect.
#:
#: Therefore, the :attr:`Terminal.kind` of each :class:`Terminal` is
#: essentially a singleton. This global variable reflects that, and a warning
#: is emitted if somebody expects otherwise.
python-blessed-1.20.0/blessed/terminal.pyi 0000664 0000000 0000000 00000010323 14565701425 0020521 0 ustar 00root root 0000000 0000000 # 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: ...
python-blessed-1.20.0/blessed/win_terminal.py 0000664 0000000 0000000 00000013254 14565701425 0021233 0 ustar 00root root 0000000 0000000 # -*- 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
python-blessed-1.20.0/blessed/win_terminal.pyi 0000664 0000000 0000000 00000000515 14565701425 0021400 0 ustar 00root root 0000000 0000000 # 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]: ...
python-blessed-1.20.0/docs/ 0000775 0000000 0000000 00000000000 14565701425 0015473 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/docs/_static/ 0000775 0000000 0000000 00000000000 14565701425 0017121 5 ustar 00root root 0000000 0000000 python-blessed-1.20.0/docs/_static/blessed_3rdparty_macht.gif 0000664 0000000 0000000 00000566656 14565701425 0024265 0 ustar 00root root 0000000 0000000 GIF89a = ش$$U$$ $HUH$UlUHUتH$ lHHHUlUlUUl$ l$ lH l$U$ llUH UHHUlHU HlUHH Hllت lll $$ $U$ $l HU$ $lH $ت l $H H $ $ HUHUlH l$lU$UlUlUlUH $U$lUHUHUHUlUH Ul U$ UlHlHlH$UH$ UllHHشll UUH HH HتHHl Hl H lllllتl $U$$ Hl UUتU$$HHl $ U U lUH HUؐؐشU $U Hl !NETSCAPE2.0 ! , `@D C :daÈVaGAj$EOLY2ǖ#]2M3_Bϛ0
eIhѣA"]S'ѧ=JjәVsbu%ׯNjzUYeNMI[EW+\rKޭ+s_tJ