Commits

Jonathan Eunice committed 30b7014

removed show to separate package

Comments (0)

Files changed (6)

  *  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
 =====
 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
 =======================
 
 
  *  The ``say`` name was inspired by Perl's `say <http://perldoc.perl.org/functions/say.html>`_,
     but the similarity stops there.
+    
+ *  The ``show`` debug printing functions have been split into a separate package,
+    `show <http://pypi.python.org/pypi/show>`_.
    
- *  Automated multi-version testing with the wonderful
+ *  Automated multi-version testing is managed with the wonderful
     `pytest <http://pypi.python.org/pypi/pytest>`_
-    and `tox <http://pypi.python.org/pypi/tox>`_ modules has commenced. ``say`` is now
+    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).
  
 from say.core import *
-from say.show import *
-from say.linecacher import *

say/linecacher.py

-"""Like linecache, but with support for getting lines from
-interactive Python and iPython use, too."""
-
-import sys, linecache
-
-isInteractive = hasattr(sys, 'ps1') or hasattr(sys, 'ipcompleter')
-# http://stackoverflow.com/questions/967369/python-find-out-if-running-in-shell-or-not-e-g-sun-grid-engine-queue
-
-if isInteractive:
-    import readline as rl
-    
-    class History(object):
-        """
-        Singleton proxy for readline
-        """
-        
-        def __init__(self):
-            self._lines  = [ None ] # first item is None to compensate: 0-based arrays
-                                    # but 1-based line numbers
-            self._lines.append(rl.get_history_item(rl.get_current_history_length()))
-            rl.clear_history()
-            self._lastseen = rl.get_current_history_length()  # have we seen it all?
-            
-        @property
-        def lines(self):
-            """
-            The magically self-updating lines property.
-            """
-            self._update()
-            return self._lines
-            
-        def _update(self):
-            """
-            If the lines have not been recently updated (readlines knows more lines than
-            we do), import those lines.
-            """
-            cur_hist_len = rl.get_current_history_length()
-            if cur_hist_len > self._lastseen:
-                for i in range(self._lastseen+1, cur_hist_len+1):
-                    self._lines.extend(rl.get_history_item(i).splitlines())
-                self._lastseen = cur_hist_len
-                
-            # Fancier splitlines() thing required because iPython stores history
-            # lines for multi-line strings with embedded newlines. Iteractive Python
-            # stores them individually.
-        
-        def prev(self, offset=0):
-            """
-            Show the previous line. Or can go back a few, with offset
-            """
-            return self.lines[-2-abs(offset)]
-        
-        def clear(self):
-            """
-            Obliviate! Clear the history.
-            """
-            rl.clear_history()
-            self._lines  = [ None ] # first item is None to compensate: 0-based arrays
-                                    # but 1-based line numbers
-            self._lines.append(rl.get_history_item(rl.get_current_history_length()))
-        
-        #def show(self):
-        #    """
-        #    Show last items.
-        #    """
-        #    for lineno, line in enumerate(self.lines[1:], start=1):
-        #        print "{0:3}: {1}".format(lineno, line)
-                
-    history = History()
-    
-    def frame_to_source_info(frame):
-        """
-        Given a Python call frame, e.g. from ``inspect.currentframe()`` or any of
-        its ``f_back`` predecessors, return the best filename and lineno combination.
-        """
-        filename = frame.f_code.co_filename
-        lineno   = frame.f_lineno
-        # print "lineno", lineno
-        if filename.startswith( ('<stdin>', '<ipython-input') ):
-            if lineno == 1:
-                lineno = len(history.lines)
-            return ('<stdin>', lineno)
-        return (filename, lineno)
-
-    def getline(filename, lineno):
-        """
-        Replace ``linecache.getline()`` with function that first determines if
-        we need to get from history, or from a regular file.
-        """
-        # print "getline: filename = ", filename, "lineno = ", lineno
-        if filename == '<stdin>':
-            index = -1 if lineno == 1 else lineno - 1
-            # for interactive Python, lineno == 1 a lot
-            return history.lines[index]
-        else:
-            return linecache.getline(filename, lineno)
-        
-else:
-    history = None
-    getline = linecache.getline
-    
-    def frame_to_source_info(frame):
-        """
-        Given a Python call frame, e.g. from ``inspect.currentframe()`` or any of
-        its ``f_back`` predecessors, return the best filename and lineno combination.
-        If not running interactively, just use the Python data structures.
-        """
-        return (frame.f_code.co_filename, frame.f_lineno)

