Commits

Anonymous committed 1cfacef

Comments (0)

Files changed (103)

+"""
+py.test and pylib: rapid testing and development utils
+
+this module uses apipkg.py for lazy-loading sub modules
+and classes.  The initpkg-dictionary  below specifies
+name->value mappings where value can be another namespace
+dictionary or an import path.  
+
+(c) Holger Krekel and others, 2004-2010
+"""
+__version__ = version = "1.3.1"
+
+import py.apipkg
+
+py.apipkg.initpkg(__name__, dict(
+    # access to all standard lib modules
+    std = '._std:std',
+    # access to all posix errno's as classes
+    error = '._error:error',
+
+    _pydir = '.__metainfo:pydir',
+    version = 'py:__version__', # backward compatibility
+
+    cmdline = {
+        'pytest':     '._cmdline.pytest:main',
+        'pylookup':   '._cmdline.pylookup:main',
+        'pycountloc': '._cmdline.pycountlog:main',
+        'pylookup':   '._cmdline.pylookup:main',
+        'pycountloc': '._cmdline.pycountloc:main',
+        'pycleanup':  '._cmdline.pycleanup:main',
+        'pywhich'        : '._cmdline.pywhich:main',
+        'pysvnwcrevert'  : '._cmdline.pysvnwcrevert:main',
+        'pyconvert_unittest'  : '._cmdline.pyconvert_unittest:main',
+    },
+
+    test = {
+        # helpers for use from test functions or collectors
+        '__onfirstaccess__' : '._test.config:onpytestaccess',
+        '__doc__'           : '._test:__doc__',
+        # configuration/initialization related test api
+        'config'            : '._test.config:config_per_process',
+        'ensuretemp'        : '._test.config:ensuretemp',
+        'collect': {
+            'Collector' : '._test.collect:Collector',
+            'Directory' : '._test.collect:Directory',
+            'File'      : '._test.collect:File',
+            'Item'      : '._test.collect:Item',
+            'Module'    : '._test.pycollect:Module',
+            'Class'     : '._test.pycollect:Class',
+            'Instance'  : '._test.pycollect:Instance',
+            'Generator' : '._test.pycollect:Generator',
+            'Function'  : '._test.pycollect:Function',
+            '_fillfuncargs' : '._test.funcargs:fillfuncargs',
+        },
+        'cmdline': {
+            'main' : '._test.cmdline:main', # backward compat
+        },
+    },
+
+    # hook into the top-level standard library
+    process = {
+        '__doc__'        : '._process:__doc__',
+        'cmdexec'        : '._process.cmdexec:cmdexec',
+        'kill'           : '._process.killproc:kill',
+        'ForkedFunc'     : '._process.forkedfunc:ForkedFunc',
+    },
+
+    path = {
+        '__doc__'        : '._path:__doc__',
+        'svnwc'          : '._path.svnwc:SvnWCCommandPath',
+        'svnurl'         : '._path.svnurl:SvnCommandPath',
+        'local'          : '._path.local:LocalPath',
+        'SvnAuth'        : '._path.svnwc:SvnAuth',
+    },
+
+    # some nice slightly magic APIs
+    magic = {
+        'invoke'           : '._code.oldmagic:invoke',
+        'revoke'           : '._code.oldmagic:revoke',
+        'patch'            : '._code.oldmagic:patch',
+        'revert'           : '._code.oldmagic:revert',
+        'autopath'         : '._path.local:autopath',
+        'AssertionError'   : '._code.oldmagic2:AssertionError',
+    },
+
+    # python inspection/code-generation API
+    code = {
+        '__doc__'           : '._code:__doc__',
+        'compile'           : '._code.source:compile_',
+        'Source'            : '._code.source:Source',
+        'Code'              : '._code.code:Code',
+        'Frame'             : '._code.code:Frame',
+        'ExceptionInfo'     : '._code.code:ExceptionInfo',
+        'Traceback'         : '._code.code:Traceback',
+        'getfslineno'       : '._code.source:getfslineno',
+        'getrawcode'        : '._code.code:getrawcode',
+        'patch_builtins'    : '._code.code:patch_builtins',
+        'unpatch_builtins'  : '._code.code:unpatch_builtins',
+        '_AssertionError'   : '._code.assertion:AssertionError',
+        '_reinterpret_old'  : '._code.assertion:reinterpret_old',
+        '_reinterpret'      : '._code.assertion:reinterpret',
+    },
+
+    # backports and additions of builtins
+    builtin = {
+        '__doc__'        : '._builtin:__doc__',
+        'enumerate'      : '._builtin:enumerate',
+        'reversed'       : '._builtin:reversed',
+        'sorted'         : '._builtin:sorted',
+        'set'            : '._builtin:set',
+        'frozenset'      : '._builtin:frozenset',
+        'BaseException'  : '._builtin:BaseException',
+        'GeneratorExit'  : '._builtin:GeneratorExit',
+        'print_'         : '._builtin:print_',
+        '_reraise'       : '._builtin:_reraise',
+        '_tryimport'     : '._builtin:_tryimport',
+        'exec_'          : '._builtin:exec_',
+        '_basestring'    : '._builtin:_basestring',
+        '_totext'        : '._builtin:_totext',
+        '_isbytes'       : '._builtin:_isbytes',
+        '_istext'        : '._builtin:_istext',
+        '_getimself'     : '._builtin:_getimself',
+        '_getfuncdict'   : '._builtin:_getfuncdict',
+        '_getcode'       : '._builtin:_getcode',
+        'builtins'       : '._builtin:builtins',
+        'execfile'       : '._builtin:execfile',
+        'callable'       : '._builtin:callable',
+    },
+
+    # input-output helping
+    io = {
+        '__doc__'             : '._io:__doc__',
+        'dupfile'             : '._io.capture:dupfile',
+        'TextIO'              : '._io.capture:TextIO',
+        'BytesIO'             : '._io.capture:BytesIO',
+        'FDCapture'           : '._io.capture:FDCapture',
+        'StdCapture'          : '._io.capture:StdCapture',
+        'StdCaptureFD'        : '._io.capture:StdCaptureFD',
+        'TerminalWriter'      : '._io.terminalwriter:TerminalWriter',
+        'ansi_print'          : '._io.terminalwriter:ansi_print', 
+        'get_terminal_width'  : '._io.terminalwriter:get_terminal_width',
+        'saferepr'            : '._io.saferepr:saferepr',
+    },
+
+    # small and mean xml/html generation
+    xml = {
+        '__doc__'            : '._xmlgen:__doc__',
+        'html'               : '._xmlgen:html',
+        'Tag'                : '._xmlgen:Tag',
+        'raw'                : '._xmlgen:raw',
+        'Namespace'          : '._xmlgen:Namespace',
+        'escape'             : '._xmlgen:escape',
+    },
+
+    log = {
+        # logging API ('producers' and 'consumers' connected via keywords)
+        '__doc__'            : '._log:__doc__',
+        '_apiwarn'           : '._log.warning:_apiwarn',
+        'Producer'           : '._log.log:Producer',
+        'setconsumer'        : '._log.log:setconsumer',
+        '_setstate'          : '._log.log:setstate',
+        '_getstate'          : '._log.log:getstate',
+        'Path'               : '._log.log:Path',
+        'STDOUT'             : '._log.log:STDOUT',
+        'STDERR'             : '._log.log:STDERR',
+        'Syslog'             : '._log.log:Syslog',
+    },
+
+    # compatibility modules (deprecated)
+    compat = {
+        '__doc__'         : '._compat:__doc__',
+        'doctest'         : '._compat.dep_doctest:doctest',
+        'optparse'        : '._compat.dep_optparse:optparse',
+        'textwrap'        : '._compat.dep_textwrap:textwrap',
+        'subprocess'      : '._compat.dep_subprocess:subprocess',
+    },
+))
+import py
+pydir = py.path.local(py.__file__).dirpath()
+import sys
+
+try:
+    reversed = reversed
+except NameError:
+    def reversed(sequence):
+        """reversed(sequence) -> reverse iterator over values of the sequence
+
+        Return a reverse iterator
+        """
+        if hasattr(sequence, '__reversed__'):
+            return sequence.__reversed__()
+        if not hasattr(sequence, '__getitem__'):
+            raise TypeError("argument to reversed() must be a sequence")
+        return reversed_iterator(sequence)
+
+    class reversed_iterator(object):
+
+        def __init__(self, seq):
+            self.seq = seq
+            self.remaining = len(seq)
+
+        def __iter__(self):
+            return self
+
+        def next(self):
+            i = self.remaining
+            if i > 0:
+                i -= 1
+                item = self.seq[i]
+                self.remaining = i
+                return item
+            raise StopIteration
+
+        def __length_hint__(self):
+            return self.remaining
+
+try:
+    sorted = sorted
+except NameError:
+    builtin_cmp = cmp # need to use cmp as keyword arg
+
+    def sorted(iterable, cmp=None, key=None, reverse=0):
+        use_cmp = None
+        if key is not None:
+            if cmp is None:
+                def use_cmp(x, y):
+                    return builtin_cmp(x[0], y[0])
+            else:
+                def use_cmp(x, y):
+                    return cmp(x[0], y[0])
+            l = [(key(element), element) for element in iterable]
+        else:
+            if cmp is not None:
+                use_cmp = cmp
+            l = list(iterable)
+        if use_cmp is not None:
+            l.sort(use_cmp)
+        else:
+            l.sort()
+        if reverse:
+            l.reverse()
+        if key is not None:
+            return [element for (_, element) in l]
+        return l
+
+try:
+    set, frozenset = set, frozenset
+except NameError:
+    from sets import set, frozenset 
+
+# pass through
+enumerate = enumerate 
+
+try:
+    BaseException = BaseException
+except NameError:
+    BaseException = Exception
+
+try:
+    GeneratorExit = GeneratorExit
+except NameError:
+    class GeneratorExit(Exception):
+        """ This exception is never raised, it is there to make it possible to
+        write code compatible with CPython 2.5 even in lower CPython
+        versions."""
+        pass
+    GeneratorExit.__module__ = 'exceptions'
+
+if sys.version_info >= (3, 0):
+    exec ("print_ = print ; exec_=exec")
+    import builtins
+
+    # some backward compatibility helpers 
+    _basestring = str 
+    def _totext(obj, encoding=None):
+        if isinstance(obj, bytes):
+            obj = obj.decode(encoding)
+        elif not isinstance(obj, str):
+            obj = str(obj)
+        return obj
+
+    def _isbytes(x): 
+        return isinstance(x, bytes)
+    def _istext(x): 
+        return isinstance(x, str)
+
+    def _getimself(function):
+        return getattr(function, '__self__', None)
+
+    def _getfuncdict(function):
+        return getattr(function, "__dict__", None)
+
+    def _getcode(function):
+        return getattr(function, "__code__", None)
+
+    def execfile(fn, globs=None, locs=None):
+        if globs is None:
+            back = sys._getframe(1)
+            globs = back.f_globals
+            locs = back.f_locals
+            del back
+        elif locs is None:
+            locs = globs
+        fp = open(fn, "rb")
+        try:
+            source = fp.read()
+        finally:
+            fp.close()
+        co = compile(source, fn, "exec", dont_inherit=True)
+        exec_(co, globs, locs)
+
+    def callable(obj):
+        return hasattr(obj, "__call__")
+
+else:
+    import __builtin__ as builtins
+    _totext = unicode 
+    _basestring = basestring
+    execfile = execfile
+    callable = callable
+    def _isbytes(x): 
+        return isinstance(x, str)
+    def _istext(x): 
+        return isinstance(x, unicode)
+
+    def _getimself(function):
+        return getattr(function, 'im_self', None)
+
+    def _getfuncdict(function):
+        return getattr(function, "__dict__", None)
+
+    def _getcode(function):
+        try:
+            return getattr(function, "__code__")
+        except AttributeError:
+            return getattr(function, "func_code", None)
+
+    def print_(*args, **kwargs):
+        """ minimal backport of py3k print statement. """ 
+        sep = ' '
+        if 'sep' in kwargs:
+            sep = kwargs.pop('sep')
+        end = '\n'
+        if 'end' in kwargs:
+            end = kwargs.pop('end')
+        file = 'file' in kwargs and kwargs.pop('file') or sys.stdout
+        if kwargs:
+            args = ", ".join([str(x) for x in kwargs])
+            raise TypeError("invalid keyword arguments: %s" % args)
+        at_start = True
+        for x in args:
+            if not at_start:
+                file.write(sep)
+            file.write(str(x))
+            at_start = False
+        file.write(end)
+
+    def exec_(obj, globals=None, locals=None):
+        """ minimal backport of py3k exec statement. """ 
+        __tracebackhide__ = True
+        if globals is None: 
+            frame = sys._getframe(1)
+            globals = frame.f_globals 
+            if locals is None:
+                locals = frame.f_locals
+        elif locals is None:
+            locals = globals
+        exec2(obj, globals, locals) 
+
+if sys.version_info >= (3,0):
+    exec ("""
+def _reraise(cls, val, tb):
+    __tracebackhide__ = True
+    assert hasattr(val, '__traceback__')
+    raise val
+""")
+else:
+    exec ("""
+def _reraise(cls, val, tb):
+    __tracebackhide__ = True
+    raise cls, val, tb
+def exec2(obj, globals, locals):
+    __tracebackhide__ = True
+    exec obj in globals, locals 
+""")
+
+def _tryimport(*names):
+    """ return the first successfully imported module. """ 
+    assert names
+    for name in names:
+        try:
+            return __import__(name, None, None, '__doc__')
+        except ImportError:
+            excinfo = sys.exc_info()
+    _reraise(*excinfo)

