michael@0: ========= michael@0: Blessings michael@0: ========= michael@0: michael@0: Coding with Blessings looks like this... :: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: t = Terminal() michael@0: michael@0: print t.bold('Hi there!') michael@0: print t.bold_red_on_bright_green('It hurts my eyes!') michael@0: michael@0: with t.location(0, t.height - 1): michael@0: print 'This is at the bottom.' michael@0: michael@0: Or, for byte-level control, you can drop down and play with raw terminal michael@0: capabilities:: michael@0: michael@0: print '{t.bold}All your {t.red}bold and red base{t.normal}'.format(t=t) michael@0: print t.wingo(2) michael@0: michael@0: The Pitch michael@0: ========= michael@0: michael@0: Blessings lifts several of curses_' limiting assumptions, and it makes your michael@0: code pretty, too: michael@0: michael@0: * Use styles, color, and maybe a little positioning without clearing the whole michael@0: screen first. michael@0: * Leave more than one screenful of scrollback in the buffer after your program michael@0: exits, like a well-behaved command-line app should. michael@0: * Get rid of all those noisy, C-like calls to ``tigetstr`` and ``tparm``, so michael@0: your code doesn't get crowded out by terminal bookkeeping. michael@0: * Act intelligently when somebody redirects your output to a file, omitting the michael@0: terminal control codes the user doesn't want to see (optional). michael@0: michael@0: .. _curses: http://docs.python.org/library/curses.html michael@0: michael@0: Before And After michael@0: ---------------- michael@0: michael@0: Without Blessings, this is how you'd print some underlined text at the bottom michael@0: of the screen:: michael@0: michael@0: from curses import tigetstr, setupterm, tparm michael@0: from fcntl import ioctl michael@0: from os import isatty michael@0: import struct michael@0: import sys michael@0: from termios import TIOCGWINSZ michael@0: michael@0: # If we want to tolerate having our output piped to other commands or michael@0: # files without crashing, we need to do all this branching: michael@0: if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()): michael@0: setupterm() michael@0: sc = tigetstr('sc') michael@0: cup = tigetstr('cup') michael@0: rc = tigetstr('rc') michael@0: underline = tigetstr('smul') michael@0: normal = tigetstr('sgr0') michael@0: else: michael@0: sc = cup = rc = underline = normal = '' michael@0: print sc # Save cursor position. michael@0: if cup: michael@0: # tigetnum('lines') doesn't always update promptly, hence this: michael@0: height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0] michael@0: print tparm(cup, height - 1, 0) # Move cursor to bottom. michael@0: print 'This is {under}underlined{normal}!'.format(under=underline, michael@0: normal=normal) michael@0: print rc # Restore cursor position. michael@0: michael@0: Phew! That was long and full of incomprehensible trash! Let's try it again, michael@0: this time with Blessings:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: with term.location(0, term.height - 1): michael@0: print 'This is', term.underline('pretty!') michael@0: michael@0: Much better. michael@0: michael@0: What It Provides michael@0: ================ michael@0: michael@0: Blessings provides just one top-level object: ``Terminal``. Instantiating a michael@0: ``Terminal`` figures out whether you're on a terminal at all and, if so, does michael@0: any necessary terminal setup. After that, you can proceed to ask it all sorts michael@0: of things about the terminal. Terminal terminal terminal. michael@0: michael@0: Simple Formatting michael@0: ----------------- michael@0: michael@0: Lots of handy formatting codes ("capabilities" in low-level parlance) are michael@0: available as attributes on a ``Terminal``. For example:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: print 'I am ' + term.bold + 'bold' + term.normal + '!' michael@0: michael@0: You can also use them as wrappers so you don't have to say ``normal`` michael@0: afterward:: michael@0: michael@0: print 'I am', term.bold('bold') + '!' michael@0: michael@0: Or, if you want fine-grained control while maintaining some semblance of michael@0: brevity, you can combine it with Python's string formatting, which makes michael@0: attributes easy to access:: michael@0: michael@0: print 'All your {t.red}base {t.underline}are belong to us{t.normal}'.format(t=term) michael@0: michael@0: Simple capabilities of interest include... michael@0: michael@0: * ``bold`` michael@0: * ``reverse`` michael@0: * ``underline`` michael@0: * ``no_underline`` (which turns off underlining) michael@0: * ``blink`` michael@0: * ``normal`` (which turns off everything, even colors) michael@0: * ``clear_eol`` (clear to the end of the line) michael@0: * ``clear_bol`` (clear to beginning of line) michael@0: * ``clear_eos`` (clear to end of screen) michael@0: michael@0: Here are a few more which are less likely to work on all terminals: michael@0: michael@0: * ``dim`` michael@0: * ``italic`` and ``no_italic`` michael@0: * ``shadow`` and ``no_shadow`` michael@0: * ``standout`` and ``no_standout`` michael@0: * ``subscript`` and ``no_subscript`` michael@0: * ``superscript`` and ``no_superscript`` michael@0: * ``flash`` (which flashes the screen once) michael@0: michael@0: Note that, while the inverse of ``underline`` is ``no_underline``, the only way michael@0: to turn off ``bold`` or ``reverse`` is ``normal``, which also cancels any michael@0: custom colors. This is because there's no way to tell the terminal to undo michael@0: certain pieces of formatting, even at the lowest level. michael@0: michael@0: You might notice that the above aren't the typical incomprehensible terminfo michael@0: capability names; we alias a few of the harder-to-remember ones for michael@0: readability. However, you aren't limited to these: you can reference any michael@0: string-returning capability listed on the `terminfo man page`_ by the name michael@0: under the "Cap-name" column: for example, ``term.rum``. michael@0: michael@0: .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/ michael@0: michael@0: Color michael@0: ----- michael@0: michael@0: 16 colors, both foreground and background, are available as easy-to-remember michael@0: attributes:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: print term.red + term.on_green + 'Red on green? Ick!' + term.normal michael@0: print term.bright_red + term.on_bright_blue + 'This is even worse!' + term.normal michael@0: michael@0: You can also call them as wrappers, which sets everything back to normal at the michael@0: end:: michael@0: michael@0: print term.red_on_green('Red on green? Ick!') michael@0: print term.yellow('I can barely see it.') michael@0: michael@0: The available colors are... michael@0: michael@0: * ``black`` michael@0: * ``red`` michael@0: * ``green`` michael@0: * ``yellow`` michael@0: * ``blue`` michael@0: * ``magenta`` michael@0: * ``cyan`` michael@0: * ``white`` michael@0: michael@0: You can set the background color instead of the foreground by prepending michael@0: ``on_``, as in ``on_blue``. There is also a ``bright`` version of each color: michael@0: for example, ``on_bright_blue``. michael@0: michael@0: There is also a numerical interface to colors, which takes an integer from michael@0: 0-15:: michael@0: michael@0: term.color(5) + 'Hello' + term.normal michael@0: term.on_color(3) + 'Hello' + term.normal michael@0: michael@0: term.color(5)('Hello') michael@0: term.on_color(3)('Hello') michael@0: michael@0: If some color is unsupported (for instance, if only the normal colors are michael@0: available, not the bright ones), trying to use it will, on most terminals, have michael@0: no effect: the foreground and background colors will stay as they were. You can michael@0: get fancy and do different things depending on the supported colors by checking michael@0: `number_of_colors`_. michael@0: michael@0: .. _`number_of_colors`: http://packages.python.org/blessings/#blessings.Terminal.number_of_colors michael@0: michael@0: Compound Formatting michael@0: ------------------- michael@0: michael@0: If you want to do lots of crazy formatting all at once, you can just mash it michael@0: all together:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: print term.bold_underline_green_on_yellow + 'Woo' + term.normal michael@0: michael@0: Or you can use your newly coined attribute as a wrapper, which implicitly sets michael@0: everything back to normal afterward:: michael@0: michael@0: print term.bold_underline_green_on_yellow('Woo') michael@0: michael@0: This compound notation comes in handy if you want to allow users to customize michael@0: the formatting of your app: just have them pass in a format specifier like michael@0: "bold_green" on the command line, and do a quick ``getattr(term, michael@0: that_option)('Your text')`` when you do your formatting. michael@0: michael@0: I'd be remiss if I didn't credit couleur_, where I probably got the idea for michael@0: all this mashing. michael@0: michael@0: .. _couleur: http://pypi.python.org/pypi/couleur michael@0: michael@0: Parametrized Capabilities michael@0: ------------------------- michael@0: michael@0: Some capabilities take parameters. Rather than making you dig up ``tparm()`` michael@0: all the time, we simply make such capabilities into callable strings. You can michael@0: pass the parameters right in:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: print term.move(10, 1) michael@0: michael@0: Here are some of interest: michael@0: michael@0: ``move`` michael@0: Position the cursor elsewhere. Parameters are y coordinate, then x michael@0: coordinate. michael@0: ``move_x`` michael@0: Move the cursor to the given column. michael@0: ``move_y`` michael@0: Move the cursor to the given row. michael@0: michael@0: You can also reference any other string-returning capability listed on the michael@0: `terminfo man page`_ by its name under the "Cap-name" column. michael@0: michael@0: .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/ michael@0: michael@0: Height and Width michael@0: ---------------- michael@0: michael@0: It's simple to get the height and width of the terminal, in characters:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: height = term.height michael@0: width = term.width michael@0: michael@0: These are newly updated each time you ask for them, so they're safe to use from michael@0: SIGWINCH handlers. michael@0: michael@0: Temporary Repositioning michael@0: ----------------------- michael@0: michael@0: Sometimes you need to flit to a certain location, print something, and then michael@0: return: for example, when updating a progress bar at the bottom of the screen. michael@0: ``Terminal`` provides a context manager for doing this concisely:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: with term.location(0, term.height - 1): michael@0: print 'Here is the bottom.' michael@0: print 'This is back where I came from.' michael@0: michael@0: Parameters to ``location()`` are ``x`` and then ``y``, but you can also pass michael@0: just one of them, leaving the other alone. For example... :: michael@0: michael@0: with term.location(y=10): michael@0: print 'We changed just the row.' michael@0: michael@0: If you want to reposition permanently, see ``move``, in an example above. michael@0: michael@0: Pipe Savvy michael@0: ---------- michael@0: michael@0: If your program isn't attached to a terminal, like if it's being piped to michael@0: another command or redirected to a file, all the capability attributes on michael@0: ``Terminal`` will return empty strings. You'll get a nice-looking file without michael@0: any formatting codes gumming up the works. michael@0: michael@0: If you want to override this--like if you anticipate your program being piped michael@0: through ``less -r``, which handles terminal escapes just fine--pass michael@0: ``force_styling=True`` to the ``Terminal`` constructor. michael@0: michael@0: In any case, there is an ``is_a_tty`` attribute on ``Terminal`` that lets you michael@0: see whether the attached stream seems to be a terminal. If it's false, you michael@0: might refrain from drawing progress bars and other frippery, since you're michael@0: apparently headed into a pipe:: michael@0: michael@0: from blessings import Terminal michael@0: michael@0: term = Terminal() michael@0: if term.is_a_tty: michael@0: with term.location(0, term.height - 1): michael@0: print 'Progress: [=======> ]' michael@0: print term.bold('Important stuff') michael@0: michael@0: Shopping List michael@0: ============= michael@0: michael@0: There are decades of legacy tied up in terminal interaction, so attention to michael@0: detail and behavior in edge cases make a difference. Here are some ways michael@0: Blessings has your back: michael@0: michael@0: * Uses the terminfo database so it works with any terminal type michael@0: * Provides up-to-the-moment terminal height and width, so you can respond to michael@0: terminal size changes (SIGWINCH signals). (Most other libraries query the michael@0: ``COLUMNS`` and ``LINES`` environment variables or the ``cols`` or ``lines`` michael@0: terminal capabilities, which don't update promptly, if at all.) michael@0: * Avoids making a mess if the output gets piped to a non-terminal michael@0: * Works great with standard Python string templating michael@0: * Provides convenient access to all terminal capabilities, not just a sugared michael@0: few michael@0: * Outputs to any file-like object, not just stdout michael@0: * Keeps a minimum of internal state, so you can feel free to mix and match with michael@0: calls to curses or whatever other terminal libraries you like michael@0: michael@0: Blessings does not provide... michael@0: michael@0: * Native color support on the Windows command prompt. However, it should work michael@0: when used in concert with colorama_. michael@0: michael@0: .. _colorama: http://pypi.python.org/pypi/colorama/0.2.4 michael@0: michael@0: Bugs michael@0: ==== michael@0: michael@0: Bugs or suggestions? Visit the `issue tracker`_. michael@0: michael@0: .. _`issue tracker`: https://github.com/erikrose/blessings/issues/new michael@0: michael@0: License michael@0: ======= michael@0: michael@0: Blessings is under the MIT License. See the LICENSE file. michael@0: michael@0: Version History michael@0: =============== michael@0: michael@0: 1.3 michael@0: * Add ``number_of_colors``, which tells you how many colors the terminal michael@0: supports. michael@0: * Made ``color(n)`` and ``on_color(n)`` callable to wrap a string, like the michael@0: named colors can. Also, make them both fall back to the ``setf`` and michael@0: ``setb`` capabilities (like the named colors do) if the ANSI ``setaf`` and michael@0: ``setab`` aren't available. michael@0: * Allow ``color`` attr to act as an unparametrized string, not just a michael@0: callable. michael@0: * Make ``height`` and ``width`` examine any passed-in stream before falling michael@0: back to stdout. (This rarely if ever affects actual behavior; it's mostly michael@0: philosophical.) michael@0: * Make caching simpler and slightly more efficient. michael@0: * Get rid of a reference cycle between Terminals and FormattingStrings. michael@0: * Update docs to reflect that terminal addressing (as in ``location()``) is michael@0: 0-based. michael@0: michael@0: 1.2 michael@0: * Added support for Python 3! We need 3.2.3 or greater, because the curses michael@0: library couldn't decide whether to accept strs or bytes before that michael@0: (http://bugs.python.org/issue10570). michael@0: * Everything that comes out of the library is now unicode. This lets us michael@0: support Python 3 without making a mess of the code, and Python 2 should michael@0: continue to work unless you were testing types (and badly). Please file a michael@0: bug if this causes trouble for you. michael@0: * Changed to the MIT License for better world domination. michael@0: * Added Sphinx docs. michael@0: michael@0: 1.1 michael@0: * Added nicely named attributes for colors. michael@0: * Introduced compound formatting. michael@0: * Added wrapper behavior for styling and colors. michael@0: * Let you force capabilities to be non-empty, even if the output stream is michael@0: not a terminal. michael@0: * Added the ``is_a_tty`` attribute for telling whether the output stream is a michael@0: terminal. michael@0: * Sugared the remaining interesting string capabilities. michael@0: * Let ``location()`` operate on just an x *or* y coordinate. michael@0: michael@0: 1.0 michael@0: * Extracted Blessings from nose-progressive, my `progress-bar-having, michael@0: traceback-shortcutting, rootin', tootin' testrunner`_. It provided the michael@0: tootin' functionality. michael@0: michael@0: .. _`progress-bar-having, traceback-shortcutting, rootin', tootin' testrunner`: http://pypi.python.org/pypi/nose-progressive/