Source

say / README.rst

Full commit
Jonathan Eunice c968e29 
Jonathan Eunice f33c61e 
Jonathan Eunice 51852a9 

Jonathan Eunice f33c61e 
Jonathan Eunice 51852a9 

Jonathan Eunice 843bcb6 


Jonathan Eunice f26236e 
Jonathan Eunice 7dcd2af 
Jonathan Eunice f26236e 


Jonathan Eunice 93396db 
Jonathan Eunice f26236e 

Jonathan Eunice ea1a709 

Jonathan Eunice f33c61e 





Jonathan Eunice ea1a709 
Jonathan Eunice f33c61e 

Jonathan Eunice 100edc8 
Jonathan Eunice f33c61e 

Jonathan Eunice 6ecd21a 
Jonathan Eunice ea1a709 
Jonathan Eunice 51852a9 


Jonathan Eunice ea1a709 

Jonathan Eunice 2dbb021 
Jonathan Eunice f33c61e 

Jonathan Eunice 51852a9 
Jonathan Eunice f33c61e 
Jonathan Eunice f41efe5 


Jonathan Eunice d45b64f 
Jonathan Eunice 2dbb021 


Jonathan Eunice f33c61e 
Jonathan Eunice 4f7c99d 







Jonathan Eunice ea1a709 




Jonathan Eunice 8b1a283 
Jonathan Eunice ea1a709 





Jonathan Eunice 4f7c99d 







Jonathan Eunice ea1a709 
Jonathan Eunice 9564137 

Jonathan Eunice f33c61e 
Jonathan Eunice 51852a9 
Jonathan Eunice 7dcd2af 

Jonathan Eunice f33c61e 
Jonathan Eunice 2dbb021 
Jonathan Eunice f33c61e 
Jonathan Eunice 7dcd2af 
Jonathan Eunice f33c61e 
Jonathan Eunice 51852a9 
Jonathan Eunice f26236e 

Jonathan Eunice 7dcd2af 
Jonathan Eunice 51852a9 
Jonathan Eunice 2dbb021 

Jonathan Eunice 2feb063 
Jonathan Eunice 2dbb021 
Jonathan Eunice d45b64f 



Jonathan Eunice 51852a9 
Jonathan Eunice 2dbb021 





Jonathan Eunice f33c61e 
Jonathan Eunice 51852a9 



Jonathan Eunice 2dbb021 


Jonathan Eunice 51852a9 

Jonathan Eunice 2dbb021 
Jonathan Eunice 93396db 

Jonathan Eunice f26236e 


























Jonathan Eunice 93396db 







Jonathan Eunice f26236e 

Jonathan Eunice 93396db 




Jonathan Eunice f26236e 
Jonathan Eunice 93396db 




Jonathan Eunice f26236e 
Jonathan Eunice 93396db 





Jonathan Eunice 7e2c400 
Jonathan Eunice 2dbb021 





Jonathan Eunice f26236e 

Jonathan Eunice 2dbb021 
Jonathan Eunice f26236e 



Jonathan Eunice 2dbb021 
Jonathan Eunice f26236e 
Jonathan Eunice 51852a9 








Jonathan Eunice f26236e 

Jonathan Eunice 51852a9 




Jonathan Eunice f33c61e 



Jonathan Eunice 51852a9 
Jonathan Eunice f33c61e 

















Jonathan Eunice 100edc8 

Jonathan Eunice 2dbb021 
Jonathan Eunice f26236e 
Jonathan Eunice 100edc8 
Jonathan Eunice a93f8a7 
Jonathan Eunice 2dbb021 

Jonathan Eunice f26236e 
Jonathan Eunice 2dbb021 
Jonathan Eunice 51852a9 


Jonathan Eunice a93f8a7 

Jonathan Eunice 51852a9 

















Jonathan Eunice f33c61e 

Jonathan Eunice 6ecd21a 
Jonathan Eunice e70dfe3 






Jonathan Eunice 93396db 
Jonathan Eunice f33c61e 
Jonathan Eunice 4f7c99d 





Jonathan Eunice e70dfe3 
Jonathan Eunice 4f7c99d 

Jonathan Eunice f33c61e 
Jonathan Eunice e70dfe3 



Jonathan Eunice 100edc8 
Jonathan Eunice e70dfe3 


Jonathan Eunice 4f7c99d 





Jonathan Eunice 100edc8 
Jonathan Eunice f33c61e 





Jonathan Eunice 100edc8 



Jonathan Eunice f33c61e 
Jonathan Eunice 100edc8 
``print``, ``format``, and ``%``, evolved.

    **Q:** It's been *forty years* since ``C`` introduced ``printf()`` and the basic
    formatted printing of positional parameters. Isn't it time for an upgrade?

    **A:** Yes! ZOMG, yes!
    