py/_cmdline/__init__.py

+#

py/_cmdline/pycleanup.py

+#!/usr/bin/env python 
+
+"""\
+py.cleanup [PATH] ...
+
+Delete typical python development related files recursively under the specified PATH (which defaults to the current working directory). Don't follow links and don't recurse into directories with a dot.  Optionally remove setup.py related files and empty
+directories. 
+
+"""
+import py
+import sys, subprocess
+
+def main():
+    parser = py.std.optparse.OptionParser(usage=__doc__)
+    parser.add_option("-e", metavar="ENDING", 
+        dest="endings", default=[".pyc", "$py.class"], action="append", 
+        help=("(multi) recursively remove files with the given ending." 
+             " '.pyc' and '$py.class' are in the default list."))
+    parser.add_option("-d", action="store_true", dest="removedir",
+                      help="remove empty directories.")
+    parser.add_option("-s", action="store_true", dest="setup",
+                      help="remove 'build' and 'dist' directories next to setup.py files")
+    parser.add_option("-a", action="store_true", dest="all",
+                      help="synonym for '-S -d -e pip-log.txt'")
+    parser.add_option("-n", "--dryrun", dest="dryrun", default=False, 
+        action="store_true", 
+        help="don't actually delete but display would-be-removed filenames.")
+    (options, args) = parser.parse_args()
+
+    Cleanup(options, args).main()
+
+class Cleanup:
+    def __init__(self, options, args):
+        if not args:
+            args = ["."]
+        self.options = options
+        self.args = [py.path.local(x) for x in args]
+        if options.all:
+            options.setup = True
+            options.removedir = True
+            options.endings.append("pip-log.txt")
+
+    def main(self):
+        if self.options.setup:
+            for arg in self.args:
+                self.setupclean(arg)
+        
+        for path in self.args:
+            py.builtin.print_("cleaning path", path, 
+                "of extensions", self.options.endings)
+            for x in path.visit(self.shouldremove, self.recursedir):
+                self.remove(x)
+        if self.options.removedir:
+            for x in path.visit(lambda x: x.check(dir=1), self.recursedir):
+                if not x.listdir():
+                    self.remove(x)
+
+    def shouldremove(self, p):
+        for ending in self.options.endings:
+            if p.basename.endswith(ending):
+                return True
+
+    def recursedir(self, path):
+        return path.check(dotfile=0, link=0)
+
+    def remove(self, path):
+        if not path.check():
+            return
+        if self.options.dryrun:
+            py.builtin.print_("would remove", path)
+        else:
+            py.builtin.print_("removing", path)
+            path.remove()
+
+    def XXXcallsetup(self, setup, *args):
+        old = setup.dirpath().chdir()
+        try:
+            subprocess.call([sys.executable, str(setup)] + list(args))
+        finally:
+            old.chdir()
+            
+    def setupclean(self, path):
+        for x in path.visit("setup.py", self.recursedir):
+            basepath = x.dirpath()
+            self.remove(basepath / "build")
+            self.remove(basepath / "dist")

