Jonathan Eunice avatar Jonathan Eunice committed f6e20f5

Further fixes to show.dir. First, trivial pass at PEP8 cleanup.

Comments (0)

Files changed (8)

 **NB** ``changed()`` used to be called ``watch()``. The old ``watch()`` method
 will still work (it's just an alias to ``changed``), but in the future it will
 be going away.
+
+Showing What's There
+====================
+
+It's often helpful to figure out "what am I dealing with here? what attributes or
+methods or properties are available to me?" This is where ``show.dir`` comes into
+play. You could do ``show(dir(x))``, but ``show.dir(x)`` will show you more information,
+and do so more compactly. It also allows you to filter out the often huge
+hubbub of some objects. By default, it doesn't show any attributes starting with
+double underscore
+``__``. You can control what's omitted with the ``omit`` keyword argument.  ``show.dir(x, omit=None)`` shows everything,
+while ``show.dir(x, omit='_* proxy*')`` omits all the methods starting with an
+underscore or the word "proxy."
     
 Interactive Limitations
 =======================
 To Do
 =====
 
- *  Investigate another debug printing library, `q >https://github.com/zestyping/q>`_ to see if there are things we can learn from it. The inline 
+ *  Investigate another debug printing library, `q <https://github.com/zestyping/q>`_ to see if there are things we can learn from it. The inline 
     showing and simple function tracing, for example.
  *  Better integrate ``show`` and ``show.props`` (and maybe ``show.items``). 
     Property showing is great, but if the object happens to be a dictionary,
     no properties are shown. Ideally should have one way to investigate
     an object, regardless of what it is; the problem is determining how
     much of the object to show, especially if it's a large collection
-    (e.g. ``list`` or ``dict``). 
+    (e.g. ``list`` or ``dict``).
+ *  show.dir and show.chars
+ *  Other proxy objects?
  *  Out of band debugging console. 
  *  Integration with ``disptrace`` ?
  *  Test support for Windows interactive mechanisms with readline.
+ *  show.place() - show execution milestones that can be turned on or off easily
 
+Recent Updates
+==============
+
+ *  Just added direct support for showing properties even when proxied through
+    another object. Currently limited to SQLAlchemy proxies. More to come.
+ 
 Notes
 =====
 
  *  Automated multi-version testing managed with the wonderful
     `pytest <http://pypi.python.org/pypi/pytest>`_
     and `tox <http://pypi.python.org/pypi/tox>`_. ``show`` is 
-    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).
+    successfully packaged for, and tested against, most late-model verions of
+    Python: 2.6, 2.7, and 3.3, as well as PyPy 2.0.2 (based on 2.7.3).
  
  *  The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
     `@jeunice on Twitter <http://twitter.com/jeunice>`_
 **NB** ``changed()`` used to be called ``watch()``. The old ``watch()`` method
 will still work (it's just an alias to ``changed``), but in the future it will
 be going away.
+
+Showing What's There
+====================
+
+It's often helpful to figure out "what am I dealing with here? what attributes or
+methods or properties are available to me?" This is where ``show.dir`` comes into
+play. You could do ``show(dir(x))``, but ``show.dir(x)`` will show you more information,
+and do so more compactly. It also allows you to filter out the often huge
+hubbub of some objects. By default, it doesn't show any attributes starting with
+double underscore
+``__``. You can control what's omitted with the ``omit`` keyword argument.  ``show.dir(x, omit=None)`` shows everything,
+while ``show.dir(x, omit='_* proxy*')`` omits all the methods starting with an
+underscore or the word "proxy."
     
 Interactive Limitations
 =======================
 To Do
 =====
 
- *  Investigate another debug printing library, `q >https://github.com/zestyping/q>`_ to see if there are things we can learn from it. The inline 
+ *  Investigate another debug printing library, `q <https://github.com/zestyping/q>`_ to see if there are things we can learn from it. The inline 
     showing and simple function tracing, for example.
  *  Better integrate ``show`` and ``show.props`` (and maybe ``show.items``). 
     Property showing is great, but if the object happens to be a dictionary,
     no properties are shown. Ideally should have one way to investigate
     an object, regardless of what it is; the problem is determining how
     much of the object to show, especially if it's a large collection
-    (e.g. ``list`` or ``dict``). 
+    (e.g. ``list`` or ``dict``).
+ *  show.dir and show.chars
+ *  Other proxy objects?
  *  Out of band debugging console. 
  *  Integration with ``disptrace`` ?
  *  Test support for Windows interactive mechanisms with readline.
+ *  show.place() - show execution milestones that can be turned on or off easily
 
+Recent Updates
+==============
+
+ *  Just added direct support for showing properties even when proxied through
+    another object. Currently limited to SQLAlchemy proxies. More to come.
+ 
 Notes
 =====
 
  *  Automated multi-version testing managed with the wonderful
     `pytest <http://pypi.python.org/pypi/pytest>`_
     and `tox <http://pypi.python.org/pypi/tox>`_. ``show`` is 
-    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).
+    successfully packaged for, and tested against, most late-model verions of
+    Python: 2.6, 2.7, and 3.3, as well as PyPy 2.0.2 (based on 2.7.3).
  
  *  The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
     `@jeunice on Twitter <http://twitter.com/jeunice>`_
 system = str(sys.platform).lower()
 impl = platform.python_implementation()
 
