Jonathan Eunice avatar Jonathan Eunice committed ea65cb0

show.watch is working with locals only

Comments (0)

Files changed (4)

 Installation
 ============
 
-::
+To install the latest version::
 
-    pip install show
+    pip install -U show
 
 To ``easy_install`` under a specific Python version (3.3 in this example)::
 
-    python3.3 -m easy_install show
+    python3.3 -m easy_install --upgrade show
     
 (You may need to prefix these with "sudo " to authorize installation.)
 
 setup(
     name='show',
-    version=verno("0.482"),
+    version=verno("0.519"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Debug print statements, done right. E.g. show(x)',
     long_description=open('README').read(),
     url='https://bitbucket.org/jeunice/show',
     packages=['show'],
-    install_requires=['six', 'options>=0.417', 'say>=0.83', 'stuf>=0.9.10', 'mementos>=0.5', 'codegen'],
+    install_requires=['six', 'options>=0.426', 'say>=0.833', 'stuf>=0.9.10', 'mementos>=0.5', 'codegen'],
     tests_require = ['tox', 'pytest', 'six'],
     # zip_safe = True,
     keywords='debug print display show',
 
 import inspect, sys, os, re, six
 from options import Options, OptionsContext, Transient 
-from say import Say, fmt
+from say import Say, fmt, say
 from show.linecacher import *
 import linecache, ast, codegen
 from mementos import MementoMetaclass, with_metaclass
 import textwrap
 
+from options.nulltype import NullType
+Private = NullType('Private')
+Impossible = NullType('Impossible')
+
 def wrapped_if(value, prefix="", suffix="", transform=None):
     """
     If a string has a value, then transform it (optinally) and add the prefix and
 # probably cannot make this work from interactive Python
 # http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell
 
-def format_escape(s):
+def cwsv_or_list(data):
     """
-    Double { and } characters in a string to 'escape' them so ``str.format``
-    doesn't treat them as template characters. NB This is NOT idempotent!
-    Escaping more than once (when { or } are present ) = ERROR.
+    Take a list, a comma-separated values string, or a whitespace-separated values
+    string, and return a list.
     """
-    return s.replace('{', '{{').replace('}', '}}') 
-    
+    if not data:
+        return []
+    elif isinstance(data, list):
+        return data
+    elif ',' in data:
+        return data.strip().split(',')
+    else:
+        return data.strip().split()
+
+
 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."""
         sep="  ",           # separate items with two spaces, by default
         retvalue=False,     # return the value printed?
         props=Transient,    # props desired to print (given at call time)
+        omit=Transient,     # vars not to print (for show.locals)
     )
 
     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
+        self._watching = {} # remembers last value of variables for given frames
     
     def call_location(self, caller):
         """
             func_location = wrapped_if(module_name, ":") + wrapped_if(co_name, "", "()")
             return ':'.join([func_location, str(lineno)])
     
-    @staticmethod
-    def value_repr(value):
+    def value_repr(self, value):
         """
         Return a ``repr()`` string for value that has any brace characters (e.g.
         for ``dict``--and in Python 3, ``set`--literals) doubled so that they
         are not interpreted as format template characters when the composed string
         is eventually output by ``say``.
         """
-        fvalue = repr(value)
-        return format_escape(fvalue)
+        return self.say.escape(repr(value))
 
     def arg_format(self, name, value, caller):
         """
         caller = inspect.currentframe().f_back
         return self._showcore(args, kwargs, caller, self.arg_format_props, opts)
     
+    def locals(self, *args, **kwargs):
+        """
+        Show all local vars, plus any other values mentioned.
+        """
+        opts = self.options.push(kwargs)
+        caller = inspect.currentframe().f_back
+        assert not args # for now
+        locdict = caller.f_locals
+        omit = cwsv_or_list(opts.omit)
+    
+        names = [ n for n in sorted(locdict.keys()) if not n.startswith('@py_assert') and n not in omit ]
+    
+        # Construct the result string
+        valstr = opts.sep.join([ self.arg_format(name, locdict[name], caller) for name in names ])
+        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 watch(self, *args, **kwargs):
+        """
+        Show the local variables, then again only when changed.
+        """
+        opts = self.options.push(kwargs)
+        caller = inspect.currentframe().f_back
+        assert not args # for now
+        
+        locdict = dict([ (k, v) for (k, v) in caller.f_locals.items() if not k.startswith('@py_assert') ])
+        watching = self._watching.get(id(caller), None)
+        if watching is None:
+            to_show = {}
+            to_show.update(locdict)
+            self._watching[id(caller)] = watching = to_show
+        else:
+            to_show = {}
+            for k, v in locdict.items():
+                if k not in watching or v != watching[k]:
+                    to_show[k] = v
+                    watching[k] = v
+                
+        omit = cwsv_or_list(opts.omit)
+            
+        names = [ n for n in sorted(to_show.keys()) if n not in omit ]
+        
+        # Construct the result string
+        if names:
+            valstr = opts.sep.join([ self.arg_format(name, to_show[name], caller) for name in names ])
+        else:
+            valstr = '<unchanged>'
+        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
+    
 class ShowContext(OptionsContext):
     """
     Context helper to support Python's with statement.  Generally called

test/test_show.py

 
 show = Show(where=False, retvalue=True)
 
+global_var = 'hey!'
+
 def nospaces(s):
     return s.replace(' ', '')
     
     
     p = P('Joe')
     assert show.props(p) == "p: name='Joe' namer='joe' _name='JOE'"
+    
+def test_show_locals():
+    x = 1
+    y = 2
+    assert show.locals() == 'x: 1  y: 2'
+    
+    a = 22
+    b = 23
+    assert show.locals(omit='x') == 'a: 22  b: 23  y: 2'
+    # assert show.locals(global_var) == "global_var: 'hey!'  x: 1  y: 2"
+    
+def test_show_watch():
+    x = 1
+    assert show.watch() == 'x: 1'
+    y = 2
+    assert show.watch() == 'y: 2'
+    x = 4
+    assert show.watch() == 'x: 4'
+    x = 5
+    y = 3
+    assert show.watch() == 'x: 5  y: 3'
+    assert show.watch() == '<unchanged>'
+
     
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.