``say`` supplements or replaces Python's ``print``
statement/function, ``format`` function/method, and ``%`` string interpolation
operator with higher-level facilities:

 *  Straightforward string formatting with DRY, Pythonic
    templates that piggyback the built in ``format()`` method,  
    formatting syntax, and well-proven underlying engine.
 *  A single output mechanism compatible with both Python 2.x and Python 3.x.
 *  Indentation and wrapping (to help stucture output)
 *  Convenience printing functions for horizontal rules (lines), titles, and
    vertical whitespace.
 *  A parallel function, ``show()``,  that displays the current value of
    variables.

Usage
=====

::

    from say import say, fmt, show
    
    x = 12
    nums = list(range(4))
    
    say("There are {x} things.")
    say("Nums has {len(nums)} items: {nums}")
    
yields::

    There are 12 things.
    Nums has 4 items: [0, 1, 2, 3]

``say`` is basically a simpler, nicer recasting of::
    
    print "There are {} things.".format(x)
    print "Nums has {} items: {}".format(len(nums), nums)
    
(NB in Python 2.6 one must number each of the ``{}`` placeholders--e.g. ``"Nums
has {0} items: {1}"``-- in order to avoid a ``ValueError: zero length field name
in format`` error. Python 2.7 and later assume the placeholders are sequential.)
    
The more items that are being printed, and the complicated the ``format``
invocation, the more valuable having it stated in-line becomes. Note that full
expressions are are supported. They are evaluated in the context of the caller.

Debug Printing
==============

Sometimes programs print so that users can see things, and sometimes they print
so that develpopers can. ``say()`` is the API for standard printing functions. A
parallel API, ``show()``, helps rapidly print the current state of variables. It
enables "debugging print statements" that don't require the craptastic
repetition of ``print "x = {x}".format(x)``. Instead, just::

    show(x)
    show(nums)
    show(x, nums, len(nums))
    
yields::

    x: 12
    nums: [0, 1, 2, 3]
    x: 12  nums: [0, 1, 2, 3]  len(nums): 4

All of the standard keyword options for ``say()`` work for ``show()`` as well.
If you'd like to see where the data is being produced, ``show.set(where=True)``
will turn on location reporting.

Sadly, because Python provides weaker introspection during
interactive operation, (see e.g. `this <http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell>`_)
``show()`` is somewhat limited in interactive use. It works at the main
level and usually in imported modules, but does work within the body of functions defined
interactively.

Printing Where You Like
=======================

``say()`` writes to a list of files--by default just ``sys.stdout``. But
with it simple configuration call, it will write to different--even
multiple--files::

    from say import say, stdout
    
    say.setfiles([stdout, "report.txt"])
    say(...)   # now prints to both stdout and report.txt

This has the advantage of allowing you to both capture and see
program output, without changing
any code (other than the config statement). You can also define your own targeted ``Say`` instances::

    from say import say, Say, stderr
    
    err = say.clone().setfiles([stderr, 'error.txt'])
    err("Failed with error {errcode}")  # writes to stderr, error.txt
    
Note that ``stdout`` and ``stderr`` are just convenience aliases to
the respective 
``sys`` equivalents.

Printing When You Like
======================

If you want to stop printing for a while::

    say.set(silent=True)  # no printing until set to False
    
Or transiently::

    say(...stuff..., silent=not verbose) # prints iff bool(verbose) is True

Of course, you don't have to print to any file. There's a predefined sayer
``fmt()`` that works exactly like ``say()`` and inherits most of
its options, but 
doesn't print. (The
``C`` analogy: ``say`` **:** ``fmt`` **::** ``printf`` **:** ``sprintf``.)

Indentation and Wrapping
========================

Indentation is a common way to display data hierarchically. ``say`` will
help you manage it. For example::

    say('TITLE')
    for item in items:
        say(item, indent=1)
   
will indent the items by one indentation level (by default, each indent
level is four spaces, but
you can change that with the ``indent_str`` option). 

If you want to change the default indentation level::

    say.set(indent=1)      # to an absolute level
    say.set(indent='+1')   # strings => set relative to current level
    
    ...
    
    say.set(indent=0)      # to get back to the default, no indent

Or you can use a ``with`` construct::

    with say.settings(indent='+1'):
        say(...)
        
        # anything say() emits here will be auto-indented +1 levels
        
    # anything say() emits here, after the with, will not be indented +1

And if you have a lot of data or text to print, you can easily wrap it::

    say("This is a really long...blah blah blah", wrap=40)
    