-install_requires = ['six', 'options>=0.451', 'say>=0.969', 'stuf>=0.9.10',
+install_requires = ['six', 'options>=0.5', 'say>=0.969', 'stuf>=0.9.12',
                     'mementos>=0.506', 'codegen' ]
 
 if 'darwin' in system:
     packages=['show'],
     install_requires=install_requires,
     tests_require = ['tox', 'pytest', 'six'],
-    # zip_safe = True,
+    zip_safe = False,
     keywords='debug print display show',
     classifiers=linelist("""
         Development Status :: 4 - Beta
 """Debugging print features. """
 
-import inspect, sys, os, re, six
-from options import Options, OptionsContext, Transient 
+import inspect
+import sys
+import os
+import re
+import six
+import fnmatch
+from options import Options, OptionsContext, Transient
 from say import Say, fmt, say
 from show.linecacher import *
 from show.introspect import *
+from show.util import *
 
 from options.nulltype import NullType
 Private    = NullType('Private')
 
 CallArgs.add_target_func('show')
 
+
 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)
 
 QUOTE_CHARS = ('"', "'", '"""', "'''")
 
-            
+
 # probably cannot make this work from interactive Python
 # http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell
 
         return data.strip().split(',')
     else:
         return data.strip().split()
-    
+
+
 def ellipsis(s, maxlen=232):
     s = str(s)
     if len(s) > maxlen:
-        return s[:maxlen-3] + '...'
+        return s[:maxlen - 3] + '...'
     else:
         return s
 
 FUNKY = (function, module, type, type(_XYZ.method), type(_xyz.method), type(len)) # funky => functional infrastructure
 
 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."""
-    
+
+    """
+    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?
         props=Transient,    # props desired to print (given at call time)
-        omit=Transient,     # vars not to print (for show.locals)
+        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
         show=True,          # show or not
     )
                 co_name = '__main__'
             func_location = wrapped_if(module_name, ":") + wrapped_if(co_name, "", "()")
             return ':'.join([func_location, str(lineno)])
-    
+
     def value_repr(self, value):
         """
         Return a ``repr()`` string for value that has any brace characters (e.g.
         """
         return self.say.escape(self.opts.fmtfunc(value))
 
-    def arg_format(self, name, value, caller):
+    def arg_format(self, name, value, caller, opts):
         """
         Format a single argument. Strings returned formatted.
         """
         else:
             return ': '.join( [ name, self.value_repr(value) ] )
 
-    def arg_format_items(self, name, value, caller):
+    def arg_format_items(self, name, value, caller, opts):
         """
         Format a single argument to show items of a collection.
         """
             else:
                 return "{0}: {1}".format(name, fvalue)
 
-    def arg_format_props(self, name, value, caller, ignore_double=True, ignore_funky=True):
+    def arg_format_dir(self, name, value, caller, opts):
+        """
+        Format a single argument to show items of a collection.
+        """
+        if name.startswith(QUOTE_CHARS):
+            ret = fmt(value, **{'_callframe': caller})
+            return ret
+        else:
+            attnames = omitnames(dir(value), opts.omit)
+            return "{0}{1}: {2}".format(name, type(value), ' '.join(attnames))
+
+    def arg_format_props(self, name, value, caller, opts, ignore_funky=True):
         """
         Format a single argument to show properties.
         """
                 props = self.opts.props
                 if props and isinstance(props, str):
                     proplist = props.split(',') if ',' in props else props.split()
+                    proplist = [ p.strip() for p in proplist ]
                 else:
-                        
                     propkeys = object_props(value)
-                    # TODO: move more of the __ and function avoidance into introspect
-                    if ignore_double:
-                        propkeys = [ p for p in propkeys if not p.startswith('__') ]
-                    
+                    if opts.omit:
+                        propkeys = omitnames(propkeys, opts.omit)
                     if ignore_funky:
                         propkeys = [ p for p in propkeys if not isinstance(getattr(value, p), FUNKY) ]
-                    if self.opts.omit:
-                        omitlist = cwsv_or_list(self.opts.omit) 
-                        propkeys = [ p for p in propkeys if not p in omitlist ]
 
                     proplist = sorted(propkeys, key=lambda x: x.replace('_','~'))
                 #propvals = [ "{0}={1}".format(p, self.value_repr(getattr(value, p))) for p in proplist ]
                 propvals = [ "    {0}={1}".format(p, ellipsis(self.value_repr(getattr(value, p)))) for p in proplist ]
                 if hasattr(value, 'items'):
                     propvals += [ "    {0}: {1}".format(k, ellipsis(self.value_repr(v))) for k,v in value.items() ]
-                return "{0} ({1}):\n{2}".format(name, type(value).__name__, '\n'.join(propvals))
+                return "{0}{1}:\n{2}".format(name, type(value), '\n'.join(propvals))
             except Exception:
-                return "{0} ({1}): {2}".format(name, type(value).__name__, self.value_repr(value))
-       
+                return "{0}{1}: {2}".format(name, type(value), self.value_repr(value))
+
     def get_arg_tuples(self, caller, values):
         """
         Return a list of argument name, value tuples with the given values.
         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
         self.options.set(**kwargs)
         if kwargs:
             self.say.set(**kwargs)
-    
+
     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.
-        """            
-        
+        """
+
         child = Show()
         child.options = self.options.push(kwargs)
-        
+
         # introspect caller to find what is being assigned to
         caller = inspect.currentframe().f_back
         filename, lineno = frame_to_source_info(caller)
         name = getline(filename, lineno).strip().split()[0]
         CallArgs.add_target_func(name)
         return child
-    
+
     def _showcore(self, args, kwargs, caller, formatter, opts):
         """
         Do core work of showing the args.
         """
         self.opts = opts
         argtuples = self.get_arg_tuples(caller, args)
-        
+
         # Construct the result string
-        valstr = opts.sep.join([ formatter(name, value, caller) for name, value in argtuples ])
+        valstr = opts.sep.join([ formatter(name, value, caller, opts) 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 __gt__(self, other):
+        """
+        Simple, non-functional call. Experimental.  
+        """
+
+        opts = self.options.push({})
+        caller = inspect.currentframe().f_back
+
+        return self._showcore([other], {}, caller, self.arg_format, opts)
+
+    def __rshift__(self, other):
+        """
+        Simple, non-functional call. Experimental.  
+        """
+
+        opts = self.options.push({})
+        caller = inspect.currentframe().f_back
+
+        self._showcore([other], {}, caller, self.arg_format, opts)
+        return other
+
+        # This doesn't quite work as intended because code parser isn't
+        # smart if (show>>a) + (show>>a) appears twice on the same line.
+
     def __call__(self, *args, **kwargs):
         """
         Main entry point for Show objects.
         """
         opts = self.opts = self.options.push(kwargs)
         caller = inspect.currentframe().f_back
-        formatter = self.arg_format if not opts.props else self.arg_format_props
-        return self._showcore(args, kwargs, caller, formatter, opts)
-    
+        formatter = self.arg_format_props if opts.props else self.arg_format
+        result = self._showcore(args, kwargs, caller, formatter, opts)
+        return result
+
+        # FF is (for now) aborted attempt to dive deeper in cases when
+        # about to present a highly generic representation. Will need to
+        # restructure/refactor _showcore in order to make this work.
+
+        # if result.startswith('<') and result.endswith('>') and ' object at 0x' in result:
+            # about to return a generic <__main__.User object at 0x10c73dbd0>
+            # so try harder
+            # formatter = self.arg_format_props
+            # return self._showcore(args, kwargs, caller, formatter, opts)
+        # else:
+        #    return result
+
     # TODO: Define __div__ and __truediv__ (for py3) like __call__, but must fix call
     #       position parsing to make that work (ie, different look => different parsing
     #       required)
-        
+
     def items(self, *args, **kwargs):
         """
         Show items of a collection.
         opts = self.options.push(kwargs)
         caller = inspect.currentframe().f_back
         return self._showcore(args, kwargs, caller, self.arg_format_items, opts)
-        
+
+    def dir(self, *args, **kwargs):
+        """
+        Show the attributes possible for the given object(s)
+        """
+        opts = self.options.push(kwargs)
+        opts.setdefault('omit', '__*')
+
+        caller = inspect.currentframe().f_back
+        return self._showcore(args, kwargs, caller, self.arg_format_dir, opts)
+
     def props(self, *args, **kwargs):
         """
         Show properties of objects.
         """
         opts = self.opts = self.options.push(kwargs)
+        opts.setdefault('omit', '__*')
         if len(args) > 1 and isinstance(args[-1], str):
-            used = opts.addflat([ args[-1] ], ['props'])
+            used = opts.addflat([args[-1]], ['props'])
             args = args[:-1]
         if opts.sep == Show.options.sep:
             opts.sep = '\n\n'
         caller = inspect.currentframe().f_back
         return self._showcore(args, kwargs, caller, self.arg_format_props, opts)
-    
+
         # should this check for and show (perhaps with ^ annotation), properties
         # of object inherited from class?
-        
+
         # if no props, should show normally?
         # Ie less difference between show, show.items, show.props
         # also, maybe more automatic or easy-to-specify truncation of results?
-    
+
     def locals(self, *args, **kwargs):
         """
         Show all local vars, plus any other values mentioned.
         """
         opts = self.opts = self.options.push(kwargs)
+        opts.setdefault('omit', '')
         caller = inspect.currentframe().f_back
-        assert not args # for now
+        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 ]
-    
+
+        to_omit = ' '.join((opts.omit or []) + '@py_assert*')
+        names = omitnames(locdict.keys(), to_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 ]
         Show the local variables, then again only when changed.
         """
         opts = self.opts = self.options.push(kwargs)
-        
+
         caller = inspect.currentframe().f_back
-        
+
         f_locals = caller.f_locals
         _id = id(f_locals)
 
             # self.say("args = {args!r}")
             argtuples = self.get_arg_tuples(caller, args)
             valitems.extend(argtuples)
-        
+
         valdict = dict(valitems)
         _id = id(f_locals)
         watching = self._watching.get(_id, None)
                 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 ]
-        
+
+        names = omitnames(to_show.keys(), opts.omit)
+
         # Construct the result string
         if names:
             valstr = opts.sep.join([ self.arg_format(name, to_show[name], caller) for name in names ])
         retval = self.say(*locval, **kwargs)
         if opts.retvalue:
             return retval
-            
-    watch = changed
-    # to be removed soon
-        
+
     def inout(self, func):
         """
         Decorator that shows arguments to a function.
                 raise e
 
         return echo_func