say/show.py

-"""Debugging print features. """
-
-import string, inspect, sys, os, re, collections
-from options import Options, OptionsContext
-import linecache
-from say.core import *
-from say.linecacher import *
-import ast
-import codegen
-from mementos import MementoMetaclass
-import textwrap
-
-def wrapped_if(value, prefix="", suffix="", transform=None):
-    """
-    If a string has a value, then transform it (optinally) and add the prefix and
-    suffix. Else, return empty string. Handy for formatting operations, where
-    one often wants to add decoration iff the value exists.
-    """
-    
-    if not value:
-        return ""
-    s = transform(str(value)) if transform else str(value)
-    return (prefix or "") + s + (suffix or "")
-
-QUOTE_CHARS = ('"', "'", '"""')
-
-def with_metaclass(meta, base=object):
-    """Create a base class with a metaclass."""
-    return meta("NewBase", (base,), {})
-
-class CallArgs(with_metaclass(MementoMetaclass, ast.NodeVisitor)):
-    """
-    An ``ast.NodeVisitor`` that parses a Python function call and determines its
-    arguments from the corresponding AST. Memoized so that parsing and
-    traversing the AST is done once and only once; subseqnet requests are
-    delivered via cache lookup.
-    """
-
-    TARGET_FUNCS = set(['show', 'show.items'])  # functions we care about
-
-    def __init__(self, filepath, lineno):
-        ast.NodeVisitor.__init__(self)
-        self.filepath = filepath
-        self.lineno   = lineno
-        self.source   = None  # placeholder
-        self.ast      = None  # placeholder
-        self.args     = None  # placeholder
-        self.get_ast()
-        self.visit(self.ast)
-    
-    def get_ast(self):
-        """
-        Find the AST. Easy if single source line contains whole line. May
-        need a bit of trial-and-error if over multiple lines.
-        """
-        src = ""
-        for lastlineno in range(self.lineno, self.lineno+10):
-            src += getline(self.filepath, lastlineno)
-            try:
-                srcleft = textwrap.dedent(src)
-                self.ast = ast.parse(srcleft)
-                self.source = src
-                return
-            except IndentationError:
-                pass
-            except SyntaxError:
-                pass
-        raise RuntimeError('Failed to parse:\n{}\n'.format(src))
-        
-    def visit_Call(self, node):
-        """
-        Called for all ``ast.Call`` nodes. Collects source of each argument.
-        Note that AST ``starargs`` and ``keywords`` properties are not
-        considered. Because ``CallArgs`` parses just one line of source code out
-        of its module's context, ``ast.parse`` assumes that arguments are
-        normal, not ``*args``. And ``**kwargs`` we can ignore, because those are
-        pragmas, not data.
-        """
-        
-        def call_name(n):
-            """
-            Given an ast.Call node, return the name of the called function, if
-            discoverable. Only attempts to decode the common ``func()`` and
-            ``object.method()`` cases that we care about for the ``show``
-            module. Others return ``None``.
-            """
-            if isinstance(n.func, ast.Name):
-                return n.func.id
-            elif isinstance(n.func, ast.Attribute):
-                a = n.func
-                if isinstance(a.value, ast.Name):
-                    return '.'.join([a.value.id, a.attr])
-                else:
-                    return None # could be an attribute of a call, but for those, we don't much care
-            else:
-                raise ValueError("Uh...I'm confused!")
-        
-        name = call_name(node)
-        if name in self.TARGET_FUNCS:
-            self.args = [ codegen.to_source(arg) for arg in node.args ]
-        else:
-            # visit its children
-            ast.NodeVisitor.generic_visit(self, node)
-
-# probably cannot make this work from interactive Python
-# http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell
-
-class Show(object):
-    """Show objects print debug output in a 'name: value' format that
-    is convenient for discovering what's going on as a program runs."""
-    
-    options = Options(
-        show_module=False,  # Show the module name in the call location
-        where=False,        # show the call location of each call
-        sep="  ",           # separate items with two spaces, by default
-        retvalue=False,     # return the value printed?
-    )
-
-    def __init__(self, **kwargs):
-        self.options = 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
-    
-    def call_location(self, caller):
-        """
-        Create a call location string indicating where a show() was called.
-        """
-        if isInteractive:
-            return "<stdin>:{0}".format(len(history.lines))
-        else:
-            module_name = ""
-            if self.opts.show_module:
-                filepath = caller.f_locals.get('__file__', caller.f_globals.get('__file__', 'UNKNOWN'))
-                filename = os.path.basename(filepath)
-                module_name = re.sub(r'.py', '', filename)
-            
-            lineno = caller.f_lineno
-            co_name = caller.f_code.co_name
-            if co_name == '<module>':
-                co_name = '__main__'
-            func_location = wrapped_if(module_name, ":") + wrapped_if(co_name, "", "()")
-            return ':'.join([func_location, str(lineno)])
-    
-    def arg_format(self, name, value, caller):
-        """Format a single argument. Strings returned as-is."""
-        if name.startswith(QUOTE_CHARS):
-            return fmt(value, **{'_callframe': caller})
-        else:
-            formatted = "{0}: {1!r}".format(name, value)
-            if isinstance(value, dict):
-                # escape { and }
-                formatted = formatted.replace('{', '{{').replace('}', '}}') # escape for dict case
-            return formatted
-
-    def arg_format_items(self, name, value, caller):
-        """Format a single argument. Strings returned as-is."""
-        if name.startswith(QUOTE_CHARS):
-            ret = fmt(value, **{'_callframe': caller})
-            return ret
-        else:                
-            if isinstance(value, (list, dict, set, six.string_types)):  # weak test
-                length = len(value)
-                itemname = 'char' if isinstance(value, six.string_types) else 'item'
-                s_or_nothing = '' if length == 1 else 's'
-                return "{0} ({1} {2}{3}): {4!r}".format(name, length, itemname, s_or_nothing, value)
-            else:
-                return "{0}: {1!r}".format(name, value)
-        
-    def get_arg_tuples(self, caller, values):
-        """
-        Return a list of argument name, value tuples with the given values.
-        """
-        filename, lineno = frame_to_source_info(caller)
-        argnames = CallArgs(filename, lineno).args
-        argtuples = list(zip(list(argnames), list(values)))
-        return argtuples
-    
-    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.
-        """
-        self.options.set(**kwargs)
-        if kwargs:
-            self.say.set(**kwargs)
-    
-    def clone(self, **kwargs):
-        """
-        Create a new Say instance whose options are chained to this instance's
-        options (and thence to Say.options). kwargs become the cloned instance's
-        overlay options.
-        """
-        cloned = Show()
-        cloned.options = self.options.push(kwargs)
-        return cloned
-
-    def __call__(self, *args, **kwargs):
-        """Main entry point for Show objects."""
-
-        opts = self.opts = self.options.push(kwargs)
-        
-        # Determine argument names and values
-        caller = inspect.currentframe().f_back
-        argtuples = self.get_arg_tuples(caller, args)
-        
-        # Construct the result string
-        valstr = opts.sep.join([ self.arg_format(name, value, caller) for name, value in argtuples ])
-        locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
-
-        # Emit the result string, and optionally return it
-        retval = self.say(*locval, **kwargs)
-        if opts.retvalue:
-            return retval
-        
-    def items(self, *args, **kwargs):
-        
-        opts = self.opts = self.options.push(kwargs)
-        
-        assert len(args) == 1
-        
-        # Determine argument names and values
-        caller = inspect.currentframe().f_back
-        argtuples = self.get_arg_tuples(caller, args)
-        # say("argtuples = {argtuples!r}")
-
-        valstr = opts.sep.join([ self.arg_format_items(name, value, caller) for name, value in argtuples ])
-        locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
-
-        retval = self.say(*locval, **kwargs)
-        if opts.retvalue:
-            return retval
-
-class ShowContext(OptionsContext):
-    """
-    Context helper to support Python's with statement.  Generally called
-    from ``with show.settings(...):``
-    """
-    pass
-
-show = Show()
 
 setup(
     name='say',
-    version=verno("0.804"),
+    version=verno("0.826"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
-    description='Super-simple templated printing. E.g.: say("Hello, {whoever}!", indent=1) or show(x)',
+    description='Super-simple templated printing. E.g.: say("Hello, {whoever}!", indent=1)',
     long_description=open('README.rst').read(),
     url='https://bitbucket.org/jeunice/say',
     packages=['say'],
-    install_requires=['six', 'options>=0.325', 'stuf>=0.9.10', 'mementos>=0.465', 'codegen>=1.0'],
+    install_requires=['six', 'options>=0.4', 'stuf>=0.9.10'],
     tests_require = ['tox', 'pytest', 'six'],
     zip_safe = True,
     keywords='print format template interpolate say show',

test/test_show.py

-
-import pytest
-from say import Show
-
-show = Show(where=False, retvalue=True)
-
-def nospaces(s):
-    return s.replace(' ', '')
-    
-def test_basic(param = 'Yo'):
-
-    a = 1
-    b = 3.141
-    c = "something else!"
-    
-    assert show(a) == 'a: 1'
-    assert show(a, b) == 'a: 1  b: 3.141'
-    assert show(a, b, c) == "a: 1  b: 3.141  c: 'something else!'"
-    
-    assert show(1 + 1) == '1 + 1: 2'
-    assert nospaces(show(1+1)) == nospaces('1 + 1: 2')
-        # may have extra spaces in it, based on codegen.to_source() output
-        
-    assert show(len(c), c) == \
-                "len(c): 15  c: 'something else!'"
-    
-            # anything with paraens in it must be on separate line, given weak parser
-            # of show parameters
-            
-def test_literals():
-    assert show(1 + 1) == '1 + 1: 2'
-    assert show(1+1) == '1 + 1: 2'
-    
-    # assert nospaces(show(1+1)) == nospaces('1 + 1: 2')
-    
-    # NB output may have more or fewer spaces than actual parameter, based on codegen.to_source() 
-    # creating its 'idealized' code output
-
-def test_say_params():
-    a = 1
-    b = 3.141
-    c = "something else!"
-    
-    assert show(a, indent='+1') == '    a: 1'
-    assert show(a, b, sep='\n') == 'a: 1\nb: 3.141'
-
-def test_strings():
-    assert show('this') == "this"
-    x = 44
-    assert show("x = {x}") == 'x = 44'
-    
-def test_show_example():
-    
-    x = 12
-    nums = list(range(4))
-
-    assert show(x) == 'x: 12'
-    assert show(nums) == 'nums: [0, 1, 2, 3]'
-    assert show(x, nums, len(nums)) == \
-        'x: 12  nums: [0, 1, 2, 3]  len(nums): 4'
-    assert show(x, nums, len(nums), indent='+1') == \
-        '    x: 12  nums: [0, 1, 2, 3]  len(nums): 4'
-    assert show(x, nums, len(nums), sep='\n') == \
-        'x: 12\nnums: [0, 1, 2, 3]\nlen(nums): 4'
-    assert show(x, nums, len(nums), sep='\n', indent=1) == \
-        '    x: 12\n    nums: [0, 1, 2, 3]\n    len(nums): 4'
-
-# @pytest.mark.skipif('True')  # under construction
-def test_show_items():
-    nums = list(range(4))
-    assert show.items(nums) == \
-        'nums (4 items): [0, 1, 2, 3]'
-    
-    astring = 'this'
-    assert show.items(astring) == \
-        "astring (4 chars): 'this'"
-    
-    #d = {'a': 1, 'b': 2}
-    #assert show.items(d) == \
-    #   "d (2 items): {'a': 1, 'b': 2}"
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.