Commits

Jonathan Eunice committed 1d5b364

Improved pretty printing. Show accepts lambdas to control visibility. Added noshow for no visibility. General cleanups.

1.0.1 (September 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)
''''''''''''''''''''

* Improved robustness for interactive use. If names cannot be
detected, still gives value result with ``?`` pseudo-name.
* Improved typenames for ``show.dir`` and ``show.props``
* Improved ``show.inout`` with full call string on function
return. A bit verbose in small tests, but too easy to lose
"what was this called with??" context in real-scale usage
unless there is clear indication of how the function was
called.
* Improved omission of probably useless display properties
via ``omit`` keyword.
* Began to add support for showing properties even when proxied through
another object. Currently limited to selected SQLAlchemy and
Flask proxies. More
to come.
* Cleaned up source for better (though still quite imperfect),
PEP8 conformance
* 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).
* Probably several other things I've now forgotten.

Comments (0)

Files changed (11)

+1.0.2 (September 2013)
+''''''''''''''''''''''
 
-1.0.1
-=====
+  * Improved pretty printing of code snippets for ``@show.inout``
+    and ``@show.retval`` decorators.
+  * Made ``show`` also accept lamdbas to link to variable values.
+  * Added ``noshow`` object for easy turning off of showing.
+  * General cleanups. Tightened imports. Tweaked docs. Switched to
+    ``FmtException`` from ``say>=1.0.4``, and separated extensions
+    into won module.
+  * Drove version information into ``version.py``
+
+1.0.1 (September 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
-===
+1.0 (September 2013)
+''''''''''''''''''''
 
   * Improved robustness for interactive use. If names cannot be
     detected, still gives value result with ``?`` pseudo-name.
 # The short X.Y version.
 version = '1.0'
 # The full version, including alpha/beta/rc tags.
-release = '1.0'
+release = '1.0.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
     show_verbose.set(show=verbose_flag)
     show_verbose(x, y, z)
 
+For a more fire-and-forget experience, try setting visibility with a lambda
+parameter::
+
+    debug = True
+    show.set(show=lambda: debug)
+
+Then, whenever ``debug`` is truthy, values will be shown. When ``debug`` is
+falsy, values will not be shown.
+
+When you really, truly want ``show``'s output to
+disappear, and want to minimize
+overhead, but don't want to
+change your source code (lest you need those debug printing statements again
+shortly), try::
+
+    show = noshow
+
+This one line will replace the ``show`` object (and any of its clones) with
+parallel ``NoShow`` objects that simply don't do anything or print any output.
+
+.. note:: This assignment should be done in a global context. If done inside a
+    function, you'll need to add a corresponding ``global show`` declaration.
+
+As an alternative, you can always have::
+
+    from show import show
+    from show import noshow as show
+
+Then comment out the ``noshow`` line when you do want debug printing.
+
+.. note:: A little care is required to configure global non-showing behavior
+    if you're using ``show``'s function decorators such as ``@show.inout``.
+    Decorators are evaluated earlier in program execution than the "main flow"
+    of program execution, so it's a good idea to define the lambda or ``noshow``
+    control of visibility at the top of your program.
+
 Showing Collections
 ===================
 
 from setuptools import setup
 import sys, platform
 
+
+_PY3 = sys.version_info[0] == 3
+
 def linelist(text):
     """
     Returns each non-blank line in text enclosed in a list.
 elif 'win32' in system:
     install_requires += ['pyreadline']
 
+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('./show/version.py')
+
 
 setup(
     name='show',
-    version='1.0.2',
+    version=metadata['__version__'],
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Debug print statements, done right. E.g. show(x)',
 
-from show.core import *
-from show.linecacher import *
+from show.core import show, Show, noshow, NoShow
+from show.version import __version__
     def method(self): pass
 _xyz = _XYZ()
 
+if _PY3:
+    unicode = str
+
 FUNKY = (function, module, type, type(_XYZ.method), type(_xyz.method), type(len)) # funky => functional infrastructure
 
 class Show(object):
         omit=Transient,     # vars not to print (for those like show.locals,
                             # show.dir, etc that might default to many)
         fmtfunc=repr,       # formatting func used to format each value
+        fmtcode=unicode,    # formatting used to format code snippets
         show=True,          # show or not
     )
 
     def __init__(self, **kwargs):
-        self.options = Show.options.push(kwargs)
+        self.options = self.opts = Show.options.push(kwargs)
         self.say = Say(retvalue=self.options.retvalue)
-        self.opts = None  # per call options, set on each call to reflect transient state
         self._watching = {} # remembers last value of variables for given frames
 
     def call_location(self, caller):
         """
         return self.say.escape(self.opts.fmtfunc(value))
 
+    def code_repr(self, code):
+        """
+        Return a formatted string for code. If there are any internal brace
+        characters, they are doubled so that they are not interpreted as format
+        template characters when the composed string is eventually output by
+        ``say``.
+        """
+        return self.say.escape(self.opts.fmtcode(code))
+
     def arg_format(self, name, value, caller, opts):
         """
         Format a single argument. Strings returned formatted.
         locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
 
         # Emit the result string, and optionally return it
-        kwargs['silent'] = not opts.show
+        silent = not lambda_eval(opts.show)
+        kwargs['silent'] = silent
         kwargs['retvalue'] = opts.retvalue
         retval = self.say(*locval, **kwargs)
-        if opts.retvalue:
+        if opts.retvalue and not silent:
             return retval
 
     def __gt__(self, other):
         locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
 
         # Emit the result string, and optionally return it
+        kwargs['silent'] = not lambda_eval(opts.show)
+        kwargs['retvalue'] = opts.retvalue
+
         retval = self.say(*locval, **kwargs)
         if opts.retvalue:
             return retval
         locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
 
         # Emit the result string, and optionally return it
+        kwargs['silent'] = not lambda_eval(opts.show)
+        kwargs['retvalue'] = opts.retvalue
+
         retval = self.say(*locval, **kwargs)
         if opts.retvalue:
             return retval
         func_code = func.__code__ if _PY3 else func.func_code
         argnames = func_code.co_varnames[:func_code.co_argcount]
         fname = func.__name__ if _PY3 else func.func_name
+        opts = self.opts
 
         def echo_func(*args, **kwargs):
             argitems = list(zip(argnames,args)) + list(kwargs.items())
             argcore = ', '.join('{0}={1!r}'.format(*argtup) for argtup in argitems)
             callstr = ''.join([fname, '(', argcore, ')'])
-            self.say(callstr)
+            say_kwargs = dict(silent=not lambda_eval(opts.show), retvalue=opts.retvalue)
+            fmtcallstr = self.code_repr(callstr)
+            self.say(fmtcallstr, **say_kwargs)
             try:
                 retval = func(*args, **kwargs)
-                retstr = ''.join([callstr, ' -> ', repr(retval)])
-                self.say(retstr)
+                fmtretval = self.value_repr(retval)
+                retstr = ''.join([fmtcallstr, ' -> ', fmtretval])
+
+                self.say(retstr, **say_kwargs)
                 return retval
             except Exception as e:
                 raise e
         func_code = func.__code__ if _PY3 else func.func_code
         argnames = func_code.co_varnames[:func_code.co_argcount]
         fname = func.__name__ if _PY3 else func.func_name
+        opts = self.opts
 
         def echo_func(*args, **kwargs):
             argitems = list(zip(argnames,args)) + list(kwargs.items())
             argcore = ', '.join('{0}={1!r}'.format(*argtup) for argtup in argitems)
             callstr = ''.join([fname, '(', argcore, ')'])
+            fmtcallstr = self.code_repr(callstr)
+
+            say_kwargs = dict(silent=not lambda_eval(opts.show), retvalue=opts.retvalue)
+
             try:
                 retval = func(*args, **kwargs)
-                retstr = ''.join([callstr, ' -> ', repr(retval)])
-                self.say(retstr)
+                fmtretval = self.value_repr(retval)
+                retstr = ''.join([fmtcallstr, ' -> ', fmtretval])
+
+                self.say(retstr, **say_kwargs)
                 return retval
             except Exception as e:
-                retstr = ''.join([callstr, ' -> '])
-                self.say(retstr)
+                retstr = ''.join([fmtcallstr, ' -> '])
+
+                self.say(retstr, **say_kwargs)
                 raise e
 
         return echo_func
                 from pygments.formatters import Terminal256Formatter
                 formatter = Terminal256Formatter(style=style)
                 self.set(fmtfunc=lambda x: highlight(pf(x), lexer, formatter).strip())
+                self.set(fmtcode=lambda x: highlight(x,     lexer, formatter).strip())
                 return
             except ImportError:
-                raise ImportWarning('install pygments for ANSI formatting; falling back to plain text')
+                raise ImportWarning('install pygments for ANSI forfmatting; falling back to plain text')
                 self.set(fmtfunc=pf)
                 return
             except Exception as e:
         else:
             raise BadValue("{mode!r} is not a recognized pretty print mode")
 
+
     # TODO: Give option for showing return value differently
     # TODO: Give this decorator standard show kwargs
     # TODO: Unify inout and retval function argument/return value decorators
 
 show = Show()
 
+
+
+class NoShow(Show):
+    """
+    A Show variant that shows nothing. Maintains just enough context to respond
+    as a real Show would. Any clones will also be ``NoShow``s--again, to retain
+    similarity. Designed to squelch all output in efficient way, but not
+    requiring any changes to the source code. Maintains just enough context to
+    """
+
+    options = Show.options.add(
+        show   = False,
+        retval = False,
+    )
+
+    def __init__(self, **kwargs):
+        self.options = NoShow.options.push(kwargs)
+        self.say = None
+        self.opts = None  # per call options, set on each call to reflect transient state
+
+    def settings(self, **kwargs):
+        """
+        Open a context manager for a `with` statement. Temporarily change settings
+        for the duration of the with.
+        """
+        return ShowContext(self, kwargs)
+
+    def set(self, **kwargs):
+        """
+        Open a context manager for a `with` statement. Temporarily change settings
+        for the duration of the with.
+        """
+        return None
+
+    def clone(self, **kwargs):
+        """
+        Create a child instance whose options are chained to this instance's
+        options (and thence to Show.options). kwargs become the child instance's
+        overlay options. Because of how the source code is parsed, clones must
+        be named via simple assignment.
+        """
+        return NoShow()
+
+    def _do_nothing(self, *args, **kwargs):
+        """
+        Fake entry point. Does nothing, returns immediately.
+        """
+        return None
+
+    __call__ = __gt__ = __rshift__ = _do_nothing
+    items = dir = props = locals = changed = inout = retval = _do_nothing
+    blank_lines = hr = title = _do_nothing
+
+noshow = NoShow()
+
 # Add show to sys.modules so that "import show" is all you need.
 # sys.modules['show'] = show
 
+"""
+Definition of common exceptions
+"""
+
+from say import FmtException
+
+class ArgsUnavailable(FmtException, ValueError):
+    pass
+
+class BadValue(FmtException, ValueError):
+    pass
+
+class ParseError(FmtException, RuntimeError):
+    pass
     # provided, either instead or as an option (ie, showing the module home
     # of the type); counterargument: they can always show(type(x)) if they want
     # the full shebang
+
+
+def lambda_eval(v):
+    """
+    If v is a callable, call it and return the value. Else, return it.
+    Helpful when you want to preserve the ability to lazy-evaluate a
+    value.
+    """
+    return v() if hasattr(v, '__call__') else v
+__version__ = '1.0.2'
 
 import pytest
-from show import Show
+from show import Show, NoShow, noshow
 import sys
 import platform
 import six
     # here in this testing situation, we're testing the return not the pure
     # output
 
+
     # Test instance on-off
     assert show(x) == 'x: 1'
     assert show(x, show=True, retvalue=True) == 'x: 1'
     assert show(x * 9) == 'x * 9: 9'
     assert show_verbose(x * 9) == 'x * 9: 9'
 
+    # Now test lambda expressions for showing
+    debug = True
+    assert show(x * 5) == 'x * 5: 5'
+    show.set(show=lambda: debug)
+    show.props(show.options, omit='_*')
+    assert show.options.show() == debug
+    assert debug == True
+    assert show(x * 5) == 'x * 5: 5'
+    debug = False
+    assert show(x * 5) == None
+
+    # clean up for rest of testing
+    debug = True
+    show.set(show=True)
+
+
 def test_show_retval(capsys):
 
+    show = Show(where=False, retvalue=True)
+
     # this tweak needed to make pytest's capsys work right
     show.say.options.files = [sys.stdout]
 
 
 # Does not test interactive usage (under ipython, eg) in any automated fashion.
 # Nor does it test the formatted output through Pygments and pformat.
+
+def test_noshow(capsys):
+    show = Show(where=False, retvalue=True)
+    show = noshow
+
+    out, err = capsys.readouterr()
+    assert show('this') is None
+    assert out == ""
+    assert err == ""
         r2 = RR()
         assert typename(r2) == '<RR>'
 
-    nested()
+    nested()
+
+def test_lambda_eval():
+
+    noncallables  = [4, 4.5, 5+7j, [], {}, set()]
+    for nc in noncallables:
+        assert lambda_eval(nc) == nc
+
+    x = 12
+    assert lambda_eval(x) == x
+    assert lambda_eval(lambda: x) == x
+    assert lambda_eval(lambda: x < 10) == False
+    assert lambda_eval(lambda: x > 10) == True