python/blessings/README.rst

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 =========
michael@0 2 Blessings
michael@0 3 =========
michael@0 4
michael@0 5 Coding with Blessings looks like this... ::
michael@0 6
michael@0 7 from blessings import Terminal
michael@0 8
michael@0 9 t = Terminal()
michael@0 10
michael@0 11 print t.bold('Hi there!')
michael@0 12 print t.bold_red_on_bright_green('It hurts my eyes!')
michael@0 13
michael@0 14 with t.location(0, t.height - 1):
michael@0 15 print 'This is at the bottom.'
michael@0 16
michael@0 17 Or, for byte-level control, you can drop down and play with raw terminal
michael@0 18 capabilities::
michael@0 19
michael@0 20 print '{t.bold}All your {t.red}bold and red base{t.normal}'.format(t=t)
michael@0 21 print t.wingo(2)
michael@0 22
michael@0 23 The Pitch
michael@0 24 =========
michael@0 25
michael@0 26 Blessings lifts several of curses_' limiting assumptions, and it makes your
michael@0 27 code pretty, too:
michael@0 28
michael@0 29 * Use styles, color, and maybe a little positioning without clearing the whole
michael@0 30 screen first.
michael@0 31 * Leave more than one screenful of scrollback in the buffer after your program
michael@0 32 exits, like a well-behaved command-line app should.
michael@0 33 * Get rid of all those noisy, C-like calls to ``tigetstr`` and ``tparm``, so
michael@0 34 your code doesn't get crowded out by terminal bookkeeping.
michael@0 35 * Act intelligently when somebody redirects your output to a file, omitting the
michael@0 36 terminal control codes the user doesn't want to see (optional).
michael@0 37
michael@0 38 .. _curses: http://docs.python.org/library/curses.html
michael@0 39
michael@0 40 Before And After
michael@0 41 ----------------
michael@0 42
michael@0 43 Without Blessings, this is how you'd print some underlined text at the bottom
michael@0 44 of the screen::
michael@0 45
michael@0 46 from curses import tigetstr, setupterm, tparm
michael@0 47 from fcntl import ioctl
michael@0 48 from os import isatty
michael@0 49 import struct
michael@0 50 import sys
michael@0 51 from termios import TIOCGWINSZ
michael@0 52
michael@0 53 # If we want to tolerate having our output piped to other commands or
michael@0 54 # files without crashing, we need to do all this branching:
michael@0 55 if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()):
michael@0 56 setupterm()
michael@0 57 sc = tigetstr('sc')
michael@0 58 cup = tigetstr('cup')
michael@0 59 rc = tigetstr('rc')
michael@0 60 underline = tigetstr('smul')
michael@0 61 normal = tigetstr('sgr0')
michael@0 62 else:
michael@0 63 sc = cup = rc = underline = normal = ''
michael@0 64 print sc # Save cursor position.
michael@0 65 if cup:
michael@0 66 # tigetnum('lines') doesn't always update promptly, hence this:
michael@0 67 height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0]
michael@0 68 print tparm(cup, height - 1, 0) # Move cursor to bottom.
michael@0 69 print 'This is {under}underlined{normal}!'.format(under=underline,
michael@0 70 normal=normal)
michael@0 71 print rc # Restore cursor position.
michael@0 72
michael@0 73 Phew! That was long and full of incomprehensible trash! Let's try it again,
michael@0 74 this time with Blessings::
michael@0 75
michael@0 76 from blessings import Terminal
michael@0 77
michael@0 78 term = Terminal()
michael@0 79 with term.location(0, term.height - 1):
michael@0 80 print 'This is', term.underline('pretty!')
michael@0 81
michael@0 82 Much better.
michael@0 83
michael@0 84 What It Provides
michael@0 85 ================
michael@0 86
michael@0 87 Blessings provides just one top-level object: ``Terminal``. Instantiating a
michael@0 88 ``Terminal`` figures out whether you're on a terminal at all and, if so, does
michael@0 89 any necessary terminal setup. After that, you can proceed to ask it all sorts
michael@0 90 of things about the terminal. Terminal terminal terminal.
michael@0 91
michael@0 92 Simple Formatting
michael@0 93 -----------------
michael@0 94
michael@0 95 Lots of handy formatting codes ("capabilities" in low-level parlance) are
michael@0 96 available as attributes on a ``Terminal``. For example::
michael@0 97
michael@0 98 from blessings import Terminal
michael@0 99
michael@0 100 term = Terminal()
michael@0 101 print 'I am ' + term.bold + 'bold' + term.normal + '!'
michael@0 102
michael@0 103 You can also use them as wrappers so you don't have to say ``normal``
michael@0 104 afterward::
michael@0 105
michael@0 106 print 'I am', term.bold('bold') + '!'
michael@0 107
michael@0 108 Or, if you want fine-grained control while maintaining some semblance of
michael@0 109 brevity, you can combine it with Python's string formatting, which makes
michael@0 110 attributes easy to access::
michael@0 111
michael@0 112 print 'All your {t.red}base {t.underline}are belong to us{t.normal}'.format(t=term)
michael@0 113
michael@0 114 Simple capabilities of interest include...
michael@0 115
michael@0 116 * ``bold``
michael@0 117 * ``reverse``
michael@0 118 * ``underline``
michael@0 119 * ``no_underline`` (which turns off underlining)
michael@0 120 * ``blink``
michael@0 121 * ``normal`` (which turns off everything, even colors)
michael@0 122 * ``clear_eol`` (clear to the end of the line)
michael@0 123 * ``clear_bol`` (clear to beginning of line)
michael@0 124 * ``clear_eos`` (clear to end of screen)
michael@0 125
michael@0 126 Here are a few more which are less likely to work on all terminals:
michael@0 127
michael@0 128 * ``dim``
michael@0 129 * ``italic`` and ``no_italic``
michael@0 130 * ``shadow`` and ``no_shadow``
michael@0 131 * ``standout`` and ``no_standout``
michael@0 132 * ``subscript`` and ``no_subscript``
michael@0 133 * ``superscript`` and ``no_superscript``
michael@0 134 * ``flash`` (which flashes the screen once)
michael@0 135
michael@0 136 Note that, while the inverse of ``underline`` is ``no_underline``, the only way
michael@0 137 to turn off ``bold`` or ``reverse`` is ``normal``, which also cancels any
michael@0 138 custom colors. This is because there's no way to tell the terminal to undo
michael@0 139 certain pieces of formatting, even at the lowest level.
michael@0 140
michael@0 141 You might notice that the above aren't the typical incomprehensible terminfo
michael@0 142 capability names; we alias a few of the harder-to-remember ones for
michael@0 143 readability. However, you aren't limited to these: you can reference any
michael@0 144 string-returning capability listed on the `terminfo man page`_ by the name
michael@0 145 under the "Cap-name" column: for example, ``term.rum``.
michael@0 146
michael@0 147 .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
michael@0 148
michael@0 149 Color
michael@0 150 -----
michael@0 151
michael@0 152 16 colors, both foreground and background, are available as easy-to-remember
michael@0 153 attributes::
michael@0 154
michael@0 155 from blessings import Terminal
michael@0 156
michael@0 157 term = Terminal()
michael@0 158 print term.red + term.on_green + 'Red on green? Ick!' + term.normal
michael@0 159 print term.bright_red + term.on_bright_blue + 'This is even worse!' + term.normal
michael@0 160
michael@0 161 You can also call them as wrappers, which sets everything back to normal at the
michael@0 162 end::
michael@0 163
michael@0 164 print term.red_on_green('Red on green? Ick!')
michael@0 165 print term.yellow('I can barely see it.')
michael@0 166
michael@0 167 The available colors are...
michael@0 168
michael@0 169 * ``black``
michael@0 170 * ``red``
michael@0 171 * ``green``
michael@0 172 * ``yellow``
michael@0 173 * ``blue``
michael@0 174 * ``magenta``
michael@0 175 * ``cyan``
michael@0 176 * ``white``
michael@0 177
michael@0 178 You can set the background color instead of the foreground by prepending
michael@0 179 ``on_``, as in ``on_blue``. There is also a ``bright`` version of each color:
michael@0 180 for example, ``on_bright_blue``.
michael@0 181
michael@0 182 There is also a numerical interface to colors, which takes an integer from
michael@0 183 0-15::
michael@0 184
michael@0 185 term.color(5) + 'Hello' + term.normal
michael@0 186 term.on_color(3) + 'Hello' + term.normal
michael@0 187
michael@0 188 term.color(5)('Hello')
michael@0 189 term.on_color(3)('Hello')
michael@0 190
michael@0 191 If some color is unsupported (for instance, if only the normal colors are
michael@0 192 available, not the bright ones), trying to use it will, on most terminals, have
michael@0 193 no effect: the foreground and background colors will stay as they were. You can
michael@0 194 get fancy and do different things depending on the supported colors by checking
michael@0 195 `number_of_colors`_.
michael@0 196
michael@0 197 .. _`number_of_colors`: http://packages.python.org/blessings/#blessings.Terminal.number_of_colors
michael@0 198
michael@0 199 Compound Formatting
michael@0 200 -------------------
michael@0 201
michael@0 202 If you want to do lots of crazy formatting all at once, you can just mash it
michael@0 203 all together::
michael@0 204
michael@0 205 from blessings import Terminal
michael@0 206
michael@0 207 term = Terminal()
michael@0 208 print term.bold_underline_green_on_yellow + 'Woo' + term.normal
michael@0 209
michael@0 210 Or you can use your newly coined attribute as a wrapper, which implicitly sets
michael@0 211 everything back to normal afterward::
michael@0 212
michael@0 213 print term.bold_underline_green_on_yellow('Woo')
michael@0 214
michael@0 215 This compound notation comes in handy if you want to allow users to customize
michael@0 216 the formatting of your app: just have them pass in a format specifier like
michael@0 217 "bold_green" on the command line, and do a quick ``getattr(term,
michael@0 218 that_option)('Your text')`` when you do your formatting.
michael@0 219
michael@0 220 I'd be remiss if I didn't credit couleur_, where I probably got the idea for
michael@0 221 all this mashing.
michael@0 222
michael@0 223 .. _couleur: http://pypi.python.org/pypi/couleur
michael@0 224
michael@0 225 Parametrized Capabilities
michael@0 226 -------------------------
michael@0 227
michael@0 228 Some capabilities take parameters. Rather than making you dig up ``tparm()``
michael@0 229 all the time, we simply make such capabilities into callable strings. You can
michael@0 230 pass the parameters right in::
michael@0 231
michael@0 232 from blessings import Terminal
michael@0 233
michael@0 234 term = Terminal()
michael@0 235 print term.move(10, 1)
michael@0 236
michael@0 237 Here are some of interest:
michael@0 238
michael@0 239 ``move``
michael@0 240 Position the cursor elsewhere. Parameters are y coordinate, then x
michael@0 241 coordinate.
michael@0 242 ``move_x``
michael@0 243 Move the cursor to the given column.
michael@0 244 ``move_y``
michael@0 245 Move the cursor to the given row.
michael@0 246
michael@0 247 You can also reference any other string-returning capability listed on the
michael@0 248 `terminfo man page`_ by its name under the "Cap-name" column.
michael@0 249
michael@0 250 .. _`terminfo man page`: http://www.manpagez.com/man/5/terminfo/
michael@0 251
michael@0 252 Height and Width
michael@0 253 ----------------
michael@0 254
michael@0 255 It's simple to get the height and width of the terminal, in characters::
michael@0 256
michael@0 257 from blessings import Terminal
michael@0 258
michael@0 259 term = Terminal()
michael@0 260 height = term.height
michael@0 261 width = term.width
michael@0 262
michael@0 263 These are newly updated each time you ask for them, so they're safe to use from
michael@0 264 SIGWINCH handlers.
michael@0 265
michael@0 266 Temporary Repositioning
michael@0 267 -----------------------
michael@0 268
michael@0 269 Sometimes you need to flit to a certain location, print something, and then
michael@0 270 return: for example, when updating a progress bar at the bottom of the screen.
michael@0 271 ``Terminal`` provides a context manager for doing this concisely::
michael@0 272
michael@0 273 from blessings import Terminal
michael@0 274
michael@0 275 term = Terminal()
michael@0 276 with term.location(0, term.height - 1):
michael@0 277 print 'Here is the bottom.'
michael@0 278 print 'This is back where I came from.'
michael@0 279
michael@0 280 Parameters to ``location()`` are ``x`` and then ``y``, but you can also pass
michael@0 281 just one of them, leaving the other alone. For example... ::
michael@0 282
michael@0 283 with term.location(y=10):
michael@0 284 print 'We changed just the row.'
michael@0 285
michael@0 286 If you want to reposition permanently, see ``move``, in an example above.
michael@0 287
michael@0 288 Pipe Savvy
michael@0 289 ----------
michael@0 290
michael@0 291 If your program isn't attached to a terminal, like if it's being piped to
michael@0 292 another command or redirected to a file, all the capability attributes on
michael@0 293 ``Terminal`` will return empty strings. You'll get a nice-looking file without
michael@0 294 any formatting codes gumming up the works.
michael@0 295
michael@0 296 If you want to override this--like if you anticipate your program being piped
michael@0 297 through ``less -r``, which handles terminal escapes just fine--pass
michael@0 298 ``force_styling=True`` to the ``Terminal`` constructor.
michael@0 299
michael@0 300 In any case, there is an ``is_a_tty`` attribute on ``Terminal`` that lets you
michael@0 301 see whether the attached stream seems to be a terminal. If it's false, you
michael@0 302 might refrain from drawing progress bars and other frippery, since you're
michael@0 303 apparently headed into a pipe::
michael@0 304
michael@0 305 from blessings import Terminal
michael@0 306
michael@0 307 term = Terminal()
michael@0 308 if term.is_a_tty:
michael@0 309 with term.location(0, term.height - 1):
michael@0 310 print 'Progress: [=======> ]'
michael@0 311 print term.bold('Important stuff')
michael@0 312
michael@0 313 Shopping List
michael@0 314 =============
michael@0 315
michael@0 316 There are decades of legacy tied up in terminal interaction, so attention to
michael@0 317 detail and behavior in edge cases make a difference. Here are some ways
michael@0 318 Blessings has your back:
michael@0 319
michael@0 320 * Uses the terminfo database so it works with any terminal type
michael@0 321 * Provides up-to-the-moment terminal height and width, so you can respond to
michael@0 322 terminal size changes (SIGWINCH signals). (Most other libraries query the
michael@0 323 ``COLUMNS`` and ``LINES`` environment variables or the ``cols`` or ``lines``
michael@0 324 terminal capabilities, which don't update promptly, if at all.)
michael@0 325 * Avoids making a mess if the output gets piped to a non-terminal
michael@0 326 * Works great with standard Python string templating
michael@0 327 * Provides convenient access to all terminal capabilities, not just a sugared
michael@0 328 few
michael@0 329 * Outputs to any file-like object, not just stdout
michael@0 330 * Keeps a minimum of internal state, so you can feel free to mix and match with
michael@0 331 calls to curses or whatever other terminal libraries you like
michael@0 332
michael@0 333 Blessings does not provide...
michael@0 334
michael@0 335 * Native color support on the Windows command prompt. However, it should work
michael@0 336 when used in concert with colorama_.
michael@0 337
michael@0 338 .. _colorama: http://pypi.python.org/pypi/colorama/0.2.4
michael@0 339
michael@0 340 Bugs
michael@0 341 ====
michael@0 342
michael@0 343 Bugs or suggestions? Visit the `issue tracker`_.
michael@0 344
michael@0 345 .. _`issue tracker`: https://github.com/erikrose/blessings/issues/new
michael@0 346
michael@0 347 License
michael@0 348 =======
michael@0 349
michael@0 350 Blessings is under the MIT License. See the LICENSE file.
michael@0 351
michael@0 352 Version History
michael@0 353 ===============
michael@0 354
michael@0 355 1.3
michael@0 356 * Add ``number_of_colors``, which tells you how many colors the terminal
michael@0 357 supports.
michael@0 358 * Made ``color(n)`` and ``on_color(n)`` callable to wrap a string, like the
michael@0 359 named colors can. Also, make them both fall back to the ``setf`` and
michael@0 360 ``setb`` capabilities (like the named colors do) if the ANSI ``setaf`` and
michael@0 361 ``setab`` aren't available.
michael@0 362 * Allow ``color`` attr to act as an unparametrized string, not just a
michael@0 363 callable.
michael@0 364 * Make ``height`` and ``width`` examine any passed-in stream before falling
michael@0 365 back to stdout. (This rarely if ever affects actual behavior; it's mostly
michael@0 366 philosophical.)
michael@0 367 * Make caching simpler and slightly more efficient.
michael@0 368 * Get rid of a reference cycle between Terminals and FormattingStrings.
michael@0 369 * Update docs to reflect that terminal addressing (as in ``location()``) is
michael@0 370 0-based.
michael@0 371
michael@0 372 1.2
michael@0 373 * Added support for Python 3! We need 3.2.3 or greater, because the curses
michael@0 374 library couldn't decide whether to accept strs or bytes before that
michael@0 375 (http://bugs.python.org/issue10570).
michael@0 376 * Everything that comes out of the library is now unicode. This lets us
michael@0 377 support Python 3 without making a mess of the code, and Python 2 should
michael@0 378 continue to work unless you were testing types (and badly). Please file a
michael@0 379 bug if this causes trouble for you.
michael@0 380 * Changed to the MIT License for better world domination.
michael@0 381 * Added Sphinx docs.
michael@0 382
michael@0 383 1.1
michael@0 384 * Added nicely named attributes for colors.
michael@0 385 * Introduced compound formatting.
michael@0 386 * Added wrapper behavior for styling and colors.
michael@0 387 * Let you force capabilities to be non-empty, even if the output stream is
michael@0 388 not a terminal.
michael@0 389 * Added the ``is_a_tty`` attribute for telling whether the output stream is a
michael@0 390 terminal.
michael@0 391 * Sugared the remaining interesting string capabilities.
michael@0 392 * Let ``location()`` operate on just an x *or* y coordinate.
michael@0 393
michael@0 394 1.0
michael@0 395 * Extracted Blessings from nose-progressive, my `progress-bar-having,
michael@0 396 traceback-shortcutting, rootin', tootin' testrunner`_. It provided the
michael@0 397 tootin' functionality.
michael@0 398
michael@0 399 .. _`progress-bar-having, traceback-shortcutting, rootin', tootin' testrunner`: http://pypi.python.org/pypi/nose-progressive/

mercurial