Commits

Jonathan Eunice committed 69f33e7

Doc tweaks. FmtException added. __version__ added. Ready to push 1.0.3.
***
Ready to push 1.0.3

  • Participants
  • Parent commits d0abe13

Comments (0)

Files changed (8)

+Changes
+=======
+
+1.0.3 (September 16, 2013)
+''''''''''''''''''''''''''
+
+  * Added ``FmtException`` class
+  * Tightened imports for namespace cleanliness.
+  * Doc tweaks.
+  * Added ``__version__`` metadata common to module, ``setup.py``, and docs.
+
 1.0.2 (September 14, 2013)
---------------------------
+''''''''''''''''''''''''''
 
-  * Added ``prefix`` and ``suffix`` options to ``say`` and ``fmt``, 
+  * Added ``prefix`` and ``suffix`` options to ``say`` and ``fmt``,
     along with docs and tests.
 
 1.0.1 (September 13, 2013)
---------------------------
+''''''''''''''''''''''''''
 
   * Moved main documentation to Sphinx format in ``./docs``, and hosted
     the long-form documentation on readthedocs.org. ``README.rst`` now
     an abridged version/teaser for the module.
 
 1.0 (September 2013)
---------------------
+''''''''''''''''''''
 
   * Cleaned up source for better PEP8 conformance
-  * Bumped version number to 1.0 as part of move to `semantic 
+  * Bumped version number to 1.0 as part of move to `semantic
     versioning <http://semver.org>`_, or at least enough of it so
     as to not screw up Python installation procedures (which don't
     seem to understand 0.401 is a lesser version that 0.5, because
     401 > 5).
-
 project = u'say'
 copyright = u'2013, Jonathan Eunice'
 
+_PY3 = sys.version_info[0] == 3
+
+def getmetadata(filepath):
+    """
+    Return a dictionary of metadata from a file, without importing it. This
+    trick needed because importing can set off ImportError, in that setup.py
+    runs by definition before the modules it sets up (or their dependencies) are
+    available.
+    """
+    if _PY3:
+        exec(open(filepath).read())
+        return vars()
+    else:
+        execfile(filepath)
+        return locals()
+
+metadata = getmetadata('../say/version.py')
+
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 # The short X.Y version.
 version = '1.0'
 # The full version, including alpha/beta/rc tags.
-release = '1.0.2'
+release = metadata['__version__']
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 Often the job of output is not about individual text lines, but about creating
 multi-line files such as scripts and reports. This often leads away from standard
 output mechanisms toward template pakcages, but ``say`` has you covered here as
-well.::
+well.
+
+::
 
     from say import Text
 
 
         # Output the results of a ping command to the given file
 
-        ping {hostname} >{filepath}
+        ping {hostname!r} >{filepath!r}
     """
 
     script.write_to("script.sh")
 
+Then ``script.sh`` will contain::
+
+    !#/bin/bash
+
+    # Output the results of a ping command to the given file
+
+    ping 'server1234.example.com' >'ping-results.txt'
+
+
 ``Text`` objects are basically a list of text lines. In most cases, when you add
 text (either as multi-line strings or lists of strings), ``Text`` will
 automatically interopolate variables the same way ``say`` does. One can
 If you need more, ``textdata`` can be used alongside ``Text``.
 
 
-Your Own Iterpolators
-=====================
+Iterpolators and Exceptions
+===========================
 
-If you want to write your own functions that take strings and interpolate ``{}``
-format tempaltes in them, you can look at ``say`` souce code and see how to
-do it (see e.g. ``say.text.Text``). But there's an easier way::
+You may want to write your own functions that take strings
+and interpolate ``{}``
+format tempaltes in them. The easy way is::
 
     from say import caller_fmt
 
 context of the caller of ``ucfmt()``, which is exactly where those values would
 reside. *Voila!*
 
+And example of how this can work--and a useful tool in its own right--is ``FmtException``.
+If you want to have comprehensible error messages when something goes wrong, you
+could use ``fmt()``::
+
+    if bad_thing_has_happened:
+        raise ValueError(fmt("Parameters {x!r} or {y!r} invalid."))
+
+But if you define your own exceptions, consider subclassing ``FmtException``::
+
+    class InvalidParameters(FmtException, ValueError):
+        pass
+
+    ...
+
+    if bad_thing_has_happened:
+        raise InvalidParameters("Parameters {x!r} or {y!r} invalid.")
+
+You'll save a few characters, and the code will be simpler and more comprehensible.
+
+
 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.
+software that should work across the versions, without the hassle
+of ``from __future__ import print_function``.
 
 ``say`` attempts to mask some of the quirky compexities of the 2-to-3 divide,
 such as string encodings and codec use.
 
-
 Alternatives
 ============
 
        # 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::
     `pytest <http://pypi.python.org/pypi/pytest>`_
     and `tox <http://pypi.python.org/pypi/tox>`_. ``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).
+    Python: 2.6, 2.7, 3.2, and 3.3, as well as PyPy 2.1 (based on 2.7.3).
 
  *  ``say`` has greater ambitions than just simple template printing. It's part
     of a larger rethinking of how output should be formatted. ``show()`` and ``Text``
 
 .. toctree::
    :maxdepth: 2
+
+   CHANGES
-from say.core import *
-from say.text import Text
+from say.core import say, Say, fmt, caller_fmt, stdout, stderr, FmtException
+from say.text import Text
+from say.version import __version__
 
 def caller_fmt(*args, **kwargs):
     """