py/_cmdline/pyconvert_unittest.py

+import re
+import sys
+
+try:
+    import parser
+except ImportError:
+    parser = None
+
+d={}
+#  d is the dictionary of unittest changes, keyed to the old name
+#  used by unittest.
+#  d[old][0] is the new replacement function.
+#  d[old][1] is the operator you will substitute, or '' if there is none.
+#  d[old][2] is the possible number of arguments to the unittest
+#  function.
+
+# Old Unittest Name             new name         operator  # of args
+d['assertRaises']           = ('raises',               '', ['Any'])
+d['fail']                   = ('raise AssertionError', '', [0,1])
+d['assert_']                = ('assert',               '', [1,2])
+d['failIf']                 = ('assert not',           '', [1,2])
+d['assertEqual']            = ('assert',            ' ==', [2,3])
+d['failIfEqual']            = ('assert not',        ' ==', [2,3])
+d['assertIn']               = ('assert',            ' in', [2,3])
+d['assertNotIn']            = ('assert',            ' not in', [2,3])
+d['assertNotEqual']         = ('assert',            ' !=', [2,3])
+d['failUnlessEqual']        = ('assert',            ' ==', [2,3])
+d['assertAlmostEqual']      = ('assert round',      ' ==', [2,3,4])
+d['failIfAlmostEqual']      = ('assert not round',  ' ==', [2,3,4])
+d['assertNotAlmostEqual']   = ('assert round',      ' !=', [2,3,4])
+d['failUnlessAlmostEquals'] = ('assert round',      ' ==', [2,3,4])
+
+#  the list of synonyms
+d['failUnlessRaises']      = d['assertRaises']
+d['failUnless']            = d['assert_']
+d['assertEquals']          = d['assertEqual']
+d['assertNotEquals']       = d['assertNotEqual']
+d['assertAlmostEquals']    = d['assertAlmostEqual']
+d['assertNotAlmostEquals'] = d['assertNotAlmostEqual']
+
+# set up the regular expressions we will need
+leading_spaces = re.compile(r'^(\s*)') # this never fails
+
+pat = ''
+for k in d.keys():  # this complicated pattern to match all unittests
+    pat += '|' + r'^(\s*)' + 'self.' + k + r'\(' # \tself.whatever(
+
+old_names = re.compile(pat[1:])
+linesep='\n'        # nobody will really try to convert files not read
+                    # in text mode, will they?
+
+
+def blocksplitter(fp):
+    '''split a file into blocks that are headed by functions to rename'''
+
+    blocklist = []
+    blockstring = ''
+
+    for line in fp:
+        interesting = old_names.match(line)
+        if interesting :
+            if blockstring:
+                blocklist.append(blockstring)
+                blockstring = line # reset the block
+        else:
+            blockstring += line
+            
+    blocklist.append(blockstring)
+    return blocklist
+
+def rewrite_utest(block):
+    '''rewrite every block to use the new utest functions'''
+
+    '''returns the rewritten unittest, unless it ran into problems,
+       in which case it just returns the block unchanged.
+    '''
+    utest = old_names.match(block)
+
+    if not utest:
+        return block
+
+    old = utest.group(0).lstrip()[5:-1] # the name we want to replace
+    new = d[old][0] # the name of the replacement function
+    op  = d[old][1] # the operator you will use , or '' if there is none.
+    possible_args = d[old][2]  # a list of the number of arguments the
+                               # unittest function could possibly take.
+                
+    if possible_args == ['Any']: # just rename assertRaises & friends
+        return re.sub('self.'+old, new, block)
+
+    message_pos = possible_args[-1]
+    # the remaining unittests can have an optional message to print
+    # when they fail.  It is always the last argument to the function.
+
+    try:
+        indent, argl, trailer = decompose_unittest(old, block)
+
+    except SyntaxError: # but we couldn't parse it!
+        return block
+    
+    argnum = len(argl)
+    if argnum not in possible_args:
+        # sanity check - this one isn't real either
+        return block
+
+    elif argnum == message_pos:
+        message = argl[-1]
+        argl = argl[:-1]
+    else:
+        message = None
+
+    if argnum is 0 or (argnum is 1 and argnum is message_pos): #unittest fail()
+        string = ''
+        if message:
+            message = ' ' + message
+
+    elif message_pos is 4:  # assertAlmostEqual & friends
+        try:
+            pos = argl[2].lstrip()
+        except IndexError:
+            pos = '7' # default if none is specified
+        string = '(%s -%s, %s)%s 0' % (argl[0], argl[1], pos, op )
+
+    else: # assert_, assertEquals and all the rest
+        string = ' ' + op.join(argl)
+
+    if message:
+        string = string + ',' + message
+
+    return indent + new + string + trailer
+
+def decompose_unittest(old, block):
+    '''decompose the block into its component parts'''
+
+    ''' returns indent, arglist, trailer 
+        indent -- the indentation
+        arglist -- the arguments to the unittest function
+        trailer -- any extra junk after the closing paren, such as #commment
+    '''
+ 
+    indent = re.match(r'(\s*)', block).group()
+    pat = re.search('self.' + old + r'\(', block)
+
+    args, trailer = get_expr(block[pat.end():], ')')
+    arglist = break_args(args, [])
+
+    if arglist == ['']: # there weren't any
+        return indent, [], trailer
+
+    for i in range(len(arglist)):
+        try:
+            parser.expr(arglist[i].lstrip('\t '))
+        except SyntaxError:
+            if i == 0:
+                arglist[i] = '(' + arglist[i] + ')'
+            else:
+                arglist[i] = ' (' + arglist[i] + ')'
+
+    return indent, arglist, trailer
+
+def break_args(args, arglist):
+    '''recursively break a string into a list of arguments'''
+    try:
+        first, rest = get_expr(args, ',')
+        if not rest:
+            return arglist + [first]
+        else:
+            return [first] + break_args(rest, arglist)
+    except SyntaxError:
+        return arglist + [args]
+
+def get_expr(s, char):
+    '''split a string into an expression, and the rest of the string'''
+
+    pos=[]
+    for i in range(len(s)):
+        if s[i] == char:
+            pos.append(i)
+    if pos == []:
+        raise SyntaxError # we didn't find the expected char.  Ick.
+     
+    for p in pos:
+        # make the python parser do the hard work of deciding which comma
+        # splits the string into two expressions
+        try:
+            parser.expr('(' + s[:p] + ')')
+            return s[:p], s[p+1:]
+        except SyntaxError: # It's not an expression yet
+            pass
+    raise SyntaxError       # We never found anything that worked.
+
+
+def main():
+    import sys
+    import py
+
+    usage = "usage: %prog [-s [filename ...] | [-i | -c filename ...]]"
+    optparser = py.std.optparse.OptionParser(usage)
+
+    def select_output (option, opt, value, optparser, **kw):
+        if hasattr(optparser, 'output'):
+            optparser.error(
+                'Cannot combine -s -i and -c options. Use one only.')
+        else:
+            optparser.output = kw['output']
+
+    optparser.add_option("-s", "--stdout", action="callback",
+                         callback=select_output,
+                         callback_kwargs={'output':'stdout'},
+                         help="send your output to stdout")
+
+    optparser.add_option("-i", "--inplace", action="callback",
+                         callback=select_output,
+                         callback_kwargs={'output':'inplace'},
+                         help="overwrite files in place")
+
+    optparser.add_option("-c", "--copy", action="callback",
+                         callback=select_output,
+                         callback_kwargs={'output':'copy'},
+                         help="copy files ... fn.py --> fn_cp.py")
+
+    options, args = optparser.parse_args()
+
+    output = getattr(optparser, 'output', 'stdout')
+
+    if output in ['inplace', 'copy'] and not args:
+        optparser.error(
+                '-i and -c option  require at least one filename')
+
+    if not args:
+        s = ''
+        for block in blocksplitter(sys.stdin):
+            s += rewrite_utest(block)
+        sys.stdout.write(s)
+
+    else:
+        for infilename in args: # no error checking to see if we can open, etc.
+            infile = file(infilename)
+            s = ''
+            for block in blocksplitter(infile):
+                s += rewrite_utest(block)
+            if output == 'inplace':
+                outfile = file(infilename, 'w+')
+            elif output == 'copy': # yes, just go clobber any existing .cp
+                outfile = file (infilename[:-3]+ '_cp.py', 'w+')
+            else:
+                outfile = sys.stdout
+
+            outfile.write(s)
+
+    
+if __name__ == '__main__':
+    main()