Will automatically wrap the text to the given width (using Python's standard ``textwrap`` module).

While it's easy enough for any ``print`` statement or function to have a few
space characters added to its format string, it's easy to mistakenly type too
many or too few spaces, or to forget to type them in some format strings. And if
you're indenting strings that themselves may contain multiple lines, the simple
``print`` approach breaks because won't take multi-line strings into account.
And it won't be integrated with wrapping.

``say``, however, simply handles the indent level and wrapping, and it properly
handles the multi-line string case. Subsequent lines will be just as nicely and
correctly indented as the first one--something not otherwise easily accomplished
without adding gunky, complexifying string manipulation code to every place in
your program that prints strings.

This starts to illustrate the "do the right thing" philosophy behind ``say``. So
many languages' printing and formatting functions a restricted to "outputting
values" at a low level. They may format basic data types, but they don't provide
straightforward ways to do neat text transformations like indentation that let
programmers rapidly provide correct, highly-formatted ouput. Over time, ``say``
will provide higher-level formatting options. For now: indentation and wrapping.

Encodings
=========

``say()`` and 
``fmt()`` try to work with Unicode strings, for example providing them as
return values. But character encodings remain a fractious and often exasperating
part of IT. When writing formatted strings, ``say`` handles this by encoding
into ``utf-8``.

If you are using strings containing ``utf-8`` rather than Unicode characters, ``say`` 
may complain. But it complains in the same places the built-in ``format()`` does,
so no harm done. (Python 3 doesn't generally allow ``utf-8`` in strings, so it's
cleaner on this front.)

You can get creative with the encoding::

    say('I am a truck!', encoding='base64')  # SSBhbSBhIHRydWNrIQo=

Or change the default::

    say.set(encoding='rot-13')
    
Knock yourself out with `all the exciting opportunites <http://docs.python.org/library/codecs.html#standard-encodings>`_!
If you really want the formatted text returned just as it is written to files,
use the ``encoded`` option. Set to ``True`` and it returns text in the output
encoding. Or set to an actual encoding name, and that will be the return encoding.

``say()`` returns the formatted text with one small tweak: it removes the final
newline if a newline is the very last character. Though odd, this is exactly
what you need if you're going to ``print`` or
``say`` the resulting text without a gratuitous "extra" newline.

Titles and Horizontal Rules
===========================

``say`` defines a few convenience formatting functions::

    say.title('Errors', sep='-')
    for i,e in enumerate(errors, start=1):
        say("{i:3}: {e['name'].upper()}")
        
might yield::

    --------------- Errors ---------------
      1: I/O ERROR
      2: COMPUTE ERROR

A similar method ``hr`` produces just a horizontal line, like
the HTML ``<hr>`` element. For either, one can optionally 
specify the width (``width``), character repeated to make the line (``sep``),
and vertical separation/whitespace above and below the item (``vsep``).
Good options for the separator might be be '-', '=', or parts of the `Unicode 
box drawing character set <http://en.wikipedia.org/wiki/Box-drawing_character>`_.

Python 3
========

Say works virtually the same way in Python 2 and Python 3. This can simplify 
software that should work across the versions, without all the ``from __future__
import print_function`` hassle.

``say`` attempts to mask some of the quirky compexities of the 2-to-3 divide,
such as string encodings and codec use. 

Alternatives
============

 * `ScopeFormatter <http://pypi.python.org/pypi/ScopeFormatter>`_
   provides variable interpolation into strings. It is amazingly
   compact and elegant. Sadly, it only interpolates Python names, not full
   expressions. ``say`` has full expressions, as well as a framework for
   higher-level printing features beyond ``ScopeFormatter``'s...um...scope.
   
 * Even simpler are invocations of ``%`` or ``format()``
   using ``locals()``. E.g.::
   
       name = "Joe"
       print "Hello, %(name)!" % locals()
       # or
       print "Hello, {name}!".format(**locals())
       
   Unfortunately this has even more limitations than ``ScopeFormatter``: it only supports
   local variables, not globals or expressions. And the interpolation code seems
   gratuitous. Simpler::
   
      say("Hello, {name}!")

Notes
=====

 *  The ``say`` name was inspired by Perl's `say <http://perldoc.perl.org/functions/say.html>`_,
    but the similarity stops there.
   
 *  Automated multi-version testing with the wonderful
    `pytest <http://pypi.python.org/pypi/pytest>`_
    and `tox <http://pypi.python.org/pypi/tox>`_ modules has commenced. ``say`` is now
    successfully packaged for, and tested against, all late-model verions of
    Python: 2.6, 2.7, 3.2, and 3.3, as well as PyPy 1.9 (based on 2.7.2).
 
 *  Debug printing via ``show()`` is now operational.
    Draft support for interactive Python and iPython. Works within imported modules,
    and at the interactive prompt, but cannot be used
    within interactively defined functions. Unknown whether that is a hard limit
    of Python introspection, or something that can be worked around over time.

 *  ``say`` has greater ambitions than just simple template printing. It's part
    of a larger rethinking of how output should be formatted. ``show()``
    is an initial down-payment. Stay tuned for more.
 
 *  In addition to being a practical module in its own right, ``say`` is
    testbed for `options <http://pypi.python.org/pypi/options>`_, a package
    that provides high-flexibility option, configuration, and parameter
    management.
 
 *  The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
    `@jeunice on Twitter <http://twitter.com/jeunice>`_
    welcomes your comments and suggestions.
    
To-Dos
======

 *  Provide code that allows ``pylint`` to see that variables used inside
    the ``say`` and ``fmt`` format strings are indeed thereby used.

Installation
============

::

    pip install say

To ``easy_install`` under a specific Python version (3.3 in this example)::

    python3.3 -m easy_install say
    
(You may need to prefix these with "sudo " to authorize installation.)