-    Like fmt(), but iterpolating strings not from the caller's context, but the caller's caller's
-    context.  It sounds uber meta, but it helps easily make other routines be able to do what
-    fmt() can do.
+    Like ``fmt()``, but iterpolating strings not from the caller's context, but
+    the caller's caller's context. It sounds uber meta, but it helps easily make
+    other routines be able to do what ``fmt()`` can do.
     """
     kwargs['_callframe'] = inspect.currentframe().f_back.f_back
     return fmt(*args, **kwargs)
+
+
+class FmtException(Exception):
+    """
+    An exception class that formats its arguments in the calling context.
+    Also, an example of how ``caller_fmt`` can be used.
+    """
+    def __init__(self, message):
+        formatted = caller_fmt(message)
+        super(FmtException, self).__init__(formatted)
 #! /usr/bin/env python
 from setuptools import setup
 import sys
+import os
 
+_PY3 = sys.version_info[0] == 3
 
 def linelist(text):
     """
     Returns each non-blank line in text enclosed in a list.
     """
     return [ l.strip() for l in text.strip().splitlines() if l.split() ]
-    
+
     # The double-mention of l.strip() is yet another fine example of why
     # Python needs en passant aliasing.
 
+def getmetadata(filepath):
+    """
+    Return a dictionary of metadata from a file, without importing it. This
+    trick needed because importing can set off ImportError, in that setup.py
+    runs by definition before the modules it sets up (or their dependencies) are
+    available.
+    """
+    if _PY3:
+        exec(open(filepath).read())
+        return vars()
+    else:
+        execfile(filepath)
+        return locals()
+
+metadata = getmetadata('./say/version.py')
+
 setup(
     name='say',
-    version="1.0.2",
+    version=metadata['__version__'],
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Super-simple templated printing. E.g.: say("Hello, {whoever}!", indent=1)',
 
 import six
 import os
-from say import Say, fmt, stdout, caller_fmt
+from say import Say, fmt, stdout, caller_fmt, FmtException
 import pytest
 
 globalvar = 99
     y = 'x'
 
     assert ucfmt("{y!r} is {x}") == "'X' IS 12"
+
+def test_FmtException():
+
+    class Blooper(FmtException, ValueError):
+        pass
+
+    x = 12
+    try:
+        raise Blooper('{x} is cray, yo')
+    except ValueError as e:
+        assert e.args[0] == '12 is cray, yo'
+    except Exception:
+        assert False

test/test_text.py

     t.append('and {s!r}')
     assert t.text == "1 < 2\nthis thing x\nand 'this thing'"
 
+def test_scripting_example():
+
+    hostname = 'server1234.example.com'
+    filepath = 'ping-results.txt'
+
+    script = Text()
+    script += """
+        !#/bin/bash
+
+        # Output the results of a ping command to the given file
+
+        ping {hostname!r} >{filepath!r}
+    """
+
+    assert str(script) == "!#/bin/bash\n\n# Output the results of a ping command to the given file\n\nping 'server1234.example.com' >'ping-results.txt'"
+
 
 def test_append_raw():
     t = Text()