diff options
-rw-r--r-- | blessings/formatters.py | 47 | ||||
-rw-r--r-- | blessings/keyboard.py | 13 | ||||
-rw-r--r-- | blessings/sequences.py | 10 | ||||
-rw-r--r-- | blessings/terminal.py | 98 | ||||
-rw-r--r-- | docs/api.rst | 12 | ||||
-rw-r--r-- | docs/conf.py | 34 | ||||
-rw-r--r-- | docs/further.rst | 21 | ||||
-rw-r--r-- | docs/history.rst | 28 | ||||
-rw-r--r-- | docs/index.rst | 2 | ||||
-rw-r--r-- | docs/intro.rst | 3 | ||||
-rw-r--r-- | docs/overview.rst | 375 |
11 files changed, 358 insertions, 285 deletions
diff --git a/blessings/formatters.py b/blessings/formatters.py index e9dacc0..8a0f4e4 100644 --- a/blessings/formatters.py +++ b/blessings/formatters.py @@ -1,4 +1,4 @@ -"""This sub-module provides formatting functions.""" +"""This sub-module provides sequence-formatting functions.""" # standard imports import curses @@ -31,10 +31,11 @@ def _make_compoundables(colors): return colors | _compoundables -#: Valid colors and their background (on), bright, and bright-bg derivatives. +#: Valid colors and their background (on), bright, +#: and bright-background derivatives. COLORS = _make_colors() -#: Attributes and colors which may be compounded by underscore (``u'_'``). +#: Attributes and colors which may be compounded by underscore. COMPOUNDABLES = _make_compoundables(COLORS) @@ -71,7 +72,7 @@ class ParameterizingString(six.text_type): Return evaluated terminal capability (self), receiving arguments ``*args``, followed by the terminating sequence (self.normal) into - a FormattingString capable of being called. + a :class:`FormattingString` capable of being called. :rtype: :class:`FormattingString` or :class:`NullCallableString` """ @@ -154,7 +155,6 @@ class ParameterizingProxyString(six.text_type): receives two integers. See documentation in terminfo(5) for the given capability. - :returns: class instance of :class:`FormattingString`. :rtype: FormattingString """ return FormattingString(self.format(*self._fmt_args(*args)), @@ -165,16 +165,13 @@ def get_proxy_string(term, attr): """ Proxy and return callable string for proxied attributes. - :param blessings.Terminal term: :class:`~.Terminal` instance. + :param Terminal term: :class:`~.Terminal` instance. :param 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. - - - Returns instance of ParameterizingProxyString or NullCallableString. """ # normalize 'screen-256color', or 'ansi.sys' to its basic names term_kind = next(iter(_kind for _kind in ('screen', 'ansi',) @@ -257,12 +254,12 @@ class NullCallableString(six.text_type): 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 ``ParameterizingString``, as there are - only about half a dozen string-returning capabilities listed in + 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 ``FormattingString`` without + the first arg, acting in place of :class:`FormattingString` without any attributes. """ if len(args) != 1 or isinstance(args[0], int): @@ -298,7 +295,6 @@ def split_compound(compound): :param str compound: a string that may contain compounds, separated by underline (``_``). - :returns: list of at least 1 length containing strings. :rtype: list """ merged_segs = [] @@ -316,11 +312,11 @@ def resolve_capability(term, attr): """ Resolve a raw terminal capability using :func:`tigetstr`. - :param blessings.Terminal term: :class:`~.Terminal` instance. - :param str attr: terminal capability name (see terminfo(5)). + :param Terminal term: :class:`~.Terminal` instance. + :param 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 ``term.kind``. + 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 @@ -337,13 +333,15 @@ def resolve_color(term, color): This function supports :func:`resolve_attribute`. - :param blessings.Terminal term: :class:`~.Terminal` instance. + :param Terminal term: :class:`~.Terminal` instance. :param 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. - :rtype: :class:`NullCallableString` when ``term.number_of_colors`` is 0, + :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() @@ -351,7 +349,8 @@ def resolve_color(term, color): # 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. + # 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) @@ -369,15 +368,17 @@ def resolve_attribute(term, attr): """ Resolve a terminal attribute name into a capability class. - :param blessings.Terminal term: :class:`~.Terminal` instance. + :param Terminal term: :class:`~.Terminal` instance. :param 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. - :rtype: :class:`NullCallableString` if the given ``term.number_of_colors`` - is 0, otherwise :class:`FormattingString`. + :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) diff --git a/blessings/keyboard.py b/blessings/keyboard.py index 8b021ea..8037163 100644 --- a/blessings/keyboard.py +++ b/blessings/keyboard.py @@ -24,10 +24,11 @@ class Keystroke(six.text_type): 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``. + 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 @@ -214,10 +215,10 @@ def resolve_sequence(text, mapper, codes): def _inject_curses_keynames(): r""" - Inject KEY_{names} that we think would be useful into the curses module. + Inject KEY_NAMES that we think would be useful into the curses module. This function compliments the global constant - :var:`DEFAULT_SEQUENCE_MIXIN`. It is important to note that this + :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. diff --git a/blessings/sequences.py b/blessings/sequences.py index 71ce0c0..00241de 100644 --- a/blessings/sequences.py +++ b/blessings/sequences.py @@ -383,7 +383,8 @@ class SequenceTextWrapper(textwrap.TextWrapper): textwrap.TextWrapper.__init__(self, width, **kwargs) def _wrap_chunks(self, chunks): - """Sequence-aware :meth:`textwrap.TextWrapper._wrap_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 @@ -478,13 +479,6 @@ class SequenceTextWrapper(textwrap.TextWrapper): # cur_len will be zero, so the next line will be entirely # devoted to the long word that we can't handle right now. - # append the built-in docstring to our "sequence-aware" ones. - _wrap_chunks.__doc__ += ( - '\n' + textwrap.TextWrapper._wrap_chunks.__doc__) - - _handle_long_word.__doc__ += ( - '\n' + textwrap.TextWrapper._handle_long_word.__doc__) - SequenceTextWrapper.__doc__ = textwrap.TextWrapper.__doc__ diff --git a/blessings/terminal.py b/blessings/terminal.py index b2a4c2a..bee9104 100644 --- a/blessings/terminal.py +++ b/blessings/terminal.py @@ -107,20 +107,25 @@ class Terminal(object): :param str kind: A terminal string as taken by :func:`curses.setupterm`. Defaults to the value of the ``TERM`` Environment variable. - :param stream: A file-like object representing the Terminal. 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. + + .. note:: A terminal of only one ``kind`` may be initialized for + each process. See :obj:`_CUR_TERM`. + + :param 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. :param bool force_styling: Whether to force the emission of capabilities even if :obj:`sys.__stdout__` does not seem to be - connected to a terminal. 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. + connected to a terminal. If you want to force styling to not happen, + use ``force_styling=None``. - If you want to force styling to not happen, pass - ``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 # Using the global statement (col 8) @@ -223,7 +228,7 @@ class Terminal(object): 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 ``term.normal``. This capability + 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)``. @@ -246,47 +251,27 @@ class Terminal(object): @property def kind(self): - """ - Name of this terminal type. - - :rtype: str - """ + """Name of this terminal type.""" return self._kind @property def does_styling(self): - """ - Whether this instance will emit terminal sequences. - - :rtype: bool - """ + """Whether this instance will emit terminal sequences.""" return self._does_styling @property def is_a_tty(self): - """ - Whether :attr:`~.stream` is a terminal. - - :rtype: bool - """ + """Whether :attr:`~.stream` is a terminal.""" return self._is_a_tty @property def height(self): - """ - The height of the terminal (by number of character cells). - - :rtype: int - """ + """The height of the terminal (by number of character cells).""" return self._height_and_width().ws_row @property def width(self): - """ - The width of the terminal (by number of character cells). - - :rtype: int - """ + """The width of the terminal (by number of character cells).""" return self._height_and_width().ws_col @staticmethod @@ -360,12 +345,12 @@ class Terminal(object): :rtype: None Move the cursor to a certain position on entry, let you print stuff - there, then return the cursor to its original position: + there, then return the cursor to its original position:: - >>> term = Terminal() - >>> with term.location(2, 5): - ... print('Hello, world!') - >>> print('previous location') + term = Terminal() + with term.location(2, 5): + print('Hello, world!') + print('previous location') This context manager yields no value, its side-effect is to write the "save cursor position (sc)" sequence upon entering to @@ -403,7 +388,7 @@ class Terminal(object): the primary screen buffer on entering, and to restore it again upon exit. The secondary screen buffer entered while using the context manager also remains, and is faithfully restored again - on the next entrance: + on the next entrance:: with term.fullscreen(), term.hidden_cursor(): main() @@ -427,7 +412,7 @@ class Terminal(object): This context manager yields no value, its side-effect is to emit the ``hide_cursor`` sequence to :attr:`stream` on entering, and - to emit ``normal_cursor`` sequence upon exit: + to emit ``normal_cursor`` sequence upon exit:: with term.fullscreen(), term.hidden_cursor(): main() @@ -479,9 +464,9 @@ class Terminal(object): :rtype: str - :attr:`~.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 set to default. + 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 set to default. """ if self._normal: return self._normal @@ -792,10 +777,9 @@ class Terminal(object): set the :mod:`termios` attributes of the terminal attached to :obj:`sys.__stdin__`. - .. note:: you must explicitly print any input received you'd like it - shown on output. And, if providing any kind of editing, you must - also explicitly handle backspacing and other line editing - control characters. + .. note:: you must explicitly print any input received if you'd like + it displayed. And, if providing any kind of editing, you must + also handle backspace and other line editing control characters. .. note:: :func:`tty.setcbreak` sets ``VMIN = 1`` and ``VTIME = 0``, see http://www.unixwiz.net/techtips/termios-vmin-vtime.html @@ -979,15 +963,15 @@ class WINSZ(collections.namedtuple('WINSZ', ( #: 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:: +#: 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 setupterm(), so the value of cur_term cannot be changed -#: once set: subsequent calls to setupterm() have no effect. +#: terminals through :func:`curses.setupterm`, so the value of cur_term cannot +#: be changed once set: subsequent calls to :func:`setupterm` have no effect. #: -#: Therefore, the ``kind`` of each Terminal() is, in essence, a singleton. -#: This global variable reflects that, and a warning is emitted if somebody -#: expects otherwise. +#: 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 diff --git a/docs/api.rst b/docs/api.rst index 16e4347..9bca2fa 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -8,6 +8,7 @@ terminal.py :members: :undoc-members: :special-members: __getattr__ +.. autodata:: _CUR_TERM formatters.py ------------- @@ -16,6 +17,9 @@ formatters.py :members: :undoc-members: :private-members: + :special-members: __call__ +.. autodata:: COLORS +.. autodata:: COMPOUNDABLES keyboard.py ----------- @@ -24,6 +28,11 @@ keyboard.py :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 ------------ @@ -32,3 +41,6 @@ sequences.py :members: :undoc-members: :private-members: +.. autofunction:: _sort_sequences +.. autofunction:: _build_numeric_capability +.. autofunction:: _build_any_numeric_capability diff --git a/docs/conf.py b/docs/conf.py index 7f710b0..9e5ae92 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,9 +2,6 @@ import sys import os -# local imports -from blessings import Terminal - # 3rd-party import sphinx_rtd_theme import sphinx.environment @@ -32,12 +29,33 @@ github_project_url = "https://github.com/erikrose/blessings" # # https://github.com/SuperCowPowers/workbench/issues/172 # https://groups.google.com/forum/#!topic/sphinx-users/GNx7PVXoZIU -# http://stackoverflow.com/questions/12772927/specifying-an-online-image-in-sphinx-restructuredtext-format +# http://stackoverflow.com/a/28778969 +# def _warn_node(self, msg, node): if not msg.startswith('nonlocal image URI found:'): self._warnfunc(msg, '%s:%s' % get_source_line(node)) sphinx.environment.BuildEnvironment.warn_node = _warn_node +# Monkey-patch functools.wraps and contextlib.wraps +# https://github.com/sphinx-doc/sphinx/issues/1711#issuecomment-93126473 +import functools +def no_op_wraps(func): + """ + Replaces functools.wraps in order to undo wrapping when generating Sphinx documentation + """ + import sys + if func.__module__ is None or 'blessings' not in func.__module__: + return functools.orig_wraps(func) + def wrapper(decorator): + sys.stderr.write('patched for function signature: {0!r}\n'.format(func)) + return func + return wrapper +functools.orig_wraps = functools.wraps +functools.wraps = no_op_wraps +import contextlib +contextlib.wraps = no_op_wraps +from blessings.terminal import * + # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -45,8 +63,12 @@ sphinx.environment.BuildEnvironment.warn_node = _warn_node # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', 'github', ] +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'github', + 'sphinx_paramlinks', + ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/further.rst b/docs/further.rst index 4847814..ed13d81 100644 --- a/docs/further.rst +++ b/docs/further.rst @@ -6,7 +6,7 @@ that dive deeper into Terminal I/O programming than :class:`~.Terminal` offers. Here are some recommended readings to help you along: - `terminfo(5) - <http://www.openbsd.org/cgi-bin/man.cgi?query=terminfo&apropos=0&sektion=5>`_ + <http://invisible-island.net/ncurses/man/terminfo.5.html>`_ 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**. @@ -16,8 +16,8 @@ Here are some recommended readings to help you along: of your preferred posix-like operating system. - `The TTY demystified - <http://www.linusakesson.net/programming/tty/index.php>`_ by - Linus Åkesson. + <http://www.linusakesson.net/programming/tty/index.php>`_ + by Linus Åkesson. - `A Brief Introduction to Termios <https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios/>`_ by @@ -53,18 +53,19 @@ Here are some recommended readings to help you along: terminal types) emitted by most terminal capabilities to an action in a series of ``case`` switch statements. - - Many modern libraries are now based `https://github.com/GNOME/vte - <libvte>`_ (or just 'vte'): Gnome Terminal, sakura, Terminator, Lilyterm, - ROXTerm, evilvte, Termit, Termite, Tilda, tinyterm, lxterminal. - - `Thomas E. Dickey <http://invisible-island.net/>` has been maintaining + - Many modern libraries are now based on `libvte + <https://github.com/GNOME/vte>`_ (or just 'vte'): Gnome Terminal, + sakura, Terminator, Lilyterm, ROXTerm, evilvte, Termit, Termite, Tilda, + tinyterm, lxterminal. + - `Thomas E. Dickey <http://invisible-island.net/>`_ has been maintaining `xterm <http://invisible-island.net/xterm/xterm.html>`_, as well as a - primary maintainer of many related packages such as `ncurses + primary maintainer of many related packages such as `ncurses <http://invisible-island.net/ncurses/ncurses.html>`_ for quite a long while. There is often speculation and misinformation among developers of terminal emulators and programs that interact with them. Thomas Dickey's analysis is always thorough and complete. - xterm, urxvt, SyncTerm, and EtherTerm. - - There are far too many to name, Chose one you like! Thomas Dickey + - 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 @@ -76,7 +77,7 @@ Here are some recommended readings to help you along: - `FreeBSD <https://github.com/freebsd/freebsd/blob/master/sys/kern/tty.c>`_ - `OpenBSD <http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/tty.c?content-type=text/plain>`_ - - `Illumos (Solaris) <https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/io/tty_common.c>` + - `Illumos (Solaris) <https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/io/tty_common.c>`_ - `Minix <https://github.com/minix3/minix/blob/master/minix/drivers/tty/tty/tty.c>`_ - `Linux <https://github.com/torvalds/linux/blob/master/drivers/tty/n_tty.c>`_ diff --git a/docs/history.rst b/docs/history.rst index c7f5417..ce5a321 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -1,8 +1,9 @@ Version History =============== -2.0 - * This release is primarily a set of contributions from +1.9.5 + * This release is primarily a set of contributions from the + `blessed <https://github.com/jquast/blessed>` fork by :ghuser:`jquast` unless otherwise indicated. **new features**: @@ -34,7 +35,7 @@ Version History 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. - - New public attribute: :attr:`~.kind`: the very same as given + - New public attribute: :attr:`~.kind`: the very same as given by the keyword argument of the same (or, determined by and equivalent to the ``TERM`` Environment variable). - Some attributes are now properties and raise exceptions when assigned, @@ -52,9 +53,10 @@ Version History - The '2to3' tool is no longer used for python 3 support - Converted nose tests to pytest via tox. Added a TeamCity build farm to include OSX and FreeBSD testing. ``tox`` is now the primary entry point - with which to execute tests, run static analysis, and build documentation. - - py.test fixtures and ``@as_subprocess`` decorator for testing of many more - terminal types than just 'xterm-256-color' as previously tested. + with which to execute tests, run static analysis, and build + documentation. + - py.test fixtures and ``@as_subprocess`` decorator for testing of many + more terminal types than just 'xterm-256-color' as previously tested. - ``setup.py develop`` ensures a virtualenv and installs tox. - 100% (combined) coverage. @@ -118,10 +120,10 @@ Version History 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. + * 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 @@ -133,9 +135,9 @@ Version History 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 :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`. diff --git a/docs/index.rst b/docs/index.rst index 986f483..37794b3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,9 @@ Contents: intro overview - api further pains + api history ======= diff --git a/docs/intro.rst b/docs/intro.rst index 41b53e5..fefb747 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -119,7 +119,8 @@ detail and behavior in edge cases make a difference. Here are some ways * Outputs to any file-like object (*StringIO*, file), not just *stdout*. * 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. -* Safely decodes internationalization keyboard input to their unicode equivalents. +* Safely decodes internationalization keyboard input to their unicod e + equivalents. * Safely decodes multibyte sequences for application/arrow keys. * Allows the printable length of strings containing sequences to be determined. * Provides plenty of context managers to safely express various terminal modes, diff --git a/docs/overview.rst b/docs/overview.rst index 6b7215c..44c5a6c 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -1,44 +1,63 @@ Overview ======== -Blessings provides just **one** top-level object: *Terminal*. Instantiating a -*Terminal* figures out whether you're on a terminal at all and, if so, does -any necessary setup. After that, you can proceed to ask it all sorts of things -about the terminal, such as its size and color support, and use its styling -to construct strings containing color and styling. Also, 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. +Blessings 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() -Simple Formatting ------------------ +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: -Lots of handy formatting codes are available as attributes on a *Terminal* class -instance. For example:: + >>> term.green_reverse('ALL SYSTEMS GO') + u'\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 blessings 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*. +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 *Terminal* instance as an argument for ``.format`` string -method, so that capabilities can be displayed in-line for more complex strings:: +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: @@ -68,7 +87,7 @@ The less commonly supported capabilities: ``no_shadow`` Exit shadow text mode. ``standout`` - Enable standout mode (often, an alias for ``reverse``.). + Enable standout mode (often, an alias for ``reverse``). ``no_standout`` Exit standout mode. ``subscript`` @@ -88,13 +107,13 @@ 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 to a *Terminal* -instance. If it is not a supported capability, or a non-tty is used as an -output stream, an empty string is returned. +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. @@ -110,8 +129,8 @@ Color terminals are capable of at least 8 basic colors. The same colors, prefixed with *bright_* (synonymous with *bold_*), such as *bright_blue*, provides 16 colors in total. -The same colors, prefixed with *on_* sets the background color, some -terminals also provide an additional 8 high-intensity versions using +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 blessings import Terminal @@ -119,50 +138,53 @@ terminals also provide an additional 8 high-intensity versions using term = Terminal() print(term.on_bright_blue('Blue skies!')) + print(term.bright_red_on_bright_yellow('Pepperoni Pizza!')) -There is also a numerical interface to colors, which takes an integer from -0-15.:: +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 blessings import Terminal term = Terminal() - for n in range(16): - print(term.color(n)('Color {}'.format(n))) + 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. -If the terminal defined by the **TERM** environment variable does not support -colors, these simply return empty strings, or the string passed as an argument -when used as a callable, without any video attributes. If the **TERM** defines -a terminal that does support colors, but actually does not, they are usually -harmless. +Colorless Terminals +~~~~~~~~~~~~~~~~~~~ -Colorless terminals, such as the amber or green monochrome *vt220*, do not +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, such as a selection bar, to simply select -a foreground color, followed by reverse video to achieve the desired -background color effect:: +desirable in some applications to simply select a foreground color, followed +by reverse video to achieve the desired background color effect:: from blessings import Terminal term = Terminal() - print('some terminals {standout} more than others'.format( - standout=term.green_reverse('standout'))) - -Which appears as *bright white on green* on color terminals, or *black text -on amber or green* on monochrome terminals. + print(term.green_reverse('some terminals standout more than others')) -You can check whether the terminal definition used supports colors, and how -many, using the ``number_of_colors`` property, which returns any of *0*, -*8* or *256* for terminal types such as *vt220*, *ansi*, and -*xterm-256color*, respectively. +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, unlink *black*, *bright_black* is not -invisible -- it is actually a very dark shade of gray! +.. 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:: @@ -174,28 +196,30 @@ all together:: 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:: +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:: #!/usr/bin/env python import argparse + from blessings import Terminal parser = argparse.ArgumentParser( description='displays argument as specified style') parser.add_argument('style', type=str, help='style formatter') parser.add_argument('text', type=str, nargs='+') - from blessings import Terminal - term = Terminal() + args = parser.parse_args() style = getattr(term, args.style) print(style(' '.join(args.text))) -Saved as **tprint.py**, this could be called simply:: +Saved as **tprint.py**, this could be used like:: $ ./tprint.py bright_blue_reverse Blue Skies @@ -203,29 +227,34 @@ Saved as **tprint.py**, this could be called simply:: Moving The Cursor ----------------- -When you want to move the cursor, you have a few choices, the -``location(x=None, y=None)`` context manager, ``move(y, x)``, ``move_y(row)``, -and ``move_x(col)`` attributes. +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. -**NOTE**: The ``location()`` method receives arguments in form of *(x, y)*, -whereas the ``move()`` argument receives arguments in form of *(y, x)*. This -is a flaw in the original `erikrose/blessings`_ implementation, but remains -for compatibility. +.. note:: The :meth:`~.Terminal.location` method receives arguments in form + of *(x, y)*, whereas the ``move()`` capability receives arguments in form + of *(y, x)*. This will be changed to match in the 2.0 release, :ghissue:`58`. Moving Temporarily ~~~~~~~~~~~~~~~~~~ -A context manager, ``location()`` is provided to move the cursor to an -*(x, y)* screen position and restore the previous position upon exit:: +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 blessings 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 ``location()`` are **optional** *x* and/or *y*:: +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.') @@ -236,8 +265,7 @@ When omitted, it saves the cursor position and restore it upon exit:: print(term.move(1, 1) + 'Hi') print(term.move(9, 9) + 'Mom') -**NOTE**: calls to ``location()`` may not be nested, as only one location -may be saved. +.. note:: calls to :meth:`~.Terminal.location` may not be nested. Moving Permanently @@ -269,14 +297,17 @@ cursor one character in various directions: * ``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. +.. 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 *height* and *width* properties of the *Terminal* class instance:: +Use the :attr:`~.Terminal.height` and :attr:`~.Terminal.width` properties to +determine the size of the window:: from blessings import Terminal @@ -285,7 +316,8 @@ Use the *height* and *width* properties of the *Terminal* class instance:: with term.location(x=term.width / 3, y=term.height / 3): print('1/3 ways in!') -These are always current, so they may be used with a callback from SIGWINCH_ signals.:: +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 blessings import Terminal @@ -297,7 +329,8 @@ These are always current, so they may be used with a callback from SIGWINCH_ sig signal.signal(signal.SIGWINCH, on_resize) - term.inkey() + # wait for keypress + term.keystroke() Clearing The Screen @@ -336,7 +369,7 @@ There's also a context manager you can use as a shortcut:: with term.fullscreen(): print(term.move_y(term.height // 2) + term.center('press any key').rstrip()) - term.inkey() + term.keystroke() Pipe Savvy @@ -344,13 +377,13 @@ 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 -*Terminal* will return empty strings. You'll get a nice-looking file without -any formatting codes gumming up the works. +: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 ``force_styling=True`` to the *Terminal* constructor. +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 *does_styling* attribute on *Terminal* that lets +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:: @@ -361,34 +394,38 @@ bars and other frippery and just stick to content:: if term.does_styling: with term.location(x=0, y=term.height - 1): print('Progress: [=======> ]') - print(term.bold('Important stuff')) + print(term.bold("60%")) Sequence Awareness ------------------ Blessings may measure the printable width of strings containing sequences, -providing ``.center``, ``.ljust``, and ``.rjust`` methods, using the -terminal screen's width as the default *width* value:: +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 blessings import Terminal term = Terminal() - with term.location(y=term.height / 2): - print (term.center(term.bold('X')) + 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. -Any string containing sequences may have its printable length measured using the -``.length`` method. Additionally, ``textwrap.wrap()`` is supplied on the Terminal -class as method ``.wrap`` method that is also sequence-aware, so now you may -word-wrap strings containing sequences. The following example displays a poem -from Tao Te Ching, word-wrapped to 25 columns:: +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 blessings import Terminal term = Terminal() - poem = (term.bold_blue('Plan difficult tasks'), - term.blue('through the simplest tasks'), + 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')) @@ -399,64 +436,77 @@ from Tao Te Ching, word-wrapped to 25 columns:: Keyboard Input -------------- -The built-in python function ``raw_input`` function does not return a value until +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 that emit multibyte sequences. +keypress, much less arrow or function keys. -Special `termios(4)`_ routines are required to enter Non-canonical mode, known -in curses as `cbreak(3)`_. When calling read on input stream, only bytes are -received, which must be decoded to unicode. +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``: blessings handles all of these special cases for you! -Blessings handles all of these special cases!! +keystroke_input +~~~~~~~~~~~~~~~ -cbreak -~~~~~~ - -The context manager ``cbreak`` can be used to enter *key-at-a-time* mode: Any -keypress by the user is immediately consumed by read calls:: +The context manager :meth:`~.Terminal.keystroke_input` can be used to enter +*key-at-a-time* mode: Any keypress by the user is immediately consumed by read +calls:: from blessings import Terminal import sys - t = Terminal() + term = Terminal() - with t.cbreak(): - # blocks until any key is pressed. + with term.keystroke_input(): + # block until any single key is pressed. sys.stdin.read(1) -raw -~~~ +The mode entered using :meth:`~.Terminal.keystroke_input` is called +`cbreak(3)`_ in curses: -The context manager ``raw`` is the same as ``cbreak``, except interrupt (^C), -quit (^\\), suspend (^Z), and flow control (^S, ^Q) characters are not trapped, -but instead sent directly as their natural character. This is necessary if you -actually want to handle the receipt of Ctrl+C + 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. -inkey -~~~~~ +:meth:`~.Terminal.keystroke_input` also accepts optional parameter +:paramref:`~.Terminal.keystroke_input.raw` which may be set as *True*. When used, +the given behavior is described in `raw(3)`_ as follows: -The method ``inkey`` resolves many issues with terminal input by returning -a unicode-derived *Keypress* instance. Although its return value may be -printed, joined with, or compared to other unicode strings, it also provides -the special attributes ``is_sequence`` (bool), ``code`` (int), -and ``name`` (str):: + The raw and noraw routines place the terminal into or out of raw mode. + Raw mode is similar to cbreak mode, in that characters typed are immediately + passed through to the user program. The differences are that in raw mode, + the interrupt, quit, suspend, and flow control characters are all passed + through uninterpreted, instead of generating a signal. + +keystroke +~~~~~~~~~ + +The method :meth:`~.Terminal.keystroke` combined with `keystroke_input`_ +completes the circle of providing key-at-a-time keyboard input with multibyte +encoding and awareness of application keys. + +:meth:`~.Terminal.keystroke` 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 blessings import Terminal - t = Terminal() + term = Terminal() print("press 'q' to quit.") - with t.cbreak(): - val = None + with term.keystroke_input(): + val = u'' while val not in (u'q', u'Q',): - val = t.inkey(timeout=5) + val = term.keystroke(timeout=5) if not val: # timeout print("It sure is quiet in here ...") elif val.is_sequence: - print("got sequence: {}.".format((str(val), val.name, val.code))) + print("got sequence: {0}.".format((str(val), val.name, val.code))) elif val: - print("got {}.".format(val)) + print("got {0}.".format(val)) print('bye!') Its output might appear as:: @@ -473,45 +523,51 @@ Its output might appear as:: got q. bye! -A ``timeout`` value of *None* (default) will block forever. Any other value -specifies the length of time to poll for input, if no input is received after -such time has elapsed, an empty string is returned. A ``timeout`` value of *0* -is non-blocking. +A :paramref:`~.Terminal.keystroke.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.keystroke.timeout` value of *0* is non-blocking. keyboard codes ~~~~~~~~~~~~~~ -The return value of the *Terminal* method ``inkey`` is an instance of the -class ``Keystroke``, and may be inspected for its property ``is_sequence`` -(bool). When *True*, the value is a **multibyte sequence**, representing -a special non-alphanumeric key of your keyboard. - -The ``code`` property (int) may then be compared with attributes of -*Terminal*, which are duplicated from those seen in the manpage -`curs_getch(3)`_ or the curses_ module, with the following helpful -aliases: - -* use ``KEY_DELETE`` for ``KEY_DC`` (chr(127)). -* use ``KEY_TAB`` for chr(9). -* use ``KEY_INSERT`` for ``KEY_IC``. -* use ``KEY_PGUP`` for ``KEY_PPAGE``. -* use ``KEY_PGDOWN`` for ``KEY_NPAGE``. -* use ``KEY_ESCAPE`` for ``KEY_EXIT``. -* use ``KEY_SUP`` for ``KEY_SR`` (shift + up). -* use ``KEY_SDOWN`` for ``KEY_SF`` (shift + down). -* use ``KEY_DOWN_LEFT`` for ``KEY_C1`` (keypad lower-left). -* use ``KEY_UP_RIGHT`` for ``KEY_A1`` (keypad upper-left). -* use ``KEY_DOWN_RIGHT`` for ``KEY_C3`` (keypad lower-left). -* use ``KEY_UP_RIGHT`` for ``KEY_A3`` (keypad lower-right). -* use ``KEY_CENTER`` for ``KEY_B2`` (keypad center). -* use ``KEY_BEGIN`` for ``KEY_BEG``. - -The *name* property of the return value of ``inkey()`` will prefer -these aliases over the built-in curses_ names. - -The following are **not** available in the curses_ module, but +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 <https://docs.python.org/3/library/curses.html#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: + +=================== ============= ==================== +blessings 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: +context manager is used with numlock on: * ``KEY_KP_MULTIPLY`` * ``KEY_KP_ADD`` @@ -521,11 +577,10 @@ context manager is used: * ``KEY_KP_DIVIDE`` * ``KEY_KP_0`` through ``KEY_KP_9`` -.. _`erikrose/blessings`: https://github.com/erikrose/blessings -.. _curses: https://docs.python.org/library/curses.html .. _couleur: https://pypi.python.org/pypi/couleur .. _wcwidth: https://pypi.python.org/pypi/wcwidth .. _`cbreak(3)`: http://www.openbsd.org/cgi-bin/man.cgi?query=cbreak&apropos=0&sektion=3 +.. _`raw(3)`: http://www.openbsd.org/cgi-bin/man.cgi?query=raw&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://invisible-island.net/ncurses/man/terminfo.5.html |