blessed-1.14.2/0000755000076500000240000000000013066002636013654 5ustar jquaststaff00000000000000blessed-1.14.2/.coveragerc0000644000076500000240000000030512636072353016000 0ustar jquaststaff00000000000000[run] branch = True source = blessed parallel = True [report] omit = blessed/tests/* exclude_lines = pragma: no cover precision = 1 [paths] source = blessed/ /opt/TeamCity/*/blessed/*.py blessed-1.14.2/blessed/0000755000076500000240000000000013066002636015275 5ustar jquaststaff00000000000000blessed-1.14.2/blessed/__init__.py0000644000076500000240000000110313065337122017401 0ustar jquaststaff00000000000000""" A thin, practical wrapper around terminal capabilities in Python. http://pypi.python.org/pypi/blessed """ # std imports import platform as _platform # local from blessed.terminal import Terminal if ('3', '0', '0') <= _platform.python_version_tuple() < ('3', '2', '2+'): # 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.14.2' blessed-1.14.2/blessed/_capabilities.py0000644000076500000240000001424113065337122020441 0ustar jquaststaff00000000000000"""Terminal capability builder patterns.""" # std imports import re try: from collections import OrderedDict except ImportError: # python 2.6 requires 3rd party library (backport) # # pylint: disable=import-error # Unable to import 'ordereddict' from ordereddict 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'), # one could get carried away, such as by adding '\x1b#8' (dec tube # alignment test) by reversing basic vt52, ansi, and xterm sequence # parsers. There is plans to do just that for ANSI.SYS support. } CAPABILITIES_ADDITIVES = { 'color256': ('color', re.escape('\x1b') + r'\[38;5;(\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', ) blessed-1.14.2/blessed/formatters.py0000644000076500000240000004061212617737020020043 0ustar jquaststaff00000000000000"""This sub-module provides sequence-formatting functions.""" # standard imports import curses # 3rd-party import six def _make_colors(): """ Return set of valid colors and their derivatives. :rtype: set """ derivatives = ('on', 'bright', 'on_bright',) colors = set('black red green yellow blue magenta cyan white'.split()) return set(['_'.join((_deravitive, _color)) for _deravitive in derivatives for _color in colors]) | colors def _make_compoundables(colors): """ Return given set ``colors`` along with all "compoundable" attributes. :arg set colors: set of color names as string. :rtype: set """ _compoundables = set('bold underline reverse blink dim italic shadow ' 'standout subscript superscript'.split()) return colors | _compoundables #: Valid colors and their background (on), bright, #: and bright-background derivatives. COLORS = _make_colors() #: Attributes and colors which may be compounded by underscore. COMPOUNDABLES = _make_compoundables(COLORS) class ParameterizingString(six.text_type): r""" A Unicode string which can be called as a parameterizing termcap. For example:: >>> term = Terminal() >>> color = ParameterizingString(term.color, term.normal, 'color') >>> color(9)('color #9') u'\x1b[91mcolor #9\x1b(B\x1b[m' """ def __new__(cls, *args): """ Class constructor accepting 3 positional arguments. :arg cap: parameterized string suitable for curses.tparm() :arg normal: terminating sequence for this capability (optional). :arg name: name of this terminal capability (optional). """ assert len(args) and len(args) < 4, args new = six.text_type.__new__(cls, args[0]) new._normal = len(args) > 1 and args[1] or u'' new._name = len(args) > 2 and args[2] or u'' 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. :rtype: :class:`FormattingString` or :class:`NullCallableString` """ 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 len(args) and isinstance(args[0], six.string_types): raise TypeError( "A native or nonexistent capability template, %r received" " invalid argument %r: %s. You probably misspelled a" " formatting call like `bright_red'" % ( 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, *args): """ Class constructor accepting 4 positional arguments. :arg fmt: format string suitable for displaying terminal sequences. :arg callable: receives __call__ arguments for formatting fmt. :arg normal: terminating sequence for this capability (optional). :arg name: name of this terminal capability (optional). """ assert len(args) and len(args) < 4, args assert isinstance(args[0], tuple), args[0] assert callable(args[0][1]), args[0][1] new = six.text_type.__new__(cls, args[0][0]) new._fmt_args = args[0][1] new._normal = len(args) > 1 and args[1] or u'' new._name = len(args) > 2 and args[2] or u'' 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 """ return FormattingString(self.format(*self._fmt_args(*args)), self._normal) 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) 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:: >>> 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, *args): """ Class constructor accepting 2 positional arguments. :arg sequence: terminal attribute sequence. :arg normal: terminating sequence for this attribute (optional). """ assert 1 <= len(args) <= 2, args new = six.text_type.__new__(cls, args[0]) new._normal = len(args) > 1 and args[1] or u'' return new def __call__(self, *args): """Return ``text`` joined by ``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): raise TypeError("Positional argument #{idx} is {is_type} " "expected any of {expected_types}: " "{ucs_part!r}".format( idx=idx, ucs_part=ucs_part, is_type=type(ucs_part), expected_types=six.string_types, )) postfix = u'' if len(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 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.""" new = six.text_type.__new__(cls, u'') return new 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 len(args) == 0 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 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 """ 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 """ # Decode sequences as latin1, as they are always 8-bit bytes, so when # b'\xff' is returned, this must be decoded to u'\xff'. if not term.does_styling: return u'' val = curses.tigetstr(term._sugar.get(attr, attr)) 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` """ if term.number_of_colors == 0: return NullCallableString() # NOTE(erikrose): Does curses automatically exchange red and blue and cyan # and yellow when a terminal supports setf/setb rather than setaf/setab? # I'll be blasted if I can find any documentation. The following # assumes it does: to terminfo(5) describes color(1) as COLOR_RED when # using setaf, but COLOR_BLUE when using setf. color_cap = (term._background_color if 'on_' in color else term._foreground_color) # 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 = color_cap(getattr(curses, attr) + offset) return FormattingString(fmt_attr, 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", respectively. :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 COMPOUNDABLES for fmt in formatters): resolution = (resolve_attribute(term, fmt) for fmt in formatters) return FormattingString(u''.join(resolution), term.normal) else: # 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)) if proxy is not None: return proxy return ParameterizingString(tparm_capseq, term.normal, attr) blessed-1.14.2/blessed/keyboard.py0000644000076500000240000004174012607273731017463 0ustar jquaststaff00000000000000"""This sub-module provides 'keyboard awareness'.""" # std imports import curses.has_key import curses import time import re # 3rd party import six try: from collections import OrderedDict except ImportError: # python 2.6 requires 3rd party library (backport) # # pylint: disable=import-error # Unable to import 'ordereddict' from ordereddict import OrderedDict 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 (self._name is None and six.text_type.__repr__(self) or 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 dict( [(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 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) # 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 This function supports :func:`get_terminal_sequences` to discover the preferred input sequence for the left and right application keys. Return dict of sequences ``term._cuf1``, and ``term._cub1``, valued as ``KEY_RIGHT``, ``KEY_LEFT`` (when appropriate). 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. """ keymap = dict() 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. # capability_names = curses.has_key._capability_names 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 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 set(seq[:i] for seq in sequences for i in range(1, len(seq))) def resolve_sequence(text, mapper, codes): r""" Return :class:`Keystroke` instance for given sequence ``text``. 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 determine that ``xxx`` remains unresolved. :arg 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 """ 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. """ if timeout is not None: if timeout == 0: return 0 return max(0, timeout - (time.time() - stime)) 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: # 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)) if ucs: buf += ucs # 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 True: ucs = term.inkey(timeout=0) if not ucs: break buf += ucs match = re.search(pattern=pattern, string=buf) if match is not None: # match break if timeout is not None: if not _time_left(stime, timeout): # timeout break return match, buf def _inject_curses_keynames(): r""" Inject KEY_NAMES that we think would be useful into the curses module. This function compliments the global constant :obj:`DEFAULT_SEQUENCE_MIXIN`. It is important to note that this function has the side-effect of **injecting** new attributes to the curses module, and is called from the global namespace at time of import. 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. It is not necessary to directly "monkeypatch" the curses module to contain these constants, as they will also be accessible as attributes of the Terminal class instance, they are provided only for convenience when mixed in with other curses code. """ _lastval = max(get_curses_keycodes().values()) for key in ('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 += 1 setattr(curses, 'KEY_{0}'.format(key), _lastval) _inject_curses_keynames() #: 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), curses.KEY_TAB), (six.unichr(27), curses.KEY_EXIT), (six.unichr(127), curses.KEY_DC), (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[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), # return (u"\x1bOj", curses.KEY_KP_MULTIPLY), # * (u"\x1bOk", curses.KEY_KP_ADD), # + (u"\x1bOl", curses.KEY_KP_SEPARATOR), # , (u"\x1bOm", curses.KEY_KP_SUBTRACT), # - (u"\x1bOn", curses.KEY_KP_DECIMAL), # . (u"\x1bOo", curses.KEY_KP_DIVIDE), # / (u"\x1bOX", curses.KEY_KP_EQUAL), # = (u"\x1bOp", curses.KEY_KP_0), # 0 (u"\x1bOq", curses.KEY_KP_1), # 1 (u"\x1bOr", curses.KEY_KP_2), # 2 (u"\x1bOs", curses.KEY_KP_3), # 3 (u"\x1bOt", curses.KEY_KP_4), # 4 (u"\x1bOu", curses.KEY_KP_5), # 5 (u"\x1bOv", curses.KEY_KP_6), # 6 (u"\x1bOw", curses.KEY_KP_7), # 7 (u"\x1bOx", curses.KEY_KP_8), # 8 (u"\x1bOy", curses.KEY_KP_9), # 9 # 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',) blessed-1.14.2/blessed/sequences.py0000644000076500000240000003614012636077424017657 0ustar jquaststaff00000000000000# encoding: utf-8 """This module provides 'sequence awareness'.""" # std imports import functools import textwrap import math import re # local from blessed._capabilities import CAPABILITIES_CAUSE_MOVEMENT # 3rd party import wcwidth import six __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): # pylint: disable=redundant-keyword-arg return '(?P<{self.name}>{self.pattern})'.format(self=self) @property def re_compiled(self): 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, None) if value is not None: return value unit = { 'parm_left_cursor': -1, 'parm_right_cursor': 1 }.get(self.name, None) 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 attribute: The terminfo(5) capability name by which this pattern is known. :arg int nparams: number of positional arguments for callable. :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+)?``. """ _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+)', _numeric_regex, _outp) else: pattern = re.sub(r'\d+', _numeric_regex, _outp) return cls(name, pattern, attribute) class SequenceTextWrapper(textwrap.TextWrapper): """This 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`. 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 if lines: indent = self.subsequent_indent else: indent = 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: cur_line.append(chunks.pop()) cur_len += chunk_len else: break 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 if width < 1: space_left = 1 else: space_left = 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): """ Class constructor. :arg 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 right-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``, right-aligned by ``width``. :rtype: str """ rightside = fillchar * int( (max(0.0, float(width - 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 - 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) - 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 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. """ # because control characters may return -1, "clip" their length to 0. clip = functools.partial(max, 0) return sum(clip(wcwidth.wcwidth(w_char)) for w_char in self.strip_seqs()) # we require ur"" for the docstring, but it is not supported by all # python versions. if length.__doc__ is not None: length.__doc__ += ( u""" For example: >>> from blessed import Terminal >>> from blessed.sequences import Sequence >>> term = Terminal() >>> msg = term.clear + term.red(u'コンニチハ'), term >>> Sequence(msg).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). """) 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 """ 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 """ 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 """ return self.strip_seqs().rstrip(chars) def strip_seqs(self): """ Return ``text`` stripped of only its terminal sequences. :rtype: str """ gen = iter_parse(self._term, self.padd()) return u''.join(text for text, cap in gen if not cap) def padd(self): """ Return non-destructive horizontal movement as destructive spacing. :rtype: str """ 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] else: 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 re.finditer(term._caps_compiled_any, text): 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 """ try: text, capability = next(iter_parse(term, text)) if capability: return len(text) except StopIteration: return 0 return 0 blessed-1.14.2/blessed/terminal.py0000644000076500000240000013170413065337122017470 0ustar jquaststaff00000000000000# encoding: utf-8 """This module contains :class:`Terminal`, the primary API entry point.""" # pylint: disable=too-many-lines # Too many lines in module (1027/1000) import codecs import collections import contextlib import curses import functools import io import locale import os import select import struct import sys import time import warnings import re try: import termios import fcntl import tty HAS_TTY = True 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 '{0}'. The following methods " "of Terminal are dummy/no-op unless a deriving class overrides " "them: {1}".format(sys.platform.lower(), ', '.join(_TTY_METHODS))) warnings.warn(_MSG_NOSUPPORT) HAS_TTY = False try: InterruptedError except NameError: # alias py2 exception to py3 # pylint: disable=redefined-builtin InterruptedError = select.error try: from collections import OrderedDict except ImportError: # python 2.6 requires 3rd party library (backport) # # pylint: disable=import-error # Unable to import 'ordereddict' from ordereddict import OrderedDict # local imports from .formatters import (ParameterizingString, NullCallableString, resolve_capability, resolve_attribute, ) from ._capabilities import ( CAPABILITIES_RAW_MIXIN, CAPABILITIES_ADDITIVES, CAPABILITY_DATABASE, ) from .sequences import (SequenceTextWrapper, Sequence, Termcap, ) from .keyboard import (get_keyboard_sequences, get_leading_prefixes, get_keyboard_codes, resolve_sequence, _read_until, _time_left, ) 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 = dict( save='sc', restore='rc', # 'clear' clears the whole screen. clear_eol='el', clear_bol='el1', clear_eos='ed', position='cup', # deprecated enter_fullscreen='smcup', exit_fullscreen='rmcup', move='cup', move_x='hpa', move_y='vpa', move_left='cub1', move_right='cuf1', move_up='cuu1', move_down='cud1', hide_cursor='civis', normal_cursor='cnorm', reset_colors='op', # oc doesn't work on my OS X terminal. 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._keyboard_fd = None # Default stream is stdout, keyboard valid as stdin only when # output stream is stdout or stderr and is a tty. if stream is None: stream = sys.__stdout__ if stream in (sys.__stdout__, sys.__stderr__): self._keyboard_fd = sys.__stdin__.fileno() # 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 try: stream_fd = (stream.fileno() if hasattr(stream, 'fileno') and callable(stream.fileno) else None) except io.UnsupportedOperation: stream_fd = None self._stream = stream self._is_a_tty = stream_fd is not None and os.isatty(stream_fd) self._does_styling = ((self.is_a_tty or force_styling) and force_styling is not None) # _keyboard_fd only non-None if both stdin and stdout is a tty. self._keyboard_fd = (self._keyboard_fd if self._keyboard_fd is not None and self.is_a_tty and os.isatty(self._keyboard_fd) else None) self._normal = None # cache normal attr, preventing recursive lookups # The descriptor to direct terminal initialization sequences to. self._init_descriptor = (stream_fd is None and sys.__stdout__.fileno() or stream_fd) self._kind = kind or os.environ.get('TERM', 'unknown') if self.does_styling: # Initialize curses (call setupterm). # # Make things like tigetstr() work. Explicit args make setupterm() # work even when -s is passed to nosetests. Lean toward sending # init sequences to the stream if it has a file descriptor, and # send them to stdout as a fallback, since they have to go # somewhere. try: curses.setupterm(self._kind, self._init_descriptor) except curses.error as err: warnings.warn('Failed to setupterm(kind={0!r}): {1}' .format(self._kind, err)) self._kind = None self._does_styling = False else: if _CUR_TERM is None or self._kind == _CUR_TERM: _CUR_TERM = self._kind else: 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,)) # initialize capabilities and terminal keycodes database self.__init__capabilities() self.__init__keycodes() 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 = 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 locale.setlocale(locale.LC_ALL, '') self._encoding = locale.getpreferredencoding() or 'ascii' try: self._keyboard_decoder = codecs.getincrementaldecoder( self._encoding)() except LookupError as err: # encoding is illegal or unsupported, use 'ascii' warnings.warn('LookupError: {0}, fallback to ASCII for ' 'keyboard.'.format(err)) self._encoding = 'ascii' 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 parametrized 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() val = resolve_attribute(self, attr) # Cache capability resolution: note this will prevent this # __getattr__ method for being called again. That's the idea! 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 @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 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: 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 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). """ for fd in (self._init_descriptor, sys.__stdout__): try: if fd is not None: return self._winsize(fd) except IOError: pass return WINSZ(ws_row=int(os.getenv('LINES', '25')), ws_col=int(os.getenv('COLUMNS', '80')), ws_xpixel=None, ws_ypixel=None) @contextlib.contextmanager def location(self, x=None, y=None): """ Context manager for temporarily moving the cursor. Move the cursor to a certain position on entry, let you print stuff there, then return the cursor to its original position:: term = Terminal() with term.location(2, 5): for x in xrange(10): print('I can do it %i times!' % x) print('We're back to the 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. .. note:: The store- and restore-cursor capabilities used internally provide no stack. This means that :meth:`location` calls cannot be nested: only one should be entered at a time. """ # 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 (row, column). 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 ``(row, col)`` return value matches the parameter order of the ``move`` capability, so that the following sequence should cause the cursor to not move at all:: >>> term = Terminal() >>> term.move(*term.get_location())) .. warning:: You might first test that a terminal is capable of informing you of its location, while using a timeout, before later calling. When a timeout is specified, always ensure the return value is conditionally checked for ``(-1, -1)``. """ # 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 http://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) query_str = self.u7 or u'\x1b[6n' # determine response format as a regular expression response_re = self.caps['cursor_report'].re_compiled # 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__() # emit the 'query cursor position' sequence, self.stream.write(query_str) self.stream.flush() # expect a response, match, data = _read_until(term=self, pattern=response_re, timeout=timeout) # ensure response sequence is excluded from subsequent input, if match: data = (data[:match.start()] + data[match.end():]) # re-buffer keyboard data, if any self.ungetch(data) if match: # return matching sequence response, the cursor location. row, col = match.groups() return int(row), int(col) finally: if ctx is not None: ctx.__exit__(None, None, None) # 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 @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) try: yield finally: self.stream.write(self.exit_fullscreen) @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) try: yield finally: self.stream.write(self.normal_cursor) @property def color(self): """ A callable string that sets the foreground color. :arg int num: The foreground color index. This should be within the bounds of :attr:`~.number_of_colors`. :rtype: ParameterizingString The capability is unparameterized until called and passed a number, 0-15, 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. """ if not self.does_styling: return NullCallableString() return ParameterizingString(self._foreground_color, self.normal, 'color') @property def on_color(self): """ A callable capability that sets the background color. :arg int num: The background color index. :rtype: ParameterizingString """ if not self.does_styling: return NullCallableString() return ParameterizingString(self._background_color, self.normal, 'on_color') @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 @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): """ Read-only property: number of colors supported by terminal. Common values are 0, 8, 16, 88, and 256. Most commonly, this may be used to test whether the terminal supports colors. Though the underlying capability returns -1 when there is no color support, we return 0. This lets you test more Pythonically:: if term.number_of_colors: ... """ # This is actually the only remotely useful numeric capability. We # don't name it after the underlying capability, because we deviate # slightly from its behavior, and we might someday wish to give direct # access to it. # trim value to 0, as tigetnum('colors') returns -1 if no support, # and -2 if no such capability. return max(0, self.does_styling and curses.tigetnum('colors') or -1) @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 """ # 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 """ 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 """ if width is None: width = self.width return Sequence(text, self).center(width, fillchar) 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 >>> 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 >>> 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 >>> 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 >>> 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, **kwds): r""" Return ``text`` split by individual character elements and sequences. :arg kwds: remaining keyword arguments for :func:`re.split`. :rtype: list[str] >>> term.split_seqs(term.underline(u'xyz')) ['\x1b[4m', 'x', 'y', 'z', '\x1b(B', '\x1b[m'] """ pattern = self._caps_unnamed_any return list(filter(None, re.split(pattern, text, **kwds))) def wrap(self, text, width=None, **kwargs): """ 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. :rtype: list See :class:`textwrap.TextWrapper` for keyword arguments that can customize wrapping behaviour. """ width = self.width if width is None else width lines = [] for line in text.splitlines(): lines.extend( (_linewrap for _linewrap in SequenceTextWrapper( width=width, term=self, **kwargs).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 ucs: String to be buffered as keyboard input. """ self._keyboard_buf.extendleft(text) def kbhit(self, timeout=None, **_kwargs): """ 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. """ if _kwargs.pop('_intr_continue', None) is not None: warnings.warn('keyword argument _intr_continue deprecated: ' 'beginning v1.9.6, behavior is as though such ' 'value is always True.') if _kwargs: raise TypeError('inkey() got unexpected keyword arguments {!r}' .format(_kwargs)) stime = time.time() ready_r = [None, ] check_r = [self._keyboard_fd] if self._keyboard_fd is not None else [] while HAS_TTY and True: 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) yield finally: self.stream.write(self.rmkx) def inkey(self, timeout=None, esc_delay=0.35, **_kwargs): """ 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. :raises RuntimeError: When :attr:`stream` is not a terminal, having no keyboard attached, a ``timeout`` value of ``None`` would block indefinitely, prevented by by raising an exception. .. 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! """ if _kwargs.pop('_intr_continue', None) is not None: warnings.warn('keyword argument _intr_continue deprecated: ' 'beginning v1.9.6, behavior is as though such ' 'value is always True.') if _kwargs: raise TypeError('inkey() got unexpected keyword arguments {!r}' .format(_kwargs)) if timeout is None and self._keyboard_fd is None: raise RuntimeError( 'Terminal.inkey() called, but no terminal with keyboard ' 'attached to process. This call would hang forever.') 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) #: 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. _CUR_TERM = None blessed-1.14.2/blessed/tests/0000755000076500000240000000000013066002636016437 5ustar jquaststaff00000000000000blessed-1.14.2/blessed/tests/__init__.py0000644000076500000240000000000012636072020020532 0ustar jquaststaff00000000000000blessed-1.14.2/blessed/tests/accessories.py0000644000076500000240000001757212636072007021331 0ustar jquaststaff00000000000000# -*- coding: utf-8 -*- """Accessories for automated py.test runner.""" # standard imports from __future__ import with_statement, print_function import contextlib import subprocess import functools import traceback import termios import codecs import curses import sys import pty import os # local from blessed import Terminal # 3rd-party import pytest import six TestTerminal = functools.partial(Terminal, kind='xterm-256color') SEND_SEMAPHORE = SEMAPHORE = b'SEMAPHORE\n' RECV_SEMAPHORE = b'SEMAPHORE\r\n' many_lines_params = [40, 80] # we must test a '1' column for conditional in _handle_long_word many_columns_params = [1, 10] if os.environ.get('TEST_QUICK'): many_lines_params = [80,] many_columns_params = [25,] all_terms_params = 'xterm screen ansi vt220 rxvt cons25 linux'.split() if os.environ.get('TEST_FULL'): try: all_terms_params = [ # use all values of the first column of data in output of 'toe -a' _term.split(None, 1)[0] for _term in subprocess.Popen(('toe', '-a'), stdout=subprocess.PIPE, close_fds=True) .communicate()[0].splitlines()] except OSError: pass elif os.environ.get('TEST_QUICK'): all_terms_params = 'xterm screen ansi linux'.split() def init_subproc_coverage(run_note): try: import coverage except ImportError: return None _coveragerc = os.path.join( os.path.dirname(__file__), os.pardir, os.pardir, '.coveragerc') cov = coverage.Coverage(config_file=_coveragerc) cov.set_option("run:note", run_note) cov.start() return cov class as_subprocess(object): """This helper executes test cases in a child process, avoiding a python-internal bug of _curses: setupterm() may not be called more than once per process. """ _CHILD_PID = 0 encoding = 'utf8' def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): pid_testrunner = os.getpid() pid, master_fd = pty.fork() if pid == self._CHILD_PID: # child process executes function, raises exception # if failed, causing a non-zero exit code, using the # protected _exit() function of ``os``; to prevent the # 'SystemExit' exception from being thrown. cov = init_subproc_coverage( "@as_subprocess-{pid};{func_name}(*{args}, **{kwargs})" .format(pid=os.getpid(), func_name=self.func, args=args, kwargs=kwargs)) try: self.func(*args, **kwargs) except Exception: e_type, e_value, e_tb = sys.exc_info() o_err = list() for line in traceback.format_tb(e_tb): o_err.append(line.rstrip().encode('utf-8')) o_err.append(('-=' * 20).encode('ascii')) o_err.extend([_exc.rstrip().encode('utf-8') for _exc in traceback.format_exception_only( e_type, e_value)]) os.write(sys.__stdout__.fileno(), b'\n'.join(o_err)) os.close(sys.__stdout__.fileno()) os.close(sys.__stderr__.fileno()) os.close(sys.__stdin__.fileno()) if cov is not None: cov.stop() cov.save() os._exit(1) else: if cov is not None: cov.stop() cov.save() os._exit(0) # detect rare fork in test runner, when bad bugs happen if pid_testrunner != os.getpid(): print('TEST RUNNER HAS FORKED, {0}=>{1}: EXIT' .format(pid_testrunner, os.getpid()), file=sys.stderr) os._exit(1) exc_output = six.text_type() decoder = codecs.getincrementaldecoder(self.encoding)() while True: try: _exc = os.read(master_fd, 65534) except OSError: # linux EOF break if not _exc: # bsd EOF break exc_output += decoder.decode(_exc) # parent process asserts exit code is 0, causing test # to fail if child process raised an exception/assertion pid, status = os.waitpid(pid, 0) os.close(master_fd) # Display any output written by child process # (esp. any AssertionError exceptions written to stderr). exc_output_msg = 'Output in child process:\n%s\n%s\n%s' % ( u'=' * 40, exc_output, u'=' * 40,) assert exc_output == '', exc_output_msg # Also test exit status is non-zero assert os.WEXITSTATUS(status) == 0 def read_until_semaphore(fd, semaphore=RECV_SEMAPHORE, encoding='utf8', timeout=10): """Read file descriptor ``fd`` until ``semaphore`` is found. Used to ensure the child process is awake and ready. For timing tests; without a semaphore, the time to fork() would be (incorrectly) included in the duration of the test, which can be very length on continuous integration servers (such as Travis-CI). """ # note that when a child process writes xyz\\n, the parent # process will read xyz\\r\\n -- this is how pseudo terminals # behave; a virtual terminal requires both carriage return and # line feed, it is only for convenience that \\n does both. outp = six.text_type() decoder = codecs.getincrementaldecoder(encoding)() semaphore = semaphore.decode('ascii') while not outp.startswith(semaphore): try: _exc = os.read(fd, 1) except OSError: # Linux EOF break if not _exc: # BSD EOF break outp += decoder.decode(_exc, final=False) assert outp.startswith(semaphore), ( 'Semaphore not recv before EOF ' '(expected: %r, got: %r)' % (semaphore, outp,)) return outp[len(semaphore):] def read_until_eof(fd, encoding='utf8'): """Read file descriptor ``fd`` until EOF. Return decoded string.""" decoder = codecs.getincrementaldecoder(encoding)() outp = six.text_type() while True: try: _exc = os.read(fd, 100) except OSError: # linux EOF break if not _exc: # bsd EOF break outp += decoder.decode(_exc, final=False) return outp @contextlib.contextmanager def echo_off(fd): """Ensure any bytes written to pty fd are not duplicated as output.""" try: attrs = termios.tcgetattr(fd) attrs[3] = attrs[3] & ~termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, attrs) yield finally: attrs[3] = attrs[3] | termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, attrs) def unicode_cap(cap): """Return the result of ``tigetstr`` except as Unicode.""" try: val = curses.tigetstr(cap) except curses.error: val = None if val: return val.decode('latin1') return u'' def unicode_parm(cap, *parms): """Return the result of ``tparm(tigetstr())`` except as Unicode.""" try: cap = curses.tigetstr(cap) except curses.error: cap = None if cap: try: val = curses.tparm(cap, *parms) except curses.error: val = None if val: return val.decode('latin1') return u'' @pytest.fixture(params=all_terms_params) def all_terms(request): """Common kind values for all kinds of terminals.""" return request.param @pytest.fixture(params=many_lines_params) def many_lines(request): """Various number of lines for screen height.""" return request.param @pytest.fixture(params=many_columns_params) def many_columns(request): """Various number of columns for screen width.""" return request.param blessed-1.14.2/blessed/tests/test_core.py0000644000076500000240000003507212636072007021010 0ustar jquaststaff00000000000000# -*- coding: utf-8 -*- "Core blessed Terminal() tests." # std import collections import warnings import platform import locale import time import math import sys import imp import os import io # local from .accessories import ( as_subprocess, TestTerminal, unicode_cap, all_terms ) # 3rd party import mock import pytest import six def test_export_only_Terminal(): "Ensure only Terminal instance is exported for import * statements." import blessed assert blessed.__all__ == ('Terminal',) def test_null_location(all_terms): "Make sure ``location()`` with no args just does position restoration." @as_subprocess def child(kind): t = TestTerminal(stream=six.StringIO(), force_styling=True) with t.location(): pass expected_output = u''.join( (unicode_cap('sc'), unicode_cap('rc'))) assert (t.stream.getvalue() == expected_output) child(all_terms) def test_flipped_location_move(all_terms): "``location()`` and ``move()`` receive counter-example arguments." @as_subprocess def child(kind): buf = six.StringIO() t = TestTerminal(stream=buf, force_styling=True) y, x = 10, 20 with t.location(y, x): xy_val = t.move(x, y) yx_val = buf.getvalue()[len(t.sc):] assert xy_val == yx_val child(all_terms) def test_yield_keypad(): "Ensure ``keypad()`` writes keyboard_xmit and keyboard_local." @as_subprocess def child(kind): # given, t = TestTerminal(stream=six.StringIO(), force_styling=True) expected_output = u''.join((t.smkx, t.rmkx)) # exercise, with t.keypad(): pass # verify. assert (t.stream.getvalue() == expected_output) child(kind='xterm') def test_null_fileno(): "Make sure ``Terminal`` works when ``fileno`` is ``None``." @as_subprocess def child(): # This simulates piping output to another program. out = six.StringIO() out.fileno = None t = TestTerminal(stream=out) assert (t.save == u'') child() def test_number_of_colors_without_tty(): "``number_of_colors`` should return 0 when there's no tty." @as_subprocess def child_256_nostyle(): t = TestTerminal(stream=six.StringIO()) assert (t.number_of_colors == 0) @as_subprocess def child_256_forcestyle(): t = TestTerminal(stream=six.StringIO(), force_styling=True) assert (t.number_of_colors == 256) @as_subprocess def child_8_forcestyle(): kind = 'ansi' if platform.system().lower() == 'freebsd': # 'ansi' on freebsd returns 0 colors, we use the 'cons25' driver, # compatible with its kernel tty.c kind = 'cons25' t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) assert (t.number_of_colors == 8) @as_subprocess def child_0_forcestyle(): t = TestTerminal(kind='vt220', stream=six.StringIO(), force_styling=True) assert (t.number_of_colors == 0) child_0_forcestyle() child_8_forcestyle() child_256_forcestyle() child_256_nostyle() def test_number_of_colors_with_tty(): "test ``number_of_colors`` 0, 8, and 256." @as_subprocess def child_256(): t = TestTerminal() assert (t.number_of_colors == 256) @as_subprocess def child_8(): kind = 'ansi' if platform.system().lower() == 'freebsd': # 'ansi' on freebsd returns 0 colors, we use the 'cons25' driver, # compatible with its kernel tty.c kind = 'cons25' t = TestTerminal(kind=kind) assert (t.number_of_colors == 8) @as_subprocess def child_0(): t = TestTerminal(kind='vt220') assert (t.number_of_colors == 0) child_0() child_8() child_256() def test_init_descriptor_always_initted(all_terms): "Test height and width with non-tty Terminals." @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO()) assert t._init_descriptor == sys.__stdout__.fileno() assert (isinstance(t.height, int)) assert (isinstance(t.width, int)) assert t.height == t._height_and_width()[0] assert t.width == t._height_and_width()[1] child(all_terms) def test_force_styling_none(all_terms): "If ``force_styling=None`` is used, don't ever do styling." @as_subprocess def child(kind): t = TestTerminal(kind=kind, force_styling=None) assert (t.save == '') assert (t.color(9) == '') assert (t.bold('oi') == 'oi') child(all_terms) def test_setupterm_singleton_issue33(): "A warning is emitted if a new terminal ``kind`` is used per process." @as_subprocess def child(): warnings.filterwarnings("error", category=UserWarning) # instantiate first terminal, of type xterm-256color term = TestTerminal(force_styling=True) try: # a second instantiation raises UserWarning term = TestTerminal(kind="vt220", force_styling=True) except UserWarning: err = sys.exc_info()[1] assert (err.args[0].startswith( 'A terminal of kind "vt220" has been requested') ), err.args[0] assert ('a terminal of kind "xterm-256color" will ' 'continue to be returned' in err.args[0]), err.args[0] else: # unless term is not a tty and setupterm() is not called assert not term.is_a_tty or False, 'Should have thrown exception' warnings.resetwarnings() child() def test_setupterm_invalid_issue39(): "A warning is emitted if TERM is invalid." # https://bugzilla.mozilla.org/show_bug.cgi?id=878089 # # if TERM is unset, defaults to 'unknown', which should # fail to lookup and emit a warning on *some* systems. # freebsd actually has a termcap entry for 'unknown' @as_subprocess def child(): warnings.filterwarnings("error", category=UserWarning) try: term = TestTerminal(kind='unknown', force_styling=True) except UserWarning: err = sys.exc_info()[1] assert err.args[0] == ( "Failed to setupterm(kind='unknown'): " "setupterm: could not find terminal") else: if platform.system().lower() != 'freebsd': assert not term.is_a_tty and not term.does_styling, ( 'Should have thrown exception') warnings.resetwarnings() child() def test_setupterm_invalid_has_no_styling(): "An unknown TERM type does not perform styling." # https://bugzilla.mozilla.org/show_bug.cgi?id=878089 # if TERM is unset, defaults to 'unknown', which should # fail to lookup and emit a warning, only. @as_subprocess def child(): warnings.filterwarnings("ignore", category=UserWarning) term = TestTerminal(kind='xxXunknownXxx', force_styling=True) assert term.kind is None assert not term.does_styling assert term.number_of_colors == 0 warnings.resetwarnings() child() def test_missing_ordereddict_uses_module(monkeypatch): "ordereddict module is imported when without collections.OrderedDict." import blessed.keyboard if hasattr(collections, 'OrderedDict'): monkeypatch.delattr('collections.OrderedDict') try: imp.reload(blessed.keyboard) except ImportError as err: assert err.args[0] in ("No module named ordereddict", # py2 "No module named 'ordereddict'") # py3 sys.modules['ordereddict'] = mock.Mock() sys.modules['ordereddict'].OrderedDict = -1 imp.reload(blessed.keyboard) assert blessed.keyboard.OrderedDict == -1 del sys.modules['ordereddict'] monkeypatch.undo() imp.reload(blessed.keyboard) else: assert platform.python_version_tuple() < ('2', '7') # reached by py2.6 def test_python3_2_raises_exception(monkeypatch): "Test python version 3.0 through 3.2 raises an exception." import blessed monkeypatch.setattr('platform.python_version_tuple', lambda: ('3', '2', '2')) try: imp.reload(blessed) except ImportError as err: assert err.args[0] == ( 'Blessed needs Python 3.2.3 or greater for Python 3 ' 'support due to http://bugs.python.org/issue10570.') monkeypatch.undo() imp.reload(blessed) else: assert False, 'Exception should have been raised' def test_without_dunder(): "Ensure dunder does not remain in module (py2x InterruptedError test." import blessed.terminal assert '_' not in dir(blessed.terminal) def test_IOUnsupportedOperation(): "Ensure stream that throws IOUnsupportedOperation results in non-tty." @as_subprocess def child(): import blessed.terminal def side_effect(): raise io.UnsupportedOperation mock_stream = mock.Mock() mock_stream.fileno = side_effect term = TestTerminal(stream=mock_stream) assert term.stream == mock_stream assert not term.does_styling assert not term.is_a_tty assert term.number_of_colors == 0 child() def test_winsize_IOError_returns_environ(): """When _winsize raises IOError, defaults from os.environ given.""" @as_subprocess def child(): def side_effect(fd): raise IOError term = TestTerminal() term._winsize = side_effect os.environ['COLUMNS'] = '1984' os.environ['LINES'] = '1888' assert term._height_and_width() == (1888, 1984, None, None) child() def test_yield_fullscreen(all_terms): "Ensure ``fullscreen()`` writes enter_fullscreen and exit_fullscreen." @as_subprocess def child(kind): t = TestTerminal(stream=six.StringIO(), force_styling=True) t.enter_fullscreen = u'BEGIN' t.exit_fullscreen = u'END' with t.fullscreen(): pass expected_output = u''.join((t.enter_fullscreen, t.exit_fullscreen)) assert (t.stream.getvalue() == expected_output) child(all_terms) def test_yield_hidden_cursor(all_terms): "Ensure ``hidden_cursor()`` writes hide_cursor and normal_cursor." @as_subprocess def child(kind): t = TestTerminal(stream=six.StringIO(), force_styling=True) t.hide_cursor = u'BEGIN' t.normal_cursor = u'END' with t.hidden_cursor(): pass expected_output = u''.join((t.hide_cursor, t.normal_cursor)) assert (t.stream.getvalue() == expected_output) child(all_terms) def test_no_preferredencoding_fallback_ascii(): "Ensure empty preferredencoding value defaults to ascii." @as_subprocess def child(): with mock.patch('locale.getpreferredencoding') as get_enc: get_enc.return_value = u'' t = TestTerminal() assert t._encoding == 'ascii' child() def test_unknown_preferredencoding_warned_and_fallback_ascii(): """Ensure a locale without a codec emits a warning.""" @as_subprocess def child(): with mock.patch('locale.getpreferredencoding') as get_enc: with warnings.catch_warnings(record=True) as warned: get_enc.return_value = '---unknown--encoding---' t = TestTerminal() assert t._encoding == 'ascii' assert len(warned) == 1 assert issubclass(warned[-1].category, UserWarning) assert "fallback to ASCII" in str(warned[-1].message) child() def test_win32_missing_tty_modules(monkeypatch): "Ensure dummy exception is used when io is without UnsupportedOperation." @as_subprocess def child(): OLD_STYLE = False try: original_import = getattr(__builtins__, '__import__') OLD_STYLE = True except AttributeError: original_import = __builtins__['__import__'] tty_modules = ('termios', 'fcntl', 'tty') def __import__(name, *args, **kwargs): if name in tty_modules: raise ImportError return original_import(name, *args, **kwargs) for module in tty_modules: sys.modules.pop(module, None) warnings.filterwarnings("error", category=UserWarning) try: if OLD_STYLE: __builtins__.__import__ = __import__ else: __builtins__['__import__'] = __import__ try: import blessed.terminal imp.reload(blessed.terminal) except UserWarning: err = sys.exc_info()[1] assert err.args[0] == blessed.terminal._MSG_NOSUPPORT warnings.filterwarnings("ignore", category=UserWarning) import blessed.terminal imp.reload(blessed.terminal) assert not blessed.terminal.HAS_TTY term = blessed.terminal.Terminal('ansi') # https://en.wikipedia.org/wiki/VGA-compatible_text_mode # see section '#PC_common_text_modes' assert term.height == 25 assert term.width == 80 finally: if OLD_STYLE: setattr(__builtins__, '__import__', original_import) else: __builtins__['__import__'] = original_import warnings.resetwarnings() import blessed.terminal imp.reload(blessed.terminal) child() def test_time_left(): """test '_time_left' routine returns correct positive delta difference.""" from blessed.keyboard import _time_left # given stime =~ "10 seconds ago" stime = (time.time() - 10) # timeleft(now, 15s) = 5s remaining timeout = 15 result = _time_left(stime=stime, timeout=timeout) # we expect roughly 4.999s remain assert math.ceil(result) == 5.0 def test_time_left_infinite_None(): """keyboard '_time_left' routine returns None when given None.""" from blessed.keyboard import _time_left assert _time_left(stime=time.time(), timeout=None) is None def test_termcap_repr(): "Ensure ``hidden_cursor()`` writes hide_cursor and normal_cursor." given_ttype='vt220' given_capname = 'cursor_up' expected = [r"", r""] @as_subprocess def child(): import blessed term = blessed.Terminal(given_ttype) given = repr(term.caps[given_capname]) assert given in expected child() blessed-1.14.2/blessed/tests/test_formatters.py0000644000076500000240000003453412636076523022257 0ustar jquaststaff00000000000000# -*- coding: utf-8 -*- """Tests string formatting functions.""" # std import curses # 3rd-party import mock import pytest def test_parameterizing_string_args_unspecified(monkeypatch): """Test default args of formatters.ParameterizingString.""" from blessed.formatters import ParameterizingString, FormattingString # first argument to tparm() is the sequence name, returned as-is; # subsequent arguments are usually Integers. tparm = lambda *args: u'~'.join( arg.decode('latin1') if not num else '%s' % (arg,) for num, arg in enumerate(args)).encode('latin1') monkeypatch.setattr(curses, 'tparm', tparm) # given, pstr = ParameterizingString(u'') # exercise __new__ assert str(pstr) == u'' assert pstr._normal == u'' assert pstr._name == u'' # exercise __call__ zero = pstr(0) assert type(zero) is FormattingString assert zero == u'~0' assert zero('text') == u'~0text' # exercise __call__ with multiple args onetwo = pstr(1, 2) assert type(onetwo) is FormattingString assert onetwo == u'~1~2' assert onetwo('text') == u'~1~2text' def test_parameterizing_string_args(monkeypatch): """Test basic formatters.ParameterizingString.""" from blessed.formatters import ParameterizingString, FormattingString # first argument to tparm() is the sequence name, returned as-is; # subsequent arguments are usually Integers. tparm = lambda *args: u'~'.join( arg.decode('latin1') if not num else '%s' % (arg,) for num, arg in enumerate(args)).encode('latin1') monkeypatch.setattr(curses, 'tparm', tparm) # given, pstr = ParameterizingString(u'cap', u'norm', u'seq-name') # exercise __new__ assert str(pstr) == u'cap' assert pstr._normal == u'norm' assert pstr._name == u'seq-name' # exercise __call__ zero = pstr(0) assert type(zero) is FormattingString assert zero == u'cap~0' assert zero('text') == u'cap~0textnorm' # exercise __call__ with multiple args onetwo = pstr(1, 2) assert type(onetwo) is FormattingString assert onetwo == u'cap~1~2' assert onetwo('text') == u'cap~1~2textnorm' def test_parameterizing_string_type_error(monkeypatch): """Test formatters.ParameterizingString raising TypeError.""" from blessed.formatters import ParameterizingString def tparm_raises_TypeError(*args): raise TypeError('custom_err') monkeypatch.setattr(curses, 'tparm', tparm_raises_TypeError) # given, pstr = ParameterizingString(u'cap', u'norm', u'cap-name') # ensure TypeError when given a string raises custom exception try: pstr('XYZ') assert False, "previous call should have raised TypeError" except TypeError as err: assert (err.args[0] == ( # py3x "A native or nonexistent capability template, " "'cap-name' received invalid argument ('XYZ',): " "custom_err. You probably misspelled a " "formatting call like `bright_red'") or err.args[0] == ( "A native or nonexistent capability template, " "u'cap-name' received invalid argument ('XYZ',): " "custom_err. You probably misspelled a " "formatting call like `bright_red'")) # ensure TypeError when given an integer raises its natural exception try: pstr(0) assert False, "previous call should have raised TypeError" except TypeError as err: assert err.args[0] == "custom_err" def test_formattingstring(monkeypatch): """Test simple __call__ behavior of formatters.FormattingString.""" from blessed.formatters import FormattingString # given, with arg pstr = FormattingString(u'attr', u'norm') # exercise __call__, assert pstr._normal == u'norm' assert str(pstr) == u'attr' assert pstr('text') == u'attrtextnorm' # given, with empty attribute pstr = FormattingString(u'', u'norm') assert pstr('text') == u'text' def test_nested_formattingstring(monkeypatch): """Test nested __call__ behavior of formatters.FormattingString.""" from blessed.formatters import FormattingString # given, with arg pstr = FormattingString(u'a1-', u'n-') zstr = FormattingString(u'a2-', u'n-') # exercise __call__ assert pstr('x-', zstr('f-'), 'q-') == 'a1-x-a2-f-n-a1-q-n-' def test_nested_formattingstring_type_error(monkeypatch): """Test formatters.FormattingString raising TypeError.""" from blessed.formatters import FormattingString # given, pstr = FormattingString(u'a-', u'n-') expected_msg = ( "Positional argument #1 is {0} expected any of " .format(type(1))) # exercise, with pytest.raises(TypeError) as err: pstr('text', 1, '...') # verify, assert expected_msg in '{0}'.format(err) def test_nullcallablestring(monkeypatch): """Test formatters.NullCallableString""" from blessed.formatters import (NullCallableString) # given, with arg pstr = NullCallableString() # exercise __call__, assert str(pstr) == u'' assert pstr('text') == u'text' assert pstr('text', 'moretext') == u'textmoretext' assert pstr(99, 1) == u'' assert pstr() == u'' assert pstr(0) == u'' def test_split_compound(): """Test formatters.split_compound.""" from blessed.formatters import split_compound assert split_compound(u'') == [u''] assert split_compound(u'a_b_c') == [u'a', u'b', u'c'] assert split_compound(u'a_on_b_c') == [u'a', u'on_b', u'c'] assert split_compound(u'a_bright_b_c') == [u'a', u'bright_b', u'c'] assert split_compound(u'a_on_bright_b_c') == [u'a', u'on_bright_b', u'c'] def test_resolve_capability(monkeypatch): """Test formatters.resolve_capability and term sugaring """ from blessed.formatters import resolve_capability # given, always returns a b'seq' tigetstr = lambda attr: ('seq-%s' % (attr,)).encode('latin1') monkeypatch.setattr(curses, 'tigetstr', tigetstr) term = mock.Mock() term._sugar = dict(mnemonic='xyz') # exercise assert resolve_capability(term, 'mnemonic') == u'seq-xyz' assert resolve_capability(term, 'natural') == u'seq-natural' # given, where tigetstr returns None tigetstr_none = lambda attr: None monkeypatch.setattr(curses, 'tigetstr', tigetstr_none) # exercise, assert resolve_capability(term, 'natural') == u'' # given, where does_styling is False def raises_exception(*args): assert False, "Should not be called" term.does_styling = False monkeypatch.setattr(curses, 'tigetstr', raises_exception) # exercise, assert resolve_capability(term, 'natural') == u'' def test_resolve_color(monkeypatch): """Test formatters.resolve_color.""" from blessed.formatters import (resolve_color, FormattingString, NullCallableString) color_cap = lambda digit: 'seq-%s' % (digit,) monkeypatch.setattr(curses, 'COLOR_RED', 1984) # given, terminal with color capabilities term = mock.Mock() term._background_color = color_cap term._foreground_color = color_cap term.number_of_colors = -1 term.normal = 'seq-normal' # exercise, red = resolve_color(term, 'red') assert type(red) == FormattingString assert red == u'seq-1984' assert red('text') == u'seq-1984textseq-normal' # exercise bold, +8 bright_red = resolve_color(term, 'bright_red') assert type(bright_red) == FormattingString assert bright_red == u'seq-1992' assert bright_red('text') == u'seq-1992textseq-normal' # given, terminal without color term.number_of_colors = 0 # exercise, red = resolve_color(term, 'red') assert type(red) == NullCallableString assert red == u'' assert red('text') == u'text' # exercise bold, bright_red = resolve_color(term, 'bright_red') assert type(bright_red) == NullCallableString assert bright_red == u'' assert bright_red('text') == u'text' def test_resolve_attribute_as_color(monkeypatch): """ Test simple resolve_attribte() given color name. """ import blessed from blessed.formatters import resolve_attribute resolve_color = lambda term, digit: 'seq-%s' % (digit,) COLORS = set(['COLORX', 'COLORY']) COMPOUNDABLES = set(['JOINT', 'COMPOUND']) monkeypatch.setattr(blessed.formatters, 'resolve_color', resolve_color) monkeypatch.setattr(blessed.formatters, 'COLORS', COLORS) monkeypatch.setattr(blessed.formatters, 'COMPOUNDABLES', COMPOUNDABLES) term = mock.Mock() assert resolve_attribute(term, 'COLORX') == u'seq-COLORX' def test_resolve_attribute_as_compoundable(monkeypatch): """ Test simple resolve_attribte() given a compoundable. """ import blessed from blessed.formatters import resolve_attribute, FormattingString resolve_cap = lambda term, digit: 'seq-%s' % (digit,) COMPOUNDABLES = set(['JOINT', 'COMPOUND']) monkeypatch.setattr(blessed.formatters, 'resolve_capability', resolve_cap) monkeypatch.setattr(blessed.formatters, 'COMPOUNDABLES', COMPOUNDABLES) term = mock.Mock() term.normal = 'seq-normal' compound = resolve_attribute(term, 'JOINT') assert type(compound) is FormattingString assert str(compound) == u'seq-JOINT' assert compound('text') == u'seq-JOINTtextseq-normal' def test_resolve_attribute_non_compoundables(monkeypatch): """ Test recursive compounding of resolve_attribute(). """ import blessed from blessed.formatters import resolve_attribute, ParameterizingString uncompoundables = lambda attr: ['split', 'compound'] resolve_cap = lambda term, digit: 'seq-%s' % (digit,) monkeypatch.setattr(blessed.formatters, 'split_compound', uncompoundables) monkeypatch.setattr(blessed.formatters, 'resolve_capability', resolve_cap) tparm = lambda *args: u'~'.join( arg.decode('latin1') if not num else '%s' % (arg,) for num, arg in enumerate(args)).encode('latin1') monkeypatch.setattr(curses, 'tparm', tparm) term = mock.Mock() term.normal = 'seq-normal' # given pstr = resolve_attribute(term, 'not-a-compoundable') assert type(pstr) == ParameterizingString assert str(pstr) == u'seq-not-a-compoundable' # this is like calling term.move_x(3) assert pstr(3) == u'seq-not-a-compoundable~3' # this is like calling term.move_x(3)('text') assert pstr(3)('text') == u'seq-not-a-compoundable~3textseq-normal' def test_resolve_attribute_recursive_compoundables(monkeypatch): """ Test recursive compounding of resolve_attribute(). """ import blessed from blessed.formatters import resolve_attribute, FormattingString # patch, resolve_cap = lambda term, digit: 'seq-%s' % (digit,) monkeypatch.setattr(blessed.formatters, 'resolve_capability', resolve_cap) tparm = lambda *args: u'~'.join( arg.decode('latin1') if not num else '%s' % (arg,) for num, arg in enumerate(args)).encode('latin1') monkeypatch.setattr(curses, 'tparm', tparm) monkeypatch.setattr(curses, 'COLOR_RED', 6502) monkeypatch.setattr(curses, 'COLOR_BLUE', 6800) color_cap = lambda digit: 'seq-%s' % (digit,) term = mock.Mock() term._background_color = color_cap term._foreground_color = color_cap term.normal = 'seq-normal' # given, pstr = resolve_attribute(term, 'bright_blue_on_red') # exercise, assert type(pstr) == FormattingString assert str(pstr) == 'seq-6808seq-6502' assert pstr('text') == 'seq-6808seq-6502textseq-normal' def test_pickled_parameterizing_string(monkeypatch): """Test pickle-ability of a formatters.ParameterizingString.""" from blessed.formatters import ParameterizingString, FormattingString # simply send()/recv() over multiprocessing Pipe, a simple # pickle.loads(dumps(...)) did not reproduce this issue, from multiprocessing import Pipe import pickle # first argument to tparm() is the sequence name, returned as-is; # subsequent arguments are usually Integers. tparm = lambda *args: u'~'.join( arg.decode('latin1') if not num else '%s' % (arg,) for num, arg in enumerate(args)).encode('latin1') monkeypatch.setattr(curses, 'tparm', tparm) # given, pstr = ParameterizingString(u'seqname', u'norm', u'cap-name') # multiprocessing Pipe implicitly pickles. r, w = Pipe() # exercise picklability of ParameterizingString for proto_num in range(pickle.HIGHEST_PROTOCOL): assert pstr == pickle.loads(pickle.dumps(pstr, protocol=proto_num)) w.send(pstr) assert r.recv() == pstr # exercise picklability of FormattingString # -- the return value of calling ParameterizingString zero = pstr(0) for proto_num in range(pickle.HIGHEST_PROTOCOL): assert zero == pickle.loads(pickle.dumps(zero, protocol=proto_num)) w.send(zero) assert r.recv() == zero def test_tparm_returns_null(monkeypatch): """ Test 'tparm() returned NULL' is caught (win32 PDCurses systems). """ # on win32, any calls to tparm raises curses.error with message, # "tparm() returned NULL", function PyCurses_tparm of _cursesmodule.c from blessed.formatters import ParameterizingString, NullCallableString def tparm(*args): raise curses.error("tparm() returned NULL") monkeypatch.setattr(curses, 'tparm', tparm) term = mock.Mock() term.normal = 'seq-normal' pstr = ParameterizingString(u'cap', u'norm', u'seq-name') value = pstr(u'x') assert type(value) is NullCallableString def test_tparm_other_exception(monkeypatch): """ Test 'tparm() returned NULL' is caught (win32 PDCurses systems). """ # on win32, any calls to tparm raises curses.error with message, # "tparm() returned NULL", function PyCurses_tparm of _cursesmodule.c from blessed.formatters import ParameterizingString, NullCallableString def tparm(*args): raise curses.error("unexpected error in tparm()") monkeypatch.setattr(curses, 'tparm', tparm) term = mock.Mock() term.normal = 'seq-normal' pstr = ParameterizingString(u'cap', u'norm', u'seq-name') try: pstr(u'x') assert False, "previous call should have raised curses.error" except curses.error: pass blessed-1.14.2/blessed/tests/test_keyboard.py0000644000076500000240000007737212636072007021671 0ustar jquaststaff00000000000000# -*- coding: utf-8 -*- "Tests for keyboard support." # std imports import functools import tempfile import signal import curses import time import math import tty # NOQA import pty import sys import os # local from .accessories import ( init_subproc_coverage, read_until_eof, read_until_semaphore, SEND_SEMAPHORE, RECV_SEMAPHORE, as_subprocess, TestTerminal, SEMAPHORE, all_terms, echo_off, ) # 3rd-party import pytest import mock import six if sys.version_info[0] == 3: unichr = chr @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_kbhit_interrupted(): "kbhit() should not be interrupted with a signal handler." pid, master_fd = pty.fork() if pid == 0: cov = init_subproc_coverage('test_kbhit_interrupted') # child pauses, writes semaphore and begins awaiting input global got_sigwinch got_sigwinch = False def on_resize(sig, action): global got_sigwinch got_sigwinch = True term = TestTerminal() signal.signal(signal.SIGWINCH, on_resize) read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.raw(): assert term.inkey(timeout=1.05) == u'' os.write(sys.__stdout__.fileno(), b'complete') assert got_sigwinch if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) read_until_semaphore(master_fd) stime = time.time() os.kill(pid, signal.SIGWINCH) output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'complete' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 1.0 @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_kbhit_interrupted_nonetype(): "kbhit() should also allow interruption with timeout of None." pid, master_fd = pty.fork() if pid == 0: cov = init_subproc_coverage('test_kbhit_interrupted_nonetype') # child pauses, writes semaphore and begins awaiting input global got_sigwinch got_sigwinch = False def on_resize(sig, action): global got_sigwinch got_sigwinch = True term = TestTerminal() signal.signal(signal.SIGWINCH, on_resize) read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.raw(): term.inkey(timeout=1) os.write(sys.__stdout__.fileno(), b'complete') assert got_sigwinch if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) read_until_semaphore(master_fd) stime = time.time() time.sleep(0.05) os.kill(pid, signal.SIGWINCH) output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'complete' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 1.0 def test_break_input_no_kb(): "cbreak() should not call tty.setcbreak() without keyboard." @as_subprocess def child(): with tempfile.NamedTemporaryFile() as stream: term = TestTerminal(stream=stream) with mock.patch("tty.setcbreak") as mock_setcbreak: with term.cbreak(): assert not mock_setcbreak.called assert term._keyboard_fd is None child() def test_raw_input_no_kb(): "raw should not call tty.setraw() without keyboard." @as_subprocess def child(): with tempfile.NamedTemporaryFile() as stream: term = TestTerminal(stream=stream) with mock.patch("tty.setraw") as mock_setraw: with term.raw(): assert not mock_setraw.called assert term._keyboard_fd is None child() def test_raw_input_with_kb(): "raw should call tty.setraw() when with keyboard." @as_subprocess def child(): term = TestTerminal() assert term._keyboard_fd is not None with mock.patch("tty.setraw") as mock_setraw: with term.raw(): assert mock_setraw.called child() def test_notty_kb_is_None(): "term._keyboard_fd should be None when os.isatty returns False." # in this scenerio, stream is sys.__stdout__, # but os.isatty(0) is False, # such as when piping output to less(1) @as_subprocess def child(): with mock.patch("os.isatty") as mock_isatty: mock_isatty.return_value = False term = TestTerminal() assert term._keyboard_fd is None child() def test_kbhit_no_kb(): "kbhit() always immediately returns False without a keyboard." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) stime = time.time() assert term._keyboard_fd is None assert not term.kbhit(timeout=1.1) assert math.floor(time.time() - stime) == 1.0 child() def test_keystroke_0s_cbreak_noinput(): "0-second keystroke without input; '' should be returned." @as_subprocess def child(): term = TestTerminal() with term.cbreak(): stime = time.time() inp = term.inkey(timeout=0) assert (inp == u'') assert (math.floor(time.time() - stime) == 0.0) child() def test_keystroke_0s_cbreak_noinput_nokb(): "0-second keystroke without data in input stream and no keyboard/tty." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) with term.cbreak(): stime = time.time() inp = term.inkey(timeout=0) assert (inp == u'') assert (math.floor(time.time() - stime) == 0.0) child() @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_keystroke_1s_cbreak_noinput(): "1-second keystroke without input; '' should be returned after ~1 second." @as_subprocess def child(): term = TestTerminal() with term.cbreak(): stime = time.time() inp = term.inkey(timeout=1) assert (inp == u'') assert (math.floor(time.time() - stime) == 1.0) child() @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_keystroke_1s_cbreak_noinput_nokb(): "1-second keystroke without input or keyboard." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) with term.cbreak(): stime = time.time() inp = term.inkey(timeout=1) assert (inp == u'') assert (math.floor(time.time() - stime) == 1.0) child() def test_keystroke_0s_cbreak_with_input(): "0-second keystroke with input; Keypress should be immediately returned." pid, master_fd = pty.fork() if pid == 0: cov = init_subproc_coverage('test_keystroke_0s_cbreak_with_input') # child pauses, writes semaphore and begins awaiting input term = TestTerminal() read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): inp = term.inkey(timeout=0) os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) os.write(master_fd, u'x'.encode('ascii')) read_until_semaphore(master_fd) stime = time.time() output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'x' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 def test_keystroke_cbreak_with_input_slowly(): "0-second keystroke with input; Keypress should be immediately returned." pid, master_fd = pty.fork() if pid == 0: cov = init_subproc_coverage('test_keystroke_cbreak_with_input_slowly') # child pauses, writes semaphore and begins awaiting input term = TestTerminal() read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): while True: inp = term.inkey(timeout=0.5) os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) if inp == 'X': break if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) os.write(master_fd, u'a'.encode('ascii')) time.sleep(0.1) os.write(master_fd, u'b'.encode('ascii')) time.sleep(0.1) os.write(master_fd, u'cdefgh'.encode('ascii')) time.sleep(0.1) os.write(master_fd, u'X'.encode('ascii')) read_until_semaphore(master_fd) stime = time.time() output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'abcdefghX' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 def test_keystroke_0s_cbreak_multibyte_utf8(): "0-second keystroke with multibyte utf-8 input; should decode immediately." # utf-8 bytes represent "latin capital letter upsilon". pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_keystroke_0s_cbreak_multibyte_utf8') term = TestTerminal() read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): inp = term.inkey(timeout=0) os.write(sys.__stdout__.fileno(), inp.encode('utf-8')) if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) os.write(master_fd, u'\u01b1'.encode('utf-8')) read_until_semaphore(master_fd) stime = time.time() output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'Ʊ' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 @pytest.mark.skipif(os.environ.get('TRAVIS', None) is not None, reason="travis-ci does not handle ^C very well.") def test_keystroke_0s_raw_input_ctrl_c(): "0-second keystroke with raw allows receiving ^C." pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_keystroke_0s_raw_input_ctrl_c') term = TestTerminal() read_until_semaphore(sys.__stdin__.fileno(), semaphore=SEMAPHORE) with term.raw(): os.write(sys.__stdout__.fileno(), RECV_SEMAPHORE) inp = term.inkey(timeout=0) os.write(sys.__stdout__.fileno(), inp.encode('latin1')) if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, SEND_SEMAPHORE) # ensure child is in raw mode before sending ^C, read_until_semaphore(master_fd) os.write(master_fd, u'\x03'.encode('latin1')) stime = time.time() output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert (output == u'\x03' or output == u'' and not os.isatty(0)) assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 def test_keystroke_0s_cbreak_sequence(): "0-second keystroke with multibyte sequence; should decode immediately." pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_keystroke_0s_cbreak_sequence') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): inp = term.inkey(timeout=0) os.write(sys.__stdout__.fileno(), inp.name.encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, u'\x1b[D'.encode('ascii')) read_until_semaphore(master_fd) stime = time.time() output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'KEY_LEFT' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_keystroke_1s_cbreak_with_input(): "1-second keystroke w/multibyte sequence; should return after ~1 second." pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_keystroke_1s_cbreak_with_input') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): inp = term.inkey(timeout=3) os.write(sys.__stdout__.fileno(), inp.name.encode('utf-8')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): read_until_semaphore(master_fd) stime = time.time() time.sleep(1) os.write(master_fd, u'\x1b[C'.encode('ascii')) output = read_until_eof(master_fd) pid, status = os.waitpid(pid, 0) assert output == u'KEY_RIGHT' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 1.0 @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_esc_delay_cbreak_035(): "esc_delay will cause a single ESC (\\x1b) to delay for 0.35." pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_esc_delay_cbreak_035') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): stime = time.time() inp = term.inkey(timeout=5) measured_time = (time.time() - stime) * 100 os.write(sys.__stdout__.fileno(), ( '%s %i' % (inp.name, measured_time,)).encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): read_until_semaphore(master_fd) stime = time.time() os.write(master_fd, u'\x1b'.encode('ascii')) key_name, duration_ms = read_until_eof(master_fd).split() pid, status = os.waitpid(pid, 0) assert key_name == u'KEY_ESCAPE' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 assert 34 <= int(duration_ms) <= 45, duration_ms @pytest.mark.skipif(os.environ.get('TEST_QUICK', None) is not None, reason="TEST_QUICK specified") def test_esc_delay_cbreak_135(): "esc_delay=1.35 will cause a single ESC (\\x1b) to delay for 1.35." pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_esc_delay_cbreak_135') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): stime = time.time() inp = term.inkey(timeout=5, esc_delay=1.35) measured_time = (time.time() - stime) * 100 os.write(sys.__stdout__.fileno(), ( '%s %i' % (inp.name, measured_time,)).encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): read_until_semaphore(master_fd) stime = time.time() os.write(master_fd, u'\x1b'.encode('ascii')) key_name, duration_ms = read_until_eof(master_fd).split() pid, status = os.waitpid(pid, 0) assert key_name == u'KEY_ESCAPE' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 1.0 assert 134 <= int(duration_ms) <= 145, int(duration_ms) def test_esc_delay_cbreak_timout_0(): """esc_delay still in effect with timeout of 0 ("nonblocking").""" pid, master_fd = pty.fork() if pid == 0: # child cov = init_subproc_coverage('test_esc_delay_cbreak_timout_0') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): stime = time.time() inp = term.inkey(timeout=0) measured_time = (time.time() - stime) * 100 os.write(sys.__stdout__.fileno(), ( '%s %i' % (inp.name, measured_time,)).encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): os.write(master_fd, u'\x1b'.encode('ascii')) read_until_semaphore(master_fd) stime = time.time() key_name, duration_ms = read_until_eof(master_fd).split() pid, status = os.waitpid(pid, 0) assert key_name == u'KEY_ESCAPE' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 assert 34 <= int(duration_ms) <= 45, int(duration_ms) def test_esc_delay_cbreak_nonprefix_sequence(): "ESC a (\\x1ba) will return an ESC immediately" pid, master_fd = pty.fork() if pid is 0: # child cov = init_subproc_coverage('test_esc_delay_cbreak_nonprefix_sequence') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): stime = time.time() esc = term.inkey(timeout=5) inp = term.inkey(timeout=5) measured_time = (time.time() - stime) * 100 os.write(sys.__stdout__.fileno(), ( '%s %s %i' % (esc.name, inp, measured_time,)).encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): read_until_semaphore(master_fd) stime = time.time() os.write(master_fd, u'\x1ba'.encode('ascii')) key1_name, key2, duration_ms = read_until_eof(master_fd).split() pid, status = os.waitpid(pid, 0) assert key1_name == u'KEY_ESCAPE' assert key2 == u'a' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 assert -1 <= int(duration_ms) <= 15, duration_ms def test_esc_delay_cbreak_prefix_sequence(): "An unfinished multibyte sequence (\\x1b[) will delay an ESC by .35 " pid, master_fd = pty.fork() if pid is 0: # child cov = init_subproc_coverage('test_esc_delay_cbreak_prefix_sequence') term = TestTerminal() os.write(sys.__stdout__.fileno(), SEMAPHORE) with term.cbreak(): stime = time.time() esc = term.inkey(timeout=5) inp = term.inkey(timeout=5) measured_time = (time.time() - stime) * 100 os.write(sys.__stdout__.fileno(), ( '%s %s %i' % (esc.name, inp, measured_time,)).encode('ascii')) sys.stdout.flush() if cov is not None: cov.stop() cov.save() os._exit(0) with echo_off(master_fd): read_until_semaphore(master_fd) stime = time.time() os.write(master_fd, u'\x1b['.encode('ascii')) key1_name, key2, duration_ms = read_until_eof(master_fd).split() pid, status = os.waitpid(pid, 0) assert key1_name == u'KEY_ESCAPE' assert key2 == u'[' assert os.WEXITSTATUS(status) == 0 assert math.floor(time.time() - stime) == 0.0 assert 34 <= int(duration_ms) <= 45, duration_ms def test_get_location_0s(): "0-second get_location call without response." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) stime = time.time() y, x = term.get_location(timeout=0) assert (math.floor(time.time() - stime) == 0.0) assert (y, x) == (-1, -1) child() def test_get_location_0s_under_raw(): "0-second get_location call without response under raw mode." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) with term.raw(): stime = time.time() y, x = term.get_location(timeout=0) assert (math.floor(time.time() - stime) == 0.0) assert (y, x) == (-1, -1) child() def test_get_location_0s_reply_via_ungetch(): "0-second get_location call with response." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) stime = time.time() # monkey patch in an invalid response ! term.ungetch(u'\x1b[10;10R') y, x = term.get_location(timeout=0.01) assert (math.floor(time.time() - stime) == 0.0) assert (y, x) == (10, 10) child() def test_get_location_0s_reply_via_ungetch_under_raw(): "0-second get_location call with response under raw mode." @as_subprocess def child(): term = TestTerminal(stream=six.StringIO()) with term.raw(): stime = time.time() # monkey patch in an invalid response ! term.ungetch(u'\x1b[10;10R') y, x = term.get_location(timeout=0.01) assert (math.floor(time.time() - stime) == 0.0) assert (y, x) == (10, 10) child() def test_keystroke_default_args(): "Test keyboard.Keystroke constructor with default arguments." from blessed.keyboard import Keystroke ks = Keystroke() assert ks._name is None assert ks.name == ks._name assert ks._code is None assert ks.code == ks._code assert u'x' == u'x' + ks assert not ks.is_sequence assert repr(ks) in ("u''", # py26, 27 "''",) # py33 def test_a_keystroke(): "Test keyboard.Keystroke constructor with set arguments." from blessed.keyboard import Keystroke ks = Keystroke(ucs=u'x', code=1, name=u'the X') assert ks._name == u'the X' assert ks.name == ks._name assert ks._code == 1 assert ks.code == ks._code assert u'xx' == u'x' + ks assert ks.is_sequence assert repr(ks) == "the X" def test_get_keyboard_codes(): "Test all values returned by get_keyboard_codes are from curses." from blessed.keyboard import ( get_keyboard_codes, CURSES_KEYCODE_OVERRIDE_MIXIN, ) exemptions = dict(CURSES_KEYCODE_OVERRIDE_MIXIN) for value, keycode in get_keyboard_codes().items(): if keycode in exemptions: assert value == exemptions[keycode] continue assert hasattr(curses, keycode) assert getattr(curses, keycode) == value def test_alternative_left_right(): "Test _alternative_left_right behavior for space/backspace." from blessed.keyboard import _alternative_left_right term = mock.Mock() term._cuf1 = u'' term._cub1 = u'' assert not bool(_alternative_left_right(term)) term._cuf1 = u' ' term._cub1 = u'\b' assert not bool(_alternative_left_right(term)) term._cuf1 = u'seq-right' term._cub1 = u'seq-left' assert (_alternative_left_right(term) == { u'seq-right': curses.KEY_RIGHT, u'seq-left': curses.KEY_LEFT}) def test_cuf1_and_cub1_as_RIGHT_LEFT(all_terms): "Test that cuf1 and cub1 are assigned KEY_RIGHT and KEY_LEFT." from blessed.keyboard import get_keyboard_sequences @as_subprocess def child(kind): term = TestTerminal(kind=kind, force_styling=True) keymap = get_keyboard_sequences(term) if term._cuf1: assert term._cuf1 in keymap assert keymap[term._cuf1] == term.KEY_RIGHT if term._cub1: assert term._cub1 in keymap if term._cub1 == '\b': assert keymap[term._cub1] == term.KEY_BACKSPACE else: assert keymap[term._cub1] == term.KEY_LEFT child(all_terms) def test_get_keyboard_sequences_sort_order(): "ordereddict ensures sequences are ordered longest-first." @as_subprocess def child(kind): term = TestTerminal(kind=kind, force_styling=True) maxlen = None for sequence, code in term._keymap.items(): if maxlen is not None: assert len(sequence) <= maxlen assert sequence maxlen = len(sequence) child(kind='xterm-256color') def test_get_keyboard_sequence(monkeypatch): "Test keyboard.get_keyboard_sequence. " import curses.has_key import blessed.keyboard (KEY_SMALL, KEY_LARGE, KEY_MIXIN) = range(3) (CAP_SMALL, CAP_LARGE) = 'cap-small cap-large'.split() (SEQ_SMALL, SEQ_LARGE, SEQ_MIXIN, SEQ_ALT_CUF1, SEQ_ALT_CUB1) = ( b'seq-small-a', b'seq-large-abcdefg', b'seq-mixin', b'seq-alt-cuf1', b'seq-alt-cub1_') # patch curses functions monkeypatch.setattr(curses, 'tigetstr', lambda cap: {CAP_SMALL: SEQ_SMALL, CAP_LARGE: SEQ_LARGE}[cap]) monkeypatch.setattr(curses.has_key, '_capability_names', dict(((KEY_SMALL, CAP_SMALL,), (KEY_LARGE, CAP_LARGE,)))) # patch global sequence mix-in monkeypatch.setattr(blessed.keyboard, 'DEFAULT_SEQUENCE_MIXIN', ( (SEQ_MIXIN.decode('latin1'), KEY_MIXIN),)) # patch for _alternative_left_right term = mock.Mock() term._cuf1 = SEQ_ALT_CUF1.decode('latin1') term._cub1 = SEQ_ALT_CUB1.decode('latin1') keymap = blessed.keyboard.get_keyboard_sequences(term) assert list(keymap.items()) == [ (SEQ_LARGE.decode('latin1'), KEY_LARGE), (SEQ_ALT_CUB1.decode('latin1'), curses.KEY_LEFT), (SEQ_ALT_CUF1.decode('latin1'), curses.KEY_RIGHT), (SEQ_SMALL.decode('latin1'), KEY_SMALL), (SEQ_MIXIN.decode('latin1'), KEY_MIXIN)] def test_resolve_sequence(): "Test resolve_sequence for order-dependent mapping." from blessed.keyboard import resolve_sequence, OrderedDict mapper = OrderedDict(((u'SEQ1', 1), (u'SEQ2', 2), # takes precedence over LONGSEQ, first-match (u'KEY_LONGSEQ_longest', 3), (u'LONGSEQ', 4), # wont match, LONGSEQ is first-match in this order (u'LONGSEQ_longer', 5), # falls through for L{anything_else} (u'L', 6))) codes = {1: u'KEY_SEQ1', 2: u'KEY_SEQ2', 3: u'KEY_LONGSEQ_longest', 4: u'KEY_LONGSEQ', 5: u'KEY_LONGSEQ_longer', 6: u'KEY_L'} ks = resolve_sequence(u'', mapper, codes) assert ks == u'' assert ks.name is None assert ks.code == None assert not ks.is_sequence assert repr(ks) in ("u''", # py26, 27 "''",) # py33 ks = resolve_sequence(u'notfound', mapper=mapper, codes=codes) assert ks == u'n' assert ks.name is None assert ks.code is None assert not ks.is_sequence assert repr(ks) in (u"u'n'", "'n'",) ks = resolve_sequence(u'SEQ1', mapper, codes) assert ks == u'SEQ1' assert ks.name == u'KEY_SEQ1' assert ks.code == 1 assert ks.is_sequence assert repr(ks) in (u"KEY_SEQ1", "KEY_SEQ1") ks = resolve_sequence(u'LONGSEQ_longer', mapper, codes) assert ks == u'LONGSEQ' assert ks.name == u'KEY_LONGSEQ' assert ks.code == 4 assert ks.is_sequence assert repr(ks) in (u"KEY_LONGSEQ", "KEY_LONGSEQ") ks = resolve_sequence(u'LONGSEQ', mapper, codes) assert ks == u'LONGSEQ' assert ks.name == u'KEY_LONGSEQ' assert ks.code == 4 assert ks.is_sequence assert repr(ks) in (u"KEY_LONGSEQ", "KEY_LONGSEQ") ks = resolve_sequence(u'Lxxxxx', mapper, codes) assert ks == u'L' assert ks.name == u'KEY_L' assert ks.code == 6 assert ks.is_sequence assert repr(ks) in (u"KEY_L", "KEY_L") def test_keyboard_prefixes(): "Test keyboard.prefixes" from blessed.keyboard import get_leading_prefixes keys = ['abc', 'abdf', 'e', 'jkl'] pfs = get_leading_prefixes(keys) assert pfs == set([u'a', u'ab', u'abd', u'j', u'jk']) def test_keypad_mixins_and_aliases(): """ Test PC-Style function key translations when in ``keypad`` mode.""" # Key plain app modified # Up ^[[A ^[OA ^[[1;mA # Down ^[[B ^[OB ^[[1;mB # Right ^[[C ^[OC ^[[1;mC # Left ^[[D ^[OD ^[[1;mD # End ^[[F ^[OF ^[[1;mF # Home ^[[H ^[OH ^[[1;mH @as_subprocess def child(kind): term = TestTerminal(kind=kind, force_styling=True) from blessed.keyboard import resolve_sequence resolve = functools.partial(resolve_sequence, mapper=term._keymap, codes=term._keycodes) assert resolve(unichr(10)).name == "KEY_ENTER" assert resolve(unichr(13)).name == "KEY_ENTER" assert resolve(unichr(8)).name == "KEY_BACKSPACE" assert resolve(unichr(9)).name == "KEY_TAB" assert resolve(unichr(27)).name == "KEY_ESCAPE" assert resolve(unichr(127)).name == "KEY_DELETE" assert resolve(u"\x1b[A").name == "KEY_UP" assert resolve(u"\x1b[B").name == "KEY_DOWN" assert resolve(u"\x1b[C").name == "KEY_RIGHT" assert resolve(u"\x1b[D").name == "KEY_LEFT" assert resolve(u"\x1b[U").name == "KEY_PGDOWN" assert resolve(u"\x1b[V").name == "KEY_PGUP" assert resolve(u"\x1b[H").name == "KEY_HOME" assert resolve(u"\x1b[F").name == "KEY_END" assert resolve(u"\x1b[K").name == "KEY_END" assert resolve(u"\x1bOM").name == "KEY_ENTER" assert resolve(u"\x1bOj").name == "KEY_KP_MULTIPLY" assert resolve(u"\x1bOk").name == "KEY_KP_ADD" assert resolve(u"\x1bOl").name == "KEY_KP_SEPARATOR" assert resolve(u"\x1bOm").name == "KEY_KP_SUBTRACT" assert resolve(u"\x1bOn").name == "KEY_KP_DECIMAL" assert resolve(u"\x1bOo").name == "KEY_KP_DIVIDE" assert resolve(u"\x1bOX").name == "KEY_KP_EQUAL" assert resolve(u"\x1bOp").name == "KEY_KP_0" assert resolve(u"\x1bOq").name == "KEY_KP_1" assert resolve(u"\x1bOr").name == "KEY_KP_2" assert resolve(u"\x1bOs").name == "KEY_KP_3" assert resolve(u"\x1bOt").name == "KEY_KP_4" assert resolve(u"\x1bOu").name == "KEY_KP_5" assert resolve(u"\x1bOv").name == "KEY_KP_6" assert resolve(u"\x1bOw").name == "KEY_KP_7" assert resolve(u"\x1bOx").name == "KEY_KP_8" assert resolve(u"\x1bOy").name == "KEY_KP_9" assert resolve(u"\x1b[1~").name == "KEY_FIND" assert resolve(u"\x1b[2~").name == "KEY_INSERT" assert resolve(u"\x1b[3~").name == "KEY_DELETE" assert resolve(u"\x1b[4~").name == "KEY_SELECT" assert resolve(u"\x1b[5~").name == "KEY_PGUP" assert resolve(u"\x1b[6~").name == "KEY_PGDOWN" assert resolve(u"\x1b[7~").name == "KEY_HOME" assert resolve(u"\x1b[8~").name == "KEY_END" assert resolve(u"\x1b[OA").name == "KEY_UP" assert resolve(u"\x1b[OB").name == "KEY_DOWN" assert resolve(u"\x1b[OC").name == "KEY_RIGHT" assert resolve(u"\x1b[OD").name == "KEY_LEFT" assert resolve(u"\x1b[OF").name == "KEY_END" assert resolve(u"\x1b[OH").name == "KEY_HOME" assert resolve(u"\x1bOP").name == "KEY_F1" assert resolve(u"\x1bOQ").name == "KEY_F2" assert resolve(u"\x1bOR").name == "KEY_F3" assert resolve(u"\x1bOS").name == "KEY_F4" child('xterm') blessed-1.14.2/blessed/tests/test_length_sequence.py0000644000076500000240000004375212617737020023237 0ustar jquaststaff00000000000000# encoding: utf-8 # std imports import itertools import termios import struct import fcntl import sys import os # local from blessed.tests.accessories import ( all_terms, as_subprocess, TestTerminal, many_columns, many_lines, ) # 3rd party import pytest import six def test_length_cjk(): @as_subprocess def child(): term = TestTerminal(kind='xterm-256color') # given, given = term.bold_red(u'コンニチハ, セカイ!') expected = sum((2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1,)) # exercise, assert term.length(given) == expected child() def test_length_ansiart(): @as_subprocess def child(): import codecs from blessed.sequences import Sequence term = TestTerminal(kind='xterm-256color') # this 'ansi' art contributed by xzip!impure for another project, # unlike most CP-437 DOS ansi art, this is actually utf-8 encoded. fname = os.path.join(os.path.dirname(__file__), 'wall.ans') lines = codecs.open(fname, 'r', 'utf-8').readlines() assert term.length(lines[0]) == 67 # ^[[64C^[[34m▄▓▄ assert term.length(lines[1]) == 75 assert term.length(lines[2]) == 78 assert term.length(lines[3]) == 78 assert term.length(lines[4]) == 78 assert term.length(lines[5]) == 78 assert term.length(lines[6]) == 77 child() def test_sequence_length(all_terms): """Ensure T.length(string containing sequence) is correcterm.""" @as_subprocess def child(kind): term = TestTerminal(kind=kind) # Create a list of ascii characters, to be separated # by word, to be zipped up with a cycling list of # terminal sequences. Then, compare the length of # each, the basic plain_texterm.__len__ vs. the Terminal # method length. They should be equal. plain_text = (u'The softest things of the world ' u'Override the hardest things of the world ' u'That which has no substance ' u'Enters into that which has no openings') if term.bold: assert (term.length(term.bold) == 0) assert (term.length(term.bold(u'x')) == 1) assert (term.length(term.bold_red) == 0) assert (term.length(term.bold_red(u'x')) == 1) assert (term.strip(term.bold) == u'') assert (term.rstrip(term.bold) == u'') assert (term.lstrip(term.bold) == u'') assert (term.strip(term.bold(u' x ')) == u'x') assert (term.strip(term.bold(u'z x q'), 'zq') == u' x ') assert (term.rstrip(term.bold(u' x ')) == u' x') assert (term.lstrip(term.bold(u' x ')) == u'x ') assert (term.strip(term.bold_red) == u'') assert (term.rstrip(term.bold_red) == u'') assert (term.lstrip(term.bold_red) == u'') assert (term.strip(term.bold_red(u' x ')) == u'x') assert (term.rstrip(term.bold_red(u' x ')) == u' x') assert (term.lstrip(term.bold_red(u' x ')) == u'x ') assert (term.strip_seqs(term.bold) == u'') assert (term.strip_seqs(term.bold(u' x ')) == u' x ') assert (term.strip_seqs(term.bold_red) == u'') assert (term.strip_seqs(term.bold_red(u' x ')) == u' x ') if term.underline: assert (term.length(term.underline) == 0) assert (term.length(term.underline(u'x')) == 1) assert (term.length(term.underline_red) == 0) assert (term.length(term.underline_red(u'x')) == 1) assert (term.strip(term.underline) == u'') assert (term.strip(term.underline(u' x ')) == u'x') assert (term.strip(term.underline_red) == u'') assert (term.strip(term.underline_red(u' x ')) == u'x') assert (term.rstrip(term.underline_red(u' x ')) == u' x') assert (term.lstrip(term.underline_red(u' x ')) == u'x ') assert (term.strip_seqs(term.underline) == u'') assert (term.strip_seqs(term.underline(u' x ')) == u' x ') assert (term.strip_seqs(term.underline_red) == u'') assert (term.strip_seqs(term.underline_red(u' x ')) == u' x ') if term.reverse: assert (term.length(term.reverse) == 0) assert (term.length(term.reverse(u'x')) == 1) assert (term.length(term.reverse_red) == 0) assert (term.length(term.reverse_red(u'x')) == 1) assert (term.strip(term.reverse) == u'') assert (term.strip(term.reverse(u' x ')) == u'x') assert (term.strip(term.reverse_red) == u'') assert (term.strip(term.reverse_red(u' x ')) == u'x') assert (term.rstrip(term.reverse_red(u' x ')) == u' x') assert (term.lstrip(term.reverse_red(u' x ')) == u'x ') assert (term.strip_seqs(term.reverse) == u'') assert (term.strip_seqs(term.reverse(u' x ')) == u' x ') assert (term.strip_seqs(term.reverse_red) == u'') assert (term.strip_seqs(term.reverse_red(u' x ')) == u' x ') if term.blink: assert (term.length(term.blink) == 0) assert (term.length(term.blink(u'x')) == 1) assert (term.length(term.blink_red) == 0) assert (term.length(term.blink_red(u'x')) == 1) assert (term.strip(term.blink) == u'') assert (term.strip(term.blink(u' x ')) == u'x') assert (term.strip(term.blink(u'z x q'), u'zq') == u' x ') assert (term.strip(term.blink_red) == u'') assert (term.strip(term.blink_red(u' x ')) == u'x') assert (term.strip_seqs(term.blink) == u'') assert (term.strip_seqs(term.blink(u' x ')) == u' x ') assert (term.strip_seqs(term.blink_red) == u'') assert (term.strip_seqs(term.blink_red(u' x ')) == u' x ') if term.home: assert (term.length(term.home) == 0) assert (term.strip(term.home) == u'') if term.clear_eol: assert (term.length(term.clear_eol) == 0) assert (term.strip(term.clear_eol) == u'') if term.enter_fullscreen: assert (term.length(term.enter_fullscreen) == 0) assert (term.strip(term.enter_fullscreen) == u'') if term.exit_fullscreen: assert (term.length(term.exit_fullscreen) == 0) assert (term.strip(term.exit_fullscreen) == u'') # horizontally, we decide move_down and move_up are 0, assert (term.length(term.move_down) == 0) assert (term.length(term.move_down(2)) == 0) assert (term.length(term.move_up) == 0) assert (term.length(term.move_up(2)) == 0) # other things aren't so simple, somewhat edge cases, # moving backwards and forwards horizontally must be # accounted for as a "length", as # will result in a printed column length of 12 (even # though columns 2-11 are non-destructive space assert (term.length(u'x\b') == 0) assert (term.strip(u'x\b') == u'') # XXX why are some terminals width of 9 here ?? assert (term.length(u'\t') in (8, 9)) assert (term.strip(u'\t') == u'') assert (term.length(u'_' + term.move_left) == 0) if term.cub: assert (term.length((u'_' * 10) + term.cub(10)) == 0) assert (term.length(term.move_right) == 1) if term.cuf: assert (term.length(term.cuf(10)) == 10) # vertical spacing is unaccounted as a 'length' assert (term.length(term.move_up) == 0) assert (term.length(term.cuu(10)) == 0) assert (term.length(term.move_down) == 0) assert (term.length(term.cud(10)) == 0) # this is how manpages perform underlining, this is done # with the 'overstrike' capability of teletypes, and aparently # less(1), '123' -> '1\b_2\b_3\b_' text_wseqs = u''.join(itertools.chain( *zip(plain_text, itertools.cycle(['\b_'])))) assert (term.length(text_wseqs) == len(plain_text)) child(all_terms) def test_env_winsize(): """Test height and width is appropriately queried in a pty.""" @as_subprocess def child(): # set the pty's virtual window size os.environ['COLUMNS'] = '99' os.environ['LINES'] = '11' term = TestTerminal(stream=six.StringIO()) save_init = term._init_descriptor save_stdout = sys.__stdout__ try: term._init_descriptor = None sys.__stdout__ = None winsize = term._height_and_width() width = term.width height = term.height finally: term._init_descriptor = save_init sys.__stdout__ = save_stdout assert winsize.ws_col == width == 99 assert winsize.ws_row == height == 11 child() def test_winsize(many_lines, many_columns): """Test height and width is appropriately queried in a pty.""" @as_subprocess def child(lines=25, cols=80): # set the pty's virtual window size val = struct.pack('HHHH', lines, cols, 0, 0) fcntl.ioctl(sys.__stdout__.fileno(), termios.TIOCSWINSZ, val) term = TestTerminal() winsize = term._height_and_width() assert term.width == cols assert term.height == lines assert winsize.ws_col == cols assert winsize.ws_row == lines child(lines=many_lines, cols=many_columns) def test_Sequence_alignment_fixed_width(): @as_subprocess def child(kind): term = TestTerminal(kind=kind) pony_msg = 'pony express, all aboard, choo, choo!' pony_len = len(pony_msg) pony_colored = u''.join( ['%s%s' % (term.color(n % 7), ch,) for n, ch in enumerate(pony_msg)]) pony_colored += term.normal ladjusted = term.ljust(pony_colored, 88) radjusted = term.rjust(pony_colored, 88) centered = term.center(pony_colored, 88) assert (term.length(pony_colored) == pony_len) assert (term.length(centered.strip()) == pony_len) assert (term.length(centered) == len(pony_msg.center(88))) assert (term.length(ladjusted.strip()) == pony_len) assert (term.length(ladjusted) == len(pony_msg.ljust(88))) assert (term.length(radjusted.strip()) == pony_len) assert (term.length(radjusted) == len(pony_msg.rjust(88))) def test_Sequence_alignment(all_terms): """Tests methods related to Sequence class, namely ljust, rjust, center.""" @as_subprocess def child(kind, lines=25, cols=80): # set the pty's virtual window size val = struct.pack('HHHH', lines, cols, 0, 0) fcntl.ioctl(sys.__stdout__.fileno(), termios.TIOCSWINSZ, val) term = TestTerminal(kind=kind) pony_msg = 'pony express, all aboard, choo, choo!' pony_len = len(pony_msg) pony_colored = u''.join( ['%s%s' % (term.color(n % 7), ch,) for n, ch in enumerate(pony_msg)]) pony_colored += term.normal ladjusted = term.ljust(pony_colored) radjusted = term.rjust(pony_colored) centered = term.center(pony_colored) assert (term.length(pony_colored) == pony_len) assert (term.length(centered.strip()) == pony_len) assert (term.length(centered) == len(pony_msg.center(term.width))) assert (term.length(ladjusted.strip()) == pony_len) assert (term.length(ladjusted) == len(pony_msg.ljust(term.width))) assert (term.length(radjusted.strip()) == pony_len) assert (term.length(radjusted) == len(pony_msg.rjust(term.width))) child(kind=all_terms) def test_sequence_is_movement_false(all_terms): """Test parser about sequences that do not move the cursor.""" @as_subprocess def child(kind): from blessed.sequences import measure_length term = TestTerminal(kind=kind) assert (0 == measure_length(u'', term)) # not even a mbs assert (0 == measure_length(u'xyzzy', term)) # negative numbers, though printable as %d, do not result # in movement; just garbage. Also not a valid sequence. assert (0 == measure_length(term.cuf(-333), term)) assert (len(term.clear_eol) == measure_length(term.clear_eol, term)) # various erases don't *move* assert (len(term.clear_bol) == measure_length(term.clear_bol, term)) assert (len(term.clear_eos) == measure_length(term.clear_eos, term)) assert (len(term.bold) == measure_length(term.bold, term)) # various paints don't move assert (len(term.red) == measure_length(term.red, term)) assert (len(term.civis) == measure_length(term.civis, term)) if term.cvvis: assert (len(term.cvvis) == measure_length(term.cvvis, term)) assert (len(term.underline) == measure_length(term.underline, term)) assert (len(term.reverse) == measure_length(term.reverse, term)) for _num in (0, term.number_of_colors): expected = len(term.color(_num)) given = measure_length(term.color(_num), term) assert (expected == given) assert (len(term.normal_cursor) == measure_length(term.normal_cursor, term)) assert (len(term.hide_cursor) == measure_length(term.hide_cursor, term)) assert (len(term.save) == measure_length(term.save, term)) assert (len(term.italic) == measure_length(term.italic, term)) assert (len(term.standout) == measure_length(term.standout, term) ), (term.standout, term._wont_move) child(all_terms) def test_termcap_will_move_false(all_terms): """Test parser about sequences that do not move the cursor.""" @as_subprocess def child(kind): from blessed.sequences import iter_parse term = TestTerminal(kind=kind) if term.clear_eol: assert not next(iter_parse(term, term.clear_eol))[1].will_move if term.clear_bol: assert not next(iter_parse(term, term.clear_bol))[1].will_move if term.clear_eos: assert not next(iter_parse(term, term.clear_eos))[1].will_move if term.bold: assert not next(iter_parse(term, term.bold))[1].will_move if term.red: assert not next(iter_parse(term, term.red))[1].will_move if term.civis: assert not next(iter_parse(term, term.civis))[1].will_move if term.cvvis: assert not next(iter_parse(term, term.cvvis))[1].will_move if term.underline: assert not next(iter_parse(term, term.underline))[1].will_move if term.reverse: assert not next(iter_parse(term, term.reverse))[1].will_move if term.color(0): assert not next(iter_parse(term, term.color(0)))[1].will_move if term.normal_cursor: assert not next(iter_parse(term, term.normal_cursor))[1].will_move if term.save: assert not next(iter_parse(term, term.save))[1].will_move if term.italic: assert not next(iter_parse(term, term.italic))[1].will_move if term.standout: assert not next(iter_parse(term, term.standout))[1].will_move child(all_terms) def test_sequence_is_movement_true(all_terms): """Test parsers about sequences that move the cursor.""" @as_subprocess def child(kind): from blessed.sequences import measure_length term = TestTerminal(kind=kind) # movements assert (len(term.move(98, 76)) == measure_length(term.move(98, 76), term)) assert (len(term.move(54)) == measure_length(term.move(54), term)) assert not term.cud1 or (len(term.cud1) == measure_length(term.cud1, term)) assert not term.cub1 or (len(term.cub1) == measure_length(term.cub1, term)) assert not term.cuf1 or (len(term.cuf1) == measure_length(term.cuf1, term)) assert not term.cuu1 or (len(term.cuu1) == measure_length(term.cuu1, term)) assert not term.cub or (len(term.cub(333)) == measure_length(term.cub(333), term)) assert not term.cuf or (len(term.cuf(333)) == measure_length(term.cuf(333), term)) assert not term.home or (len(term.home) == measure_length(term.home, term)) assert not term.restore or (len(term.restore) == measure_length(term.restore, term)) assert not term.clear or (len(term.clear) == measure_length(term.clear, term)) child(all_terms) def test_termcap_will_move_true(all_terms): """Test parser about sequences that move the cursor.""" @as_subprocess def child(kind): from blessed.sequences import iter_parse term = TestTerminal(kind=kind) assert next(iter_parse(term, term.move(98, 76)))[1].will_move assert next(iter_parse(term, term.move(54)))[1].will_move assert next(iter_parse(term, term.cud1))[1].will_move assert next(iter_parse(term, term.cub1))[1].will_move assert next(iter_parse(term, term.cuf1))[1].will_move assert next(iter_parse(term, term.cuu1))[1].will_move if term.cub(333): assert next(iter_parse(term, term.cub(333)))[1].will_move if term.cuf(333): assert next(iter_parse(term, term.cuf(333)))[1].will_move assert next(iter_parse(term, term.home))[1].will_move assert next(iter_parse(term, term.restore))[1].will_move assert next(iter_parse(term, term.clear))[1].will_move child(all_terms) def test_foreign_sequences(): """Test parsers about sequences received from foreign sources.""" @as_subprocess def child(kind): from blessed.sequences import measure_length term = TestTerminal(kind=kind) assert measure_length(u'\x1b[m', term) == len('\x1b[m') child(kind='ansi') blessed-1.14.2/blessed/tests/test_sequences.py0000644000076500000240000004000612636072007022044 0ustar jquaststaff00000000000000# -*- coding: utf-8 -*- """Tests for Terminal() sequences and sequence-awareness.""" # std imports import platform import random import sys import os # local from .accessories import ( all_terms, as_subprocess, TestTerminal, unicode_parm, many_columns, unicode_cap, ) # 3rd party import pytest import mock import six def test_capability(): """Check that capability lookup works.""" @as_subprocess def child(): # Also test that Terminal grabs a reasonable default stream. This test # assumes it will be run from a tty. t = TestTerminal() sc = unicode_cap('sc') assert t.save == sc assert t.save == sc # Make sure caching doesn't screw it up. child() def test_capability_without_tty(): """Assert capability templates are '' when stream is not a tty.""" @as_subprocess def child(): t = TestTerminal(stream=six.StringIO()) assert t.save == u'' assert t.red == u'' child() def test_capability_with_forced_tty(): """force styling should return sequences even for non-ttys.""" @as_subprocess def child(): t = TestTerminal(stream=six.StringIO(), force_styling=True) assert t.save == unicode_cap('sc') child() def test_parametrization(): """Test parameterizing a capability.""" @as_subprocess def child(): assert TestTerminal().cup(3, 4) == unicode_parm('cup', 3, 4) child() def test_height_and_width(): """Assert that ``height_and_width()`` returns full integers.""" @as_subprocess def child(): t = TestTerminal() # kind shouldn't matter. assert isinstance(t.height, int) assert isinstance(t.width, int) child() def test_stream_attr(): """Make sure Terminal ``stream`` is stdout by default.""" @as_subprocess def child(): assert TestTerminal().stream == sys.__stdout__ child() def test_location_with_styling(all_terms): """Make sure ``location()`` works on all terminals.""" @as_subprocess def child_with_styling(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.location(3, 4): t.stream.write(u'hi') expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', unicode_parm('cup', 4, 3), u'hi', unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output) child_with_styling(all_terms) def test_location_without_styling(): """Make sure ``location()`` silently passes without styling.""" @as_subprocess def child_without_styling(): """No side effect for location as a context manager without styling.""" t = TestTerminal(stream=six.StringIO(), force_styling=None) with t.location(3, 4): t.stream.write(u'hi') assert t.stream.getvalue() == u'hi' child_without_styling() def test_horizontal_location(all_terms): """Make sure we can move the cursor horizontally without changing rows.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.location(x=5): pass _hpa = unicode_parm('hpa', 5) if not _hpa and (kind.startswith('screen') or kind.startswith('ansi')): _hpa = u'\x1b[6G' expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', _hpa, unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output), ( repr(t.stream.getvalue()), repr(expected_output)) child(all_terms) def test_vertical_location(all_terms): """Make sure we can move the cursor horizontally without changing rows.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.location(y=5): pass _vpa = unicode_parm('vpa', 5) if not _vpa and (kind.startswith('screen') or kind.startswith('ansi')): _vpa = u'\x1b[6d' expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', _vpa, unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output) child(all_terms) def test_inject_move_x(): """Test injection of hpa attribute for screen/ansi (issue #55).""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) COL = 5 with t.location(x=COL): pass expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', u'\x1b[{0}G'.format(COL + 1), unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output) assert (t.move_x(COL) == u'\x1b[{0}G'.format(COL + 1)) child('screen') child('screen-256color') child('ansi') def test_inject_move_y(): """Test injection of vpa attribute for screen/ansi (issue #55).""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) ROW = 5 with t.location(y=ROW): pass expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', u'\x1b[{0}d'.format(ROW + 1), unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output) assert (t.move_y(ROW) == u'\x1b[{0}d'.format(ROW + 1)) child('screen') child('screen-256color') child('ansi') def test_inject_civis_and_cnorm_for_ansi(): """Test injection of cvis attribute for ansi.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.hidden_cursor(): pass expected_output = u'\x1b[?25l\x1b[?25h' assert (t.stream.getvalue() == expected_output) child('ansi') def test_inject_sc_and_rc_for_ansi(): """Test injection of sc and rc (save and restore cursor) for ansi.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.location(): pass expected_output = u'\x1b[s\x1b[u' assert (t.stream.getvalue() == expected_output) child('ansi') def test_zero_location(all_terms): """Make sure ``location()`` pays attention to 0-valued args.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=True) with t.location(0, 0): pass expected_output = u''.join( (unicode_cap('sc') or u'\x1b[s', unicode_parm('cup', 0, 0), unicode_cap('rc') or u'\x1b[u')) assert (t.stream.getvalue() == expected_output) child(all_terms) def test_mnemonic_colors(all_terms): """Make sure color shortcuts work.""" @as_subprocess def child(kind): def color(t, num): return t.number_of_colors and unicode_parm('setaf', num) or '' def on_color(t, num): return t.number_of_colors and unicode_parm('setab', num) or '' # Avoid testing red, blue, yellow, and cyan, since they might someday # change depending on terminal type. t = TestTerminal(kind=kind) assert (t.white == color(t, 7)) assert (t.green == color(t, 2)) # Make sure it's different than white. assert (t.on_black == on_color(t, 0)) assert (t.on_green == on_color(t, 2)) assert (t.bright_black == color(t, 8)) assert (t.bright_green == color(t, 10)) assert (t.on_bright_black == on_color(t, 8)) assert (t.on_bright_green == on_color(t, 10)) child(all_terms) def test_callable_numeric_colors(all_terms): """``color(n)`` should return a formatting wrapper.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) if t.magenta: assert t.color(5)('smoo') == t.magenta + 'smoo' + t.normal else: assert t.color(5)('smoo') == 'smoo' if t.on_magenta: assert t.on_color(5)('smoo') == t.on_magenta + 'smoo' + t.normal else: assert t.color(5)(u'smoo') == 'smoo' if t.color(4): assert t.color(4)(u'smoo') == t.color(4) + u'smoo' + t.normal else: assert t.color(4)(u'smoo') == 'smoo' if t.on_green: assert t.on_color(2)('smoo') == t.on_green + u'smoo' + t.normal else: assert t.on_color(2)('smoo') == 'smoo' if t.on_color(6): assert t.on_color(6)('smoo') == t.on_color(6) + u'smoo' + t.normal else: assert t.on_color(6)('smoo') == 'smoo' child(all_terms) def test_null_callable_numeric_colors(all_terms): """``color(n)`` should be a no-op on null terminals.""" @as_subprocess def child(kind): t = TestTerminal(stream=six.StringIO(), kind=kind) assert (t.color(5)('smoo') == 'smoo') assert (t.on_color(6)('smoo') == 'smoo') child(all_terms) def test_naked_color_cap(all_terms): """``term.color`` should return a stringlike capability.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) assert (t.color + '' == t.setaf + '') child(all_terms) def test_formatting_functions(all_terms): """Test simple and compound formatting wrappers.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) # test simple sugar, if t.bold: expected_output = u''.join((t.bold, u'hi', t.normal)) else: expected_output = u'hi' assert t.bold(u'hi') == expected_output # Plain strs for Python 2.x if t.green: expected_output = u''.join((t.green, 'hi', t.normal)) else: expected_output = u'hi' assert t.green('hi') == expected_output # Test unicode if t.underline: expected_output = u''.join((t.underline, u'boö', t.normal)) else: expected_output = u'boö' assert (t.underline(u'boö') == expected_output) if t.subscript: expected_output = u''.join((t.subscript, u'[1]', t.normal)) else: expected_output = u'[1]' assert (t.subscript(u'[1]') == expected_output) child(all_terms) def test_compound_formatting(all_terms): """Test simple and compound formatting wrappers.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) if any((t.bold, t.green)): expected_output = u''.join((t.bold, t.green, u'boö', t.normal)) else: expected_output = u'boö' assert t.bold_green(u'boö') == expected_output if any((t.on_bright_red, t.bold, t.bright_green, t.underline)): expected_output = u''.join( (t.on_bright_red, t.bold, t.bright_green, t.underline, u'meh', t.normal)) else: expected_output = u'meh' assert (t.on_bright_red_bold_bright_green_underline('meh') == expected_output) child(all_terms) def test_nested_formatting(all_terms): """Test complex nested compound formatting, wow!""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) # Test deeply nested styles given = t.green('-a-', t.bold('-b-', t.underline('-c-'), '-d-'), '-e-') expected = u''.join(( t.green, '-a-', t.bold, '-b-', t.underline, '-c-', t.normal, t.green, t.bold, '-d-', t.normal, t.green, '-e-', t.normal)) assert given == expected # Test off-and-on nested styles given = t.green('off ', t.underline('ON'), ' off ', t.underline('ON'), ' off') expected = u''.join(( t.green, 'off ', t.underline, 'ON', t.normal, t.green , ' off ', t.underline, 'ON', t.normal, t.green, ' off', t.normal)) assert given == expected def test_formatting_functions_without_tty(all_terms): """Test crazy-ass formatting wrappers when there's no tty.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind, stream=six.StringIO(), force_styling=False) assert (t.bold(u'hi') == u'hi') assert (t.green('hi') == u'hi') # Test non-ASCII chars, no longer really necessary: assert (t.bold_green(u'boö') == u'boö') assert (t.bold_underline_green_on_red('loo') == u'loo') # Test deeply nested styles given = t.green('-a-', t.bold('-b-', t.underline('-c-'), '-d-'), '-e-') expected = u'-a--b--c--d--e-' assert given == expected # Test off-and-on nested styles given = t.green('off ', t.underline('ON'), ' off ', t.underline('ON'), ' off') expected = u'off ON off ON off' assert given == expected assert (t.on_bright_red_bold_bright_green_underline('meh') == u'meh') child(all_terms) def test_nice_formatting_errors(all_terms): """Make sure you get nice hints if you misspell a formatting wrapper.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) try: t.bold_misspelled('hey') assert not t.is_a_tty or False, 'Should have thrown exception' except TypeError: e = sys.exc_info()[1] assert 'probably misspelled' in e.args[0] try: t.bold_misspelled(u'hey') # unicode assert not t.is_a_tty or False, 'Should have thrown exception' except TypeError: e = sys.exc_info()[1] assert 'probably misspelled' in e.args[0] try: t.bold_misspelled(None) # an arbitrary non-string assert not t.is_a_tty or False, 'Should have thrown exception' except TypeError: e = sys.exc_info()[1] assert 'probably misspelled' not in e.args[0] if platform.python_implementation() != 'PyPy': # PyPy fails to toss an exception, Why?! try: t.bold_misspelled('a', 'b') # >1 string arg assert not t.is_a_tty or False, 'Should have thrown exception' except TypeError: e = sys.exc_info()[1] assert 'probably misspelled' in e.args[0], e.args child(all_terms) def test_null_callable_string(all_terms): """Make sure NullCallableString tolerates all kinds of args.""" @as_subprocess def child(kind): t = TestTerminal(stream=six.StringIO(), kind=kind) assert (t.clear == '') assert (t.move(1 == 2) == '') assert (t.move_x(1) == '') assert (t.bold() == '') assert (t.bold('', 'x', 'huh?') == 'xhuh?') assert (t.clear('x') == 'x') child(all_terms) def test_padd(): """ Test Terminal.padd(seq). """ @as_subprocess def child(): from blessed.sequences import Sequence from blessed import Terminal term = Terminal('xterm-256color') assert Sequence('xyz\b', term).padd() == u'xy' assert Sequence('xyz\b-', term).padd() == u'xy-' assert Sequence('xxxx\x1b[3Dzz', term).padd() == u'xzz' assert Sequence('\x1b[3D', term).padd() == u'' # "Trim left" child() def test_split_seqs(all_terms): """Test Terminal.split_seqs.""" @as_subprocess def child(kind): from blessed import Terminal term = Terminal(kind) if term.sc and term.rc: given_text = term.sc + 'AB' + term.rc + 'CD' expected = [term.sc, 'A', 'B', term.rc, 'C', 'D'] result = list(term.split_seqs(given_text)) assert result == expected if term.bold: given_text = term.bold + 'bbq' expected = [term.bold, 'b', 'b', 'q'] result = list(term.split_seqs(given_text)) assert result == expected child(all_terms) blessed-1.14.2/blessed/tests/test_wrap.py0000644000076500000240000001050112636072007021017 0ustar jquaststaff00000000000000# std import os import textwrap # local from .accessories import ( as_subprocess, TestTerminal, many_columns, ) # 3rd party import pytest TEXTWRAP_KEYWORD_COMBINATIONS = [ dict(break_long_words=False, drop_whitespace=False, subsequent_indent=''), dict(break_long_words=False, drop_whitespace=True, subsequent_indent=''), dict(break_long_words=False, drop_whitespace=False, subsequent_indent=' '), dict(break_long_words=False, drop_whitespace=True, subsequent_indent=' '), dict(break_long_words=True, drop_whitespace=False, subsequent_indent=''), dict(break_long_words=True, drop_whitespace=True, subsequent_indent=''), dict(break_long_words=True, drop_whitespace=False, subsequent_indent=' '), dict(break_long_words=True, drop_whitespace=True, subsequent_indent=' '), ] if os.environ.get('TEST_QUICK', None) is not None: # test only one feature: everything on TEXTWRAP_KEYWORD_COMBINATIONS = [ dict(break_long_words=True, drop_whitespace=True, subsequent_indent=' ') ] def test_SequenceWrapper_invalid_width(): """Test exception thrown from invalid width""" WIDTH = -3 @as_subprocess def child(): term = TestTerminal() try: my_wrapped = term.wrap(u'------- -------------', WIDTH) except ValueError as err: assert err.args[0] == ( "invalid width %r(%s) (must be integer > 0)" % ( WIDTH, type(WIDTH))) else: assert False, 'Previous stmt should have raised exception.' del my_wrapped # assigned but never used child() @pytest.mark.parametrize("kwargs", TEXTWRAP_KEYWORD_COMBINATIONS) def test_SequenceWrapper(many_columns, kwargs): """Test that text wrapping matches internal extra options.""" @as_subprocess def child(width, pgraph, kwargs): # build a test paragraph, along with a very colorful version term = TestTerminal() attributes = ('bright_red', 'on_bright_blue', 'underline', 'reverse', 'red_reverse', 'red_on_white', 'superscript', 'subscript', 'on_bright_white') term.bright_red('x') term.on_bright_blue('x') term.underline('x') term.reverse('x') term.red_reverse('x') term.red_on_white('x') term.superscript('x') term.subscript('x') term.on_bright_white('x') pgraph_colored = u''.join([ getattr(term, (attributes[idx % len(attributes)]))(char) if char != u' ' else u' ' for idx, char in enumerate(pgraph)]) internal_wrapped = textwrap.wrap(pgraph, width=width, **kwargs) my_wrapped = term.wrap(pgraph, width=width, **kwargs) my_wrapped_colored = term.wrap(pgraph_colored, width=width, **kwargs) # ensure we textwrap ascii the same as python assert internal_wrapped == my_wrapped # ensure content matches for each line, when the sequences are # stripped back off of each line for line_no, (left, right) in enumerate( zip(internal_wrapped, my_wrapped_colored)): assert left == term.strip_seqs(right) # ensure our colored textwrap is the same paragraph length assert (len(internal_wrapped) == len(my_wrapped_colored)) child(width=many_columns, kwargs=kwargs, pgraph=u' Z! a bc defghij klmnopqrstuvw<<>>xyz012345678900 '*2) child(width=many_columns, kwargs=kwargs, pgraph=u'a bb ccc') def test_multiline(): """Test that text wrapping matches internal extra options.""" @as_subprocess def child(): # build a test paragraph, along with a very colorful version term = TestTerminal() given_string = ('\n' + (32 * 'A') + '\n' + (32 * 'B') + '\n' + (32 * 'C') + '\n\n') expected = [ '', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'AA', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', 'BB', 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', 'CC', '', ] result = term.wrap(given_string, width=30) assert expected == result child() blessed-1.14.2/blessed/tests/wall.ans0000644000076500000240000000375412607273732020121 0ustar jquaststaff00000000000000▄▓▄ ░░ █▀▀█▀██▀████████▀█▀▀█ █▀▀█▀██▀▀█▀█xz(imp) ▄▄▄ ▓█████ ▄▄ █▀▀█░ ▀▀▀▀▀ █████▓ ▓█████ ░▓ █▀▀█░ ▓█████ ░▓ █▀▀█░ ░▄ ░░ ▐▄░▄▀ ▀▀▀▀▀▀ █▓ ▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀ ██ ▀▀▀▀▀ ▀▀▀▀▀▀ ██ ▀▀▀▀▀ █▄▌▌▄▄ █▓██ ░▓████ ▐ ████░ ░████ ▄█ ████▓░ ░▓████ ██ ████░ ░▓████ ██ ████░ █▐▄▄█░ ██▓▀ ░▓████ █ ████░ ░████ ▐ ████▓░ ░▓████ ▄█▌████░ ░▓████ ▄█▌████░ ▌█████ ▀ ░▓████▄█▄▄███ ░ ░ ███▄▄▄▄████▓░ ░▓████▄▄▄▄███ ░ ░▓████▄▄▄▄███ ░ ▀▀▀▓ blessed-1.14.2/blessed.egg-info/0000755000076500000240000000000013066002636016767 5ustar jquaststaff00000000000000blessed-1.14.2/blessed.egg-info/dependency_links.txt0000644000076500000240000000000113066002636023035 0ustar jquaststaff00000000000000 blessed-1.14.2/blessed.egg-info/PKG-INFO0000644000076500000240000002125513066002636020071 0ustar jquaststaff00000000000000Metadata-Version: 1.1 Name: blessed Version: 1.14.2 Summary: A thin, practical wrapper around terminal styling, screen positioning, and keyboard input. Home-page: https://github.com/jquast/blessed Author: Jeff Quast, Erik Rose Author-email: contact@jeffquast.com License: MIT Description: .. image:: https://img.shields.io/travis/jquast/blessed/master.svg :alt: Travis Continuous Integration :target: https://travis-ci.org/jquast/blessed/ .. image:: https://img.shields.io/teamcity/https/teamcity-master.pexpect.org/s/Blessed_BuildHead.svg :alt: TeamCity Build status :target: https://teamcity-master.pexpect.org/viewType.html?buildTypeId=Blessed_BuildHead&branch_Blessed=%3Cdefault%3E&tab=buildTypeStatusDiv .. image:: https://coveralls.io/repos/jquast/blessed/badge.svg?branch=master&service=github :alt: Coveralls Code Coverage :target: https://coveralls.io/github/jquast/blessed?branch=master .. image:: https://img.shields.io/pypi/v/blessed.svg :alt: Latest Version :target: https://pypi.python.org/pypi/blessed .. image:: https://img.shields.io/pypi/dm/blessed.svg :alt: Downloads :target: https://pypi.python.org/pypi/blessed .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join Chat :target: https://gitter.im/jquast/blessed Introduction ============ Blessed is a thin, practical wrapper around terminal capabilities in Python. Coding with *Blessed* looks like this... :: from blessed import Terminal t = Terminal() print(t.bold('Hi there!')) print(t.bold_red_on_bright_green('It hurts my eyes!')) with t.location(0, t.height - 1): print(t.center(t.blink('press any key to continue.'))) with t.cbreak(): inp = t.inkey() print('You pressed ' + repr(inp)) Brief Overview -------------- *Blessed* is a more simplified wrapper around curses_, providing : * Styles, color, and maybe a little positioning without necessarily clearing the whole screen first. * Works great with standard Python string formatting. * Provides up-to-the-moment terminal height and width, so you can respond to terminal size changes. * Avoids making a mess if the output gets piped to a non-terminal: outputs to any file-like object such as *StringIO*, files, or pipes. * Uses the `terminfo(5)`_ database so it works with any terminal type and supports any terminal capability: No more C-like calls to tigetstr_ and tparm_. * Keeps a minimum of internal state, so you can feel free to mix and match with calls to curses or whatever other terminal libraries you like. * Provides plenty of context managers to safely express terminal modes, automatically restoring the terminal to a safe state on exit. * Act intelligently when somebody redirects your output to a file, omitting all of the terminal sequences such as styling, colors, or positioning. * Dead-simple keyboard handling: safely decoding unicode input in your system's preferred locale and supports application/arrow keys. * Allows the printable length of strings containing sequences to be determined. Blessed **does not** provide... * Windows command prompt support. A PDCurses_ build of python for windows provides only partial support at this time -- there are plans to merge with the ansi_ module in concert with colorama_ to resolve this. `Patches welcome `_! Before And After ---------------- With the built-in curses_ module, this is how you would typically print some underlined text at the bottom of the screen:: from curses import tigetstr, setupterm, tparm from fcntl import ioctl from os import isatty import struct import sys from termios import TIOCGWINSZ # If we want to tolerate having our output piped to other commands or # files without crashing, we need to do all this branching: if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()): setupterm() sc = tigetstr('sc') cup = tigetstr('cup') rc = tigetstr('rc') underline = tigetstr('smul') normal = tigetstr('sgr0') else: sc = cup = rc = underline = normal = '' # Save cursor position. print(sc) if cup: # tigetnum('lines') doesn't always update promptly, hence this: height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0] # Move cursor to bottom. print(tparm(cup, height - 1, 0)) print('This is {under}underlined{normal}!' .format(under=underline, normal=normal)) # Restore cursor position. print(rc) The same program with *Blessed* is simply:: from blessed import Terminal term = Terminal() with term.location(0, term.height - 1): print('This is' + term.underline('underlined') + '!') Requirements ------------ *Blessed* is tested with Python 2.7, 3.4, and 3.5 on Debian Linux, Mac, and FreeBSD. Further Documentation --------------------- More documentation can be found at http://blessed.readthedocs.org/en/latest/ Bugs, Contributing, Support --------------------------- **Bugs** or suggestions? Visit the `issue tracker`_ and file an issue. We welcome your bug reports and feature suggestions! Would you like to **contribute**? That's awesome! We've written a `guide `_ to help you. Are you stuck and need **support**? Give `stackoverflow`_ a try. If you're still having trouble, we'd like to hear about it! Open an issue in the `issue tracker`_ with a well-formed question. License ------- *Blessed* is under the MIT License. See the LICENSE file. Forked ------ *Blessed* is a fork of `blessings `_. Changes since 1.7 have all been proposed but unaccepted upstream. Furthermore, a project in the node.js language of the `same name `_ is **not** related, or a fork of each other in any way. .. _`issue tracker`: https://github.com/jquast/blessed/issues/ .. _curses: https://docs.python.org/3/library/curses.html .. _tigetstr: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tigetstr.3 .. _tparm: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tparm.3 .. _ansi: https://github.com/tehmaze/ansi .. _colorama: https://pypi.python.org/pypi/colorama .. _PDCurses: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses .. _`terminfo(5)`: http://invisible-island.net/ncurses/man/terminfo.5.html .. _`stackoverflow`: http://stackoverflow.com/ Keywords: terminal,sequences,tty,curses,ncurses,formatting,style,color,console,keyboard,ansi,xterm Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Console :: Curses Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: Terminals blessed-1.14.2/blessed.egg-info/requires.txt0000644000076500000240000000003213066002636021362 0ustar jquaststaff00000000000000wcwidth>=0.1.4 six>=1.9.0 blessed-1.14.2/blessed.egg-info/SOURCES.txt0000644000076500000240000000162513066002636020657 0ustar jquaststaff00000000000000.coveragerc LICENSE MANIFEST.in README.rst requirements-about.txt requirements-analysis.txt requirements-docs.txt requirements-tests.txt requirements.txt setup.cfg setup.py tox.ini version.json blessed/__init__.py blessed/_capabilities.py blessed/formatters.py blessed/keyboard.py blessed/sequences.py blessed/terminal.py blessed.egg-info/PKG-INFO blessed.egg-info/SOURCES.txt blessed.egg-info/dependency_links.txt blessed.egg-info/requires.txt blessed.egg-info/top_level.txt blessed.egg-info/zip-safe blessed/tests/__init__.py blessed/tests/accessories.py blessed/tests/test_core.py blessed/tests/test_formatters.py blessed/tests/test_keyboard.py blessed/tests/test_length_sequence.py blessed/tests/test_sequences.py blessed/tests/test_wrap.py blessed/tests/wall.ans docs/api.rst docs/contributing.rst docs/examples.rst docs/further.rst docs/history.rst docs/index.rst docs/intro.rst docs/overview.rst docs/pains.rstblessed-1.14.2/blessed.egg-info/top_level.txt0000644000076500000240000000001013066002636021510 0ustar jquaststaff00000000000000blessed blessed-1.14.2/blessed.egg-info/zip-safe0000644000076500000240000000000112456570561020430 0ustar jquaststaff00000000000000 blessed-1.14.2/docs/0000755000076500000240000000000013066002636014604 5ustar jquaststaff00000000000000blessed-1.14.2/docs/api.rst0000644000076500000240000000144212617737020016113 0ustar jquaststaff00000000000000API Documentation ================= terminal.py ----------- .. automodule:: blessed.terminal :members: :undoc-members: :special-members: __getattr__ .. autodata:: _CUR_TERM formatters.py ------------- .. automodule:: blessed.formatters :members: :undoc-members: :private-members: :special-members: __call__ .. autodata:: COLORS .. autodata:: COMPOUNDABLES keyboard.py ----------- .. automodule:: blessed.keyboard :members: :undoc-members: :private-members: :special-members: __new__ .. autofunction:: _alternative_left_right .. autofunction:: _inject_curses_keynames .. autodata:: DEFAULT_SEQUENCE_MIXIN .. autodata:: CURSES_KEYCODE_OVERRIDE_MIXIN sequences.py ------------ .. automodule:: blessed.sequences :members: :undoc-members: :private-members: blessed-1.14.2/docs/contributing.rst0000644000076500000240000000350512657676130020063 0ustar jquaststaff00000000000000Contributing ============ 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 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. Blessed has nearly 100% line coverage, with roughly 1/2 of the codebase in the form of tests, which are further combined by a matrix of varying ``TERM`` types, providing plenty of existing test cases to augment or duplicate in your favor. Style and Static Analysis ~~~~~~~~~~~~~~~~~~~~~~~~~ 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 through the **sa** target, invoked using:: tox -esa All standards enforced by the underlying style checker tools are adhered to, with the declarative exception of those found in `landscape.yml `_, or inline using ``pylint: disable=`` directives. blessed-1.14.2/docs/examples.rst0000644000076500000240000000601212607273732017162 0ustar jquaststaff00000000000000Examples ======== A few programs are provided with blessed to help interactively test the various API features, but also serve as examples of using blessed to develop applications. These examples are not distributed with the package -- they are only available in the github repository. You can retrieve them by cloning the repository, or simply downloading the "raw" file link. editor.py --------- https://github.com/jquast/blessed/blob/master/bin/editor.py This program demonstrates using the directional keys and noecho input mode. It acts as a (very dumb) fullscreen editor, with support for saving a file, as well as including a rudimentary line-editor. keymatrix.py ------------ https://github.com/jquast/blessed/blob/master/bin/keymatrix.py This program displays a "gameboard" of all known special KEY_NAME constants. When the key is depressed, it is highlighted, as well as displaying the unicode sequence, integer code, and friendly-name of any key pressed. on_resize.py ------------ https://github.com/jquast/blessed/blob/master/bin/on_resize.py This program installs a SIGWINCH signal handler, which detects screen resizes while also polling for input, displaying keypresses. This demonstrates how a program can react to screen resize events. progress_bar.py --------------- https://github.com/jquast/blessed/blob/master/bin/progress_bar.py This program demonstrates a simple progress bar. All text is written to stderr, to avoid the need to "flush" or emit newlines, and makes use of the move_x (hpa) capability to "overstrike" the display a scrolling progress bar. .. _tprint.py: tprint.py --------- https://github.com/jquast/blessed/blob/master/bin/tprint.py This program demonstrates how users may customize FormattingString styles. Accepting a string style, such as "bold" or "bright_red" as the first argument, all subsequent arguments are displayed by the given style. This shows how a program could provide user-customizable compound formatting names to configure a program's styling. worms.py -------- https://github.com/jquast/blessed/blob/master/bin/worms.py This program demonstrates how an interactive game could be made with blessed. It is similar to `NIBBLES.BAS `_ or "snake" of early mobile platforms. resize.py --------- https://github.com/jquast/blessed/blob/master/bin/resize.py This program demonstrates the :meth:`~.get_location` method, behaving similar to `resize(1) `_ : set environment and terminal settings to current window size. The window size is determined by eliciting an answerback sequence from the connecting terminal emulator. .. _detect-multibyte.py: detect-multibyte.py ------------------- https://github.com/jquast/blessed/blob/master/bin/detect-multibyte.py This program also demonstrates how the :meth:`~.get_location` method can be used to reliably test whether the terminal emulator of the connecting client is capable of rendering multibyte characters as a single cell. blessed-1.14.2/docs/further.rst0000644000076500000240000001177012607273732017032 0ustar jquaststaff00000000000000Further Reading =============== As a developer's API, blessed is often bundled with frameworks and toolsets that dive deeper into Terminal I/O programming than :class:`~.Terminal` offers. Here are some recommended readings to help you along: - `terminfo(5) `_ manpage of your preferred posix-like operating system. The capabilities available as attributes of :class:`~.Terminal` are directly mapped to those listed in the column **Cap-name**. - `termios(4) `_ of your preferred posix-like operating system. - `The TTY demystified `_ by Linus Åkesson. - `A Brief Introduction to Termios `_ by Nelson Elhage. - Richard Steven's `Advance Unix Programming `_ ("AUP") provides two very good chapters, "Terminal I/O" and "Pseudo Terminals". - GNU's `The Termcap Manual `_ by Richard M. Stallman. - `Chapter 4 `_ of CUNY's course material for *Introduction to System Programming*, by `Stewart Weiss `_ - `Chapter 11 `_ of the IEEE Open Group Base Specifications Issue 7, "General Terminal Interface" - The GNU C Library documentation, section `Low-Level Terminal Interface `_ - The source code of many popular terminal emulators. If there is ever any question of "the meaning of a terminal capability", or whether or not your preferred terminal emulator actually handles them, read the source! These are often written in the C language, and directly map the "Control Sequence Inducers" (CSI, literally ``\x1b[`` for most modern terminal types) emitted by most terminal capabilities to an action in a series of ``case`` switch statements. - Many modern libraries are now based on `libvte `_ (or just 'vte'): Gnome Terminal, sakura, Terminator, Lilyterm, ROXTerm, evilvte, Termit, Termite, Tilda, tinyterm, lxterminal. - xterm, urxvt, SyncTerm, and EtherTerm. - There are far too many to name, Chose one you like! - The source code of the tty(4), pty(4), and the given "console driver" for any posix-like operating system. If you search thoroughly enough, you will eventually discover a terminal sequence decoder, usually a ``case`` switch that translates ``\x1b[0m`` into a "reset color" action towards the video driver. Though ``tty.c`` is linked here (the only kernel file common among them), it is probably not the most interesting, but it can get you started: - `FreeBSD `_ - `OpenBSD `_ - `Illumos (Solaris) `_ - `Minix `_ - `Linux `_ The TTY driver is a great introduction to Kernel and Systems programming, because familiar components may be discovered and experimented with. It is available on all operating systems (except windows), and because of its critical nature, examples of efficient file I/O, character buffers (often implemented as "ring buffers") and even fine-grained kernel locking can be found. - `Thomas E. Dickey `_ has been maintaining `xterm `_, as well as a primary maintainer of many related packages such as `ncurses `_ for quite a long while. - `termcap & terminfo (O'Reilly Nutshell) `_ by Linda Mui, Tim O'Reilly, and John Strang. - Note that System-V systems, also known as `Unix98 `_ (SunOS, HP-UX, AIX and others) use a `Streams `_ interface. On these systems, the `ioctl(2) `_ interface provides the ``PUSH`` and ``POP`` parameters to communicate with a Streams device driver, which differs significantly from Linux and BSD. Many of these systems provide compatible interfaces for Linux, but they may not always be as complete as the counterpart they emulate, most especially in regards to managing pseudo-terminals. blessed-1.14.2/docs/history.rst0000644000076500000240000003055013065337122017042 0ustar jquaststaff00000000000000Version History =============== 1.14 * bugfix: :meth:`~.Terminal.wrap` misbehaved for text containing newlines, :ghissue:`74`. * bugfix: TypeError when using ``PYTHONOPTIMIZE=2`` environment variable, :ghissue:`84`. * bugfix: define ``blessed.__version__`` value, :ghissue:`92`. * bugfix: detect sequences ``\x1b[0K`` and ``\x1b2K``, :ghissue:`95`. 1.13 * enhancement: :meth:`~.Terminal.split_seqs` introduced, and 4x cost reduction in related sequence-aware functions, :ghissue:`29`. * deprecated: ``blessed.sequences.measure_length`` function superseded by :func:`~.iter_parse` if necessary. * deprecated: warnings about "binary-packed capabilities" are no longer emitted on strange terminal types, making best effort. 1.12 * enhancement: :meth:`~.Terminal.get_location` returns the ``(row, col)`` position of the cursor at the time of call for attached terminal. * enhancement: a keyboard now detected as *stdin* when :paramref:`~.Terminal.__init__.stream` is :obj:`sys.stderr`. 1.11 * enhancement: :meth:`~.Terminal.inkey` can return more quickly for combinations such as ``Alt + Z`` when ``MetaSendsEscape`` is enabled, :ghissue:`30`. * enhancement: :class:`~.FormattingString` may now be nested, such as ``t.red('red', t.underline('rum'))``, :ghissue:`61` 1.10 * workaround: provide ``sc`` and ``rc`` for Terminals of ``kind='ansi'``, repairing :meth:`~.Terminal.location` :ghissue:`44`. * bugfix: length of simple SGR reset sequence ``\x1b[m`` was not correctly determined on all terminal types, :ghissue:`45`. * deprecated: ``_intr_continue`` arguments introduced in 1.8 are now marked deprecated in 1.10: beginning with python 3.5, the default behavior is as though this argument is always True, `PEP-475 `_, blessed does the same. 1.9 * enhancement: :paramref:`~.Terminal.wrap.break_long_words` now supported by :meth:`Terminal.wrap` * Ignore :class:`curses.error` message ``'tparm() returned NULL'``: this occurs on win32 or other platforms using a limited curses implementation, such as PDCurses_, where :func:`curses.tparm` is not implemented, or no terminal capability database is available. * Context manager :meth:`~.keypad` emits sequences that enable "application keys" such as the diagonal keys on the numpad. This is equivalent to :meth:`curses.window.keypad`. * bugfix: translate keypad application keys correctly. * enhancement: no longer depend on the '2to3' tool for python 3 support. * enhancement: allow ``civis`` and ``cnorm`` (*hide_cursor*, *normal_hide*) to work with terminal-type *ansi* by emulating support by proxy. * enhancement: new public attribute: :attr:`~.kind`: the very same as given :paramref:`Terminal.__init__.kind` keyword argument. Or, when not given, determined by and equivalent to the ``TERM`` Environment variable. 1.8 * enhancement: export keyboard-read function as public method ``getch()``, so that it may be overridden by custom terminal implementers. * enhancement: allow :meth:`~.inkey` and :meth:`~.kbhit` to return early when interrupted by signal by passing argument ``_intr_continue=False``. * enhancement: allow ``hpa`` and ``vpa`` (*move_x*, *move_y*) to work on tmux(1) or screen(1) by emulating support by proxy. * enhancement: add :meth:`~.Terminal.rstrip` and :meth:`~.Terminal.lstrip`, strips both sequences and trailing or leading whitespace, respectively. * enhancement: include wcwidth_ library support for :meth:`~.Terminal.length`: the printable width of many kinds of CJK (Chinese, Japanese, Korean) ideographs and various combining characters may now be determined. * enhancement: better support for detecting the length or sequences of externally-generated *ecma-48* codes when using ``xterm`` or ``aixterm``. * bugfix: when :func:`locale.getpreferredencoding` returns empty string or an encoding that is not valid for ``codecs.getincrementaldecoder``, fallback to ASCII and emit a warning. * bugfix: ensure :class:`~.FormattingString` and :class:`~.ParameterizingString` may be pickled. * bugfix: allow `~.inkey` and related to be called without a keyboard. * **change**: ``term.keyboard_fd`` is set ``None`` if ``stream`` or ``sys.stdout`` is not a tty, making ``term.inkey()``, ``term.cbreak()``, ``term.raw()``, no-op. * bugfix: ``\x1bOH`` (KEY_HOME) was incorrectly mapped as KEY_LEFT. 1.7 * Forked github project `erikrose/blessings`_ to `jquast/blessed`_, this project was previously known as **blessings** version 1.6 and prior. * introduced: context manager :meth:`~.cbreak`, which is equivalent to entering terminal state by :func:`tty.setcbreak` and returning on exit, as well as the lesser recommended :meth:`~.raw`, pairing from :func:`tty.setraw`. * introduced: :meth:`~.inkey`, which will return one or more characters received by the keyboard as a unicode sequence, with additional attributes :attr:`~.Keystroke.code` and :attr:`~.Keystroke.name`. This allows application keys (such as the up arrow, or home key) to be detected. Optional value :paramref:`~.inkey.timeout` allows for timed poll. * introduced: :meth:`~.Terminal.center`, :meth:`~.Terminal.rjust`, :meth:`~.Terminal.ljust`, allowing text containing sequences to be aligned to detected horizontal screen width, or by :paramref:`~.Terminal.center.width` specified. * introduced: :meth:`~.wrap` method. Allows text containing sequences to be word-wrapped without breaking mid-sequence, honoring their printable width. * introduced: :meth:`~.Terminal.strip`, strips all sequences *and* whitespace. * introduced: :meth:`~.Terminal.strip_seqs` strip only sequences. * introduced: :meth:`~.Terminal.rstrip` and :meth:`~.Terminal.lstrip` strips both sequences and trailing or leading whitespace, respectively. * bugfix: cannot call :func:`curses.setupterm` more than once per process (from :meth:`Terminal.__init__`): Previously, blessed pretended to support several instances of different Terminal :attr:`~.kind`, but was actually using the :attr:`~.kind` specified by the first instantiation of :class:`~.Terminal`. A warning is now issued. Although this is misbehavior is still allowed, a :class:`warnings.WarningMessage` is now emitted to notify about subsequent terminal misbehavior. * bugfix: resolved issue where :attr:`~.number_of_colors` fails when :attr:`~.does_styling` is ``False``. Resolves issue where piping tests output would fail. * bugfix: warn and set :attr:`~.does_styling` to ``False`` when the given :attr:`~.kind` is not found in the terminal capability database. * bugfix: allow unsupported terminal capabilities to be callable just as supported capabilities, so that the return value of :attr:`~.color`\(n) may be called on terminals without color capabilities. * bugfix: for terminals without underline, such as vt220, ``term.underline('text')`` would emit ``'text' + term.normal``. Now it emits only ``'text'``. * enhancement: some attributes are now properties, raise exceptions when assigned. * enhancement: pypy is now a supported python platform implementation. * enhancement: removed pokemon ``curses.error`` exceptions. * enhancement: do not ignore :class:`curses.error` exceptions, unhandled curses errors are legitimate errors and should be reported as a bug. * enhancement: converted nose tests to pytest, merged travis and tox. * enhancement: pytest fixtures, paired with a new ``@as_subprocess`` decorator are used to test a multitude of terminal types. * enhancement: test accessories ``@as_subprocess`` resolves various issues with different terminal types that previously went untested. * deprecation: python2.5 is no longer supported (as tox does not supported). 1.6 * Add :attr:`~.does_styling`. This takes :attr:`~.force_styling` into account and should replace most uses of :attr:`~.is_a_tty`. * Make :attr:`~.is_a_tty` a read-only property like :attr:`~.does_styling`. Writing to it never would have done anything constructive. * Add :meth:`~.fullscreen`` and :meth:`hidden_cursor` to the auto-generated docs. 1.5.1 * Clean up fabfile, removing the redundant ``test`` command. * Add Travis support. * Make ``python setup.py test`` work without spurious errors on 2.6. * Work around a tox parsing bug in its config file. * Make context managers clean up after themselves even if there's an exception (Vitja Makarov :ghpull:`29`). * Parameterizing a capability no longer crashes when there is no tty (Vitja Makarov :ghpull:`31`) 1.5 * Add syntactic sugar and documentation for ``enter_fullscreen`` and ``exit_fullscreen``. * Add context managers :meth:`~.fullscreen` and :meth:`~.hidden_cursor`. * Now you can force a :class:`~.Terminal` to never to emit styles by passing keyword argument ``force_styling=None``. 1.4 * Add syntactic sugar for cursor visibility control and single-space-movement capabilities. * Endorse the :meth:`~.location` context manager for restoring cursor position after a series of manual movements. * Fix a bug in which :meth:`~.location` that wouldn't do anything when passed zeros. * Allow tests to be run with ``python setup.py test``. 1.3 * Added :attr:`~.number_of_colors`, which tells you how many colors the terminal supports. * Made :attr:`~.color`\(n) and :attr:`~.on_color`\(n) callable to wrap a string, like the named colors can. Also, make them both fall back to the ``setf`` and ``setb`` capabilities (like the named colors do) if the termcap entries for ``setaf`` and ``setab`` are not available. * Allowed :attr:`~.color` to act as an unparametrized string, not just a callable. * Made :attr:`~.height` and :attr:`~.width` examine any passed-in stream before falling back to stdout (This rarely if ever affects actual behavior; it's mostly philosophical). * Made caching simpler and slightly more efficient. * Got rid of a reference cycle between :class:`~.Terminal` and :class:`~.FormattingString`. * Updated docs to reflect that terminal addressing (as in :meth:`~location`) is 0-based. 1.2 * Added support for Python 3! We need 3.2.3 or greater, because the curses library couldn't decide whether to accept strs or bytes before that (http://bugs.python.org/issue10570). * Everything that comes out of the library is now unicode. This lets us support Python 3 without making a mess of the code, and Python 2 should continue to work unless you were testing types (and badly). Please file a bug if this causes trouble for you. * Changed to the MIT License for better world domination. * Added Sphinx docs. 1.1 * Added nicely named attributes for colors. * Introduced compound formatting. * Added wrapper behavior for styling and colors. * Let you force capabilities to be non-empty, even if the output stream is not a terminal. * Added :attr:`~.is_a_tty` to determine whether the output stream is a terminal. * Sugared the remaining interesting string capabilities. * Allow :meth:`~.location` to operate on just an x *or* y coordinate. 1.0 * Extracted Blessed from `nose-progressive`_. .. _`nose-progressive`: http://pypi.python.org/pypi/nose-progressive/ .. _`erikrose/blessings`: https://github.com/erikrose/blessings .. _`jquast/blessed`: https://github.com/jquast/blessed .. _`issue tracker`: https://github.com/jquast/blessed/issues/ .. _curses: https://docs.python.org/library/curses.html .. _couleur: https://pypi.python.org/pypi/couleur .. _colorama: https://pypi.python.org/pypi/colorama .. _wcwidth: https://pypi.python.org/pypi/wcwidth .. _`cbreak(3)`: http://www.openbsd.org/cgi-bin/man.cgi?query=cbreak&apropos=0&sektion=3 .. _`curs_getch(3)`: http://www.openbsd.org/cgi-bin/man.cgi?query=curs_getch&apropos=0&sektion=3 .. _`termios(4)`: http://www.openbsd.org/cgi-bin/man.cgi?query=termios&apropos=0&sektion=4 .. _`terminfo(5)`: http://www.openbsd.org/cgi-bin/man.cgi?query=terminfo&apropos=0&sektion=5 .. _tigetstr: http://www.openbsd.org/cgi-bin/man.cgi?query=tigetstr&sektion=3 .. _tparm: http://www.openbsd.org/cgi-bin/man.cgi?query=tparm&sektion=3 .. _SIGWINCH: https://en.wikipedia.org/wiki/SIGWINCH .. _`API Documentation`: http://blessed.rtfd.org .. _`PDCurses`: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses .. _`ansi`: https://github.com/tehmaze/ansi blessed-1.14.2/docs/index.rst0000644000076500000240000000045712607273732016462 0ustar jquaststaff00000000000000================================= Welcome to Blessed documentation! ================================= Contents: .. toctree:: :maxdepth: 3 :glob: intro overview examples further pains api contributing history ======= Indexes ======= * :ref:`genindex` * :ref:`modindex` blessed-1.14.2/docs/intro.rst0000644000076500000240000001414412636077424016506 0ustar jquaststaff00000000000000.. image:: https://img.shields.io/travis/jquast/blessed/master.svg :alt: Travis Continuous Integration :target: https://travis-ci.org/jquast/blessed/ .. image:: https://img.shields.io/teamcity/https/teamcity-master.pexpect.org/s/Blessed_BuildHead.svg :alt: TeamCity Build status :target: https://teamcity-master.pexpect.org/viewType.html?buildTypeId=Blessed_BuildHead&branch_Blessed=%3Cdefault%3E&tab=buildTypeStatusDiv .. image:: https://coveralls.io/repos/jquast/blessed/badge.svg?branch=master&service=github :alt: Coveralls Code Coverage :target: https://coveralls.io/github/jquast/blessed?branch=master .. image:: https://img.shields.io/pypi/v/blessed.svg :alt: Latest Version :target: https://pypi.python.org/pypi/blessed .. image:: https://img.shields.io/pypi/dm/blessed.svg :alt: Downloads :target: https://pypi.python.org/pypi/blessed .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join Chat :target: https://gitter.im/jquast/blessed Introduction ============ Blessed is a thin, practical wrapper around terminal capabilities in Python. Coding with *Blessed* looks like this... :: from blessed import Terminal t = Terminal() print(t.bold('Hi there!')) print(t.bold_red_on_bright_green('It hurts my eyes!')) with t.location(0, t.height - 1): print(t.center(t.blink('press any key to continue.'))) with t.cbreak(): inp = t.inkey() print('You pressed ' + repr(inp)) Brief Overview -------------- *Blessed* is a more simplified wrapper around curses_, providing : * Styles, color, and maybe a little positioning without necessarily clearing the whole screen first. * Works great with standard Python string formatting. * Provides up-to-the-moment terminal height and width, so you can respond to terminal size changes. * Avoids making a mess if the output gets piped to a non-terminal: outputs to any file-like object such as *StringIO*, files, or pipes. * Uses the `terminfo(5)`_ database so it works with any terminal type and supports any terminal capability: No more C-like calls to tigetstr_ and tparm_. * Keeps a minimum of internal state, so you can feel free to mix and match with calls to curses or whatever other terminal libraries you like. * Provides plenty of context managers to safely express terminal modes, automatically restoring the terminal to a safe state on exit. * Act intelligently when somebody redirects your output to a file, omitting all of the terminal sequences such as styling, colors, or positioning. * Dead-simple keyboard handling: safely decoding unicode input in your system's preferred locale and supports application/arrow keys. * Allows the printable length of strings containing sequences to be determined. Blessed **does not** provide... * Windows command prompt support. A PDCurses_ build of python for windows provides only partial support at this time -- there are plans to merge with the ansi_ module in concert with colorama_ to resolve this. `Patches welcome `_! Before And After ---------------- With the built-in curses_ module, this is how you would typically print some underlined text at the bottom of the screen:: from curses import tigetstr, setupterm, tparm from fcntl import ioctl from os import isatty import struct import sys from termios import TIOCGWINSZ # If we want to tolerate having our output piped to other commands or # files without crashing, we need to do all this branching: if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()): setupterm() sc = tigetstr('sc') cup = tigetstr('cup') rc = tigetstr('rc') underline = tigetstr('smul') normal = tigetstr('sgr0') else: sc = cup = rc = underline = normal = '' # Save cursor position. print(sc) if cup: # tigetnum('lines') doesn't always update promptly, hence this: height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0] # Move cursor to bottom. print(tparm(cup, height - 1, 0)) print('This is {under}underlined{normal}!' .format(under=underline, normal=normal)) # Restore cursor position. print(rc) The same program with *Blessed* is simply:: from blessed import Terminal term = Terminal() with term.location(0, term.height - 1): print('This is' + term.underline('underlined') + '!') Requirements ------------ *Blessed* is tested with Python 2.7, 3.4, and 3.5 on Debian Linux, Mac, and FreeBSD. Further Documentation --------------------- More documentation can be found at http://blessed.readthedocs.org/en/latest/ Bugs, Contributing, Support --------------------------- **Bugs** or suggestions? Visit the `issue tracker`_ and file an issue. We welcome your bug reports and feature suggestions! Would you like to **contribute**? That's awesome! We've written a `guide `_ to help you. Are you stuck and need **support**? Give `stackoverflow`_ a try. If you're still having trouble, we'd like to hear about it! Open an issue in the `issue tracker`_ with a well-formed question. License ------- *Blessed* is under the MIT License. See the LICENSE file. Forked ------ *Blessed* is a fork of `blessings `_. Changes since 1.7 have all been proposed but unaccepted upstream. Furthermore, a project in the node.js language of the `same name `_ is **not** related, or a fork of each other in any way. .. _`issue tracker`: https://github.com/jquast/blessed/issues/ .. _curses: https://docs.python.org/3/library/curses.html .. _tigetstr: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tigetstr.3 .. _tparm: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tparm.3 .. _ansi: https://github.com/tehmaze/ansi .. _colorama: https://pypi.python.org/pypi/colorama .. _PDCurses: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses .. _`terminfo(5)`: http://invisible-island.net/ncurses/man/terminfo.5.html .. _`stackoverflow`: http://stackoverflow.com/ blessed-1.14.2/docs/overview.rst0000644000076500000240000004517613065337122017221 0ustar jquaststaff00000000000000Overview ======== Blessed provides just **one** top-level object: :class:`~.Terminal`. Instantiating a :class:`~.Terminal` figures out whether you're on a terminal at all and, if so, does any necessary setup: >>> term = Terminal() After that, you can proceed to ask it all sorts of things about the terminal, such as its size: >>> term.height, term.width (34, 102) Its color support: >>> term.number_of_colors 256 And use construct strings containing color and styling: >>> term.green_reverse('ALL SYSTEMS GO') '\x1b[32m\x1b[7mALL SYSTEMS GO\x1b[m' Furthermore, the special sequences inserted with application keys (arrow and function keys) are understood and decoded, as well as your locale-specific encoded multibyte input, such as utf-8 characters. Styling and Formatting ---------------------- Lots of handy formatting codes are available as attributes on a :class:`~.Terminal` class instance. For example:: from blessed import Terminal term = Terminal() print('I am ' + term.bold + 'bold' + term.normal + '!') These capabilities (*bold*, *normal*) are translated to their sequences, which when displayed simply change the video attributes. And, when used as a callable, automatically wraps the given string with this sequence, and terminates it with *normal*. The same can be written as:: print('I am' + term.bold('bold') + '!') You may also use the :class:`~.Terminal` instance as an argument for the :meth:`str.format`` method, so that capabilities can be displayed in-line for more complex strings:: print('{t.red_on_yellow}Candy corn{t.normal} for everyone!'.format(t=term)) Capabilities ~~~~~~~~~~~~ The basic capabilities supported by most terminals are: ``bold`` Turn on 'extra bright' mode. ``reverse`` Switch fore and background attributes. ``blink`` Turn on blinking. ``normal`` Reset attributes to default. The less commonly supported capabilities: ``dim`` Enable half-bright mode. ``underline`` Enable underline mode. ``no_underline`` Exit underline mode. ``italic`` Enable italicized text. ``no_italic`` Exit italics. ``shadow`` Enable shadow text mode (rare). ``no_shadow`` Exit shadow text mode. ``standout`` Enable standout mode (often, an alias for ``reverse``). ``no_standout`` Exit standout mode. ``subscript`` Enable subscript mode. ``no_subscript`` Exit subscript mode. ``superscript`` Enable superscript mode. ``no_superscript`` Exit superscript mode. ``flash`` Visual bell, flashes the screen. Note that, while the inverse of *underline* is *no_underline*, the only way to turn off *bold* or *reverse* is *normal*, which also cancels any custom colors. Many of these are aliases, their true capability names (such as 'smul' for 'begin underline mode') may still be used. Any capability in the `terminfo(5)`_ manual, under column **Cap-name**, may be used as an attribute of a :class:`~.Terminal` instance. If it is not a supported capability, or a non-tty is used as an output stream, an empty string is returned. Colors ~~~~~~ Color terminals are capable of at least 8 basic colors. * ``black`` * ``red`` * ``green`` * ``yellow`` * ``blue`` * ``magenta`` * ``cyan`` * ``white`` The same colors, prefixed with *bright_* (synonymous with *bold_*), such as *bright_blue*, provides 16 colors in total. Prefixed with *on_*, the given color is used as the background color. Some terminals also provide an additional 8 high-intensity versions using *on_bright*, some example compound formats:: from blessed import Terminal term = Terminal() print(term.on_bright_blue('Blue skies!')) print(term.bright_red_on_bright_yellow('Pepperoni Pizza!')) You may also specify the :meth:`~.Terminal.color` index by number, which should be within the bounds of value returned by :attr:`~.Terminal.number_of_colors`:: from blessed import Terminal term = Terminal() for idx in range(term.number_of_colors): print(term.color(idx)('Color {0}'.format(idx))) You can check whether the terminal definition used supports colors, and how many, using the :attr:`~.Terminal.number_of_colors` property, which returns any of *0*, *8* or *256* for terminal types such as *vt220*, *ansi*, and *xterm-256color*, respectively. Colorless Terminals ~~~~~~~~~~~~~~~~~~~ If the terminal defined by the Environment variable **TERM** does not support colors, these simply return empty strings. When used as a callable, the string passed as an argument is returned as-is. Most sequences emitted to a terminal that does not support them are usually harmless and have no effect. Colorless terminals (such as the amber or green monochrome *vt220*) do not support colors but do support reverse video. For this reason, it may be desirable in some applications to simply select a foreground color, followed by reverse video to achieve the desired background color effect:: from blessed import Terminal term = Terminal() print(term.green_reverse('some terminals standout more than others')) Which appears as *black on green* on color terminals, but *black text on amber or green* on monochrome terminals. Whereas the more declarative formatter *black_on_green* would remain colorless. .. note:: On most color terminals, *bright_black* is not invisible -- it is actually a very dark shade of gray! Compound Formatting ~~~~~~~~~~~~~~~~~~~ If you want to do lots of crazy formatting all at once, you can just mash it all together:: from blessed import Terminal term = Terminal() print(term.bold_underline_green_on_yellow('Woo')) I'd be remiss if I didn't credit couleur_, where I probably got the idea for all this mashing. This compound notation comes in handy if you want to allow users to customize formatting, just allow compound formatters, like *bold_green*, as a command line argument or configuration item such as in the :ref:`tprint.py` demonstration script. Moving The Cursor ----------------- When you want to move the cursor, you have a few choices: - ``location(x=None, y=None)`` context manager. - ``move(row, col)`` capability. - ``move_y(row)`` capability. - ``move_x(col)`` capability. .. warning:: The :meth:`~.Terminal.location` method receives arguments in positional order *(x, y)*, whereas the ``move()`` capability receives arguments in order *(y, x)*. Please use keyword arguments as a later release may correct the argument order of :meth:`~.Terminal.location`. Finding The Cursor ------------------ We can determine the cursor's current position at anytime using :meth:`~.get_location`, returning the current (y, x) location. This uses a kind of "answer back" sequence that your terminal emulator responds to. If the terminal may not respond, the :paramref:`~.get_location.timeout` keyword argument can be specified to return coordinates (-1, -1) after a blocking timeout:: from blessed import Terminal term = Terminal() row, col = term.get_location(timeout=5) if row < term.height: print(term.move_y(term.height) + 'Get down there!') Moving Temporarily ~~~~~~~~~~~~~~~~~~ A context manager, :meth:`~.Terminal.location` is provided to move the cursor to an *(x, y)* screen position and restore the previous position upon exit:: from blessed import Terminal term = Terminal() with term.location(0, term.height - 1): print('Here is the bottom.') print('This is back where I came from.') Parameters to :meth:`~.Terminal.location` are the **optional** *x* and/or *y* keyword arguments:: with term.location(y=10): print('We changed just the row.') When omitted, it saves the cursor position and restore it upon exit:: with term.location(): print(term.move(1, 1) + 'Hi') print(term.move(9, 9) + 'Mom') .. note:: calls to :meth:`~.Terminal.location` may not be nested. Moving Permanently ~~~~~~~~~~~~~~~~~~ If you just want to move and aren't worried about returning, do something like this:: from blessed import Terminal term = Terminal() print(term.move(10, 1) + 'Hi, mom!') ``move(y, x)`` Position cursor at given **y**, **x**. ``move_x(x)`` Position cursor at column **x**. ``move_y(y)`` Position cursor at row **y**. One-Notch Movement ~~~~~~~~~~~~~~~~~~ Finally, there are some parameterless movement capabilities that move the cursor one character in various directions: * ``move_left`` * ``move_right`` * ``move_up`` * ``move_down`` .. note:: *move_down* is often valued as *\\n*, which additionally returns the carriage to column 0, depending on your terminal emulator, and may also destructively destroy any characters at the given position to the end of margin. Height And Width ---------------- Use the :attr:`~.Terminal.height` and :attr:`~.Terminal.width` properties to determine the size of the window:: from blessed import Terminal term = Terminal() height, width = term.height, term.width with term.location(x=term.width / 3, y=term.height / 3): print('1/3 ways in!') These values are always current. To detect when the size of the window changes, you may author a callback for SIGWINCH_ signals:: import signal from blessed import Terminal term = Terminal() def on_resize(sig, action): print('height={t.height}, width={t.width}'.format(t=term)) signal.signal(signal.SIGWINCH, on_resize) # wait for keypress term.inkey() Clearing The Screen ------------------- Blessed provides syntactic sugar over some screen-clearing capabilities: ``clear`` Clear the whole screen. ``clear_eol`` Clear to the end of the line. ``clear_bol`` Clear backward to the beginning of the line. ``clear_eos`` Clear to the end of screen. Full-Screen Mode ---------------- If you've ever noticed a program, such as an editor, restores the previous screen (such as your shell prompt) after exiting, you're seeing the *enter_fullscreen* and *exit_fullscreen* attributes in effect. ``enter_fullscreen`` Switch to alternate screen, previous screen is stored by terminal driver. ``exit_fullscreen`` Switch back to standard screen, restoring the same terminal state. There's also a context manager you can use as a shortcut:: from __future__ import division from blessed import Terminal term = Terminal() with term.fullscreen(): print(term.move_y(term.height // 2) + term.center('press any key').rstrip()) term.inkey() Pipe Savvy ---------- If your program isn't attached to a terminal, such as piped to a program like *less(1)* or redirected to a file, all the capability attributes on :class:`~.Terminal` will return empty strings. You'll get a nice-looking file without any formatting codes gumming up the works. If you want to override this, such as when piping output to *less -r*, pass argument value *True* to the :paramref:`~.Terminal.force_styling` parameter. In any case, there is a :attr:`~.Terminal.does_styling` attribute that lets you see whether the terminal attached to the output stream is capable of formatting. If it is *False*, you may refrain from drawing progress bars and other frippery and just stick to content:: from blessed import Terminal term = Terminal() if term.does_styling: with term.location(x=0, y=term.height - 1): print('Progress: [=======> ]') print(term.bold("60%")) Sequence Awareness ------------------ Blessed may measure the printable width of strings containing sequences, providing :meth:`~.Terminal.center`, :meth:`~.Terminal.ljust`, and :meth:`~.Terminal.rjust` methods, using the terminal screen's width as the default *width* value:: from __future__ import division from blessed import Terminal term = Terminal() with term.location(y=term.height // 2): print(term.center(term.bold('bold and centered'))) Any string containing sequences may have its printable length measured using the :meth:`~.Terminal.length` method. Additionally, a sequence-aware version of :func:`textwrap.wrap` is supplied as class as method :meth:`~.Terminal.wrap` that is also sequence-aware, so now you may word-wrap strings containing sequences. The following example displays a poem word-wrapped to 25 columns:: from blessed import Terminal term = Terminal() poem = (term.bold_cyan('Plan difficult tasks'), term.cyan('through the simplest tasks'), term.bold_cyan('Achieve large tasks'), term.cyan('through the smallest tasks')) for line in poem: print('\n'.join(term.wrap(line, width=25, subsequent_indent=' ' * 4))) Sometimes it is necessary to make sense of sequences, and to distinguish them from plain text. The :meth:`~.Terminal.split_seqs` method can allow us to iterate over a terminal string by its characters or sequences:: from blessed import Terminal term = Terminal() phrase = term.bold('bbq') print(term.split_seqs(phrase)) Will display something like, ``['\x1b[1m', 'b', 'b', 'q', '\x1b(B', '\x1b[m']`` Similarly, the method :meth:`~.Terminal.strip_seqs` may be used on a string to remove all occurrences of terminal sequences:: from blessed import Terminal term = Terminal() phrase = term.bold_black('coffee') print(repr(term.strip_seqs(phrase))) Will display only ``'coffee'`` Keyboard Input -------------- The built-in python function :func:`raw_input` does not return a value until the return key is pressed, and is not suitable for detecting each individual keypress, much less arrow or function keys. Furthermore, when calling :func:`os.read` on input stream, only bytes are received, which must be decoded to unicode using the locale-preferred encoding. Finally, multiple bytes may be emitted which must be paired with some verb like ``KEY_LEFT``: blessed handles all of these special cases for you! cbreak ~~~~~~ The context manager :meth:`~.Terminal.cbreak` can be used to enter *key-at-a-time* mode: Any keypress by the user is immediately consumed by read calls:: from blessed import Terminal import sys term = Terminal() with term.cbreak(): # block until any single key is pressed. sys.stdin.read(1) The mode entered using :meth:`~.Terminal.cbreak` is called `cbreak(3)`_ in curses: The cbreak routine disables line buffering and erase/kill character-processing (interrupt and flow control characters are unaffected), making characters typed by the user immediately available to the program. raw ~~~ :meth:`~.Terminal.raw` is similar to cbreak, except that control-C and other keystrokes are "ignored", and received as their keystroke value rather than interrupting the program with signals. Output processing is also disabled, you must print phrases with carriage return after newline. Without raw mode:: print("hello, world.") With raw mode:: print("hello, world.", endl="\r\n") inkey ~~~~~ The method :meth:`~.Terminal.inkey` combined with cbreak_ completes the circle of providing key-at-a-time keyboard input with multibyte encoding and awareness of application keys. :meth:`~.Terminal.inkey` resolves many issues with terminal input by returning a unicode-derived :class:`~.Keystroke` instance. Its return value may be printed, joined with, or compared like any other unicode strings, it also provides the special attributes :attr:`~.Keystroke.is_sequence`, :attr:`~.Keystroke.code`, and :attr:`~.Keystroke.name`:: from blessed import Terminal term = Terminal() print("press 'q' to quit.") with term.cbreak(): val = '' while val.lower() != 'q': val = term.inkey(timeout=5) if not val: # timeout print("It sure is quiet in here ...") elif val.is_sequence: print("got sequence: {0}.".format((str(val), val.name, val.code))) elif val: print("got {0}.".format(val)) print('bye!') Its output might appear as:: got sequence: ('\x1b[A', 'KEY_UP', 259). got sequence: ('\x1b[1;2A', 'KEY_SUP', 337). got sequence: ('\x1b[17~', 'KEY_F6', 270). got sequence: ('\x1b', 'KEY_ESCAPE', 361). got sequence: ('\n', 'KEY_ENTER', 343). got /. It sure is quiet in here ... got sequence: ('\x1bOP', 'KEY_F1', 265). It sure is quiet in here ... got q. bye! A :paramref:`~.Terminal.inkey.timeout` value of *None* (default) will block forever until a keypress is received. Any other value specifies the length of time to poll for input: if no input is received after the given time has elapsed, an empty string is returned. A :paramref:`~.Terminal.inkey.timeout` value of *0* is non-blocking. keyboard codes ~~~~~~~~~~~~~~ When the :attr:`~.Keystroke.is_sequence` property tests *True*, the value is a special application key of the keyboard. The :attr:`~.Keystroke.code` attribute may then be compared with attributes of :class:`~.Terminal`, which are duplicated from those found in `curs_getch(3)`_, or those `constants `_ in :mod:`curses` beginning with phrase *KEY_*. Some of these mnemonics are shorthand or predate modern PC terms and are difficult to recall. The following helpful aliases are provided instead: =================== ============= ==================== blessed curses note =================== ============= ==================== ``KEY_DELETE`` ``KEY_DC`` chr(127). ``KEY_TAB`` chr(9) ``KEY_INSERT`` ``KEY_IC`` ``KEY_PGUP`` ``KEY_PPAGE`` ``KEY_PGDOWN`` ``KEY_NPAGE`` ``KEY_ESCAPE`` ``KEY_EXIT`` ``KEY_SUP`` ``KEY_SR`` (shift + up) ``KEY_SDOWN`` ``KEY_SF`` (shift + down) ``KEY_DOWN_LEFT`` ``KEY_C1`` (keypad lower-left) ``KEY_UP_RIGHT`` ``KEY_A1`` (keypad upper-left) ``KEY_DOWN_RIGHT`` ``KEY_C3`` (keypad lower-left) ``KEY_UP_RIGHT`` ``KEY_A3`` (keypad lower-right) ``KEY_CENTER`` ``KEY_B2`` (keypad center) ``KEY_BEGIN`` ``KEY_BEG`` =================== ============= ==================== The :attr:`~.Keystroke.name` property will prefer these aliases over the built-in :mod:`curses` names. The following are **not** available in the :mod:`curses` module, but are provided for keypad support, especially where the :meth:`~.Terminal.keypad` context manager is used with numlock on: * ``KEY_KP_MULTIPLY`` * ``KEY_KP_ADD`` * ``KEY_KP_SEPARATOR`` * ``KEY_KP_SUBTRACT`` * ``KEY_KP_DECIMAL`` * ``KEY_KP_DIVIDE`` * ``KEY_KP_0`` through ``KEY_KP_9`` .. _couleur: https://pypi.python.org/pypi/couleur .. _`cbreak(3)`: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/cbreak.3 .. _`raw(3)`: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/raw.3 .. _`curs_getch(3)`: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/curs_getch.3 .. _`terminfo(5)`: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man4/termios.3 .. _SIGWINCH: https://en.wikipedia.org/wiki/SIGWINCH blessed-1.14.2/docs/pains.rst0000644000076500000240000003700312607273732016462 0ustar jquaststaff00000000000000Growing Pains ============= When making terminal applications, there are a surprisingly number of portability issues and edge cases. Although Blessed provides an abstraction for the full curses capability database, it is not sufficient to secure you from several considerations shared here. 8 and 16 colors --------------- Where 8 and 16 colors are used, they should be assumed to be the `CGA Color Palette`_. Though there is no terminal standard that proclaims that the CGA colors are used, their values are the best approximations across all common hardware terminals and terminal emulators. A recent phenomenon of users is to customize their base 16 colors to provide (often, more "washed out") color schemes. Furthermore, we are only recently getting LCD displays of colorspaces that achieve close approximation to the original video terminals. Some find these values uncomfortably intense: in their original CRT form, their contrast and brightness was lowered by hardware dials, whereas today's LCD's typically display well only near full intensity. Though we may not *detect* the colorspace of the remote terminal, **we can**: - Trust that a close approximation of the `CGA Color Palette`_ for the base 16 colors will be displayed for **most** users. - Trust that users who have made the choice to adjust their palette have made the choice to do so, and are able to re-adjust such palettes as necessary to accommodate different programs (such as through the use of "Themes"). .. note:: It has become popular to use dynamic system-wide color palette adjustments in software such as `f.lux`_, which adjust the system-wide "Color Profile" of the entire graphics display depending on the time of day. One might assume that ``term.blue("text")`` may be **completely** invisible to such users during the night! Where is brown, purple, or grey? -------------------------------- There are **only 8 color names** on a 16-color terminal: The second set of eight colors are "high intensity" versions of the first in direct series. The colors *brown*, *purple*, and *grey* are not named in the first series, though they are available: - **brown**: *yellow is brown*, only high-intensity yellow (``bright_yellow``) is yellow! - **purple**: *magenta is purple*. In earlier, 4-bit color spaces, there were only black, cyan, magenta, and white of low and high intensity, such as found on common home computers like the `ZX Spectrum`_. Additional "colors" were only possible through dithering. The color names cyan and magenta on later graphics adapters are carried over from its predecessors. Although the color cyan remained true in RGB value on 16-color to its predecessor, magenta shifted farther towards blue from red becoming purple (as true red was introduced as one of the new base 8 colors). - **grey**: there are actually **three shades of grey** (or American spelling, 'gray'), though the color attribute named 'grey' does not exist! In ascending order of intensity, the shades of grey are: - ``bold_black``: in lieu of the uselessness of an "intense black", this is color is instead mapped to "dark grey". - ``white``: white is actually mild compared to the true color 'white': this is more officially mapped to "common grey", and is often the default foreground color. - ``bright_white``: is pure white (``#ffffff``). white-on-black ~~~~~~~~~~~~~~ The default foreground and background should be assumed as *white-on-black*. For quite some time, the families of terminals produced by DEC, IBM, and Tektronix dominated the computing world with the default color scheme of *green-on-black* and less commonly *amber-on-black* monochrome displays: The inverse was a non-default configuration. The IBM 3270 clients exclusively used *green-on-black* in both hardware and software emulators, and is likely a driving factor of the default *white-on-black* appearance of the first IBM Personal Computer. The less common *black-on-white* "ink paper" style of emulators is a valid concern for those designing terminal interfaces. The color scheme of *black-on-white* directly conflicts with the intention of `bold is bright`_, where ``term.bright_red('ATTENTION!')`` may become difficult to read, as it appears as *pink on white*! History of ink-paper inspired black-on-white ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Early home computers with color video adapters, such as the Commodore 64 provided *white-on-blue* as their basic video terminal configuration. One can only assume such appearances were provided to demonstrate their color capabilities over competitors (such as the Apple ][). More common, X11's xterm and the software HyperTerm bundle with MS Windows provided an "ink on paper" *black-on-white* appearance as their default configuration. Two popular emulators continue to supply *black-on-white* by default to this day: Xorg's xterm and Apple's Terminal.app. .. note:: Windows no longer supplies a terminal emulator: the "command prompt" as we know it now uses the MSVCRT API routines to interact and does not make use of terminal sequences, even ignoring those sequences that MS-DOS family of systems previously interpreted through the ANSI.SYS driver, though it continues to default to *white-on-black*. Bold is bright -------------- **Where Bold is used, it should be assumed to be *Bright***. Due to the influence of early graphics adapters providing a set of 8 "low-intensity" and 8 "high intensity" versions of the first, the term "bold" for terminals sequences is synonymous with "high intensity" in almost all circumstances. History of bold as "wide stroke" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In typography, the true translation of "bold" is that a font should be displayed *with emphasis*. In classical terms, this would be achieved by pen be re-writing over the same letters. On a teletype or printer, this was similarly achieved by writing a character, backspacing, then repeating the same character in a form called **overstriking**. To bold a character, ``C``, one would emit the sequence ``C^HC`` where ``^H`` is backspace (0x08). To underline ``C``, one would would emit ``C^H_``. **Video terminals do not support overstriking**. Though the mdoc format for manual pages continue to emit overstriking sequences for bold and underline, translators such as mandoc will instead emit an appropriate terminal sequence. Many characters previously displayable by combining using overstriking of ASCII characters on teletypes, such as: ±, ≠, or ⩝ were delegated to a `code page`_ or lost entirely until the introduction of multibyte encodings. Much like the "ink paper" introduction in windowing systems for terminal emulators, "wide stroke" bold was introduced only much later when combined with operating systems that provided font routines such as TrueType. Enforcing white-on-black ~~~~~~~~~~~~~~~~~~~~~~~~ In conclusion, *white-on-black* should be considered the default. If there is a need to **enforce** *white-on-black* for terminal clients suspected to be defaulted as *black-on-white*, one would want to trust that a combination of ``term.home + term.white_on_black + term.clear`` should repaint the entire emulator's window with the desired effect. However, this cannot be trusted to **all** terminal emulators to perform correctly! Depending on your audience, you may instead ensure that the entire screen (including whitespace) is painted using the ``on_black`` mnemonic. Beware of customized color schemes ---------------------------------- A recent phenomenon is for users to customize these first 16 colors of their preferred emulator to colors of their own liking. Though this has always been possible with ``~/.XResources``, the introduction of PuTTy and iTerm2 to interactively adjustment these colors have made this much more common. This may cause your audience to see your intended interface in a wildly different form. Your intended presentation may appear mildly unreadable. Users are certainly free to customize their colors however they like, but it should be known that displaying ``term.black_on_red("DANGER!")`` may appear as "grey on pastel red" to your audience, reducing the intended effect of intensity. 256 colors can avoid customization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first instinct of a user who aliases ls(1) to ``ls -G`` or ``colorls``, when faced with the particularly low intensity of the default ``blue`` attribute is **to adjust their terminal emulator's color scheme of the base 16 colors**. This is not necessary: the environment variable ``LSCOLORS`` may be redefined to map an alternative color for blue, or to use ``bright_blue`` in its place. Furthermore, all common terminal text editors such as emacs or vim may be configured with "colorschemes" to make use of the 256-color support found in most modern emulators. Many readable shades of blue are available, and many programs that emit such colors can be configured to emit a higher or lower intensity variant from the full 256 color space through program configuration. Monochrome and reverse ---------------------- Note that ``reverse`` takes the current foreground and background colors and reverses them. In contrast, the compound formatter ``black_on_red`` would fail to set the background *or* foreground color on a monochrome display, resulting in the same stylization as ``normal`` -- it would not appear any different! If your userbase consists of monochrome terminals, you may wish to provide "lightbars" and other such effects using the compound formatter ``red_reverse``. In the literal sense of "set foreground color to red, then swap foreground and background", this produces a similar effect on **both** color and monochrome displays. For text, very few ``{color}_on_{color}`` formatters are visible with the base 16 colors, so you should generally wish for ``black_on_{color}`` anyway. By using ``{color}_reverse`` you may be portable with monochrome displays as well. Multibyte Encodings and Code pages ---------------------------------- A terminal that supports both multibyte encodings (UTF-8) and legacy 8-bit code pages (ISO 2022) may instruct the terminal to switch between both modes using the following sequences: - ``\x1b%G`` activates UTF-8 with an unspecified implementation level from ISO 2022 in a way that allows to go back to ISO 2022 again. - ``\x1b%@`` goes back from UTF-8 to ISO 2022 in case UTF-8 had been entered via ``\x1b%G``. - ``\x1b%/G`` switches to UTF-8 Level 1 with no return. - ``\x1b%/H`` switches to UTF-8 Level 2 with no return. - ``\x1b%/I`` switches to UTF-8 Level 3 with no return. When a terminal is in ISO 2022 mode, you may use a sequence to request a terminal to change its `code page`_. It begins by ``\x1b(``, followed by an ASCII character representing a code page selection. For example ``\x1b(U`` on the legacy VGA Linux console switches to the `IBM CP437`_ `code page`_, allowing North American MS-DOS artwork to be displayed in its natural 8-bit byte encoding. A list of standard codes and the expected code page may be found on Thomas E. Dickey's xterm control sequences section on sequences following the `Control-Sequence-Inducer`_. For more information, see `What are the issues related to UTF-8 terminal emulators? `_ by `Markus Kuhn `_ of the University of Cambridge. One can be assured that the connecting client is capable of representing UTF-8 and other multibyte character encodings by the Environment variable ``LANG``. If this is not possible or reliable, there is an intrusive detection method demonstrated in the example program :ref:`detect-multibyte.py`. Alt or meta sends Escape ------------------------ Programs using GNU readline such as bash continue to provide default mappings such as *ALT+u* to uppercase the word after cursor. This is achieved by the configuration option altSendsEscape or `metaSendsEscape `_ The default for most terminals, however, is that the meta key is bound by the operating system (such as *META + F* for find), and that *ALT* is used for inserting international keys (where the combination *ALT+u, a* is used to insert the character ``ä``). It is therefore a recommendation to **avoid alt or meta keys entirely** in applications, and instead prefer the ctrl-key combinations, so as to avoid instructing your users to configure their terminal emulators to communicate such sequences. If you wish to allow them optionally (such as through readline), the ability to detect alt or meta key combinations is achieved by prefacing the combining character with escape, so that *ALT+z* becomes *Escape + z* (or, in raw form ``\x1bz``). Blessings currently provides no further assistance in detecting these key combinations. Backspace sends delete ---------------------- Typically, backspace is ``^H`` (8, or 0x08) and delete is ^? (127, or 0x7f). On some systems however, the key for backspace is actually labeled and transmitted as "delete", though its function in the operating system behaves just as backspace. It is highly recommend to accept **both** ``KEY_DELETE`` and ``KEY_BACKSPACE`` as having the same meaning except when implementing full screen editors, and provide a choice to enable the delete mode by configuration. The misnomer of ANSI -------------------- When people say 'ANSI Sequence', they are discussing: - Standard `ECMA-48`_: Control Functions for Coded Character Sets - `ANSI X3.64 `_ from 1981, when the `American National Standards Institute `_ adopted the `ECMA-48`_ as standard, which was later withdrawn in 1997 (so in this sense it is *not* an ANSI standard). - The `ANSI.SYS`_ driver provided in MS-DOS and clones. The popularity of the IBM Personal Computer and MS-DOS of the era, and its ability to display colored text further populated the idea that such text "is ANSI". - The various code pages used in MS-DOS Personal Computers, providing "block art" characters in the 8th bit (int 127-255), paired with `ECMA-48`_ sequences supported by the MS-DOS `ANSI.SYS`_ driver to create artwork, known as `ANSI art `_. - The ANSI terminal database entry and its many descendants in the `terminfo database `_. This is mostly due to terminals compatible with SCO UNIX, which was the successor of Microsoft's Xenix, which brought some semblance of the Microsoft DOS `ANSI.SYS`_ driver capabilities. - `Select Graphics Rendition (SGR) `_ on vt100 clones, which include many of the common sequences in `ECMA-48`_. - Any sequence started by the `Control-Sequence-Inducer`_ is often mistakenly termed as an "ANSI Escape Sequence" though not appearing in `ECMA-48`_ or interpreted by the `ANSI.SYS`_ driver. The adjoining phrase "Escape Sequence" is so termed because it follows the ASCII character for the escape key (ESC, ``\x1b``). .. _code page: https://en.wikipedia.org/wiki/Code_page .. _IBM CP437: https://en.wikipedia.org/wiki/Code_page_437 .. _CGA Color Palette: https://en.wikipedia.org/wiki/Color_Graphics_Adapter#With_an_RGBI_monitor .. _f.lux: https://justgetflux.com/ .. _ZX Spectrum: https://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes#ZX_Spectrum .. _Control-Sequence-Inducer: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Controls-beginning-with-ESC .. _ANSI.SYS: http://www.kegel.com/nansi/ .. _ECMA-48: http://www.ecma-international.org/publications/standards/Ecma-048.htm blessed-1.14.2/LICENSE0000644000076500000240000000207312607273731014671 0ustar jquaststaff00000000000000Copyright (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. blessed-1.14.2/MANIFEST.in0000644000076500000240000000021112636077424015415 0ustar jquaststaff00000000000000include docs/*.rst include LICENSE include version.json include *.txt include .coveragerc include tox.ini include blessed/tests/wall.ans blessed-1.14.2/PKG-INFO0000644000076500000240000002125513066002636014756 0ustar jquaststaff00000000000000Metadata-Version: 1.1 Name: blessed Version: 1.14.2 Summary: A thin, practical wrapper around terminal styling, screen positioning, and keyboard input. Home-page: https://github.com/jquast/blessed Author: Jeff Quast, Erik Rose Author-email: contact@jeffquast.com License: MIT Description: .. image:: https://img.shields.io/travis/jquast/blessed/master.svg :alt: Travis Continuous Integration :target: https://travis-ci.org/jquast/blessed/ .. image:: https://img.shields.io/teamcity/https/teamcity-master.pexpect.org/s/Blessed_BuildHead.svg :alt: TeamCity Build status :target: https://teamcity-master.pexpect.org/viewType.html?buildTypeId=Blessed_BuildHead&branch_Blessed=%3Cdefault%3E&tab=buildTypeStatusDiv .. image:: https://coveralls.io/repos/jquast/blessed/badge.svg?branch=master&service=github :alt: Coveralls Code Coverage :target: https://coveralls.io/github/jquast/blessed?branch=master .. image:: https://img.shields.io/pypi/v/blessed.svg :alt: Latest Version :target: https://pypi.python.org/pypi/blessed .. image:: https://img.shields.io/pypi/dm/blessed.svg :alt: Downloads :target: https://pypi.python.org/pypi/blessed .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join Chat :target: https://gitter.im/jquast/blessed Introduction ============ Blessed is a thin, practical wrapper around terminal capabilities in Python. Coding with *Blessed* looks like this... :: from blessed import Terminal t = Terminal() print(t.bold('Hi there!')) print(t.bold_red_on_bright_green('It hurts my eyes!')) with t.location(0, t.height - 1): print(t.center(t.blink('press any key to continue.'))) with t.cbreak(): inp = t.inkey() print('You pressed ' + repr(inp)) Brief Overview -------------- *Blessed* is a more simplified wrapper around curses_, providing : * Styles, color, and maybe a little positioning without necessarily clearing the whole screen first. * Works great with standard Python string formatting. * Provides up-to-the-moment terminal height and width, so you can respond to terminal size changes. * Avoids making a mess if the output gets piped to a non-terminal: outputs to any file-like object such as *StringIO*, files, or pipes. * Uses the `terminfo(5)`_ database so it works with any terminal type and supports any terminal capability: No more C-like calls to tigetstr_ and tparm_. * Keeps a minimum of internal state, so you can feel free to mix and match with calls to curses or whatever other terminal libraries you like. * Provides plenty of context managers to safely express terminal modes, automatically restoring the terminal to a safe state on exit. * Act intelligently when somebody redirects your output to a file, omitting all of the terminal sequences such as styling, colors, or positioning. * Dead-simple keyboard handling: safely decoding unicode input in your system's preferred locale and supports application/arrow keys. * Allows the printable length of strings containing sequences to be determined. Blessed **does not** provide... * Windows command prompt support. A PDCurses_ build of python for windows provides only partial support at this time -- there are plans to merge with the ansi_ module in concert with colorama_ to resolve this. `Patches welcome `_! Before And After ---------------- With the built-in curses_ module, this is how you would typically print some underlined text at the bottom of the screen:: from curses import tigetstr, setupterm, tparm from fcntl import ioctl from os import isatty import struct import sys from termios import TIOCGWINSZ # If we want to tolerate having our output piped to other commands or # files without crashing, we need to do all this branching: if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()): setupterm() sc = tigetstr('sc') cup = tigetstr('cup') rc = tigetstr('rc') underline = tigetstr('smul') normal = tigetstr('sgr0') else: sc = cup = rc = underline = normal = '' # Save cursor position. print(sc) if cup: # tigetnum('lines') doesn't always update promptly, hence this: height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0] # Move cursor to bottom. print(tparm(cup, height - 1, 0)) print('This is {under}underlined{normal}!' .format(under=underline, normal=normal)) # Restore cursor position. print(rc) The same program with *Blessed* is simply:: from blessed import Terminal term = Terminal() with term.location(0, term.height - 1): print('This is' + term.underline('underlined') + '!') Requirements ------------ *Blessed* is tested with Python 2.7, 3.4, and 3.5 on Debian Linux, Mac, and FreeBSD. Further Documentation --------------------- More documentation can be found at http://blessed.readthedocs.org/en/latest/ Bugs, Contributing, Support --------------------------- **Bugs** or suggestions? Visit the `issue tracker`_ and file an issue. We welcome your bug reports and feature suggestions! Would you like to **contribute**? That's awesome! We've written a `guide `_ to help you. Are you stuck and need **support**? Give `stackoverflow`_ a try. If you're still having trouble, we'd like to hear about it! Open an issue in the `issue tracker`_ with a well-formed question. License ------- *Blessed* is under the MIT License. See the LICENSE file. Forked ------ *Blessed* is a fork of `blessings `_. Changes since 1.7 have all been proposed but unaccepted upstream. Furthermore, a project in the node.js language of the `same name `_ is **not** related, or a fork of each other in any way. .. _`issue tracker`: https://github.com/jquast/blessed/issues/ .. _curses: https://docs.python.org/3/library/curses.html .. _tigetstr: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tigetstr.3 .. _tparm: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tparm.3 .. _ansi: https://github.com/tehmaze/ansi .. _colorama: https://pypi.python.org/pypi/colorama .. _PDCurses: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses .. _`terminfo(5)`: http://invisible-island.net/ncurses/man/terminfo.5.html .. _`stackoverflow`: http://stackoverflow.com/ Keywords: terminal,sequences,tty,curses,ncurses,formatting,style,color,console,keyboard,ansi,xterm Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Console :: Curses Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: Terminals blessed-1.14.2/README.rst0000644000076500000240000001414412636077424015360 0ustar jquaststaff00000000000000.. image:: https://img.shields.io/travis/jquast/blessed/master.svg :alt: Travis Continuous Integration :target: https://travis-ci.org/jquast/blessed/ .. image:: https://img.shields.io/teamcity/https/teamcity-master.pexpect.org/s/Blessed_BuildHead.svg :alt: TeamCity Build status :target: https://teamcity-master.pexpect.org/viewType.html?buildTypeId=Blessed_BuildHead&branch_Blessed=%3Cdefault%3E&tab=buildTypeStatusDiv .. image:: https://coveralls.io/repos/jquast/blessed/badge.svg?branch=master&service=github :alt: Coveralls Code Coverage :target: https://coveralls.io/github/jquast/blessed?branch=master .. image:: https://img.shields.io/pypi/v/blessed.svg :alt: Latest Version :target: https://pypi.python.org/pypi/blessed .. image:: https://img.shields.io/pypi/dm/blessed.svg :alt: Downloads :target: https://pypi.python.org/pypi/blessed .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join Chat :target: https://gitter.im/jquast/blessed Introduction ============ Blessed is a thin, practical wrapper around terminal capabilities in Python. Coding with *Blessed* looks like this... :: from blessed import Terminal t = Terminal() print(t.bold('Hi there!')) print(t.bold_red_on_bright_green('It hurts my eyes!')) with t.location(0, t.height - 1): print(t.center(t.blink('press any key to continue.'))) with t.cbreak(): inp = t.inkey() print('You pressed ' + repr(inp)) Brief Overview -------------- *Blessed* is a more simplified wrapper around curses_, providing : * Styles, color, and maybe a little positioning without necessarily clearing the whole screen first. * Works great with standard Python string formatting. * Provides up-to-the-moment terminal height and width, so you can respond to terminal size changes. * Avoids making a mess if the output gets piped to a non-terminal: outputs to any file-like object such as *StringIO*, files, or pipes. * Uses the `terminfo(5)`_ database so it works with any terminal type and supports any terminal capability: No more C-like calls to tigetstr_ and tparm_. * Keeps a minimum of internal state, so you can feel free to mix and match with calls to curses or whatever other terminal libraries you like. * Provides plenty of context managers to safely express terminal modes, automatically restoring the terminal to a safe state on exit. * Act intelligently when somebody redirects your output to a file, omitting all of the terminal sequences such as styling, colors, or positioning. * Dead-simple keyboard handling: safely decoding unicode input in your system's preferred locale and supports application/arrow keys. * Allows the printable length of strings containing sequences to be determined. Blessed **does not** provide... * Windows command prompt support. A PDCurses_ build of python for windows provides only partial support at this time -- there are plans to merge with the ansi_ module in concert with colorama_ to resolve this. `Patches welcome `_! Before And After ---------------- With the built-in curses_ module, this is how you would typically print some underlined text at the bottom of the screen:: from curses import tigetstr, setupterm, tparm from fcntl import ioctl from os import isatty import struct import sys from termios import TIOCGWINSZ # If we want to tolerate having our output piped to other commands or # files without crashing, we need to do all this branching: if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()): setupterm() sc = tigetstr('sc') cup = tigetstr('cup') rc = tigetstr('rc') underline = tigetstr('smul') normal = tigetstr('sgr0') else: sc = cup = rc = underline = normal = '' # Save cursor position. print(sc) if cup: # tigetnum('lines') doesn't always update promptly, hence this: height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0] # Move cursor to bottom. print(tparm(cup, height - 1, 0)) print('This is {under}underlined{normal}!' .format(under=underline, normal=normal)) # Restore cursor position. print(rc) The same program with *Blessed* is simply:: from blessed import Terminal term = Terminal() with term.location(0, term.height - 1): print('This is' + term.underline('underlined') + '!') Requirements ------------ *Blessed* is tested with Python 2.7, 3.4, and 3.5 on Debian Linux, Mac, and FreeBSD. Further Documentation --------------------- More documentation can be found at http://blessed.readthedocs.org/en/latest/ Bugs, Contributing, Support --------------------------- **Bugs** or suggestions? Visit the `issue tracker`_ and file an issue. We welcome your bug reports and feature suggestions! Would you like to **contribute**? That's awesome! We've written a `guide `_ to help you. Are you stuck and need **support**? Give `stackoverflow`_ a try. If you're still having trouble, we'd like to hear about it! Open an issue in the `issue tracker`_ with a well-formed question. License ------- *Blessed* is under the MIT License. See the LICENSE file. Forked ------ *Blessed* is a fork of `blessings `_. Changes since 1.7 have all been proposed but unaccepted upstream. Furthermore, a project in the node.js language of the `same name `_ is **not** related, or a fork of each other in any way. .. _`issue tracker`: https://github.com/jquast/blessed/issues/ .. _curses: https://docs.python.org/3/library/curses.html .. _tigetstr: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tigetstr.3 .. _tparm: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tparm.3 .. _ansi: https://github.com/tehmaze/ansi .. _colorama: https://pypi.python.org/pypi/colorama .. _PDCurses: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses .. _`terminfo(5)`: http://invisible-island.net/ncurses/man/terminfo.5.html .. _`stackoverflow`: http://stackoverflow.com/ blessed-1.14.2/requirements-about.txt0000644000076500000240000000001012607273732020246 0ustar jquaststaff00000000000000pexpect blessed-1.14.2/requirements-analysis.txt0000644000076500000240000000006312636077424020771 0ustar jquaststaff00000000000000prospector[with_pyroma] restructuredtext_lint doc8 blessed-1.14.2/requirements-docs.txt0000644000076500000240000000005212667057400020070 0ustar jquaststaff00000000000000sphinx sphinx_rtd_theme sphinx-paramlinks blessed-1.14.2/requirements-tests.txt0000644000076500000240000000004412607273732020305 0ustar jquaststaff00000000000000pytest-xdist pytest-cov pytest mock blessed-1.14.2/requirements.txt0000644000076500000240000000003212607273732017142 0ustar jquaststaff00000000000000wcwidth>=0.1.4 six>=1.9.0 blessed-1.14.2/setup.cfg0000644000076500000240000000013013066002636015467 0ustar jquaststaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 blessed-1.14.2/setup.py0000755000076500000240000000440012607273732015376 0ustar jquaststaff00000000000000#!/usr/bin/env python """Distutils setup script.""" import os import setuptools def _get_install_requires(fname): import sys result = [req_line.strip() for req_line in open(fname) if req_line.strip() and not req_line.startswith('#')] # support python2.6 by using backport of 'orderedict' if sys.version_info < (2, 7): result.append('ordereddict==1.1') return result def _get_version(fname): import json return json.load(open(fname, 'r'))['version'] def _get_long_description(fname): import codecs return codecs.open(fname, 'r', 'utf8').read() HERE = os.path.dirname(__file__) setuptools.setup( name='blessed', version=_get_version( fname=os.path.join(HERE, 'version.json')), install_requires=_get_install_requires( fname=os.path.join(HERE, 'requirements.txt')), long_description=_get_long_description( fname=os.path.join(HERE, 'docs', 'intro.rst')), description=('A thin, practical wrapper around terminal styling, ' 'screen positioning, and keyboard input.'), author='Jeff Quast, Erik Rose', author_email='contact@jeffquast.com', license='MIT', packages=['blessed', 'blessed.tests'], url='https://github.com/jquast/blessed', include_package_data=True, zip_safe=True, classifiers=[ 'Intended Audience :: Developers', 'Natural Language :: English', 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: Console :: Curses', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: User Interfaces', 'Topic :: Terminals' ], keywords=['terminal', 'sequences', 'tty', 'curses', 'ncurses', 'formatting', 'style', 'color', 'console', 'keyboard', 'ansi', 'xterm'], ) blessed-1.14.2/tox.ini0000644000076500000240000000442512636077424015205 0ustar jquaststaff00000000000000[tox] envlist = about, sa, sphinx, py{26,27,34,35} skip_missing_interpreters = true [testenv] whitelist_externals = cp setenv = PYTHONIOENCODING=UTF8 passenv = TEST_QUICK TEST_FULL deps = -rrequirements-tests.txt commands = {envbindir}/py.test {posargs:\ --strict --verbose --verbose --color=yes \ --junit-xml=results.{envname}.xml \ --cov blessed blessed/tests} coverage combine cp {toxinidir}/.coverage \ {toxinidir}/._coverage.{envname}.{env:COVERAGE_ID:local} {toxinidir}/tools/custom-combine.py # CI buildchain target [testenv:coverage] deps = coverage six commands = {toxinidir}/tools/custom-combine.py # CI buildhcain target [testenv:coveralls] passenv = COVERALLS_REPO_TOKEN deps = coveralls commands = coveralls [testenv:about] deps = -rrequirements-about.txt basepython = python3.5 commands = python {toxinidir}/bin/display-sighandlers.py python {toxinidir}/bin/display-terminalinfo.py python {toxinidir}/bin/display-fpathconf.py python {toxinidir}/bin/display-maxcanon.py [testenv:sa] basepython = python3.5 deps = -rrequirements-analysis.txt -rrequirements-about.txt commands = python -m compileall -fq {toxinidir}/blessed {envbindir}/prospector \ --die-on-tool-error \ {toxinidir} {envbindir}/rst-lint README.rst {envbindir}/doc8 --ignore-path docs/_build --ignore D000 docs [testenv:sphinx] whitelist_externals = echo basepython = python3.5 deps = -rrequirements-docs.txt commands = {envbindir}/sphinx-build -v -W \ -d {toxinidir}/docs/_build/doctrees \ {posargs:-b html} docs \ {toxinidir}/docs/_build/html echo "--> open docs/_build/html/index.html for review." [testenv:py34] # there is not much difference of py34 vs. 35 in blessed # library; prefer testing integration against py35, and # just do a 'quick' on py34, if exists. setenv = TEST_QUICK=1 [testenv:py26] # and python2.6 really only tests 'orderedict' and some various # backports of import fallback of features setenv = TEST_QUICK=1 [pytest] looponfailroots = blessed norecursedirs = .git .tox build [coverage] rcfile = {toxinidir}/.coveragerc rc = --rcfile={[coverage]rcfile} blessed-1.14.2/version.json0000644000076500000240000000002513065337122016231 0ustar jquaststaff00000000000000{"version": "1.14.2"}