-    
+
     def retval(self, func):
         """
         Decorator that shows arguments to a function, and return value, once
         the function is complete.
-        """ 
+        """
         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
                 retstr = ''.join([callstr, ' -> '])
                 self.say(retstr)
                 raise e
-            
+
         return echo_func
-    
+
     def prettyprint(self, mode='ansi', sep='\n', indent=2, width=120, depth=5, style='friendly'):
         """
         Convenience method to turn on pretty-printing. Mode can be text or ansi.
     # Promote delegated formatting functions
     def blank_lines(self, *args, **kwargs):
         self.say.blank_lines(*args, **kwargs)
-        
+
     def hr(self, *args, **kwargs):
         self.say.hr(*args, **kwargs)
-    
+
     def title(self, *args, **kwargs):
         kwargs.setdefault('_callframe', inspect.currentframe().f_back)
         self.say.title(*args, **kwargs)
-            
+
+
 class ShowContext(OptionsContext):
+
     """
     Context helper to support Python's with statement.  Generally called
     from ``with show.settings(...):``
 # creation of other show objects. Currently disabled while working on it.
 
 # TODO: add easier decorator for function tracing (just @show?)
+

show/introspect.py

 Home module for inspect/introspection based activities and helpers.
 """
 
-import inspect, ast, codegen
+import inspect
+import ast
+import codegen
 from mementos import MementoMetaclass, with_metaclass
 from show.linecacher import *
 import textwrap
 
 prefix = stuf(normal='', slot='/', superclass='^', baseobject='*')
 
