Blessings
A thin, practical wrapper around terminal capabilities in Python
Install / Use
/learn @erikrose/BlessingsREADME
========= Blessings
Coding with Blessings looks like this...
.. code:: python
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:
.. code:: python
print('{t.bold}All your {t.red}bold and red base{t.normal}'.format(t=t))
print(t.wingo(2))
Full API Reference <https://blessings.readthedocs.io/>_
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 necessarily 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
tigetstrandtparm, 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:
.. code:: python
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.
That was long and full of incomprehensible trash! Let's try it again, this time with Blessings:
.. code:: python
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...
.. code:: python
from blessings import Terminal
term = Terminal()
print('I am ' + term.bold + 'bold' + term.normal + '!')
Though they are strings at heart, you can also use them as callable wrappers so
you don't have to say normal afterward:
.. code:: python
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:
.. code:: python
print('All your {t.red}base {t.underline}are belong to us{t.normal}'.format(t=term))
Simple capabilities of interest include...
boldreverseunderlineno_underline(which turns off underlining)blinknormal(which turns off everything, even colors)
Here are a few more which are less likely to work on all terminals:
dimitalicandno_italicshadowandno_shadowstandoutandno_standoutsubscriptandno_subscriptsuperscriptandno_superscriptflash(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 portable way to tell the terminal to
undo certain pieces of formatting, even at the lowest level.
You might also 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:
.. code:: python
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:
.. code:: python
print(term.red_on_green('Red on green? Ick!'))
print(term.yellow('I can barely see it.'))
The available colors are...
blackredgreenyellowbluemagentacyanwhite
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:
.. code:: python
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: https://blessings.readthedocs.io/en/latest/#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:
.. code:: python
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:
.. code:: python
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
Moving The Cursor
When you want to move the cursor to output text at a specific spot, you have a few choices.
Moving Temporarily
Most often, you'll 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:
.. code:: python
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...
.. code:: python
with term.location(y=10):
print('We changed just the row.')
If you're doing a series of ``move`` calls (see below) and want to return the
cursor to its original position afterward, call ``location()`` with no
arguments, and it will do only the position restoring:
.. code:: python
with term.location():
print(term.move(1, 1) + 'Hi')
print(term.move(9, 9) + 'Mom')
Note that, since ``location()`` uses the terminal's built-in
position-remembering machinery, you can't usefully nest multiple calls. Use
``location()`` at the outermost spot, and use simpler things like ``move``
inside.
Moving Permanently
If you just want to move and aren't worried about returning, do something like this:
.. code:: python
from blessings import Terminal
term = Terminal()
print(term.move(10, 1) + 'Hi, mom!')
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.
How does all this work? These are simply more terminal capabilities, wrapped to
give them nicer names. The added wrinkle--that they take parameters--is also
given a pleasant treatment: rather than making you dig up tparm() all the
time, we simply make these capabilities into callable strings. You'd get the
raw capability strings if you were to just print them, but they're fully
parametrized if you pass params to them as if they were functions.
Consequently, 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/termi