py/_cmdline/pycountloc.py

+#!/usr/bin/env python
+
+# hands on script to compute the non-empty Lines of Code 
+# for tests and non-test code 
+
+"""\
+py.countloc [PATHS]
+
+Count (non-empty) lines of python code and number of python files recursively
+starting from a list of paths given on the command line (starting from the
+current working directory). Distinguish between test files and normal ones and
+report them separately.
+"""
+import py
+
+def main():
+    parser = py.std.optparse.OptionParser(usage=__doc__)
+    (options, args) = parser.parse_args()
+    countloc(args)
+   
+def nodot(p):
+    return p.check(dotfile=0)
+
+class FileCounter(object):  
+    def __init__(self):
+        self.file2numlines = {}
+        self.numlines = 0
+        self.numfiles = 0
+
+    def addrecursive(self, directory, fil="*.py", rec=nodot):
+        for x in directory.visit(fil, rec): 
+            self.addfile(x)
+
+    def addfile(self, fn, emptylines=False):
+        if emptylines:
+            s = len(p.readlines())
+        else:
+            s = 0
+            for i in fn.readlines():
+                if i.strip():
+                    s += 1
+        self.file2numlines[fn] = s 
+        self.numfiles += 1
+        self.numlines += s
+
+    def getnumlines(self, fil): 
+        numlines = 0
+        for path, value in self.file2numlines.items():
+            if fil(path): 
+                numlines += value
+        return numlines 
+
+    def getnumfiles(self, fil): 
+        numfiles = 0
+        for path in self.file2numlines:
+            if fil(path): 
+                numfiles += 1
+        return numfiles
+
+def get_loccount(locations=None):
+    if locations is None:
+        localtions = [py.path.local()]
+    counter = FileCounter()
+    for loc in locations: 
+        counter.addrecursive(loc, '*.py', rec=nodot)
+
+    def istestfile(p):
+        return p.check(fnmatch='test_*.py')
+    isnottestfile = lambda x: not istestfile(x)
+
+    numfiles = counter.getnumfiles(isnottestfile) 
+    numlines = counter.getnumlines(isnottestfile) 
+    numtestfiles = counter.getnumfiles(istestfile)
+    numtestlines = counter.getnumlines(istestfile)
+   
+    return counter, numfiles, numlines, numtestfiles, numtestlines
+
+def countloc(paths=None):
+    if not paths:
+        paths = ['.']
+    locations = [py.path.local(x) for x in paths]
+    (counter, numfiles, numlines, numtestfiles,
+     numtestlines) = get_loccount(locations)
+
+    items = counter.file2numlines.items()
+    items.sort(lambda x,y: cmp(x[1], y[1]))
+    for x, y in items:
+        print("%3d %30s" % (y,x))
+    
+    print("%30s %3d" %("number of testfiles", numtestfiles))
+    print("%30s %3d" %("number of non-empty testlines", numtestlines))
+    print("%30s %3d" %("number of files", numfiles))
+    print("%30s %3d" %("number of non-empty lines", numlines))
+

py/_cmdline/pylookup.py

+#!/usr/bin/env python 
+
+"""\
+py.lookup [search_directory] SEARCH_STRING [options]
+
+Looks recursively at Python files for a SEARCH_STRING, starting from the
+present working directory. Prints the line, with the filename and line-number
+prepended."""
+
+import sys, os
+import py
+from py.io import ansi_print, get_terminal_width
+import re
+
+def rec(p):
+    return p.check(dotfile=0)
+
+parser = py.std.optparse.OptionParser(usage=__doc__)
+parser.add_option("-i", "--ignore-case", action="store_true", dest="ignorecase",
+                  help="ignore case distinctions")
+parser.add_option("-C", "--context", action="store", type="int", dest="context",
+            default=0, help="How many lines of output to show")
+
+terminal_width = get_terminal_width()
+
+def find_indexes(search_line, string):
+    indexes = []
+    before = 0
+    while 1:
+        i = search_line.find(string, before)
+        if i == -1:
+            break
+        indexes.append(i)
+        before = i + len(string)
+    return indexes
+
+def main():
+    (options, args) = parser.parse_args()
+    if len(args) == 2:
+        search_dir, string = args
+        search_dir = py.path.local(search_dir)
+    else:
+        search_dir = py.path.local()
+        string = args[0]
+    if options.ignorecase:
+        string = string.lower()
+    for x in search_dir.visit('*.py', rec):
+        # match filename directly
+        s = x.relto(search_dir)
+        if options.ignorecase:
+            s = s.lower()
+        if s.find(string) != -1:
+            sys.stdout.write("%s: filename matches %r" %(x, string) + "\n")
+
+        try:
+            s = x.read()
+        except py.error.ENOENT:
+            pass # whatever, probably broken link (ie emacs lock)
+        searchs = s
+        if options.ignorecase:
+            searchs = s.lower()
+        if s.find(string) != -1:
+            lines = s.splitlines()
+            if options.ignorecase:
+                searchlines = s.lower().splitlines()
+            else:
+                searchlines = lines
+            for i, (line, searchline) in enumerate(zip(lines, searchlines)): 
+                indexes = find_indexes(searchline, string)
+                if not indexes:
+                    continue
+                if not options.context:
+                    sys.stdout.write("%s:%d: " %(x.relto(search_dir), i+1))
+                    last_index = 0
+                    for index in indexes:
+                        sys.stdout.write(line[last_index: index])
+                        ansi_print(line[index: index+len(string)],
+                                   file=sys.stdout, esc=31, newline=False)
+                        last_index = index + len(string)
+                    sys.stdout.write(line[last_index:] + "\n")
+                else:
+                    context = (options.context)/2
+                    for count in range(max(0, i-context), min(len(lines) - 1, i+context+1)):
+                        print("%s:%d:  %s" %(x.relto(search_dir), count+1, lines[count].rstrip()))
+                    print("-" * terminal_width)

py/_cmdline/pysvnwcrevert.py

+#! /usr/bin/env python
+"""\
+py.svnwcrevert [options] WCPATH
+
+Running this script and then 'svn up' puts the working copy WCPATH in a state
+as clean as a fresh check-out.
+
+WARNING: you'll loose all local changes, obviously!
+
+This script deletes all files that have been modified
+or that svn doesn't explicitly know about, including svn:ignored files
+(like .pyc files, hint hint).
+
+The goal of this script is to leave the working copy with some files and
+directories possibly missing, but - most importantly - in a state where
+the following 'svn up' won't just crash.
+"""
+
+import sys, py
+
+def kill(p, root):
+    print('<    %s' % (p.relto(root),))
+    p.remove(rec=1)
+
+def svnwcrevert(path, root=None, precious=[]):
+    if root is None:
+        root = path
+    wcpath = py.path.svnwc(path)
+    try:
+        st = wcpath.status()
+    except ValueError:   # typically, "bad char in wcpath"
+        kill(path, root)
+        return
+    for p in path.listdir():
+        if p.basename == '.svn' or p.basename in precious:
+            continue
+        wcp = py.path.svnwc(p)
+        if wcp not in st.unchanged and wcp not in st.external:
+            kill(p, root)
+        elif p.check(dir=1):
+            svnwcrevert(p, root)
+
+# XXX add a functional test
+
+parser = py.std.optparse.OptionParser(usage=__doc__)
+parser.add_option("-p", "--precious",
+                  action="append", dest="precious", default=[],
+                  help="preserve files with this name")
+
+def main():
+    opts, args = parser.parse_args()
+    if len(args) != 1:
+        parser.print_help()
+        sys.exit(2)
+    svnwcrevert(py.path.local(args[0]), precious=opts.precious)