+
+# TODO: either update and include local version of codegen
+# (see repo on ~/pytest/Git/codegen, but needs tests fixed)
+# or switch to module like astor https://pypi.python.org/pypi/astor/0.2.1
+
 DEBUGGING = False
 
+
 class ClassProps(with_metaclass(MementoMetaclass)):
+
     """
     Memoized finder of class props. 
     """
+
     def __init__(self, cls):
         self.cls   = cls
         self.mro   = inspect.getmro(cls)
             for k, v in cprops.prefixes.items():
                 if k not in self.prefixes:
                     self.prefixes[k] = prefix.superclass + v
-        
+
     def _findprops(self, cls):
         """
         Get the properties of the given class. Follow the MRO up the chain,
         recursively, until ``object``
         """
-        
+
         try:
             classkeys = list(cls.__dict__.keys())
         except AttributeError:
             classkeys = []
-        
+
         try:
             slotkeys = list(cls.__slots__)
         except AttributeError:
             slotkeys = []
-        
+
         char = prefix.baseobject if cls is object else prefix.normal
         # prefixes = { k: char for k in classkeys }
         prefixes = dict((k, char) for k in classkeys)
-        
+
         # prefixes.update({ k: prefix.slot for k in slotkeys })
         prefixes.update(dict(k, prefix.slot) for k in slotkeys)
 
         return prefixes
