summaryrefslogtreecommitdiffstats
path: root/python/blessings
diff options
context:
space:
mode:
Diffstat (limited to 'python/blessings')
-rw-r--r--python/blessings/LICENSE19
-rw-r--r--python/blessings/MANIFEST.in3
-rw-r--r--python/blessings/PKG-INFO426
-rw-r--r--python/blessings/README.rst399
-rw-r--r--python/blessings/blessings/__init__.py450
-rw-r--r--python/blessings/blessings/tests.py231
-rw-r--r--python/blessings/setup.cfg5
-rw-r--r--python/blessings/setup.py42
-rw-r--r--python/blessings/tox.ini7
9 files changed, 1582 insertions, 0 deletions
diff --git a/python/blessings/LICENSE b/python/blessings/LICENSE
new file mode 100644
index 000000000..3d3a44e65
--- /dev/null
+++ b/python/blessings/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/python/blessings/MANIFEST.in b/python/blessings/MANIFEST.in
new file mode 100644
index 000000000..3f4fbd708
--- /dev/null
+++ b/python/blessings/MANIFEST.in
@@ -0,0 +1,3 @@
+include README.rst
+include LICENSE
+include tox.ini
diff --git a/python/blessings/PKG-INFO b/python/blessings/PKG-INFO
new file mode 100644
index 000000000..c52ca3cf9
--- /dev/null
+++ b/python/blessings/PKG-INFO
@@ -0,0 +1,426 @@
+Metadata-Version: 1.0
+Name: blessings
+Version: 1.3
+Summary: A thin, practical wrapper around terminal formatting, positioning, and more
+Home-page: https://github.com/erikrose/blessings
+Author: Erik Rose
+Author-email: erikrose@grinchcentral.com
+License: MIT
+Description: =========
+ Blessings
+ =========
+
+ Coding with Blessings looks like this... ::
+
+ from blessings 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 'This is at the bottom.'
+
+ Or, for byte-level control, you can drop down and play with raw terminal
+ capabilities::
+
+ print '{t.bold}All your {t.red}bold and red base{t.normal}'.format(t=t)
+ print t.wingo(2)
+
+ The Pitch
+ =========
+
+ Blessings lifts several of curses_' limiting assumptions, and it makes your
+ code pretty, too:
+
+ * Use styles, color, and maybe a little positioning without clearing the whole
+ screen first.
+ * Leave more than one screenful of scrollback in the buffer after your program
+ exits, like a well-behaved command-line app should.
+ * Get rid of all those noisy, C-like calls to ``tigetstr`` and ``tparm``, so
+ your code doesn't get crowded out by terminal bookkeeping.
+ * Act intelligently when somebody redirects your output to a file, omitting the
+ terminal control codes the user doesn't want to see (optional).
+
+ .. _curses: http://docs.python.org/library/curses.html
+
+ Before And After
+ ----------------
+
+ Without Blessings, this is how you'd 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 = ''
+ print sc # Save cursor position.
+ if cup:
+ # tigetnum('lines') doesn't always update promptly, hence this:
+ height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0]
+ print tparm(cup, height - 1, 0) # Move cursor to bottom.
+ print 'This is {under}underlined{normal}!'.format(under=underline,
+ normal=normal)
+ print rc # Restore cursor position.
+
+ Phew! That was long and full of incomprehensible trash! Let's try it again,
+ this time with Blessings::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ with term.location(0, term.height - 1):
+ print 'This is', term.underline('pretty!')
+
+ Much better.
+
+ What It Provides
+ ================
+
+ 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 terminal setup. After that, you can proceed to ask it all sorts
+ of things about the terminal. Terminal terminal terminal.
+
+ Simple Formatting
+ -----------------
+
+ Lots of handy formatting codes ("capabilities" in low-level parlance) are
+ available as attributes on a ``Terminal``. For example::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print 'I am ' + term.bold + 'bold' + term.normal + '!'
+
+ You can also use them as wrappers so you don't have to say ``normal``
+ afterward::
+
+ print 'I am', term.bold('bold') + '!'
+
+ Or, if you want fine-grained control while maintaining some semblance of
+ brevity, you can combine it with Python's string formatting, which makes
+ attributes easy to access::
+
+ print 'All your {t.red}base {t.underline}are belong to us{t.normal}'.format(t=term)
+
+ Simple capabilities of interest include...
+
+ * ``bold``
+ * ``reverse``
+ * ``underline``
+ * ``no_underline`` (which turns off underlining)
+ * ``blink``
+ * ``normal`` (which turns off everything, even colors)
+ * ``clear_eol`` (clear to the end of the line)
+ * ``clear_bol`` (clear to beginning of line)
+ * ``clear_eos`` (clear to end of screen)
+
+ Here are a few more which are less likely to work on all terminals:
+
+ * ``dim``
+ * ``italic`` and ``no_italic``
+ * ``shadow`` and ``no_shadow``
+ * ``standout`` and ``no_standout``
+ * ``subscript`` and ``no_subscript``
+ * ``superscript`` and ``no_superscript``
+ * ``flash`` (which flashes the screen once)
+
+ 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. This is because there's no way to tell the terminal to undo
+ certain pieces of formatting, even at the lowest level.
+
+ You might notice that the above aren't the typical incomprehensible terminfo
+ capability names; we alias a few of the harder-to-remember ones for
+ readability. However, you aren't limited to these: you can reference any
+ string-returning capability listed on the `terminfo man page`_ by the name
+ under the "Cap-name" column: for example, ``term.rum``.
+
+ .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
+
+ Color
+ -----
+
+ 16 colors, both foreground and background, are available as easy-to-remember
+ attributes::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.red + term.on_green + 'Red on green? Ick!' + term.normal
+ print term.bright_red + term.on_bright_blue + 'This is even worse!' + term.normal
+
+ You can also call them as wrappers, which sets everything back to normal at the
+ end::
+
+ print term.red_on_green('Red on green? Ick!')
+ print term.yellow('I can barely see it.')
+
+ The available colors are...
+
+ * ``black``
+ * ``red``
+ * ``green``
+ * ``yellow``
+ * ``blue``
+ * ``magenta``
+ * ``cyan``
+ * ``white``
+
+ You can set the background color instead of the foreground by prepending
+ ``on_``, as in ``on_blue``. There is also a ``bright`` version of each color:
+ for example, ``on_bright_blue``.
+
+ There is also a numerical interface to colors, which takes an integer from
+ 0-15::
+
+ term.color(5) + 'Hello' + term.normal
+ term.on_color(3) + 'Hello' + term.normal
+
+ term.color(5)('Hello')
+ term.on_color(3)('Hello')
+
+ If some color is unsupported (for instance, if only the normal colors are
+ available, not the bright ones), trying to use it will, on most terminals, have
+ no effect: the foreground and background colors will stay as they were. You can
+ get fancy and do different things depending on the supported colors by checking
+ `number_of_colors`_.
+
+ .. _`number_of_colors`: http://packages.python.org/blessings/#blessings.Terminal.number_of_colors
+
+ Compound Formatting
+ -------------------
+
+ If you want to do lots of crazy formatting all at once, you can just mash it
+ all together::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.bold_underline_green_on_yellow + 'Woo' + term.normal
+
+ Or you can use your newly coined attribute as a wrapper, which implicitly sets
+ everything back to normal afterward::
+
+ print term.bold_underline_green_on_yellow('Woo')
+
+ This compound notation comes in handy if you want to allow users to customize
+ the formatting of your app: just have them pass in a format specifier like
+ "bold_green" on the command line, and do a quick ``getattr(term,
+ that_option)('Your text')`` when you do your formatting.
+
+ I'd be remiss if I didn't credit couleur_, where I probably got the idea for
+ all this mashing.
+
+ .. _couleur: http://pypi.python.org/pypi/couleur
+
+ Parametrized Capabilities
+ -------------------------
+
+ Some capabilities take parameters. Rather than making you dig up ``tparm()``
+ all the time, we simply make such capabilities into callable strings. You can
+ pass the parameters right in::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.move(10, 1)
+
+ Here are some of interest:
+
+ ``move``
+ Position the cursor elsewhere. Parameters are y coordinate, then x
+ coordinate.
+ ``move_x``
+ Move the cursor to the given column.
+ ``move_y``
+ Move the cursor to the given row.
+
+ You can also reference any other string-returning capability listed on the
+ `terminfo man page`_ by its name under the "Cap-name" column.
+
+ .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
+
+ Height and Width
+ ----------------
+
+ It's simple to get the height and width of the terminal, in characters::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ height = term.height
+ width = term.width
+
+ These are newly updated each time you ask for them, so they're safe to use from
+ SIGWINCH handlers.
+
+ Temporary Repositioning
+ -----------------------
+
+ Sometimes you need to flit to a certain location, print something, and then
+ return: for example, when updating a progress bar at the bottom of the screen.
+ ``Terminal`` provides a context manager for doing this concisely::
+
+ 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 ``x`` and then ``y``, but you can also pass
+ just one of them, leaving the other alone. For example... ::
+
+ with term.location(y=10):
+ print 'We changed just the row.'
+
+ If you want to reposition permanently, see ``move``, in an example above.
+
+ Pipe Savvy
+ ----------
+
+ If your program isn't attached to a terminal, like if it's being piped to
+ another command 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.
+
+ If you want to override this--like if you anticipate your program being piped
+ through ``less -r``, which handles terminal escapes just fine--pass
+ ``force_styling=True`` to the ``Terminal`` constructor.
+
+ In any case, there is an ``is_a_tty`` attribute on ``Terminal`` that lets you
+ see whether the attached stream seems to be a terminal. If it's false, you
+ might refrain from drawing progress bars and other frippery, since you're
+ apparently headed into a pipe::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ if term.is_a_tty:
+ with term.location(0, term.height - 1):
+ print 'Progress: [=======> ]'
+ print term.bold('Important stuff')
+
+ Shopping List
+ =============
+
+ There are decades of legacy tied up in terminal interaction, so attention to
+ detail and behavior in edge cases make a difference. Here are some ways
+ Blessings has your back:
+
+ * Uses the terminfo database so it works with any terminal type
+ * Provides up-to-the-moment terminal height and width, so you can respond to
+ terminal size changes (SIGWINCH signals). (Most other libraries query the
+ ``COLUMNS`` and ``LINES`` environment variables or the ``cols`` or ``lines``
+ terminal capabilities, which don't update promptly, if at all.)
+ * Avoids making a mess if the output gets piped to a non-terminal
+ * Works great with standard Python string templating
+ * Provides convenient access to all terminal capabilities, not just a sugared
+ few
+ * Outputs to any file-like object, 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
+
+ Blessings does not provide...
+
+ * Native color support on the Windows command prompt. However, it should work
+ when used in concert with colorama_.
+
+ .. _colorama: http://pypi.python.org/pypi/colorama/0.2.4
+
+ Bugs
+ ====
+
+ Bugs or suggestions? Visit the `issue tracker`_.
+
+ .. _`issue tracker`: https://github.com/erikrose/blessings/issues/new
+
+ License
+ =======
+
+ Blessings is under the MIT License. See the LICENSE file.
+
+ Version History
+ ===============
+
+ 1.3
+ * Add ``number_of_colors``, which tells you how many colors the terminal
+ supports.
+ * Made ``color(n)`` and ``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 ANSI ``setaf`` and
+ ``setab`` aren't available.
+ * Allow ``color`` attr to act as an unparametrized string, not just a
+ callable.
+ * Make ``height`` and ``width`` examine any passed-in stream before falling
+ back to stdout. (This rarely if ever affects actual behavior; it's mostly
+ philosophical.)
+ * Make caching simpler and slightly more efficient.
+ * Get rid of a reference cycle between Terminals and FormattingStrings.
+ * Update docs to reflect that terminal addressing (as in ``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 the ``is_a_tty`` attribute for telling whether the output stream is a
+ terminal.
+ * Sugared the remaining interesting string capabilities.
+ * Let ``location()`` operate on just an x *or* y coordinate.
+
+ 1.0
+ * Extracted Blessings from nose-progressive, my `progress-bar-having,
+ traceback-shortcutting, rootin', tootin' testrunner`_. It provided the
+ tootin' functionality.
+
+ .. _`progress-bar-having, traceback-shortcutting, rootin', tootin' testrunner`: http://pypi.python.org/pypi/nose-progressive/
+
+Keywords: terminal,tty,curses,ncurses,formatting,style,color,console
+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.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: User Interfaces
+Classifier: Topic :: Terminals
diff --git a/python/blessings/README.rst b/python/blessings/README.rst
new file mode 100644
index 000000000..59983de86
--- /dev/null
+++ b/python/blessings/README.rst
@@ -0,0 +1,399 @@
+=========
+Blessings
+=========
+
+Coding with Blessings looks like this... ::
+
+ from blessings 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 'This is at the bottom.'
+
+Or, for byte-level control, you can drop down and play with raw terminal
+capabilities::
+
+ print '{t.bold}All your {t.red}bold and red base{t.normal}'.format(t=t)
+ print t.wingo(2)
+
+The Pitch
+=========
+
+Blessings lifts several of curses_' limiting assumptions, and it makes your
+code pretty, too:
+
+* Use styles, color, and maybe a little positioning without clearing the whole
+ screen first.
+* Leave more than one screenful of scrollback in the buffer after your program
+ exits, like a well-behaved command-line app should.
+* Get rid of all those noisy, C-like calls to ``tigetstr`` and ``tparm``, so
+ your code doesn't get crowded out by terminal bookkeeping.
+* Act intelligently when somebody redirects your output to a file, omitting the
+ terminal control codes the user doesn't want to see (optional).
+
+.. _curses: http://docs.python.org/library/curses.html
+
+Before And After
+----------------
+
+Without Blessings, this is how you'd 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 = ''
+ print sc # Save cursor position.
+ if cup:
+ # tigetnum('lines') doesn't always update promptly, hence this:
+ height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0]
+ print tparm(cup, height - 1, 0) # Move cursor to bottom.
+ print 'This is {under}underlined{normal}!'.format(under=underline,
+ normal=normal)
+ print rc # Restore cursor position.
+
+Phew! That was long and full of incomprehensible trash! Let's try it again,
+this time with Blessings::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ with term.location(0, term.height - 1):
+ print 'This is', term.underline('pretty!')
+
+Much better.
+
+What It Provides
+================
+
+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 terminal setup. After that, you can proceed to ask it all sorts
+of things about the terminal. Terminal terminal terminal.
+
+Simple Formatting
+-----------------
+
+Lots of handy formatting codes ("capabilities" in low-level parlance) are
+available as attributes on a ``Terminal``. For example::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print 'I am ' + term.bold + 'bold' + term.normal + '!'
+
+You can also use them as wrappers so you don't have to say ``normal``
+afterward::
+
+ print 'I am', term.bold('bold') + '!'
+
+Or, if you want fine-grained control while maintaining some semblance of
+brevity, you can combine it with Python's string formatting, which makes
+attributes easy to access::
+
+ print 'All your {t.red}base {t.underline}are belong to us{t.normal}'.format(t=term)
+
+Simple capabilities of interest include...
+
+* ``bold``
+* ``reverse``
+* ``underline``
+* ``no_underline`` (which turns off underlining)
+* ``blink``
+* ``normal`` (which turns off everything, even colors)
+* ``clear_eol`` (clear to the end of the line)
+* ``clear_bol`` (clear to beginning of line)
+* ``clear_eos`` (clear to end of screen)
+
+Here are a few more which are less likely to work on all terminals:
+
+* ``dim``
+* ``italic`` and ``no_italic``
+* ``shadow`` and ``no_shadow``
+* ``standout`` and ``no_standout``
+* ``subscript`` and ``no_subscript``
+* ``superscript`` and ``no_superscript``
+* ``flash`` (which flashes the screen once)
+
+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. This is because there's no way to tell the terminal to undo
+certain pieces of formatting, even at the lowest level.
+
+You might notice that the above aren't the typical incomprehensible terminfo
+capability names; we alias a few of the harder-to-remember ones for
+readability. However, you aren't limited to these: you can reference any
+string-returning capability listed on the `terminfo man page`_ by the name
+under the "Cap-name" column: for example, ``term.rum``.
+
+.. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
+
+Color
+-----
+
+16 colors, both foreground and background, are available as easy-to-remember
+attributes::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.red + term.on_green + 'Red on green? Ick!' + term.normal
+ print term.bright_red + term.on_bright_blue + 'This is even worse!' + term.normal
+
+You can also call them as wrappers, which sets everything back to normal at the
+end::
+
+ print term.red_on_green('Red on green? Ick!')
+ print term.yellow('I can barely see it.')
+
+The available colors are...
+
+* ``black``
+* ``red``
+* ``green``
+* ``yellow``
+* ``blue``
+* ``magenta``
+* ``cyan``
+* ``white``
+
+You can set the background color instead of the foreground by prepending
+``on_``, as in ``on_blue``. There is also a ``bright`` version of each color:
+for example, ``on_bright_blue``.
+
+There is also a numerical interface to colors, which takes an integer from
+0-15::
+
+ term.color(5) + 'Hello' + term.normal
+ term.on_color(3) + 'Hello' + term.normal
+
+ term.color(5)('Hello')
+ term.on_color(3)('Hello')
+
+If some color is unsupported (for instance, if only the normal colors are
+available, not the bright ones), trying to use it will, on most terminals, have
+no effect: the foreground and background colors will stay as they were. You can
+get fancy and do different things depending on the supported colors by checking
+`number_of_colors`_.
+
+.. _`number_of_colors`: http://packages.python.org/blessings/#blessings.Terminal.number_of_colors
+
+Compound Formatting
+-------------------
+
+If you want to do lots of crazy formatting all at once, you can just mash it
+all together::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.bold_underline_green_on_yellow + 'Woo' + term.normal
+
+Or you can use your newly coined attribute as a wrapper, which implicitly sets
+everything back to normal afterward::
+
+ print term.bold_underline_green_on_yellow('Woo')
+
+This compound notation comes in handy if you want to allow users to customize
+the formatting of your app: just have them pass in a format specifier like
+"bold_green" on the command line, and do a quick ``getattr(term,
+that_option)('Your text')`` when you do your formatting.
+
+I'd be remiss if I didn't credit couleur_, where I probably got the idea for
+all this mashing.
+
+.. _couleur: http://pypi.python.org/pypi/couleur
+
+Parametrized Capabilities
+-------------------------
+
+Some capabilities take parameters. Rather than making you dig up ``tparm()``
+all the time, we simply make such capabilities into callable strings. You can
+pass the parameters right in::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ print term.move(10, 1)
+
+Here are some of interest:
+
+``move``
+ Position the cursor elsewhere. Parameters are y coordinate, then x
+ coordinate.
+``move_x``
+ Move the cursor to the given column.
+``move_y``
+ Move the cursor to the given row.
+
+You can also reference any other string-returning capability listed on the
+`terminfo man page`_ by its name under the "Cap-name" column.
+
+.. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
+
+Height and Width
+----------------
+
+It's simple to get the height and width of the terminal, in characters::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ height = term.height
+ width = term.width
+
+These are newly updated each time you ask for them, so they're safe to use from
+SIGWINCH handlers.
+
+Temporary Repositioning
+-----------------------
+
+Sometimes you need to flit to a certain location, print something, and then
+return: for example, when updating a progress bar at the bottom of the screen.
+``Terminal`` provides a context manager for doing this concisely::
+
+ 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 ``x`` and then ``y``, but you can also pass
+just one of them, leaving the other alone. For example... ::
+
+ with term.location(y=10):
+ print 'We changed just the row.'
+
+If you want to reposition permanently, see ``move``, in an example above.
+
+Pipe Savvy
+----------
+
+If your program isn't attached to a terminal, like if it's being piped to
+another command 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.
+
+If you want to override this--like if you anticipate your program being piped
+through ``less -r``, which handles terminal escapes just fine--pass
+``force_styling=True`` to the ``Terminal`` constructor.
+
+In any case, there is an ``is_a_tty`` attribute on ``Terminal`` that lets you
+see whether the attached stream seems to be a terminal. If it's false, you
+might refrain from drawing progress bars and other frippery, since you're
+apparently headed into a pipe::
+
+ from blessings import Terminal
+
+ term = Terminal()
+ if term.is_a_tty:
+ with term.location(0, term.height - 1):
+ print 'Progress: [=======> ]'
+ print term.bold('Important stuff')
+
+Shopping List
+=============
+
+There are decades of legacy tied up in terminal interaction, so attention to
+detail and behavior in edge cases make a difference. Here are some ways
+Blessings has your back:
+
+* Uses the terminfo database so it works with any terminal type
+* Provides up-to-the-moment terminal height and width, so you can respond to
+ terminal size changes (SIGWINCH signals). (Most other libraries query the
+ ``COLUMNS`` and ``LINES`` environment variables or the ``cols`` or ``lines``
+ terminal capabilities, which don't update promptly, if at all.)
+* Avoids making a mess if the output gets piped to a non-terminal
+* Works great with standard Python string templating
+* Provides convenient access to all terminal capabilities, not just a sugared
+ few
+* Outputs to any file-like object, 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
+
+Blessings does not provide...
+
+* Native color support on the Windows command prompt. However, it should work
+ when used in concert with colorama_.
+
+.. _colorama: http://pypi.python.org/pypi/colorama/0.2.4
+
+Bugs
+====
+
+Bugs or suggestions? Visit the `issue tracker`_.
+
+.. _`issue tracker`: https://github.com/erikrose/blessings/issues/new
+
+License
+=======
+
+Blessings is under the MIT License. See the LICENSE file.
+
+Version History
+===============
+
+1.3
+ * Add ``number_of_colors``, which tells you how many colors the terminal
+ supports.
+ * Made ``color(n)`` and ``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 ANSI ``setaf`` and
+ ``setab`` aren't available.
+ * Allow ``color`` attr to act as an unparametrized string, not just a
+ callable.
+ * Make ``height`` and ``width`` examine any passed-in stream before falling
+ back to stdout. (This rarely if ever affects actual behavior; it's mostly
+ philosophical.)
+ * Make caching simpler and slightly more efficient.
+ * Get rid of a reference cycle between Terminals and FormattingStrings.
+ * Update docs to reflect that terminal addressing (as in ``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 the ``is_a_tty`` attribute for telling whether the output stream is a
+ terminal.
+ * Sugared the remaining interesting string capabilities.
+ * Let ``location()`` operate on just an x *or* y coordinate.
+
+1.0
+ * Extracted Blessings from nose-progressive, my `progress-bar-having,
+ traceback-shortcutting, rootin', tootin' testrunner`_. It provided the
+ tootin' functionality.
+
+.. _`progress-bar-having, traceback-shortcutting, rootin', tootin' testrunner`: http://pypi.python.org/pypi/nose-progressive/
diff --git a/python/blessings/blessings/__init__.py b/python/blessings/blessings/__init__.py
new file mode 100644
index 000000000..081288ba6
--- /dev/null
+++ b/python/blessings/blessings/__init__.py
@@ -0,0 +1,450 @@
+from collections import defaultdict
+import curses
+from curses import tigetstr, tigetnum, setupterm, tparm
+from fcntl import ioctl
+try:
+ from io import UnsupportedOperation as IOUnsupportedOperation
+except ImportError:
+ class IOUnsupportedOperation(Exception):
+ """A dummy exception to take the place of Python 3's ``io.UnsupportedOperation`` in Python 2"""
+ pass
+import os
+from os import isatty, environ
+from platform import python_version_tuple
+import struct
+import sys
+from termios import TIOCGWINSZ
+
+
+if ('3', '0', '0') <= 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('Blessings needs Python 3.2.3 or greater for Python 3 '
+ 'support due to http://bugs.python.org/issue10570.')
+
+
+__all__ = ['Terminal']
+
+
+class Terminal(object):
+ """An abstraction around terminal capabilities
+
+ Unlike curses, this doesn't require clearing the screen before doing
+ anything, and it's friendlier to use. It keeps the endless calls to
+ ``tigetstr()`` and ``tparm()`` out of your code, and it acts intelligently
+ when somebody pipes your output to a non-terminal.
+
+ Instance attributes:
+
+ ``stream``
+ The stream the terminal outputs to. It's convenient to pass the stream
+ around with the terminal; it's almost always needed when the terminal
+ is and saves sticking lots of extra args on client functions in
+ practice.
+ ``is_a_tty``
+ Whether ``stream`` appears to be a terminal. You can examine this value
+ to decide whether to draw progress bars or other frippery.
+
+ """
+ def __init__(self, kind=None, stream=None, force_styling=False):
+ """Initialize the terminal.
+
+ If ``stream`` is not a tty, I will default to returning an empty
+ Unicode string for all capability values, so things like piping your
+ output to a file won't strew escape sequences all over the place. The
+ ``ls`` command sets a precedent for this: it defaults to columnar
+ output when being sent to a tty and one-item-per-line when not.
+
+ :arg kind: A terminal string as taken by ``setupterm()``. Defaults to
+ the value of the ``TERM`` environment variable.
+ :arg stream: A file-like object representing the terminal. Defaults to
+ the original value of stdout, like ``curses.initscr()`` does.
+ :arg force_styling: Whether to force the emission of capabilities, even
+ if we don't seem to be in a terminal. This comes in handy if users
+ are trying to pipe your output through something like ``less -r``,
+ which supports terminal codes just fine but doesn't appear itself
+ to be a terminal. Just expose a command-line option, and set
+ ``force_styling`` based on it. Terminal initialization sequences
+ will be sent to ``stream`` if it has a file descriptor and to
+ ``sys.__stdout__`` otherwise. (``setupterm()`` demands to send them
+ somewhere, and stdout is probably where the output is ultimately
+ headed. If not, stderr is probably bound to the same terminal.)
+
+ """
+ if stream is None:
+ stream = sys.__stdout__
+ try:
+ stream_descriptor = (stream.fileno() if hasattr(stream, 'fileno')
+ and callable(stream.fileno)
+ else None)
+ except IOUnsupportedOperation:
+ stream_descriptor = None
+
+ self.is_a_tty = stream_descriptor is not None and isatty(stream_descriptor)
+ self._does_styling = self.is_a_tty or force_styling
+
+ # The desciptor to direct terminal initialization sequences to.
+ # sys.__stdout__ seems to always have a descriptor of 1, even if output
+ # is redirected.
+ self._init_descriptor = (sys.__stdout__.fileno()
+ if stream_descriptor is None
+ else stream_descriptor)
+ if self._does_styling:
+ # 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.
+ setupterm(kind or environ.get('TERM', 'unknown'),
+ self._init_descriptor)
+
+ self.stream = stream
+
+ # Sugary names for commonly-used capabilities, intended to help avoid trips
+ # to the terminfo man page and comments in your code:
+ _sugar = dict(
+ # Don't use "on" or "bright" as an underscore-separated chunk in any of
+ # these (e.g. on_cology or rock_on) so we don't interfere with
+ # __getattr__.
+ save='sc',
+ restore='rc',
+
+ clear_eol='el',
+ clear_bol='el1',
+ clear_eos='ed',
+ position='cup', # deprecated
+ move='cup',
+ move_x='hpa',
+ move_y='vpa',
+
+ reset_colors='op', # oc doesn't work on my OS X terminal.
+
+ normal='sgr0',
+ reverse='rev',
+ # 'bold' is just 'bold'. Similarly...
+ # blink
+ # dim
+ # flash
+ 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')
+
+ def __getattr__(self, attr):
+ """Return parametrized terminal capabilities, like bold.
+
+ For example, you can say ``term.bold`` to get the string that turns on
+ bold formatting and ``term.normal`` to get the string that turns it off
+ again. Or you can take a shortcut: ``term.bold('hi')`` bolds its
+ argument and sets everything to normal afterward. You can even combine
+ things: ``term.bold_underline_red_on_bright_green('yowzers!')``.
+
+ For a parametrized capability like ``cup``, pass the parameters too:
+ ``some_term.cup(line, column)``.
+
+ ``man terminfo`` for a complete list of capabilities.
+
+ Return values are always Unicode.
+
+ """
+ resolution = self._resolve_formatter(attr) if self._does_styling else NullCallableString()
+ setattr(self, attr, resolution) # Cache capability codes.
+ return resolution
+
+ @property
+ def height(self):
+ """The height of the terminal in characters
+
+ If no stream or a stream not representing a terminal was passed in at
+ construction, return the dimension of the controlling terminal so
+ piping to things that eventually display on the terminal (like ``less
+ -R``) work. If a stream representing a terminal was passed in, return
+ the dimensions of that terminal. If there somehow is no controlling
+ terminal, return ``None``. (Thus, you should check that ``is_a_tty`` is
+ true before doing any math on the result.)
+
+ """
+ return self._height_and_width()[0]
+
+ @property
+ def width(self):
+ """The width of the terminal in characters
+
+ See ``height()`` for some corner cases.
+
+ """
+ return self._height_and_width()[1]
+
+ def _height_and_width(self):
+ """Return a tuple of (terminal height, terminal width)."""
+ # tigetnum('lines') and tigetnum('cols') update only if we call
+ # setupterm() again.
+ for descriptor in self._init_descriptor, sys.__stdout__:
+ try:
+ return struct.unpack('hhhh', ioctl(descriptor, TIOCGWINSZ, '\000' * 8))[0:2]
+ except IOError:
+ pass
+ return None, None # Should never get here
+
+ def location(self, x=None, y=None):
+ """Return a 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):
+ print 'Hello, world!'
+ for x in xrange(10):
+ print 'I can do it %i times!' % x
+
+ Specify ``x`` to move to a certain column, ``y`` to move to a certain
+ row, or both.
+
+ """
+ return Location(self, x, y)
+
+ @property
+ def color(self):
+ """Return a capability that sets the foreground color.
+
+ The capability is unparametrized 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.
+
+ :arg num: The number, 0-15, of the color
+
+ """
+ return ParametrizingString(self._foreground_color, self.normal)
+
+ @property
+ def on_color(self):
+ """Return a capability that sets the background color.
+
+ See ``color()``.
+
+ """
+ return ParametrizingString(self._background_color, self.normal)
+
+ @property
+ def number_of_colors(self):
+ """Return the number of colors the terminal supports.
+
+ Common values are 0, 8, 16, 88, and 256.
+
+ 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:
+ ...
+
+ We also return 0 if the terminal won't tell us how many colors it
+ supports, which I think is rare.
+
+ """
+ # 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.
+ colors = tigetnum('colors') # Returns -1 if no color support, -2 if no such cap.
+ #self.__dict__['colors'] = ret # Cache it. It's not changing. (Doesn't work.)
+ return colors if colors >= 0 else 0
+
+ def _resolve_formatter(self, attr):
+ """Resolve a sugary or plain capability name, color, or compound formatting function name into a callable capability."""
+ if attr in COLORS:
+ return self._resolve_color(attr)
+ elif attr in COMPOUNDABLES:
+ # Bold, underline, or something that takes no parameters
+ return self._formatting_string(self._resolve_capability(attr))
+ else:
+ formatters = split_into_formatters(attr)
+ if all(f in COMPOUNDABLES for f in formatters):
+ # It's a compound formatter, like "bold_green_on_red". Future
+ # optimization: combine all formatting into a single escape
+ # sequence.
+ return self._formatting_string(
+ u''.join(self._resolve_formatter(s) for s in formatters))
+ else:
+ return ParametrizingString(self._resolve_capability(attr))
+
+ def _resolve_capability(self, atom):
+ """Return a terminal code for a capname or a sugary name, or an empty Unicode.
+
+ The return value is always Unicode, because otherwise it is clumsy
+ (especially in Python 3) to concatenate with real (Unicode) strings.
+
+ """
+ code = tigetstr(self._sugar.get(atom, atom))
+ if code:
+ # We can encode escape sequences as UTF-8 because they never
+ # contain chars > 127, and UTF-8 never changes anything within that
+ # range..
+ return code.decode('utf-8')
+ return u''
+
+ def _resolve_color(self, color):
+ """Resolve a color like red or on_bright_green into a callable capability."""
+ # TODO: 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.
+ color_cap = (self._background_color if 'on_' in color else
+ self._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]
+ return self._formatting_string(
+ color_cap(getattr(curses, 'COLOR_' + base_color.upper()) + offset))
+
+ @property
+ def _foreground_color(self):
+ return self.setaf or self.setf
+
+ @property
+ def _background_color(self):
+ return self.setab or self.setb
+
+ def _formatting_string(self, formatting):
+ """Return a new ``FormattingString`` which implicitly receives my notion of "normal"."""
+ return FormattingString(formatting, self.normal)
+
+
+def derivative_colors(colors):
+ """Return the names of valid color variants, given the base colors."""
+ return set([('on_' + c) for c in colors] +
+ [('bright_' + c) for c in colors] +
+ [('on_bright_' + c) for c in colors])
+
+
+COLORS = set(['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'])
+COLORS.update(derivative_colors(COLORS))
+COMPOUNDABLES = (COLORS |
+ set(['bold', 'underline', 'reverse', 'blink', 'dim', 'italic',
+ 'shadow', 'standout', 'subscript', 'superscript']))
+
+
+class ParametrizingString(unicode):
+ """A Unicode string which can be called to parametrize it as a terminal capability"""
+ def __new__(cls, formatting, normal=None):
+ """Instantiate.
+
+ :arg normal: If non-None, indicates that, once parametrized, this can
+ be used as a ``FormattingString``. The value is used as the
+ "normal" capability.
+
+ """
+ new = unicode.__new__(cls, formatting)
+ new._normal = normal
+ return new
+
+ def __call__(self, *args):
+ 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.
+ parametrized = tparm(self.encode('utf-8'), *args).decode('utf-8')
+ return (parametrized if self._normal is None else
+ FormattingString(parametrized, self._normal))
+ except curses.error:
+ # Catch "must call (at least) setupterm() first" errors, as when
+ # running simply `nosetests` (without progressive) on nose-
+ # progressive. Perhaps the terminal has gone away between calling
+ # tigetstr and calling tparm.
+ return u''
+ except TypeError:
+ # If the first non-int (i.e. incorrect) arg was a string, suggest
+ # something intelligent:
+ if len(args) == 1 and isinstance(args[0], basestring):
+ raise TypeError(
+ 'A native or nonexistent capability template received '
+ '%r when it was expecting ints. You probably misspelled a '
+ 'formatting call like bright_red_on_white(...).' % args)
+ else:
+ # Somebody passed a non-string; I don't feel confident
+ # guessing what they were trying to do.
+ raise
+
+
+class FormattingString(unicode):
+ """A Unicode string which can be called upon a piece of text to wrap it in formatting"""
+ def __new__(cls, formatting, normal):
+ new = unicode.__new__(cls, formatting)
+ new._normal = normal
+ return new
+
+ def __call__(self, text):
+ """Return a new string that is ``text`` formatted with my contents.
+
+ At the beginning of the string, I prepend the formatting that is my
+ contents. At the end, I append the "normal" sequence to set everything
+ back to defaults. The return value is always a Unicode.
+
+ """
+ return self + text + self._normal
+
+
+class NullCallableString(unicode):
+ """A dummy class to stand in for ``FormattingString`` and ``ParametrizingString``
+
+ A callable bytestring that returns an empty Unicode when called with an int
+ and the arg otherwise. We use this when there is no tty and so all
+ capabilities are blank.
+
+ """
+ def __new__(cls):
+ new = unicode.__new__(cls, u'')
+ return new
+
+ def __call__(self, arg):
+ if isinstance(arg, int):
+ return u''
+ return arg # TODO: Force even strs in Python 2.x to be unicodes? Nah. How would I know what encoding to use to convert it?
+
+
+def split_into_formatters(compound):
+ """Split a possibly compound format string into segments.
+
+ >>> split_into_formatters('bold_underline_bright_blue_on_red')
+ ['bold', 'underline', 'bright_blue', 'on_red']
+
+ """
+ merged_segs = []
+ # These occur only as prefixes, so they can always be merged:
+ mergeable_prefixes = ['on', 'bright', 'on_bright']
+ for s in compound.split('_'):
+ if merged_segs and merged_segs[-1] in mergeable_prefixes:
+ merged_segs[-1] += '_' + s
+ else:
+ merged_segs.append(s)
+ return merged_segs
+
+
+class Location(object):
+ """Context manager for temporarily moving the cursor"""
+ def __init__(self, term, x=None, y=None):
+ self.x, self.y = x, y
+ self.term = term
+
+ def __enter__(self):
+ """Save position and move to the requested column, row, or both."""
+ self.term.stream.write(self.term.save) # save position
+ if self.x and self.y:
+ self.term.stream.write(self.term.move(self.y, self.x))
+ elif self.x:
+ self.term.stream.write(self.term.move_x(self.x))
+ elif self.y:
+ self.term.stream.write(self.term.move_y(self.y))
+
+ def __exit__(self, type, value, tb):
+ """Restore original cursor position."""
+ self.term.stream.write(self.term.restore)
diff --git a/python/blessings/blessings/tests.py b/python/blessings/blessings/tests.py
new file mode 100644
index 000000000..a02a3924a
--- /dev/null
+++ b/python/blessings/blessings/tests.py
@@ -0,0 +1,231 @@
+# -*- coding: utf-8 -*-
+"""Automated tests (as opposed to human-verified test patterns)
+
+It was tempting to mock out curses to get predictable output from ``tigetstr``,
+but there are concrete integration-testing benefits in not doing so. For
+instance, ``tigetstr`` changed its return type in Python 3.2.3. So instead, we
+simply create all our test ``Terminal`` instances with a known terminal type.
+All we require from the host machine is that a standard terminfo definition of
+xterm-256color exists.
+
+"""
+from __future__ import with_statement # Make 2.5-compatible
+from curses import tigetstr, tparm
+from functools import partial
+from StringIO import StringIO
+import sys
+
+from nose import SkipTest
+from nose.tools import eq_
+
+# This tests that __all__ is correct, since we use below everything that should
+# be imported:
+from blessings import *
+
+
+TestTerminal = partial(Terminal, kind='xterm-256color')
+
+
+def unicode_cap(cap):
+ """Return the result of ``tigetstr`` except as Unicode."""
+ return tigetstr(cap).decode('utf-8')
+
+
+def unicode_parm(cap, *parms):
+ """Return the result of ``tparm(tigetstr())`` except as Unicode."""
+ return tparm(tigetstr(cap), *parms).decode('utf-8')
+
+
+def test_capability():
+ """Check that a capability lookup works.
+
+ 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')
+ eq_(t.save, sc)
+ eq_(t.save, sc) # Make sure caching doesn't screw it up.
+
+
+def test_capability_without_tty():
+ """Assert capability templates are '' when stream is not a tty."""
+ t = TestTerminal(stream=StringIO())
+ eq_(t.save, u'')
+ eq_(t.red, u'')
+
+
+def test_capability_with_forced_tty():
+ """If we force styling, capabilities had better not (generally) be empty."""
+ t = TestTerminal(stream=StringIO(), force_styling=True)
+ eq_(t.save, unicode_cap('sc'))
+
+
+def test_parametrization():
+ """Test parametrizing a capability."""
+ eq_(TestTerminal().cup(3, 4), unicode_parm('cup', 3, 4))
+
+
+def height_and_width():
+ """Assert that ``height_and_width()`` returns ints."""
+ t = TestTerminal() # kind shouldn't matter.
+ assert isinstance(int, t.height)
+ assert isinstance(int, t.width)
+
+
+def test_stream_attr():
+ """Make sure Terminal exposes a ``stream`` attribute that defaults to something sane."""
+ eq_(Terminal().stream, sys.__stdout__)
+
+
+def test_location():
+ """Make sure ``location()`` does what it claims."""
+ t = TestTerminal(stream=StringIO(), force_styling=True)
+
+ with t.location(3, 4):
+ t.stream.write(u'hi')
+
+ eq_(t.stream.getvalue(), unicode_cap('sc') +
+ unicode_parm('cup', 4, 3) +
+ u'hi' +
+ unicode_cap('rc'))
+
+
+def test_horizontal_location():
+ """Make sure we can move the cursor horizontally without changing rows."""
+ t = TestTerminal(stream=StringIO(), force_styling=True)
+ with t.location(x=5):
+ pass
+ eq_(t.stream.getvalue(), unicode_cap('sc') +
+ unicode_parm('hpa', 5) +
+ unicode_cap('rc'))
+
+
+def test_null_fileno():
+ """Make sure ``Terminal`` works when ``fileno`` is ``None``.
+
+ This simulates piping output to another program.
+
+ """
+ out = StringIO()
+ out.fileno = None
+ t = TestTerminal(stream=out)
+ eq_(t.save, u'')
+
+
+def test_mnemonic_colors():
+ """Make sure color shortcuts work."""
+ def color(num):
+ return unicode_parm('setaf', num)
+
+ def on_color(num):
+ return unicode_parm('setab', num)
+
+ # Avoid testing red, blue, yellow, and cyan, since they might someday
+ # change depending on terminal type.
+ t = TestTerminal()
+ eq_(t.white, color(7))
+ eq_(t.green, color(2)) # Make sure it's different than white.
+ eq_(t.on_black, on_color(0))
+ eq_(t.on_green, on_color(2))
+ eq_(t.bright_black, color(8))
+ eq_(t.bright_green, color(10))
+ eq_(t.on_bright_black, on_color(8))
+ eq_(t.on_bright_green, on_color(10))
+
+
+def test_callable_numeric_colors():
+ """``color(n)`` should return a formatting wrapper."""
+ t = TestTerminal()
+ eq_(t.color(5)('smoo'), t.magenta + 'smoo' + t.normal)
+ eq_(t.color(5)('smoo'), t.color(5) + 'smoo' + t.normal)
+ eq_(t.on_color(2)('smoo'), t.on_green + 'smoo' + t.normal)
+ eq_(t.on_color(2)('smoo'), t.on_color(2) + 'smoo' + t.normal)
+
+
+def test_null_callable_numeric_colors():
+ """``color(n)`` should be a no-op on null terminals."""
+ t = TestTerminal(stream=StringIO())
+ eq_(t.color(5)('smoo'), 'smoo')
+ eq_(t.on_color(6)('smoo'), 'smoo')
+
+
+def test_naked_color_cap():
+ """``term.color`` should return a stringlike capability."""
+ t = TestTerminal()
+ eq_(t.color + '', t.setaf + '')
+
+
+def test_number_of_colors_without_tty():
+ """``number_of_colors`` should return 0 when there's no tty."""
+ # Hypothesis: once setupterm() has run and decided the tty supports 256
+ # colors, it never changes its mind.
+ raise SkipTest
+
+ t = TestTerminal(stream=StringIO())
+ eq_(t.number_of_colors, 0)
+ t = TestTerminal(stream=StringIO(), force_styling=True)
+ eq_(t.number_of_colors, 0)
+
+
+def test_number_of_colors_with_tty():
+ """``number_of_colors`` should work."""
+ t = TestTerminal()
+ eq_(t.number_of_colors, 256)
+
+
+def test_formatting_functions():
+ """Test crazy-ass formatting wrappers, both simple and compound."""
+ t = TestTerminal()
+ # By now, it should be safe to use sugared attributes. Other tests test those.
+ eq_(t.bold(u'hi'), t.bold + u'hi' + t.normal)
+ eq_(t.green('hi'), t.green + u'hi' + t.normal) # Plain strs for Python 2.x
+ # Test some non-ASCII chars, probably not necessary:
+ eq_(t.bold_green(u'boö'), t.bold + t.green + u'boö' + t.normal)
+ eq_(t.bold_underline_green_on_red('boo'),
+ t.bold + t.underline + t.green + t.on_red + u'boo' + t.normal)
+ # Don't spell things like this:
+ eq_(t.on_bright_red_bold_bright_green_underline('meh'),
+ t.on_bright_red + t.bold + t.bright_green + t.underline + u'meh' + t.normal)
+
+
+def test_formatting_functions_without_tty():
+ """Test crazy-ass formatting wrappers when there's no tty."""
+ t = TestTerminal(stream=StringIO())
+ eq_(t.bold(u'hi'), u'hi')
+ eq_(t.green('hi'), u'hi')
+ # Test non-ASCII chars, no longer really necessary:
+ eq_(t.bold_green(u'boö'), u'boö')
+ eq_(t.bold_underline_green_on_red('loo'), u'loo')
+ eq_(t.on_bright_red_bold_bright_green_underline('meh'), u'meh')
+
+
+def test_nice_formatting_errors():
+ """Make sure you get nice hints if you misspell a formatting wrapper."""
+ t = TestTerminal()
+ try:
+ t.bold_misspelled('hey')
+ except TypeError, e:
+ assert 'probably misspelled' in e.args[0]
+
+ try:
+ t.bold_misspelled(u'hey') # unicode
+ except TypeError, e:
+ assert 'probably misspelled' in e.args[0]
+
+ try:
+ t.bold_misspelled(None) # an arbitrary non-string
+ except TypeError, e:
+ assert 'probably misspelled' not in e.args[0]
+
+ try:
+ t.bold_misspelled('a', 'b') # >1 string arg
+ except TypeError, e:
+ assert 'probably misspelled' not in e.args[0]
+
+
+def test_init_descriptor_always_initted():
+ """We should be able to get a height and width even on no-tty Terminals."""
+ t = Terminal(stream=StringIO())
+ eq_(type(t.height), int)
diff --git a/python/blessings/setup.cfg b/python/blessings/setup.cfg
new file mode 100644
index 000000000..861a9f554
--- /dev/null
+++ b/python/blessings/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/python/blessings/setup.py b/python/blessings/setup.py
new file mode 100644
index 000000000..6af55452d
--- /dev/null
+++ b/python/blessings/setup.py
@@ -0,0 +1,42 @@
+import sys
+
+from setuptools import setup, find_packages
+
+
+extra_setup = {}
+if sys.version_info >= (3,):
+ extra_setup['use_2to3'] = True
+
+setup(
+ name='blessings',
+ version='1.3',
+ description='A thin, practical wrapper around terminal formatting, positioning, and more',
+ long_description=open('README.rst').read(),
+ author='Erik Rose',
+ author_email='erikrose@grinchcentral.com',
+ license='MIT',
+ packages=find_packages(exclude=['ez_setup']),
+ tests_require=['Nose'],
+ url='https://github.com/erikrose/blessings',
+ include_package_data=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.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Topic :: Software Development :: Libraries',
+ 'Topic :: Software Development :: User Interfaces',
+ 'Topic :: Terminals'
+ ],
+ keywords=['terminal', 'tty', 'curses', 'ncurses', 'formatting', 'style', 'color', 'console'],
+ **extra_setup
+)
diff --git a/python/blessings/tox.ini b/python/blessings/tox.ini
new file mode 100644
index 000000000..e1753f261
--- /dev/null
+++ b/python/blessings/tox.ini
@@ -0,0 +1,7 @@
+[tox]
+envlist = py25, py26, py27, py32
+
+[testenv]
+commands = nosetests blessings
+deps = nose
+changedir = .tox # So Python 3 runs don't pick up incompatible, un-2to3'd source from the cwd