py/_cmdline/pytest.py

+#!/usr/bin/env python 
+import py
+
+def main(args=None):
+    raise SystemExit(py.test.cmdline.main(args))

py/_cmdline/pywhich.py

+#!/usr/bin/env python 
+
+"""\
+py.which [name]
+
+print the location of the given python module or package name 
+"""
+
+import sys
+
+def main():
+    name = sys.argv[1]
+    try:
+        mod = __import__(name)
+    except ImportError:
+        sys.stderr.write("could not import: " +  name + "\n")
+    else:
+        try:
+            location = mod.__file__ 
+        except AttributeError:
+            sys.stderr.write("module (has no __file__): " + str(mod))
+        else:
+            print(location)

py/_code/__init__.py

+""" python inspection/code generation API """

py/_code/_assertionnew.py

+"""
+Like _assertion.py but using builtin AST.  It should replace _assertionold.py
+eventually.
+"""
+
+import sys
+import ast
+
+import py
+from py._code.assertion import _format_explanation, BuiltinAssertionError
+
+
+if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
+    # See http://bugs.jython.org/issue1497
+    _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
+              "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
+              "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
+              "List", "Tuple")
+    _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
+              "AugAssign", "Print", "For", "While", "If", "With", "Raise",
+              "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
+              "Exec", "Global", "Expr", "Pass", "Break", "Continue")
+    _expr_nodes = set(getattr(ast, name) for name in _exprs)
+    _stmt_nodes = set(getattr(ast, name) for name in _stmts)
+    def _is_ast_expr(node):
+        return node.__class__ in _expr_nodes
+    def _is_ast_stmt(node):
+        return node.__class__ in _stmt_nodes
+else:
+    def _is_ast_expr(node):
+        return isinstance(node, ast.expr)
+    def _is_ast_stmt(node):
+        return isinstance(node, ast.stmt)
+
+
+class Failure(Exception):
+    """Error found while interpreting AST."""
+
+    def __init__(self, explanation=""):
+        self.cause = sys.exc_info()
+        self.explanation = explanation
+
+
+def interpret(source, frame, should_fail=False):
+    mod = ast.parse(source)
+    visitor = DebugInterpreter(frame)
+    try:
+        visitor.visit(mod)
+    except Failure:
+        failure = sys.exc_info()[1]
+        return getfailure(failure)
+    if should_fail:
+        return ("(assertion failed, but when it was re-run for "
+                "printing intermediate values, it did not fail.  Suggestions: "
+                "compute assert expression before the assert or use --no-assert)")
+
+def run(offending_line, frame=None):
+    if frame is None:
+        frame = py.code.Frame(sys._getframe(1))
+    return interpret(offending_line, frame)
+
+def getfailure(failure):
+    explanation = _format_explanation(failure.explanation)
+    value = failure.cause[1]
+    if str(value):
+        lines = explanation.splitlines()
+        if not lines:
+            lines.append("")
+        lines[0] += " << %s" % (value,)
+        explanation = "\n".join(lines)
+    text = "%s: %s" % (failure.cause[0].__name__, explanation)
+    if text.startswith("AssertionError: assert "):
+        text = text[16:]
+    return text
+
+
+operator_map = {
+    ast.BitOr : "|",
+    ast.BitXor : "^",
+    ast.BitAnd : "&",
+    ast.LShift : "<<",
+    ast.RShift : ">>",
+    ast.Add : "+",
+    ast.Sub : "-",
+    ast.Mult : "*",
+    ast.Div : "/",
+    ast.FloorDiv : "//",
+    ast.Mod : "%",
+    ast.Eq : "==",
+    ast.NotEq : "!=",
+    ast.Lt : "<",
+    ast.LtE : "<=",
+    ast.Gt : ">",
+    ast.GtE : ">=",
+    ast.Pow : "**",
+    ast.Is : "is",
+    ast.IsNot : "is not",
+    ast.In : "in",
+    ast.NotIn : "not in"
+}
+
+unary_map = {
+    ast.Not : "not %s",
+    ast.Invert : "~%s",
+    ast.USub : "-%s",
+    ast.UAdd : "+%s"
+}
+
+
+class DebugInterpreter(ast.NodeVisitor):
+    """Interpret AST nodes to gleam useful debugging information."""
+
+    def __init__(self, frame):
+        self.frame = frame
+
+    def generic_visit(self, node):
+        # Fallback when we don't have a special implementation.
+        if _is_ast_expr(node):
+            mod = ast.Expression(node)
+            co = self._compile(mod)
+            try:
+                result = self.frame.eval(co)
+            except Exception:
+                raise Failure()
+            explanation = self.frame.repr(result)
+            return explanation, result
+        elif _is_ast_stmt(node):
+            mod = ast.Module([node])
+            co = self._compile(mod, "exec")
+            try:
+                self.frame.exec_(co)
+            except Exception:
+                raise Failure()
+            return None, None
+        else:
+            raise AssertionError("can't handle %s" %(node,))
+
+    def _compile(self, source, mode="eval"):
+        return compile(source, "<assertion interpretation>", mode)
+
+    def visit_Expr(self, expr):
+        return self.visit(expr.value)
+
+    def visit_Module(self, mod):
+        for stmt in mod.body:
+            self.visit(stmt)
+
+    def visit_Name(self, name):
+        explanation, result = self.generic_visit(name)
+        # See if the name is local.
+        source = "%r in locals() is not globals()" % (name.id,)
+        co = self._compile(source)
+        try:
+            local = self.frame.eval(co)
+        except Exception:
+            # have to assume it isn't
+            local = False
+        if not local:
+            return name.id, result
+        return explanation, result
+
+    def visit_Compare(self, comp):
+        left = comp.left
+        left_explanation, left_result = self.visit(left)
+        got_result = False
+        for op, next_op in zip(comp.ops, comp.comparators):
+            if got_result and not result:
+                break
+            next_explanation, next_result = self.visit(next_op)
+            op_symbol = operator_map[op.__class__]
+            explanation = "%s %s %s" % (left_explanation, op_symbol,
+                                        next_explanation)
+            source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
+            co = self._compile(source)
+            try:
+                result = self.frame.eval(co, __exprinfo_left=left_result,
+                                         __exprinfo_right=next_result)
+            except Exception:
+                raise Failure(explanation)
+            else:
+                got_result = True
+            left_explanation, left_result = next_explanation, next_result
+        return explanation, result
+
+    def visit_BoolOp(self, boolop):
+        is_or = isinstance(boolop.op, ast.Or)
+        explanations = []
+        for operand in boolop.values:
+            explanation, result = self.visit(operand)
+            explanations.append(explanation)
+            if result == is_or:
+                break
+        name = is_or and " or " or " and "
+        explanation = "(" + name.join(explanations) + ")"
+        return explanation, result
+
+    def visit_UnaryOp(self, unary):
+        pattern = unary_map[unary.op.__class__]
+        operand_explanation, operand_result = self.visit(unary.operand)
+        explanation = pattern % (operand_explanation,)
+        co = self._compile(pattern % ("__exprinfo_expr",))
+        try:
+            result = self.frame.eval(co, __exprinfo_expr=operand_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, result
+
+    def visit_BinOp(self, binop):
+        left_explanation, left_result = self.visit(binop.left)
+        right_explanation, right_result = self.visit(binop.right)
+        symbol = operator_map[binop.op.__class__]
+        explanation = "(%s %s %s)" % (left_explanation, symbol,
+                                      right_explanation)
+        source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, __exprinfo_left=left_result,
+                                     __exprinfo_right=right_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, result
+
+    def visit_Call(self, call):
+        func_explanation, func = self.visit(call.func)
+        arg_explanations = []
+        ns = {"__exprinfo_func" : func}
+        arguments = []
+        for arg in call.args:
+            arg_explanation, arg_result = self.visit(arg)
+            arg_name = "__exprinfo_%s" % (len(ns),)
+            ns[arg_name] = arg_result
+            arguments.append(arg_name)
+            arg_explanations.append(arg_explanation)
+        for keyword in call.keywords:
+            arg_explanation, arg_result = self.visit(keyword.value)
+            arg_name = "__exprinfo_%s" % (len(ns),)
+            ns[arg_name] = arg_result
+            keyword_source = "%s=%%s" % (keyword.arg)
+            arguments.append(keyword_source % (arg_name,))
+            arg_explanations.append(keyword_source % (arg_explanation,))
+        if call.starargs:
+            arg_explanation, arg_result = self.visit(call.starargs)
+            arg_name = "__exprinfo_star"
+            ns[arg_name] = arg_result
+            arguments.append("*%s" % (arg_name,))
+            arg_explanations.append("*%s" % (arg_explanation,))
+        if call.kwargs:
+            arg_explanation, arg_result = self.visit(call.kwargs)
+            arg_name = "__exprinfo_kwds"
+            ns[arg_name] = arg_result
+            arguments.append("**%s" % (arg_name,))
+            arg_explanations.append("**%s" % (arg_explanation,))
+        args_explained = ", ".join(arg_explanations)
+        explanation = "%s(%s)" % (func_explanation, args_explained)
+        args = ", ".join(arguments)
+        source = "__exprinfo_func(%s)" % (args,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, **ns)
+        except Exception:
+            raise Failure(explanation)
+        # Only show result explanation if it's not a builtin call or returns a
+        # bool.
+        if not isinstance(call.func, ast.Name) or \
+                not self._is_builtin_name(call.func):
+            source = "isinstance(__exprinfo_value, bool)"
+            co = self._compile(source)
+            try:
+                is_bool = self.frame.eval(co, __exprinfo_value=result)
+            except Exception:
+                is_bool = False
+            if not is_bool:
+                pattern = "%s\n{%s = %s\n}"
+                rep = self.frame.repr(result)
+                explanation = pattern % (rep, rep, explanation)
+        return explanation, result
+
+    def _is_builtin_name(self, name):
+        pattern = "%r not in globals() and %r not in locals()"
+        source = pattern % (name.id, name.id)
+        co = self._compile(source)
+        try:
+            return self.frame.eval(co)
+        except Exception:
+            return False
+
+    def visit_Attribute(self, attr):
+        if not isinstance(attr.ctx, ast.Load):
+            return self.generic_visit(attr)
+        source_explanation, source_result = self.visit(attr.value)
+        explanation = "%s.%s" % (source_explanation, attr.attr)
+        source = "__exprinfo_expr.%s" % (attr.attr,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, __exprinfo_expr=source_result)
+        except Exception:
+            raise Failure(explanation)
+        # Check if the attr is from an instance.
+        source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
+        source = source % (attr.attr,)
+        co = self._compile(source)
+        try:
+            from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
+        except Exception:
+            from_instance = True
+        if from_instance:
+            rep = self.frame.repr(result)
+            pattern = "%s\n{%s = %s\n}"
+            explanation = pattern % (rep, rep, explanation)
+        return explanation, result
+
+    def visit_Assert(self, assrt):
+        test_explanation, test_result = self.visit(assrt.test)
+        if test_explanation.startswith("False\n{False =") and \
+                test_explanation.endswith("\n"):
+            test_explanation = test_explanation[15:-2]
+        explanation = "assert %s" % (test_explanation,)
+        if not test_result:
+            try:
+                raise BuiltinAssertionError
+            except Exception:
+                raise Failure(explanation)
+        return explanation, test_result
+
+    def visit_Assign(self, assign):
+        value_explanation, value_result = self.visit(assign.value)
+        explanation = "... = %s" % (value_explanation,)
+        name = ast.Name("__exprinfo_expr", ast.Load(), assign.value.lineno,
+                        assign.value.col_offset)
+        new_assign = ast.Assign(assign.targets, name, assign.lineno,
+                                assign.col_offset)
+        mod = ast.Module([new_assign])
+        co = self._compile(mod, "exec")
+        try:
+            self.frame.exec_(co, __exprinfo_expr=value_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, value_result

py/_code/_assertionold.py

+import py
+import sys, inspect
+from compiler import parse, ast, pycodegen
+from py._code.assertion import BuiltinAssertionError, _format_explanation
+
+passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
+
+class Failure:
+    def __init__(self, node):
+        self.exc, self.value, self.tb = sys.exc_info()
+        self.node = node
+
+class View(object):
+    """View base class.
+
+    If C is a subclass of View, then C(x) creates a proxy object around
+    the object x.  The actual class of the proxy is not C in general,
+    but a *subclass* of C determined by the rules below.  To avoid confusion
+    we call view class the class of the proxy (a subclass of C, so of View)
+    and object class the class of x.
+
+    Attributes and methods not found in the proxy are automatically read on x.
+    Other operations like setting attributes are performed on the proxy, as
+    determined by its view class.  The object x is available from the proxy
+    as its __obj__ attribute.
+
+    The view class selection is determined by the __view__ tuples and the
+    optional __viewkey__ method.  By default, the selected view class is the
+    most specific subclass of C whose __view__ mentions the class of x.
+    If no such subclass is found, the search proceeds with the parent
+    object classes.  For example, C(True) will first look for a subclass
+    of C with __view__ = (..., bool, ...) and only if it doesn't find any
+    look for one with __view__ = (..., int, ...), and then ..., object,...
+    If everything fails the class C itself is considered to be the default.
+
+    Alternatively, the view class selection can be driven by another aspect
+    of the object x, instead of the class of x, by overriding __viewkey__.
+    See last example at the end of this module.
+    """
+
+    _viewcache = {}
+    __view__ = ()
+
+    def __new__(rootclass, obj, *args, **kwds):
+        self = object.__new__(rootclass)
+        self.__obj__ = obj
+        self.__rootclass__ = rootclass
+        key = self.__viewkey__()
+        try:
+            self.__class__ = self._viewcache[key]
+        except KeyError:
+            self.__class__ = self._selectsubclass(key)
+        return self
+
+    def __getattr__(self, attr):
+        # attributes not found in the normal hierarchy rooted on View
+        # are looked up in the object's real class
+        return getattr(self.__obj__, attr)
+
+    def __viewkey__(self):
+        return self.__obj__.__class__
+
+    def __matchkey__(self, key, subclasses):
+        if inspect.isclass(key):
+            keys = inspect.getmro(key)
+        else:
+            keys = [key]
+        for key in keys:
+            result = [C for C in subclasses if key in C.__view__]
+            if result:
+                return result
+        return []
+
+    def _selectsubclass(self, key):
+        subclasses = list(enumsubclasses(self.__rootclass__))
+        for C in subclasses:
+            if not isinstance(C.__view__, tuple):
+                C.__view__ = (C.__view__,)
+        choices = self.__matchkey__(key, subclasses)
+        if not choices:
+            return self.__rootclass__
+        elif len(choices) == 1:
+            return choices[0]
+        else:
+            # combine the multiple choices
+            return type('?', tuple(choices), {})
+
+    def __repr__(self):
+        return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
+
+
+def enumsubclasses(cls):
+    for subcls in cls.__subclasses__():
+        for subsubclass in enumsubclasses(subcls):
+            yield subsubclass
+    yield cls
+
+
+class Interpretable(View):
+    """A parse tree node with a few extra methods."""
+    explanation = None
+
+    def is_builtin(self, frame):
+        return False
+
+    def eval(self, frame):
+        # fall-back for unknown expression nodes
+        try:
+            expr = ast.Expression(self.__obj__)
+            expr.filename = '<eval>'
+            self.__obj__.filename = '<eval>'
+            co = pycodegen.ExpressionCodeGenerator(expr).getCode()
+            result = frame.eval(co)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        self.result = result
+        self.explanation = self.explanation or frame.repr(self.result)
+
+    def run(self, frame):
+        # fall-back for unknown statement nodes
+        try:
+            expr = ast.Module(None, ast.Stmt([self.__obj__]))
+            expr.filename = '<run>'
+            co = pycodegen.ModuleCodeGenerator(expr).getCode()
+            frame.exec_(co)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+
+    def nice_explanation(self):
+        return _format_explanation(self.explanation)
+
+
+class Name(Interpretable):
+    __view__ = ast.Name
+
+    def is_local(self, frame):
+        source = '%r in locals() is not globals()' % self.name
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_global(self, frame):
+        source = '%r in globals()' % self.name
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_builtin(self, frame):
+        source = '%r not in locals() and %r not in globals()' % (
+            self.name, self.name)
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def eval(self, frame):
+        super(Name, self).eval(frame)
+        if not self.is_local(frame):
+            self.explanation = self.name
+
+class Compare(Interpretable):
+    __view__ = ast.Compare
+
+    def eval(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        for operation, expr2 in self.ops:
+            if hasattr(self, 'result'):
+                # shortcutting in chained expressions
+                if not frame.is_true(self.result):
+                    break
+            expr2 = Interpretable(expr2)
+            expr2.eval(frame)
+            self.explanation = "%s %s %s" % (
+                expr.explanation, operation, expr2.explanation)
+            source = "__exprinfo_left %s __exprinfo_right" % operation
+            try:
+                self.result = frame.eval(source,
+                                         __exprinfo_left=expr.result,
+                                         __exprinfo_right=expr2.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+            expr = expr2
+
+class And(Interpretable):
+    __view__ = ast.And
+
+    def eval(self, frame):
+        explanations = []
+        for expr in self.nodes:
+            expr = Interpretable(expr)
+            expr.eval(frame)
+            explanations.append(expr.explanation)
+            self.result = expr.result
+            if not frame.is_true(expr.result):
+                break
+        self.explanation = '(' + ' and '.join(explanations) + ')'
+
+class Or(Interpretable):
+    __view__ = ast.Or
+
+    def eval(self, frame):
+        explanations = []
+        for expr in self.nodes:
+            expr = Interpretable(expr)
+            expr.eval(frame)
+            explanations.append(expr.explanation)
+            self.result = expr.result
+            if frame.is_true(expr.result):
+                break
+        self.explanation = '(' + ' or '.join(explanations) + ')'
+
+
+# == Unary operations ==
+keepalive = []
+for astclass, astpattern in {
+    ast.Not    : 'not __exprinfo_expr',
+    ast.Invert : '(~__exprinfo_expr)',
+    }.items():
+
+    class UnaryArith(Interpretable):
+        __view__ = astclass
+
+        def eval(self, frame, astpattern=astpattern):
+            expr = Interpretable(self.expr)
+            expr.eval(frame)
+            self.explanation = astpattern.replace('__exprinfo_expr',
+                                                  expr.explanation)
+            try:
+                self.result = frame.eval(astpattern,
+                                         __exprinfo_expr=expr.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+    keepalive.append(UnaryArith)
+
+# == Binary operations ==
+for astclass, astpattern in {
+    ast.Add    : '(__exprinfo_left + __exprinfo_right)',
+    ast.Sub    : '(__exprinfo_left - __exprinfo_right)',
+    ast.Mul    : '(__exprinfo_left * __exprinfo_right)',
+    ast.Div    : '(__exprinfo_left / __exprinfo_right)',
+    ast.Mod    : '(__exprinfo_left % __exprinfo_right)',
+    ast.Power  : '(__exprinfo_left ** __exprinfo_right)',
+    }.items():
+
+    class BinaryArith(Interpretable):
+        __view__ = astclass
+
+        def eval(self, frame, astpattern=astpattern):
+            left = Interpretable(self.left)
+            left.eval(frame)
+            right = Interpretable(self.right)
+            right.eval(frame)
+            self.explanation = (astpattern
+                                .replace('__exprinfo_left',  left .explanation)
+                                .replace('__exprinfo_right', right.explanation))
+            try:
+                self.result = frame.eval(astpattern,
+                                         __exprinfo_left=left.result,
+                                         __exprinfo_right=right.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+    keepalive.append(BinaryArith)
+
+
+class CallFunc(Interpretable):
+    __view__ = ast.CallFunc
+
+    def is_bool(self, frame):
+        source = 'isinstance(__exprinfo_value, bool)'
+        try:
+            return frame.is_true(frame.eval(source,
+                                            __exprinfo_value=self.result))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def eval(self, frame):
+        node = Interpretable(self.node)
+        node.eval(frame)
+        explanations = []
+        vars = {'__exprinfo_fn': node.result}
+        source = '__exprinfo_fn('
+        for a in self.args:
+            if isinstance(a, ast.Keyword):
+                keyword = a.name
+                a = a.expr
+            else:
+                keyword = None
+            a = Interpretable(a)
+            a.eval(frame)
+            argname = '__exprinfo_%d' % len(vars)
+            vars[argname] = a.result
+            if keyword is None:
+                source += argname + ','
+                explanations.append(a.explanation)
+            else:
+                source += '%s=%s,' % (keyword, argname)
+                explanations.append('%s=%s' % (keyword, a.explanation))
+        if self.star_args:
+            star_args = Interpretable(self.star_args)
+            star_args.eval(frame)
+            argname = '__exprinfo_star'
+            vars[argname] = star_args.result
+            source += '*' + argname + ','
+            explanations.append('*' + star_args.explanation)
+        if self.dstar_args:
+            dstar_args = Interpretable(self.dstar_args)
+            dstar_args.eval(frame)
+            argname = '__exprinfo_kwds'
+            vars[argname] = dstar_args.result
+            source += '**' + argname + ','
+            explanations.append('**' + dstar_args.explanation)
+        self.explanation = "%s(%s)" % (
+            node.explanation, ', '.join(explanations))
+        if source.endswith(','):
+            source = source[:-1]
+        source += ')'
+        try:
+            self.result = frame.eval(source, **vars)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        if not node.is_builtin(frame) or not self.is_bool(frame):
+            r = frame.repr(self.result)
+            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
+
+class Getattr(Interpretable):
+    __view__ = ast.Getattr
+
+    def eval(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        source = '__exprinfo_expr.%s' % self.attrname
+        try:
+            self.result = frame.eval(source, __exprinfo_expr=expr.result)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        self.explanation = '%s.%s' % (expr.explanation, self.attrname)
+        # if the attribute comes from the instance, its value is interesting
+        source = ('hasattr(__exprinfo_expr, "__dict__") and '
+                  '%r in __exprinfo_expr.__dict__' % self.attrname)
+        try:
+            from_instance = frame.is_true(
+                frame.eval(source, __exprinfo_expr=expr.result))
+        except passthroughex:
+            raise
+        except:
+            from_instance = True
+        if from_instance:
+            r = frame.repr(self.result)
+            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
+
+# == Re-interpretation of full statements ==
+
+class Assert(Interpretable):
+    __view__ = ast.Assert
+
+    def run(self, frame):
+        test = Interpretable(self.test)
+        test.eval(frame)
+        # simplify 'assert False where False = ...'
+        if (test.explanation.startswith('False\n{False = ') and
+            test.explanation.endswith('\n}')):
+            test.explanation = test.explanation[15:-2]
+        # print the result as  'assert <explanation>'
+        self.result = test.result
+        self.explanation = 'assert ' + test.explanation
+        if not frame.is_true(test.result):
+            try:
+                raise BuiltinAssertionError
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+class Assign(Interpretable):
+    __view__ = ast.Assign
+
+    def run(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        self.result = expr.result
+        self.explanation = '... = ' + expr.explanation
+        # fall-back-run the rest of the assignment
+        ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
+        mod = ast.Module(None, ast.Stmt([ass]))
+        mod.filename = '<run>'
+        co = pycodegen.ModuleCodeGenerator(mod).getCode()
+        try:
+            frame.exec_(co, __exprinfo_expr=expr.result)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+
+class Discard(Interpretable):
+    __view__ = ast.Discard
+
+    def run(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        self.result = expr.result
+        self.explanation = expr.explanation
+
+class Stmt(Interpretable):
+    __view__ = ast.Stmt
+
+    def run(self, frame):
+        for stmt in self.nodes:
+            stmt = Interpretable(stmt)
+            stmt.run(frame)
+
+
+def report_failure(e):
+    explanation = e.node.nice_explanation()
+    if explanation:
+        explanation = ", in: " + explanation
+    else:
+        explanation = ""
+    sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
+
+def check(s, frame=None):
+    if frame is None:
+        frame = sys._getframe(1)
+        frame = py.code.Frame(frame)
+    expr = parse(s, 'eval')
+    assert isinstance(expr, ast.Expression)
+    node = Interpretable(expr.node)
+    try:
+        node.eval(frame)
+    except passthroughex:
+        raise
+    except Failure:
+        e = sys.exc_info()[1]
+        report_failure(e)
+    else:
+        if not frame.is_true(node.result):
+            sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
+
+
+###########################################################
+# API / Entry points
+# #########################################################
+
+def interpret(source, frame, should_fail=False):
+    module = Interpretable(parse(source, 'exec').node)
+    #print "got module", module
+    if isinstance(frame, py.std.types.FrameType):
+        frame = py.code.Frame(frame)
+    try:
+        module.run(frame)
+    except Failure:
+        e = sys.exc_info()[1]
+        return getfailure(e)
+    except passthroughex:
+        raise
+    except:
+        import traceback
+        traceback.print_exc()
+    if should_fail:
+        return ("(assertion failed, but when it was re-run for "
+                "printing intermediate values, it did not fail.  Suggestions: "
+                "compute assert expression before the assert or use --nomagic)")
+    else:
+        return None
+
+def getmsg(excinfo):
+    if isinstance(excinfo, tuple):
+        excinfo = py.code.ExceptionInfo(excinfo)
+    #frame, line = gettbline(tb)
+    #frame = py.code.Frame(frame)
+    #return interpret(line, frame)
+
+    tb = excinfo.traceback[-1] 
+    source = str(tb.statement).strip()
+    x = interpret(source, tb.frame, should_fail=True)
+    if not isinstance(x, str):
+        raise TypeError("interpret returned non-string %r" % (x,))
+    return x
+
+def getfailure(e):
+    explanation = e.node.nice_explanation()
+    if str(e.value):
+        lines = explanation.split('\n')
+        lines[0] += "  << %s" % (e.value,)
+        explanation = '\n'.join(lines)
+    text = "%s: %s" % (e.exc.__name__, explanation)
+    if text.startswith('AssertionError: assert '):
+        text = text[16:]
+    return text
+
+def run(s, frame=None):
+    if frame is None:
+        frame = sys._getframe(1)
+        frame = py.code.Frame(frame)
+    module = Interpretable(parse(s, 'exec').node)
+    try:
+        module.run(frame)
+    except Failure:
+        e = sys.exc_info()[1]
+        report_failure(e)
+
+
+if __name__ == '__main__':
+    # example:
+    def f():
+        return 5
+    def g():
+        return 3
+    def h(x):
+        return 'never'
+    check("f() * g() == 5")
+    check("not f()")
+    check("not (f() and g() or 0)")
+    check("f() == g()")
+    i = 4
+    check("i == f()")
+    check("len(f()) == 0")
+    check("isinstance(2+3+4, float)")
+
+    run("x = i")
+    check("x == 5")
+
+    run("assert not f(), 'oops'")
+    run("a, b, c = 1, 2")
+    run("a, b, c = f()")
+
+    check("max([f(),g()]) == 4")
+    check("'hello'[g()] == 'h'")
+    run("'guk%d' % h(f())")

py/_code/assertion.py

+import sys
+import py
+
+BuiltinAssertionError = py.builtin.builtins.AssertionError
+
+
+def _format_explanation(explanation):
+    # uck!  See CallFunc for where \n{ and \n} escape sequences are used
+    raw_lines = (explanation or '').split('\n')
+    # escape newlines not followed by { and }
+    lines = [raw_lines[0]]
+    for l in raw_lines[1:]:
+        if l.startswith('{') or l.startswith('}'):
+            lines.append(l)
+        else:
+            lines[-1] += '\\n' + l
+
+    result = lines[:1]
+    stack = [0]
+    stackcnt = [0]
+    for line in lines[1:]:
+        if line.startswith('{'):
+            if stackcnt[-1]:
+                s = 'and   '
+            else:
+                s = 'where '
+            stack.append(len(result))
+            stackcnt[-1] += 1
+            stackcnt.append(0)
+            result.append(' +' + '  '*(len(stack)-1) + s + line[1:])
+        else:
+            assert line.startswith('}')
+            stack.pop()
+            stackcnt.pop()
+            result[stack[-1]] += line[1:]
+    assert len(stack) == 1
+    return '\n'.join(result)
+
+
+class AssertionError(BuiltinAssertionError):
+
+    def __init__(self, *args):
+        BuiltinAssertionError.__init__(self, *args)
+        if args:
+            try:
+                self.msg = str(args[0])
+            except (KeyboardInterrupt, SystemExit):
+                raise
+            except:
+                self.msg = "<[broken __repr__] %s at %0xd>" %(
+                    args[0].__class__, id(args[0]))
+        else:
+            f = py.code.Frame(sys._getframe(1))
+            try:
+                source = f.statement
+                source = str(source.deindent()).strip()
+            except py.error.ENOENT:
+                source = None
+                # this can also occur during reinterpretation, when the
+                # co_filename is set to "<run>".
+            if source:
+                self.msg = reinterpret(source, f, should_fail=True)
+                if not self.args:
+                    self.args = (self.msg,)
+            else:
+                self.msg = None
+
+if sys.version_info > (3, 0):
+    AssertionError.__module__ = "builtins"
+    reinterpret_old = "old reinterpretation not available for py3"
+else:
+    from py._code._assertionold import interpret as reinterpret_old
+if sys.version_info >= (2, 6) or (sys.platform.startswith("java")):
+    from py._code._assertionnew import interpret as reinterpret
+else:
+    reinterpret = reinterpret_old
+    
+import py
+import sys, os.path
+
+builtin_repr = repr
+
+reprlib = py.builtin._tryimport('repr', 'reprlib')
+
+class Code(object):
+    """ wrapper around Python code objects """
+    def __init__(self, rawcode):
+        rawcode = py.code.getrawcode(rawcode)
+        self.raw = rawcode 
+        try:
+            self.filename = rawcode.co_filename
+            self.firstlineno = rawcode.co_firstlineno - 1
+            self.name = rawcode.co_name
+        except AttributeError: 
+            raise TypeError("not a code object: %r" %(rawcode,))
+        
+    def __eq__(self, other): 
+        return self.raw == other.raw
+
+    def __ne__(self, other):
+        return not self == other
+
+    def path(self):
+        """ return a path object pointing to source code"""
+        p = py.path.local(self.raw.co_filename)
+        if not p.check():
+            # XXX maybe try harder like the weird logic 
+            # in the standard lib [linecache.updatecache] does? 
+            p = self.raw.co_filename
+        return p
+                
+    path = property(path, None, None, "path of this code object")
+
+    def fullsource(self):
+        """ return a py.code.Source object for the full source file of the code
+        """
+        from py._code import source
+        full, _ = source.findsource(self.raw)
+        return full
+    fullsource = property(fullsource, None, None,
+                          "full source containing this code object")
+    
+    def source(self):
+        """ return a py.code.Source object for the code object's source only
+        """
+        # return source only for that part of code
+        return py.code.Source(self.raw)
+
+    def getargs(self):
+        """ return a tuple with the argument names for the code object
+        """
+        # handfull shortcut for getting args
+        raw = self.raw
+        return raw.co_varnames[:raw.co_argcount]
+
+class Frame(object):
+    """Wrapper around a Python frame holding f_locals and f_globals
+    in which expressions can be evaluated."""
+
+    def __init__(self, frame):
+        self.code = py.code.Code(frame.f_code)
+        self.lineno = frame.f_lineno - 1
+        self.f_globals = frame.f_globals
+        self.f_locals = frame.f_locals
+        self.raw = frame