-    
+
     @property
     def prefixed(self):
         return [ self.prefixes[p] + p for p in self.props ]
     
     # TODO: finish code to do class-based discovery of props along the MRO
     # NOT sure what the prefixing is
-    
+
+
 def object_props(o):
     """
-    Find the props of the given object. Its class properties are assumed immutable,
-    but its own properties are assumed to be changeable at a moment's notice.
+    Find the props of the given object. Its class properties are assumed
+    immutable, but its own properties are assumed to be changeable at a moment's
+    notice. Objects known to be proxy objects (e.g. for the SQLAlchemy ORM) need
+    to be handled specially, because its class attributes have nothing to do
+    with the attributes of the object they proxy.
     """
-        
+
+    try:
+        proxyprops = getattr(o, '_sa_class_manager')
+        # If did not fail, dealing with SQLAlchemy ORM proxy
+        proxykeys = list(proxyprops.keys())
+    except AttributeError:
+        proxykeys = []
+
+    if not proxykeys:
+        try:
+            proxyprops = getattr(o, '_get_current_object')
+            # If did not fail, dealing with Flask proxy
+            proxykeys = list(proxyprops())
+        except AttributeError:
+            proxykeys = []
+
     try:
         dictkeys = list(o.__dict__.keys())
     except AttributeError:
         dictkeys = []
-    
-    t = type(o)
-    try:
-        slotkeys = list(t.__slots__)
-    except AttributeError:
-        slotkeys = []
-        
-    cprops = ClassProps(t)
-    classkeys = cprops.props
-    
+
+    if proxykeys:
+        # SQLAlchemy proxy objects special - don't try looking into the
+        # class. Nothing useful there. It won't tell you what properties
+        # of the proxied object are, just of the proxy class itself.
+        # Counterproductive.
+        slotkeys, classkeys = [], []
+    else:
+        t = type(o)
+        try:
+            slotkeys = list(t.__slots__)
+        except AttributeError:
+            slotkeys = []
+
+        cprops = ClassProps(t)
+        classkeys = cprops.props
+
     # prop_prefix = cprops.prefixes.copy()
     # prop_prefix.update({ k: prefix.normal for k in dictkeys })
     # prop_prefix.update({ k: prefix.slot for k in slotkeys })
     
-    allprops = sorted(set(dictkeys + slotkeys + classkeys))
+    allprops = sorted(set(proxykeys + dictkeys + slotkeys + classkeys))
+
     if DEBUGGING:
         sd = set(allprops).symmetric_difference(dir(o))
         sdneat = sorted(s for s in sd if not s.startswith('__'))
             print(sorted(sdneat),)
     return allprops
 
-        
+
 # TODO: better mechanism for dealing with mutability?
 # perhaps check hash (or content); alternately, let clients declare that some
 # classes are mutable - in which case, delete them from cache every use
 
+
 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
     
     @classmethod
     def add_target_func(cls, name):
-        suffixes = [ '', '.items', '.props', '.changed' ]
+        suffixes = [ '', '.items', '.props', '.changed', '.dir', '.chars' ]
         names = [ name + s for s in suffixes ]
         cls.TARGET_FUNCS.update(names)
     
         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
         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``.
+            Given an AST node n which we suspect might represent the name of a target
+            callable (e.g. ``show`` or one of its attribute-define subcalls such as
+            ``show.props``), return the name of the called function, if discoverable. If
+            not one of the simple forms we typically see, return ``None``.
             """
             if isinstance(n.func, ast.Name):
                 return n.func.id
                 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!")
-        
+                return None
+
         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)
+            ast.NodeVisitor.generic_visit(self, node)
+
+    def visit_Compare(self, node):
+        """
+        Called for all ``ast.Compare`` nodes, for ``show> something syntax``.
+        Collects source of each argument.
+        """
+
+        def call_name_for_compare(n):
+            """
+            Given an AST node n which we suspect might represent the name of a target
+            callable (e.g. ``show`` or one of its attribute-define subcalls such as
+            ``show.props``), return the name of the called function, if discoverable. If
+            not one of the simple forms we typically see, return ``None``.
+            Very similar to the ``call_name`` of ``visit_Call``, except it accesses
+            slightly different node attributes to reflect different AST types.
+            """
+            if isinstance(n, ast.Name):
+                return n.id
+            elif isinstance(n, ast.Attribute):
+                if isinstance(n.value, ast.Name): 
+                    return '.'.join([n.value.id, n.attr])
+                else:
+                    return None # could be an attribute of a call, but for those, we don't much care
+            else:
+                return None
+
+        op = node.ops[0]
+        if isinstance(op, ast.Gt):
+            left, right = node.left, node.comparators[0]
+            name = call_name_for_compare(left)
+            if name in self.TARGET_FUNCS:
+                self.args = [codegen.to_source(right)]
+            else:
+                # visit its children
+                ast.NodeVisitor.generic_visit(self, left)
+                ast.NodeVisitor.generic_visit(self, right)
+
+    def visit_BinOp(self, node):
+        """
+        Called for all ``ast.Compare`` nodes, for ``show> something syntax``.
+        Collects source of each argument.
+        """
+
+        def call_name_for_binop(n):
+            """
+            Given an AST node n which we suspect might represent the name of a target
+            callable (e.g. ``show`` or one of its attribute-define subcalls such as
+            ``show.props``), return the name of the called function, if discoverable. If
+            not one of the simple forms we typically see, return ``None``.
+            Very similar to the ``call_name`` of ``visit_Call``, except it accesses
+            slightly different node attributes to reflect different AST types.
+            """
+            if isinstance(n, ast.Name):
+                return n.id
+            elif isinstance(n, ast.Attribute):
+                if isinstance(n.value, ast.Name): 
+                    return '.'.join([n.value.id, n.attr])
+                else:
+                    return None # could be an attribute of a call, but for those, we don't much care
+            else:
+                return None
+
+        if isinstance(node.op, ast.RShift):
+            left, right = node.left, node.right
+            name = call_name_for_binop(left)
+            if name in self.TARGET_FUNCS:
+                self.args = [ codegen.to_source(right) ]
+            else:
+                # visit its children
+                ast.NodeVisitor.generic_visit(self, left)
+                ast.NodeVisitor.generic_visit(self, right)

show/linecacher.py

 """Like linecache, but with support for getting lines from
 interactive Python and iPython use, too."""
 
-import sys, linecache
+import sys
+import 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:
-    
+
     system = str(sys.platform).lower()
 
     if 'win32' in system:
         import pyreadline as rl
     else:
         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._update()
             return self._lines
-            
+
         def _update(self):
             """
             If the lines have not been recently updated (readlines knows more lines than
                 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)]
-        
+            return self.lines[-2 - abs(offset)]
+
         def clear(self):
             """
             Obliviate! Clear the history.
         #    """
         #    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
             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

test/test_show.py

     d = {'a': 1, 'b': 2}
     fstr =  show.items(d)
     assert fstr == "d (2 items): {'a': 1, 'b': 2}" or fstr == "d (2 items): {'b': 2, 'a': 1}" 
+
+
+def test_show_dir():
     
+    class R(object):
+        x = 4
+        def __init__(self):
+            self.y = 44
+            self._other = 943
+    r = R()
+    
+    assert show.dir(r) == 'r: _other x y'
+    
+    tests = {
+        '_*': 'r: x y',
+        '__*': '_other x y',
+        '': 'r: __class__ __delattr__ __dict__ __doc__ __format__ __getattribute__ __hash__ __init__ __module__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ _other x y',
+        None: 'r: __class__ __delattr__ __dict__ __doc__ __format__ __getattribute__ __hash__ __init__ __module__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ _other x y',
+    }
+
+    for pat, answer in tests.items():
+        assert show.dir(r, omit=pat) == answer
+        
 def test_show_props():
     
     class O(object):
 [tox]
-envlist = py26, py27, py32, py33, pypy
+envlist = py26, py27, py33, pypy
 
 [testenv]
 changedir=test
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.