[py-svn] py-trunk commit de4155a6b4e0: rewrote the initpkg mechanism and moved py lib implementation files to

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Mon Oct 5 01:45:15 CEST 2009


# HG changeset patch -- Bitbucket.org
# Project py-trunk
# URL http://bitbucket.org/hpk42/py-trunk/overview/
# User holger krekel <holger at merlinux.eu>
# Date 1254527259 -7200
# Node ID de4155a6b4e06fcbb18513bfe2f06db48c4856c5
# Parent 34ced857eecbd95944541ef98f9fdaf3f457dd35
rewrote the initpkg mechanism and moved py lib implementation files to

_py/...  with py/__init__.py containing pointers into them

The new apipkg is only around 70 lines of code and allows
us to get rid of the infamous "py.__." by regular non-magical
"_py." imports. It is also available as a separately installable
package, see http://bitbucket.org/hpk42/apipkg

--- /dev/null
+++ b/_py/io/terminalwriter.py
@@ -0,0 +1,264 @@
+"""
+
+Helper functions for writing to terminals and files. 
+
+"""
+
+
+import sys, os
+import py
+
+def _getdimensions():
+    import termios,fcntl,struct
+    call = fcntl.ioctl(0,termios.TIOCGWINSZ,"\000"*8)
+    height,width = struct.unpack( "hhhh", call ) [:2]
+    return height, width 
+
+if sys.platform == 'win32':
+    # ctypes access to the Windows console
+
+    STD_OUTPUT_HANDLE = -11
+    STD_ERROR_HANDLE  = -12
+    FOREGROUND_BLUE      = 0x0001 # text color contains blue.
+    FOREGROUND_GREEN     = 0x0002 # text color contains green.
+    FOREGROUND_RED       = 0x0004 # text color contains red.
+    FOREGROUND_WHITE     = 0x0007
+    FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
+    BACKGROUND_BLUE      = 0x0010 # background color contains blue.
+    BACKGROUND_GREEN     = 0x0020 # background color contains green.
+    BACKGROUND_RED       = 0x0040 # background color contains red.
+    BACKGROUND_WHITE     = 0x0070
+    BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
+
+    def GetStdHandle(kind):
+        import ctypes
+        return ctypes.windll.kernel32.GetStdHandle(kind)
+
+    def SetConsoleTextAttribute(handle, attr):
+        import ctypes
+        ctypes.windll.kernel32.SetConsoleTextAttribute(
+            handle, attr)
+
+    def _getdimensions():
+        import ctypes
+        from ctypes import wintypes
+
+        SHORT = ctypes.c_short
+        class COORD(ctypes.Structure):
+            _fields_ = [('X', SHORT),
+                        ('Y', SHORT)]
+        class SMALL_RECT(ctypes.Structure):
+            _fields_ = [('Left', SHORT),
+                        ('Top', SHORT),
+                        ('Right', SHORT),
+                        ('Bottom', SHORT)]
+        class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
+            _fields_ = [('dwSize', COORD),
+                        ('dwCursorPosition', COORD),
+                        ('wAttributes', wintypes.WORD),
+                        ('srWindow', SMALL_RECT),
+                        ('dwMaximumWindowSize', COORD)]
+        STD_OUTPUT_HANDLE = -11
+        handle = GetStdHandle(STD_OUTPUT_HANDLE)
+        info = CONSOLE_SCREEN_BUFFER_INFO()
+        ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
+            handle, ctypes.byref(info))
+        # Substract one from the width, otherwise the cursor wraps
+        # and the ending \n causes an empty line to display.
+        return info.dwSize.Y, info.dwSize.X - 1
+
+def get_terminal_width():
+    try:
+        height, width = _getdimensions()
+    except (SystemExit, KeyboardInterrupt):
+        raise
+    except:
+        # FALLBACK
+        width = int(os.environ.get('COLUMNS', 80))-1
+    # XXX the windows getdimensions may be bogus, let's sanify a bit 
+    width = max(width, 40) # we alaways need 40 chars
+    return width
+
+terminal_width = get_terminal_width()
+
+# XXX unify with _escaped func below
+def ansi_print(text, esc, file=None, newline=True, flush=False):
+    if file is None:
+        file = sys.stderr
+    text = text.rstrip()
+    if esc and not isinstance(esc, tuple):
+        esc = (esc,)
+    if esc and sys.platform != "win32" and file.isatty():
+        text = (''.join(['\x1b[%sm' % cod for cod in esc])  +  
+                text +
+                '\x1b[0m')     # ANSI color code "reset"
+    if newline:
+        text += '\n'
+
+    if esc and sys.platform == "win32" and file.isatty():
+        if 1 in esc:
+            bold = True
+            esc = tuple([x for x in esc if x != 1])
+        else:
+            bold = False
+        esctable = {()   : FOREGROUND_WHITE,                 # normal
+                    (31,): FOREGROUND_RED,                   # red
+                    (32,): FOREGROUND_GREEN,                 # green
+                    (33,): FOREGROUND_GREEN|FOREGROUND_RED,  # yellow
+                    (34,): FOREGROUND_BLUE,                  # blue
+                    (35,): FOREGROUND_BLUE|FOREGROUND_RED,   # purple
+                    (36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
+                    (37,): FOREGROUND_WHITE,                 # white
+                    (39,): FOREGROUND_WHITE,                 # reset
+                    }
+        attr = esctable.get(esc, FOREGROUND_WHITE)
+        if bold:
+            attr |= FOREGROUND_INTENSITY
+        STD_OUTPUT_HANDLE = -11
+        STD_ERROR_HANDLE = -12
+        if file is sys.stderr:
+            handle = GetStdHandle(STD_ERROR_HANDLE)
+        else:
+            handle = GetStdHandle(STD_OUTPUT_HANDLE)
+        SetConsoleTextAttribute(handle, attr)
+        file.write(text)
+        SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
+    else:
+        file.write(text)
+
+    if flush:
+        file.flush()
+
+def should_do_markup(file):
+    return hasattr(file, 'isatty') and file.isatty() \
+           and os.environ.get('TERM') != 'dumb'
+
+class TerminalWriter(object):
+    _esctable = dict(black=30, red=31, green=32, yellow=33, 
+                     blue=34, purple=35, cyan=36, white=37,
+                     Black=40, Red=41, Green=42, Yellow=43, 
+                     Blue=44, Purple=45, Cyan=46, White=47,
+                     bold=1, light=2, blink=5, invert=7)
+
+    # XXX deprecate stringio argument
+    def __init__(self, file=None, stringio=False, encoding=None):
+        self.encoding = encoding 
+
+        if file is None:
+            if stringio:
+                self.stringio = file = py.io.TextIO()
+            else:
+                file = py.std.sys.stdout 
+        elif hasattr(file, '__call__'):
+            file = WriteFile(file, encoding=encoding)
+        self._file = file
+        self.fullwidth = get_terminal_width()
+        self.hasmarkup = should_do_markup(file)
+
+    def _escaped(self, text, esc):
+        if esc and self.hasmarkup:
+            text = (''.join(['\x1b[%sm' % cod for cod in esc])  +  
+                text +'\x1b[0m')
+        return text
+
+    def markup(self, text, **kw):
+        esc = []
+        for name in kw:
+            if name not in self._esctable:
+                raise ValueError("unknown markup: %r" %(name,))
+            if kw[name]:
+                esc.append(self._esctable[name])
+        return self._escaped(text, tuple(esc))
+
+    def sep(self, sepchar, title=None, fullwidth=None, **kw):
+        if fullwidth is None:
+            fullwidth = self.fullwidth
+        # the goal is to have the line be as long as possible
+        # under the condition that len(line) <= fullwidth
+        if title is not None:
+            # we want 2 + 2*len(fill) + len(title) <= fullwidth
+            # i.e.    2 + 2*len(sepchar)*N + len(title) <= fullwidth
+            #         2*len(sepchar)*N <= fullwidth - len(title) - 2
+            #         N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
+            N = (fullwidth - len(title) - 2) // (2*len(sepchar))
+            fill = sepchar * N
+            line = "%s %s %s" % (fill, title, fill)
+        else:
+            # we want len(sepchar)*N <= fullwidth
+            # i.e.    N <= fullwidth // len(sepchar)
+            line = sepchar * (fullwidth // len(sepchar))
+        # in some situations there is room for an extra sepchar at the right,
+        # in particular if we consider that with a sepchar like "_ " the
+        # trailing space is not important at the end of the line
+        if len(line) + len(sepchar.rstrip()) <= fullwidth:
+            line += sepchar.rstrip()
+
+        self.line(line, **kw)
+
+    def write(self, s, **kw):
+        if s:
+            s = self._getbytestring(s)
+            if self.hasmarkup and kw:
+                s = self.markup(s, **kw)
+            self._file.write(s)
+            self._file.flush()
+
+    def _getbytestring(self, s):
+        # XXX review this and the whole logic
+        if self.encoding and sys.version_info < (3,0) and isinstance(s, unicode):
+            return s.encode(self.encoding)
+        elif not isinstance(s, str):
+            return str(s)
+        return s
+
+    def line(self, s='', **kw):
+        self.write(s, **kw)
+        self.write('\n')
+
+class Win32ConsoleWriter(TerminalWriter):
+    def write(self, s, **kw):
+        if s:
+            s = self._getbytestring(s)
+            if self.hasmarkup:
+                handle = GetStdHandle(STD_OUTPUT_HANDLE)
+
+            if self.hasmarkup and kw:
+                attr = 0
+                if kw.pop('bold', False):
+                    attr |= FOREGROUND_INTENSITY
+
+                if kw.pop('red', False):
+                    attr |= FOREGROUND_RED
+                elif kw.pop('blue', False):
+                    attr |= FOREGROUND_BLUE
+                elif kw.pop('green', False):
+                    attr |= FOREGROUND_GREEN
+                else:
+                    attr |= FOREGROUND_WHITE
+
+                SetConsoleTextAttribute(handle, attr)
+            self._file.write(s)
+            self._file.flush()
+            if self.hasmarkup:
+                SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
+
+    def line(self, s="", **kw):
+        self.write(s+"\n", **kw)
+
+if sys.platform == 'win32':
+    TerminalWriter = Win32ConsoleWriter
+
+class WriteFile(object): 
+    def __init__(self, writemethod, encoding=None): 
+        self.encoding = encoding 
+        self._writemethod = writemethod 
+
+    def write(self, data):
+        if self.encoding:
+            data = data.encode(self.encoding)
+        self._writemethod(data)
+
+    def flush(self): 
+        return 
+
+

--- /dev/null
+++ b/_py/log/log.py
@@ -0,0 +1,184 @@
+"""
+basic logging functionality based on a producer/consumer scheme. 
+
+XXX implement this API: (maybe put it into slogger.py?)
+
+        log = Logger(
+                    info=py.log.STDOUT, 
+                    debug=py.log.STDOUT, 
+                    command=None)
+        log.info("hello", "world")
+        log.command("hello", "world")
+
+        log = Logger(info=Logger(something=...), 
+                     debug=py.log.STDOUT, 
+                     command=None)
+"""
+import py, sys
+
+class Message(object): 
+    def __init__(self, keywords, args): 
+        self.keywords = keywords 
+        self.args = args 
+
+    def content(self): 
+        return " ".join(map(str, self.args))
+
+    def prefix(self): 
+        return "[%s] " % (":".join(self.keywords))
+
+    def __str__(self): 
+        return self.prefix() + self.content() 
+
+
+class Producer(object):
+    """ (deprecated) Log producer API which sends messages to be logged
+        to a 'consumer' object, which then prints them to stdout,
+        stderr, files, etc. Used extensively by PyPy-1.1.
+    """
+    
+    Message = Message  # to allow later customization 
+    keywords2consumer = {}
+
+    def __init__(self, keywords, keywordmapper=None, **kw): 
+        if hasattr(keywords, 'split'):
+            keywords = tuple(keywords.split())
+        self._keywords = keywords
+        if keywordmapper is None:
+            keywordmapper = default_keywordmapper
+        self._keywordmapper = keywordmapper
+
+    def __repr__(self):
+        return "<py.log.Producer %s>" % ":".join(self._keywords) 
+
+    def __getattr__(self, name):
+        if '_' in name: 
+            raise AttributeError(name)
+        producer = self.__class__(self._keywords + (name,))
+        setattr(self, name, producer)
+        return producer 
+    
+    def __call__(self, *args):
+        """ write a message to the appropriate consumer(s) """
+        func = self._keywordmapper.getconsumer(self._keywords)
+        if func is not None: 
+            func(self.Message(self._keywords, args))
+
+class KeywordMapper: 
+    def __init__(self):
+        self.keywords2consumer = {}
+
+    def getstate(self):
+        return self.keywords2consumer.copy()
+    def setstate(self, state):
+        self.keywords2consumer.clear()
+        self.keywords2consumer.update(state)
+
+    def getconsumer(self, keywords):
+        """ return a consumer matching the given keywords. 
+        
+            tries to find the most suitable consumer by walking, starting from
+            the back, the list of keywords, the first consumer matching a
+            keyword is returned (falling back to py.log.default)
+        """
+        for i in range(len(keywords), 0, -1): 
+            try: 
+                return self.keywords2consumer[keywords[:i]]
+            except KeyError: 
+                continue
+        return self.keywords2consumer.get('default', default_consumer)
+
+    def setconsumer(self, keywords, consumer): 
+        """ set a consumer for a set of keywords. """ 
+        # normalize to tuples 
+        if isinstance(keywords, str): 
+            keywords = tuple(filter(None, keywords.split()))
+        elif hasattr(keywords, '_keywords'): 
+            keywords = keywords._keywords 
+        elif not isinstance(keywords, tuple): 
+            raise TypeError("key %r is not a string or tuple" % (keywords,))
+        if consumer is not None and not py.builtin.callable(consumer): 
+            if not hasattr(consumer, 'write'): 
+                raise TypeError(
+                    "%r should be None, callable or file-like" % (consumer,))
+            consumer = File(consumer)
+        self.keywords2consumer[keywords] = consumer 
+
+def default_consumer(msg): 
+    """ the default consumer, prints the message to stdout (using 'print') """
+    sys.stderr.write(str(msg)+"\n")
+
+default_keywordmapper = KeywordMapper()
+
+def setconsumer(keywords, consumer):
+    default_keywordmapper.setconsumer(keywords, consumer)
+
+def setstate(state):
+    default_keywordmapper.setstate(state)
+def getstate():
+    return default_keywordmapper.getstate()
+
+#
+# Consumers
+#
+
+class File(object): 
+    """ log consumer wrapping a file(-like) object """
+    def __init__(self, f): 
+        assert hasattr(f, 'write')
+        #assert isinstance(f, file) or not hasattr(f, 'open') 
+        self._file = f 
+
+    def __call__(self, msg): 
+        """ write a message to the log """
+        self._file.write(str(msg) + "\n")
+
+class Path(object): 
+    """ log consumer that opens and writes to a Path """
+    def __init__(self, filename, append=False, 
+                 delayed_create=False, buffering=False):
+        self._append = append
+        self._filename = str(filename)
+        self._buffering = buffering
+        if not delayed_create:
+            self._openfile()
+
+    def _openfile(self):
+        mode = self._append and 'a' or 'w'
+        f = open(self._filename, mode)
+        self._file = f
+
+    def __call__(self, msg):
+        """ write a message to the log """
+        if not hasattr(self, "_file"):
+            self._openfile()
+        self._file.write(str(msg) + "\n")
+        if not self._buffering:
+            self._file.flush()
+
+def STDOUT(msg): 
+    """ consumer that writes to sys.stdout """
+    sys.stdout.write(str(msg)+"\n")
+
+def STDERR(msg): 
+    """ consumer that writes to sys.stderr """
+    sys.stderr.write(str(msg)+"\n")
+
+class Syslog:
+    """ consumer that writes to the syslog daemon """
+
+    def __init__(self, priority = None):
+        if priority is None:
+            priority = self.LOG_INFO
+        self.priority = priority
+
+    def __call__(self, msg):
+        """ write a message to the log """
+        py.std.syslog.syslog(self.priority, str(msg))
+
+for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split():
+    _prio = "LOG_" + _prio
+    try:
+        setattr(Syslog, _prio, getattr(py.std.syslog, _prio))
+    except AttributeError:
+        pass

--- /dev/null
+++ b/_py/code/source.py
@@ -0,0 +1,347 @@
+from __future__ import generators
+import sys
+import inspect, tokenize
+import py
+cpy_compile = compile 
+
+try:
+    import _ast
+    from _ast import PyCF_ONLY_AST as _AST_FLAG
+except ImportError:
+    _AST_FLAG = 0
+    _ast = None
+
+
+class Source(object):
+    """ a immutable object holding a source code fragment,
+        possibly deindenting it.
+    """
+    def __init__(self, *parts, **kwargs):
+        self.lines = lines = []
+        de = kwargs.get('deindent', True)
+        rstrip = kwargs.get('rstrip', True) 
+        for part in parts:
+            if not part: 
+                partlines = []
+            if isinstance(part, Source):
+                partlines = part.lines
+            elif isinstance(part, py.builtin._basestring):
+                partlines = part.split('\n')
+                if rstrip:
+                    while partlines: 
+                        if partlines[-1].strip(): 
+                            break
+                        partlines.pop()
+            else:
+                partlines = getsource(part, deindent=de).lines
+            if de:
+                partlines = deindent(partlines)
+            lines.extend(partlines)
+
+    def __eq__(self, other): 
+        try:
+            return self.lines == other.lines 
+        except AttributeError: 
+            if isinstance(other, str): 
+                return str(self) == other 
+            return False 
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return self.lines[key]
+        else:
+            if key.step not in (None, 1):
+                raise IndexError("cannot slice a Source with a step")
+            return self.__getslice__(key.start, key.stop)
+
+    def __len__(self): 
+        return len(self.lines) 
+
+    def __getslice__(self, start, end):
+        newsource = Source()
+        newsource.lines = self.lines[start:end]
+        return newsource
+
+    def strip(self):
+        """ return new source object with trailing
+            and leading blank lines removed.
+        """
+        start, end = 0, len(self)
+        while start < end and not self.lines[start].strip():
+            start += 1
+        while end > start and not self.lines[end-1].strip():
+            end -= 1
+        source = Source()
+        source.lines[:] = self.lines[start:end]
+        return source
+
+    def putaround(self, before='', after='', indent=' ' * 4): 
+        """ return a copy of the source object with 
+            'before' and 'after' wrapped around it. 
+        """
+        before = Source(before)
+        after = Source(after)
+        newsource = Source()
+        lines = [ (indent + line) for line in self.lines]
+        newsource.lines = before.lines + lines +  after.lines
+        return newsource
+
+    def indent(self, indent=' ' * 4): 
+        """ return a copy of the source object with 
+            all lines indented by the given indent-string. 
+        """
+        newsource = Source()
+        newsource.lines = [(indent+line) for line in self.lines]
+        return newsource
+
+    def getstatement(self, lineno):
+        """ return Source statement which contains the
+            given linenumber (counted from 0).
+        """
+        start, end = self.getstatementrange(lineno)
+        return self[start:end]
+
+    def getstatementrange(self, lineno):
+        """ return (start, end) tuple which spans the minimal 
+            statement region which containing the given lineno.
+        """
+        # XXX there must be a better than these heuristic ways ...
+        # XXX there may even be better heuristics :-)
+        if not (0 <= lineno < len(self)):
+            raise IndexError("lineno out of range")
+
+        # 1. find the start of the statement
+        from codeop import compile_command
+        for start in range(lineno, -1, -1):
+            trylines = self.lines[start:lineno+1]
+            # quick hack to indent the source and get it as a string in one go
+            trylines.insert(0, 'def xxx():')
+            trysource = '\n '.join(trylines)
+            #              ^ space here
+            try:
+                compile_command(trysource)
+            except (SyntaxError, OverflowError, ValueError):
+                pass
+            else:
+                break   # got a valid or incomplete statement
+
+        # 2. find the end of the statement
+        for end in range(lineno+1, len(self)+1):
+            trysource = self[start:end]
+            if trysource.isparseable():
+                break
+
+        return start, end
+
+    def getblockend(self, lineno):
+        # XXX
+        lines = [x + '\n' for x in self.lines[lineno:]]
+        blocklines = inspect.getblock(lines)
+        #print blocklines
+        return lineno + len(blocklines) - 1
+
+    def deindent(self, offset=None):
+        """ return a new source object deindented by offset.
+            If offset is None then guess an indentation offset from
+            the first non-blank line.  Subsequent lines which have a
+            lower indentation offset will be copied verbatim as
+            they are assumed to be part of multilines.
+        """
+        # XXX maybe use the tokenizer to properly handle multiline
+        #     strings etc.pp?
+        newsource = Source()
+        newsource.lines[:] = deindent(self.lines, offset)
+        return newsource
+
+    def isparseable(self, deindent=True):
+        """ return True if source is parseable, heuristically
+            deindenting it by default. 
+        """
+        try:
+            import parser
+        except ImportError:
+            syntax_checker = lambda x: compile(x, 'asd', 'exec')
+        else:
+            syntax_checker = parser.suite
+    
+        if deindent:
+            source = str(self.deindent())
+        else:
+            source = str(self)
+        try:
+            #compile(source+'\n', "x", "exec")
+            syntax_checker(source+'\n')
+        except SyntaxError:
+            return False
+        else:
+            return True
+
+    def __str__(self):
+        return "\n".join(self.lines)
+
+    def compile(self, filename=None, mode='exec', 
+                flag=generators.compiler_flag, 
+                dont_inherit=0, _genframe=None):
+        """ return compiled code object. if filename is None
+            invent an artificial filename which displays
+            the source/line position of the caller frame.
+        """
+        if not filename or py.path.local(filename).check(file=0): 
+            if _genframe is None:
+                _genframe = sys._getframe(1) # the caller
+            fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
+            if not filename:
+                filename = '<codegen %s:%d>' % (fn, lineno)
+            else:
+                filename = '<codegen %r %s:%d>' % (filename, fn, lineno)
+        source = "\n".join(self.lines) + '\n'
+        try:
+            co = cpy_compile(source, filename, mode, flag)
+        except SyntaxError:
+            ex = sys.exc_info()[1]
+            # re-represent syntax errors from parsing python strings
+            msglines = self.lines[:ex.lineno]
+            if ex.offset:
+                msglines.append(" "*ex.offset + '^')
+            msglines.append("syntax error probably generated here: %s" % filename)
+            newex = SyntaxError('\n'.join(msglines))
+            newex.offset = ex.offset
+            newex.lineno = ex.lineno
+            newex.text = ex.text
+            raise newex
+        else:
+            if flag & _AST_FLAG:
+                return co
+            co_filename = MyStr(filename)
+            co_filename.__source__ = self
+            return py.code.Code(co).new(rec=1, co_filename=co_filename) 
+            #return newcode_withfilename(co, co_filename)
+
+#
+# public API shortcut functions
+#
+
+def compile_(source, filename=None, mode='exec', flags=
+            generators.compiler_flag, dont_inherit=0):
+    """ compile the given source to a raw code object,
+        which points back to the source code through
+        "co_filename.__source__".  All code objects
+        contained in the code object will recursively
+        also have this special subclass-of-string
+        filename.
+    """
+    if _ast is not None and isinstance(source, _ast.AST):
+        # XXX should Source support having AST?
+        return cpy_compile(source, filename, mode, flags, dont_inherit)
+    _genframe = sys._getframe(1) # the caller
+    s = Source(source)
+    co = s.compile(filename, mode, flags, _genframe=_genframe)
+    return co
+
+
+def getfslineno(obj):
+    try:
+        code = py.code.Code(obj)
+    except TypeError:
+        # fallback to 
+        fn = (py.std.inspect.getsourcefile(obj) or
+              py.std.inspect.getfile(obj))
+        fspath = fn and py.path.local(fn) or None
+        if fspath:
+            try:
+                _, lineno = findsource(obj)
+            except IOError:
+                lineno = None
+        else:
+            lineno = None
+    else:
+        fspath = code.path
+        lineno = code.firstlineno 
+    return fspath, lineno
+
+#
+# helper functions
+#
+class MyStr(str):
+    """ custom string which allows to add attributes. """
+
+def findsource(obj):
+    obj = py.code.getrawcode(obj)
+    try:
+        fullsource = obj.co_filename.__source__
+    except AttributeError:
+        try:
+            sourcelines, lineno = py.std.inspect.findsource(obj)
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            return None, None
+        source = Source()
+        source.lines = [line.rstrip() for line in sourcelines]
+        return source, lineno
+    else:
+        lineno = obj.co_firstlineno - 1        
+        return fullsource, lineno
+
+
+def getsource(obj, **kwargs):
+    obj = py.code.getrawcode(obj)
+    try:
+        fullsource = obj.co_filename.__source__
+    except AttributeError:
+        try:
+            strsrc = inspect.getsource(obj)
+        except IndentationError:
+            strsrc = "\"Buggy python version consider upgrading, cannot get source\""
+        assert isinstance(strsrc, str)
+        return Source(strsrc, **kwargs)
+    else:
+        lineno = obj.co_firstlineno - 1
+        end = fullsource.getblockend(lineno)
+        return Source(fullsource[lineno:end+1], deident=True)
+
+
+def deindent(lines, offset=None):
+    if offset is None:
+        for line in lines:
+            line = line.expandtabs()
+            s = line.lstrip()
+            if s:
+                offset = len(line)-len(s)
+                break
+        else:
+            offset = 0
+    if offset == 0:
+        return list(lines)
+    newlines = []
+    def readline_generator(lines):
+        for line in lines:
+            yield line + '\n'
+        while True:
+            yield ''
+        
+    r = readline_generator(lines)
+    try: 
+        readline = r.next
+    except AttributeError:
+        readline = r.__next__
+
+    try:
+        for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(readline):
+            if sline > len(lines):
+                break # End of input reached
+            if sline > len(newlines):
+                line = lines[sline - 1].expandtabs()
+                if line.lstrip() and line[:offset].isspace():
+                    line = line[offset:] # Deindent
+                newlines.append(line)
+
+            for i in range(sline, eline):
+                # Don't deindent continuing lines of
+                # multiline tokens (i.e. multiline strings)
+                newlines.append(lines[i])
+    except (IndentationError, tokenize.TokenError):
+        pass
+    # Add any lines we didn't see. E.g. if an exception was raised.
+    newlines.extend(lines[len(newlines):])
+    return newlines

--- /dev/null
+++ b/_py/path/svnurl.py
@@ -0,0 +1,365 @@
+"""
+module defining a subversion path object based on the external
+command 'svn'. This modules aims to work with svn 1.3 and higher
+but might also interact well with earlier versions. 
+"""
+
+import os, sys, time, re
+import py
+from py import path, process
+from _py.path import common
+from _py.path import svnwc as svncommon
+from _py.path.cacheutil import BuildcostAccessCache, AgingCache
+
+DEBUG=False 
+
+class SvnCommandPath(svncommon.SvnPathBase):
+    """ path implementation that offers access to (possibly remote) subversion
+    repositories. """
+
+    _lsrevcache = BuildcostAccessCache(maxentries=128)
+    _lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0)
+
+    def __new__(cls, path, rev=None, auth=None):
+        self = object.__new__(cls)
+        if isinstance(path, cls): 
+            rev = path.rev 
+            auth = path.auth
+            path = path.strpath 
+        svncommon.checkbadchars(path)
+        path = path.rstrip('/')
+        self.strpath = path
+        self.rev = rev
+        self.auth = auth
+        return self
+
+    def __repr__(self):
+        if self.rev == -1:
+            return 'svnurl(%r)' % self.strpath
+        else:
+            return 'svnurl(%r, %r)' % (self.strpath, self.rev)
+
+    def _svnwithrev(self, cmd, *args):
+        """ execute an svn command, append our own url and revision """
+        if self.rev is None:
+            return self._svnwrite(cmd, *args)
+        else:
+            args = ['-r', self.rev] + list(args)
+            return self._svnwrite(cmd, *args)
+
+    def _svnwrite(self, cmd, *args):
+        """ execute an svn command, append our own url """
+        l = ['svn %s' % cmd]
+        args = ['"%s"' % self._escape(item) for item in args]
+        l.extend(args)
+        l.append('"%s"' % self._encodedurl())
+        # fixing the locale because we can't otherwise parse
+        string = " ".join(l)
+        if DEBUG:
+            print("execing %s" % string)
+        out = self._svncmdexecauth(string)
+        return out
+
+    def _svncmdexecauth(self, cmd):
+        """ execute an svn command 'as is' """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._cmdexec(cmd)
+
+    def _cmdexec(self, cmd):
+        try:
+            out = process.cmdexec(cmd)
+        except py.process.cmdexec.Error:
+            e = sys.exc_info()[1]
+            if (e.err.find('File Exists') != -1 or
+                            e.err.find('File already exists') != -1):
+                raise py.error.EEXIST(self)
+            raise
+        return out
+
+    def _svnpopenauth(self, cmd):
+        """ execute an svn command, return a pipe for reading stdin """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._popen(cmd)
+
+    def _popen(self, cmd):
+        return os.popen(cmd)
+
+    def _encodedurl(self):
+        return self._escape(self.strpath)
+
+    def _norev_delentry(self, path):
+        auth = self.auth and self.auth.makecmdoptions() or None
+        self._lsnorevcache.delentry((str(path), auth))
+
+    def open(self, mode='r'):
+        """ return an opened file with the given mode. """
+        if mode not in ("r", "rU",): 
+            raise ValueError("mode %r not supported" % (mode,))
+        assert self.check(file=1) # svn cat returns an empty file otherwise
+        if self.rev is None:
+            return self._svnpopenauth('svn cat "%s"' % (
+                                      self._escape(self.strpath), ))
+        else:
+            return self._svnpopenauth('svn cat -r %s "%s"' % (
+                                      self.rev, self._escape(self.strpath)))
+
+    def dirpath(self, *args, **kwargs):
+        """ return the directory path of the current path joined
+            with any given path arguments.
+        """
+        l = self.strpath.split(self.sep) 
+        if len(l) < 4: 
+            raise py.error.EINVAL(self, "base is not valid") 
+        elif len(l) == 4: 
+            return self.join(*args, **kwargs) 
+        else: 
+            return self.new(basename='').join(*args, **kwargs)
+
+    # modifying methods (cache must be invalidated)
+    def mkdir(self, *args, **kwargs):
+        """ create & return the directory joined with args. 
+        pass a 'msg' keyword argument to set the commit message.
+        """
+        commit_msg = kwargs.get('msg', "mkdir by py lib invocation")
+        createpath = self.join(*args)
+        createpath._svnwrite('mkdir', '-m', commit_msg)
+        self._norev_delentry(createpath.dirpath())
+        return createpath
+
+    def copy(self, target, msg='copied by py lib invocation'):
+        """ copy path to target with checkin message msg."""
+        if getattr(target, 'rev', None) is not None:
+            raise py.error.EINVAL(target, "revisions are immutable")
+        self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg,
+                             self._escape(self), self._escape(target)))
+        self._norev_delentry(target.dirpath())
+
+    def rename(self, target, msg="renamed by py lib invocation"):
+        """ rename this path to target with checkin message msg. """
+        if getattr(self, 'rev', None) is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %(
+                             msg, self._escape(self), self._escape(target)))
+        self._norev_delentry(self.dirpath())
+        self._norev_delentry(self)
+
+    def remove(self, rec=1, msg='removed by py lib invocation'):
+        """ remove a file or directory (or a directory tree if rec=1) with
+checkin message msg."""
+        if self.rev is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
+        self._norev_delentry(self.dirpath())
+
+    def export(self, topath):
+        """ export to a local path
+
+            topath should not exist prior to calling this, returns a
+            py.path.local instance
+        """
+        topath = py.path.local(topath)
+        args = ['"%s"' % (self._escape(self),),
+                '"%s"' % (self._escape(topath),)]
+        if self.rev is not None:
+            args = ['-r', str(self.rev)] + args
+        self._svncmdexecauth('svn export %s' % (' '.join(args),))
+        return topath
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). If you specify a keyword argument 'dir=True'
+            then the path is forced to be a directory path.
+        """
+        if getattr(self, 'rev', None) is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        target = self.join(*args)
+        dir = kwargs.get('dir', 0) 
+        for x in target.parts(reverse=True): 
+            if x.check(): 
+                break 
+        else: 
+            raise py.error.ENOENT(target, "has not any valid base!") 
+        if x == target: 
+            if not x.check(dir=dir): 
+                raise dir and py.error.ENOTDIR(x) or py.error.EISDIR(x) 
+            return x 
+        tocreate = target.relto(x) 
+        basename = tocreate.split(self.sep, 1)[0]
+        tempdir = py.path.local.mkdtemp()
+        try:    
+            tempdir.ensure(tocreate, dir=dir) 
+            cmd = 'svn import -m "%s" "%s" "%s"' % (
+                    "ensure %s" % self._escape(tocreate), 
+                    self._escape(tempdir.join(basename)), 
+                    x.join(basename)._encodedurl())
+            self._svncmdexecauth(cmd) 
+            self._norev_delentry(x)
+        finally:    
+            tempdir.remove() 
+        return target
+
+    # end of modifying methods
+    def _propget(self, name):
+        res = self._svnwithrev('propget', name)
+        return res[:-1] # strip trailing newline
+
+    def _proplist(self):
+        res = self._svnwithrev('proplist')
+        lines = res.split('\n')
+        lines = [x.strip() for x in lines[1:]]
+        return svncommon.PropListDict(self, lines)
+
+    def _listdir_nameinfo(self):
+        """ return sequence of name-info directory entries of self """
+        def builder():
+            try:
+                res = self._svnwithrev('ls', '-v')
+            except process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('non-existent in that revision') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('File not found') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('not part of a repository')!=-1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('Unable to open')!=-1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.lower().find('method not allowed')!=-1:
+                    raise py.error.EACCES(self, e.err)
+                raise py.error.Error(e.err)
+            lines = res.split('\n')
+            nameinfo_seq = []
+            for lsline in lines:
+                if lsline:
+                    info = InfoSvnCommand(lsline)
+                    if info._name != '.':  # svn 1.5 produces '.' dirs, 
+                        nameinfo_seq.append((info._name, info))
+            nameinfo_seq.sort()
+            return nameinfo_seq
+        auth = self.auth and self.auth.makecmdoptions() or None
+        if self.rev is not None:
+            return self._lsrevcache.getorbuild((self.strpath, self.rev, auth),
+                                               builder)
+        else:
+            return self._lsnorevcache.getorbuild((self.strpath, auth),
+                                                 builder)
+
+    def listdir(self, fil=None, sort=None):
+        """ list directory contents, possibly filter by the given fil func
+            and possibly sorted.
+        """
+        if isinstance(fil, str):
+            fil = common.FNMatcher(fil)
+        nameinfo_seq = self._listdir_nameinfo()
+        if len(nameinfo_seq) == 1:
+            name, info = nameinfo_seq[0]
+            if name == self.basename and info.kind == 'file':
+                #if not self.check(dir=1):
+                raise py.error.ENOTDIR(self)
+        paths = [self.join(name) for (name, info) in nameinfo_seq]
+        if fil:
+            paths = [x for x in paths if fil(x)]
+        self._sortlist(paths, sort)
+        return paths
+
+
+    def log(self, rev_start=None, rev_end=1, verbose=False):
+        """ return a list of LogEntry instances for this path.
+rev_start is the starting revision (defaulting to the first one).
+rev_end is the last revision (defaulting to HEAD).
+if verbose is True, then the LogEntry instances also know which files changed.
+"""
+        assert self.check() #make it simpler for the pipe
+        rev_start = rev_start is None and "HEAD" or rev_start
+        rev_end = rev_end is None and "HEAD" or rev_end
+
+        if rev_start == "HEAD" and rev_end == 1:
+            rev_opt = ""
+        else:
+            rev_opt = "-r %s:%s" % (rev_start, rev_end)
+        verbose_opt = verbose and "-v" or ""
+        xmlpipe =  self._svnpopenauth('svn log --xml %s %s "%s"' %
+                                      (rev_opt, verbose_opt, self.strpath))
+        from xml.dom import minidom
+        tree = minidom.parse(xmlpipe)
+        result = []
+        for logentry in filter(None, tree.firstChild.childNodes):
+            if logentry.nodeType == logentry.ELEMENT_NODE:
+                result.append(svncommon.LogEntry(logentry))
+        return result
+
+#01234567890123456789012345678901234567890123467
+#   2256      hpk        165 Nov 24 17:55 __init__.py
+# XXX spotted by Guido, SVN 1.3.0 has different aligning, breaks the code!!!
+#   1312 johnny           1627 May 05 14:32 test_decorators.py
+#
+class InfoSvnCommand:
+    # the '0?' part in the middle is an indication of whether the resource is
+    # locked, see 'svn help ls'
+    lspattern = re.compile(
+        r'^ *(?P<rev>\d+) +(?P<author>.+?) +(0? *(?P<size>\d+))? '
+            '*(?P<date>\w+ +\d{2} +[\d:]+) +(?P<file>.*)$')
+    def __init__(self, line):
+        # this is a typical line from 'svn ls http://...'
+        #_    1127      jum        0 Jul 13 15:28 branch/
+        match = self.lspattern.match(line)
+        data = match.groupdict()
+        self._name = data['file']
+        if self._name[-1] == '/':
+            self._name = self._name[:-1]
+            self.kind = 'dir'
+        else:
+            self.kind = 'file'
+        #self.has_props = l.pop(0) == 'P'
+        self.created_rev = int(data['rev'])
+        self.last_author = data['author']
+        self.size = data['size'] and int(data['size']) or 0
+        self.mtime = parse_time_with_missing_year(data['date'])
+        self.time = self.mtime * 1000000
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+
+#____________________________________________________
+#
+# helper functions
+#____________________________________________________
+def parse_time_with_missing_year(timestr):
+    """ analyze the time part from a single line of "svn ls -v"
+    the svn output doesn't show the year makes the 'timestr'
+    ambigous.
+    """
+    import calendar
+    t_now = time.gmtime()
+
+    tparts = timestr.split()
+    month = time.strptime(tparts.pop(0), '%b')[1]
+    day = time.strptime(tparts.pop(0), '%d')[2]
+    last = tparts.pop(0) # year or hour:minute
+    try:
+        year = time.strptime(last, '%Y')[0]
+        hour = minute = 0
+    except ValueError:
+        hour, minute = time.strptime(last, '%H:%M')[3:5]
+        year = t_now[0]
+
+        t_result = (year, month, day, hour, minute, 0,0,0,0)
+        if t_result > t_now:
+            year -= 1
+    t_result = (year, month, day, hour, minute, 0,0,0,0)
+    return calendar.timegm(t_result)
+
+class PathEntry:
+    def __init__(self, ppart):
+        self.strpath = ppart.firstChild.nodeValue.encode('UTF-8')
+        self.action = ppart.getAttribute('action').encode('UTF-8')
+        if self.action == 'A':
+            self.copyfrom_path = ppart.getAttribute('copyfrom-path').encode('UTF-8')
+            if self.copyfrom_path:
+                self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
+

--- /dev/null
+++ b/_py/cmdline/pyrest.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+"""
+invoke 
+
+    py.rest filename1.txt directory 
+
+to generate html files from ReST.
+
+It is also possible to generate pdf files using the --topdf option.
+
+http://docutils.sourceforge.net/docs/user/rst/quickref.html
+
+"""
+
+import os, sys
+import py
+
+if hasattr(sys.stdout, 'fileno') and os.isatty(sys.stdout.fileno()):
+    def log(msg):
+        print(msg)
+else:
+    def log(msg):
+        pass
+
+
+parser = py.std.optparse.OptionParser(usage=__doc__)
+parser.add_option("--topdf", action="store_true", dest="topdf", default=False,
+                  help="generate pdf files")
+parser.add_option("--stylesheet", dest="stylesheet", default=None,
+                  help="use specified latex style sheet")
+parser.add_option("--debug", action="store_true", dest="debug",
+                  default=False,
+                  help="print debug output and don't delete files")
+
+
+def main():
+    try:
+        from _py.rest import directive, resthtml 
+        from _py.rest.latex import process_rest_file, process_configfile
+    except ImportError:
+        e = sys.exc_info()[1]
+        print(str(e))
+        sys.exit(1)
+
+    (options, args) = parser.parse_args()
+
+    if len(args) == 0:
+        filenames = [py.path.svnwc()]
+    else:
+        filenames = [py.path.svnwc(x) for x in args]
+    
+    if options.topdf:
+        directive.set_backend_and_register_directives("latex")
+        
+    for p in filenames:
+        if not p.check():
+            log("path %s not found, ignoring" % p)
+            continue
+        def fil(p):
+            return p.check(fnmatch='*.txt', versioned=True)
+        def rec(p):
+            return p.check(dotfile=0)
+        if p.check(dir=1):
+            for x in p.visit(fil, rec):
+                resthtml.process(x)
+        elif p.check(file=1):
+            if p.ext == ".rst2pdfconfig":
+                directive.set_backend_and_register_directives("latex")
+                process_configfile(p, options.debug)
+            else:
+                if options.topdf:
+                    cfg = p.new(ext=".rst2pdfconfig")
+                    if cfg.check():
+                        print("using config file %s" % (cfg, ))
+                        process_configfile(cfg, options.debug)
+                    else:
+                        process_rest_file(p.localpath,
+                                          options.stylesheet,
+                                      options.debug)
+                else:
+                    resthtml.process(p)
+

--- /dev/null
+++ b/_py/error.py
@@ -0,0 +1,83 @@
+"""
+create errno-specific classes for IO or os calls. 
+
+"""
+import sys, os, errno
+
+class Error(EnvironmentError):
+    def __repr__(self):
+        return "%s.%s %r: %s " %(self.__class__.__module__,
+                               self.__class__.__name__,
+                               self.__class__.__doc__,
+                               " ".join(map(str, self.args)),
+                               #repr(self.args)
+                                )
+
+    def __str__(self):
+        s = "[%s]: %s" %(self.__class__.__doc__,
+                          " ".join(map(str, self.args)),
+                          )
+        return s
+
+_winerrnomap = {
+    2: errno.ENOENT, 
+    3: errno.ENOENT, 
+    17: errno.EEXIST,
+    22: errno.ENOTDIR,
+    267: errno.ENOTDIR,
+    5: errno.EACCES,  # anything better?
+}
+
+class ErrorMaker(object):
+    """ lazily provides Exception classes for each possible POSIX errno 
+        (as defined per the 'errno' module).  All such instances 
+        subclass EnvironmentError.  
+    """
+    Error = Error
+    _errno2class = {}
+
+    def __getattr__(self, name):
+        eno = getattr(errno, name)
+        cls = self._geterrnoclass(eno)
+        setattr(self, name, cls)
+        return cls
+
+    def _geterrnoclass(self, eno):
+        try:
+            return self._errno2class[eno]
+        except KeyError:
+            clsname = errno.errorcode.get(eno, "UnknownErrno%d" %(eno,))
+            errorcls = type(Error)(clsname, (Error,),
+                    {'__module__':'py.error',
+                     '__doc__': os.strerror(eno)})
+            self._errno2class[eno] = errorcls
+            return errorcls
+
+    def checked_call(self, func, *args):
+        """ call a function and raise an errno-exception if applicable. """
+        __tracebackhide__ = True
+        try:
+            return func(*args)
+        except self.Error:
+            raise
+        except EnvironmentError:
+            cls, value, tb = sys.exc_info()
+            if not hasattr(value, 'errno'):
+                raise
+            __tracebackhide__ = False
+            errno = value.errno 
+            try:
+                if not isinstance(value, WindowsError): 
+                    raise NameError
+            except NameError: 
+                # we are not on Windows, or we got a proper OSError
+                cls = self._geterrnoclass(errno)
+            else: 
+                try: 
+                    cls = self._geterrnoclass(_winerrnomap[errno]) 
+                except KeyError:    
+                    raise value 
+            raise cls("%s%r" % (func.__name__, args))
+            __tracebackhide__ = True
+
+error = ErrorMaker()

--- /dev/null
+++ b/_py/path/gateway/remotepath.py
@@ -0,0 +1,47 @@
+import py, itertools
+from _py.path import common
+
+COUNTER = itertools.count()
+
+class RemotePath(common.PathBase):
+    sep = '/'
+
+    def __init__(self, channel, id, basename=None):
+        self._channel = channel
+        self._id = id
+        self._basename = basename
+        self._specs = {}
+
+    def __del__(self):
+        self._channel.send(('DEL', self._id))
+
+    def __repr__(self):
+        return 'RemotePath(%s)' % self.basename
+
+    def listdir(self, *args):
+        self._channel.send(('LIST', self._id) + args)
+        return [RemotePath(self._channel, id, basename)
+                for (id, basename) in self._channel.receive()]
+
+    def dirpath(self):
+        id = ~COUNTER.next()
+        self._channel.send(('DIRPATH', self._id, id))
+        return RemotePath(self._channel, id)
+
+    def join(self, *args):
+        id = ~COUNTER.next()
+        self._channel.send(('JOIN', self._id, id) + args)
+        return RemotePath(self._channel, id)
+
+    def _getbyspec(self, spec):
+        parts = spec.split(',')
+        ask = [x for x in parts  if x not in self._specs]
+        if ask:
+            self._channel.send(('GET', self._id, ",".join(ask)))
+            for part, value in zip(ask, self._channel.receive()):
+                self._specs[part] = value
+        return [self._specs[x] for x in parts]
+
+    def read(self):
+        self._channel.send(('READ', self._id))
+        return self._channel.receive()

--- /dev/null
+++ b/_py/builtin/builtin25.py
@@ -0,0 +1,15 @@
+
+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'

--- /dev/null
+++ b/_py/process/forkedfunc.py
@@ -0,0 +1,108 @@
+
+""" 
+    ForkedFunc provides a way to run a function in a forked process
+    and get at its return value, stdout and stderr output as well
+    as signals and exitstatusus. 
+
+    XXX see if tempdir handling is sane 
+"""
+
+import py
+import os
+import sys
+import marshal
+
+class ForkedFunc(object):
+    EXITSTATUS_EXCEPTION = 3
+    def __init__(self, fun, args=None, kwargs=None, nice_level=0):
+        if args is None:
+            args = []
+        if kwargs is None:
+            kwargs = {}
+        self.fun = fun
+        self.args = args
+        self.kwargs = kwargs
+        self.tempdir = tempdir = py.path.local.mkdtemp()
+        self.RETVAL = tempdir.ensure('retval')
+        self.STDOUT = tempdir.ensure('stdout')
+        self.STDERR = tempdir.ensure('stderr')
+
+        pid = os.fork()
+        if pid: # in parent process
+            self.pid = pid 
+        else: # in child process 
+            self._child(nice_level)
+
+    def _child(self, nice_level):
+        # right now we need to call a function, but first we need to
+        # map all IO that might happen
+        # make sure sys.stdout points to file descriptor one
+        sys.stdout = stdout = self.STDOUT.open('w')
+        sys.stdout.flush()
+        fdstdout = stdout.fileno()
+        if fdstdout != 1:
+            os.dup2(fdstdout, 1)
+        sys.stderr = stderr = self.STDERR.open('w')
+        fdstderr = stderr.fileno()
+        if fdstderr != 2:
+            os.dup2(fdstderr, 2)
+        retvalf = self.RETVAL.open("wb")
+        EXITSTATUS = 0
+        try:
+            if nice_level:
+                os.nice(nice_level)
+            try:
+                retval = self.fun(*self.args, **self.kwargs)
+                retvalf.write(marshal.dumps(retval))
+            except:
+                excinfo = py.code.ExceptionInfo()
+                stderr.write(excinfo.exconly())
+                EXITSTATUS = self.EXITSTATUS_EXCEPTION
+        finally:
+            stdout.close()
+            stderr.close()
+            retvalf.close()
+        os.close(1)
+        os.close(2)
+        os._exit(EXITSTATUS)
+    
+    def waitfinish(self, waiter=os.waitpid):
+        pid, systemstatus = waiter(self.pid, 0)
+        if systemstatus:
+            if os.WIFSIGNALED(systemstatus):
+                exitstatus = os.WTERMSIG(systemstatus) + 128
+            else:
+                exitstatus = os.WEXITSTATUS(systemstatus)
+            #raise ExecutionFailed(status, systemstatus, cmd,
+            #                      ''.join(out), ''.join(err))
+        else:
+            exitstatus = 0
+        signal = systemstatus & 0x7f
+        if not exitstatus and not signal:
+            retval = self.RETVAL.open('rb')
+            try:
+                retval_data = retval.read()
+            finally:
+                retval.close()
+            retval = marshal.loads(retval_data)
+        else:
+            retval = None
+        stdout = self.STDOUT.read()
+        stderr = self.STDERR.read()
+        self._removetemp()
+        return Result(exitstatus, signal, retval, stdout, stderr)
+
+    def _removetemp(self):
+        if self.tempdir.check():
+            self.tempdir.remove()
+
+    def __del__(self):
+        self._removetemp()
+
+class Result(object):
+    def __init__(self, exitstatus, signal, retval, stdout, stderr):
+        self.exitstatus = exitstatus
+        self.signal = signal
+        self.retval = retval
+        self.out = stdout
+        self.err = stderr

--- /dev/null
+++ b/_py/cmdline/pycleanup.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python 
+
+"""\
+py.cleanup [PATH]
+
+Delete pyc file recursively, starting from PATH (which defaults to the current
+working directory). Don't follow links and don't recurse into directories with
+a ".".
+"""
+import py
+
+def main():
+    parser = py.std.optparse.OptionParser(usage=__doc__)
+    parser.add_option("-e", "--remove", dest="ext", default=".pyc", action="store",
+        help="remove files with the given comma-separated list of extensions"
+    )
+    parser.add_option("-n", "--dryrun", dest="dryrun", default=False, 
+        action="store_true", 
+        help="display would-be-removed filenames"
+    )
+    (options, args) = parser.parse_args()
+    if not args:
+        args = ["."]
+    ext = options.ext.split(",")
+    def shouldremove(p):
+        return p.ext in ext
+        
+    for arg in args:
+        path = py.path.local(arg)
+        py.builtin.print_("cleaning path", path, "of extensions", ext)
+        for x in path.visit(shouldremove, lambda x: x.check(dotfile=0, link=0)):
+            if options.dryrun:
+                py.builtin.print_("would remove", x)
+            else:
+                py.builtin.print_("removing", x)
+                x.remove()

--- /dev/null
+++ b/_py/compat/dep_subprocess.py
@@ -0,0 +1,4 @@
+
+import py
+py.log._apiwarn("1.1", "py.compat.subprocess deprecated, use standard library version.", stacklevel="initpkg")
+subprocess = py.std.subprocess

--- /dev/null
+++ b/_py/cmdline/pywhich.py
@@ -0,0 +1,23 @@
+#!/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)

--- /dev/null
+++ b/_py/path/local.py
@@ -0,0 +1,789 @@
+"""
+local path implementation.
+"""
+import sys, os, stat, re, atexit
+import py
+from _py.path import common
+
+iswin32 = sys.platform == "win32"
+
+class Stat(object):
+    def __getattr__(self, name):
+        return getattr(self._osstatresult, "st_" + name)
+
+    def __init__(self, path, osstatresult): 
+        self.path = path 
+        self._osstatresult = osstatresult
+
+    def owner(self):
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import pwd 
+        entry = py.error.checked_call(pwd.getpwuid, self.uid)
+        return entry[0]
+    owner = property(owner, None, None, "owner of path") 
+
+    def group(self):
+        """ return group name of file. """
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import grp
+        entry = py.error.checked_call(grp.getgrgid, self.gid)
+        return entry[0]
+    group = property(group) 
+
+class PosixPath(common.PathBase):
+    def chown(self, user, group, rec=0):
+        """ change ownership to the given user and group.
+            user and group may be specified by a number or
+            by a name.  if rec is True change ownership
+            recursively.
+        """
+        uid = getuserid(user)
+        gid = getgroupid(group)
+        if rec:
+            for x in self.visit(rec=lambda x: x.check(link=0)): 
+                if x.check(link=0):
+                    py.error.checked_call(os.chown, str(x), uid, gid)
+        py.error.checked_call(os.chown, str(self), uid, gid)
+
+    def readlink(self):
+        """ return value of a symbolic link. """
+        return py.error.checked_call(os.readlink, self.strpath)
+
+    def mklinkto(self, oldname):
+        """ posix style hard link to another name. """
+        py.error.checked_call(os.link, str(oldname), str(self))
+
+    def mksymlinkto(self, value, absolute=1):
+        """ create a symbolic link with the given value (pointing to another name). """
+        if absolute:
+            py.error.checked_call(os.symlink, str(value), self.strpath)
+        else:
+            base = self.common(value)
+            # with posix local paths '/' is always a common base
+            relsource = self.__class__(value).relto(base)
+            reldest = self.relto(base)
+            n = reldest.count(self.sep)
+            target = self.sep.join(('..', )*n + (relsource, ))
+            py.error.checked_call(os.symlink, target, self.strpath)
+
+    def samefile(self, other):
+        """ return True if other refers to the same stat object as self. """
+        return py.std.os.path.samefile(str(self), str(other))
+
+def getuserid(user):
+    import pwd
+    if not isinstance(user, int):
+        user = pwd.getpwnam(user)[2]
+    return user
+
+def getgroupid(group):
+    import grp
+    if not isinstance(group, int):
+        group = grp.getgrnam(group)[2]
+    return group
+
+FSBase = not iswin32 and PosixPath or common.PathBase
+
+class LocalPath(FSBase):
+    """ object oriented interface to os.path and other local filesystem 
+        related information. 
+    """
+    sep = os.sep
+    class Checkers(common.Checkers):
+        def _stat(self):
+            try:
+                return self._statcache
+            except AttributeError:
+                try:
+                    self._statcache = self.path.stat()
+                except py.error.ELOOP:
+                    self._statcache = self.path.lstat()
+                return self._statcache
+
+        def dir(self):
+            return stat.S_ISDIR(self._stat().mode)
+
+        def file(self):
+            return stat.S_ISREG(self._stat().mode)
+
+        def exists(self):
+            return self._stat()
+
+        def link(self):
+            st = self.path.lstat()
+            return stat.S_ISLNK(st.mode)
+
+    def __new__(cls, path=None):
+        """ Initialize and return a local Path instance.
+
+        Path can be relative to the current directory.
+        If it is None then the current working directory is taken.
+        Note that Path instances always carry an absolute path.
+        Note also that passing in a local path object will simply return
+        the exact same path object. Use new() to get a new copy.
+        """
+        if isinstance(path, common.PathBase):
+            if path.__class__ == cls:
+                return path
+            path = path.strpath
+        # initialize the path
+        self = object.__new__(cls)
+        if not path:
+            self.strpath = os.getcwd()
+        elif isinstance(path, py.builtin._basestring):
+            self.strpath = os.path.abspath(os.path.normpath(str(path)))
+        else:
+            raise ValueError("can only pass None, Path instances "
+                             "or non-empty strings to LocalPath")
+        assert isinstance(self.strpath, str)
+        return self
+
+    def __hash__(self):
+        return hash(self.strpath)
+
+    def __eq__(self, other):
+        s1 = str(self)
+        s2 = str(other)
+        if iswin32: 
+            s1 = s1.lower()
+            s2 = s2.lower()
+        return s1 == s2
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __lt__(self, other):
+        return str(self) < str(other)
+
+    def remove(self, rec=1):
+        """ remove a file or directory (or a directory tree if rec=1).  """
+        if self.check(dir=1, link=0):
+            if rec:
+                # force remove of readonly files on windows 
+                if iswin32: 
+                    self.chmod(448, rec=1) # octcal 0700
+                py.error.checked_call(py.std.shutil.rmtree, self.strpath)
+            else:
+                py.error.checked_call(os.rmdir, self.strpath)
+        else:
+            if iswin32: 
+                self.chmod(448) # octcal 0700
+            py.error.checked_call(os.remove, self.strpath)
+
+    def computehash(self, hashtype="md5", chunksize=524288):
+        """ return hexdigest of hashvalue for this file. """
+        try:
+            try:
+                import hashlib as mod
+            except ImportError:
+                if hashtype == "sha1":
+                    hashtype = "sha"
+                mod = __import__(hashtype)
+            hash = getattr(mod, hashtype)()
+        except (AttributeError, ImportError):
+            raise ValueError("Don't know how to compute %r hash" %(hashtype,))
+        f = self.open('rb')
+        try:
+            while 1:
+                buf = f.read(chunksize)
+                if not buf:
+                    return hash.hexdigest()
+                hash.update(buf) 
+        finally:
+            f.close()
+
+    def new(self, **kw):
+        """ create a modified version of this path.
+            the following keyword arguments modify various path parts:
+
+              a:/some/path/to/a/file.ext
+              ||                            drive
+                |-------------|             dirname
+                                |------|    basename
+                                |--|        purebasename
+                                    |--|    ext
+        """
+        obj = object.__new__(self.__class__)
+        drive, dirname, basename, purebasename,ext = self._getbyspec(
+             "drive,dirname,basename,purebasename,ext")
+        if 'basename' in kw:
+            if 'purebasename' in kw or 'ext' in kw:
+                raise ValueError("invalid specification %r" % kw)
+        else:
+            pb = kw.setdefault('purebasename', purebasename)
+            try:
+                ext = kw['ext']
+            except KeyError:
+                pass
+            else:
+                if ext and not ext.startswith('.'):
+                    ext = '.' + ext
+            kw['basename'] = pb + ext
+
+        kw.setdefault('drive', drive)
+        kw.setdefault('dirname', dirname)
+        kw.setdefault('sep', self.sep)
+        obj.strpath = os.path.normpath(
+            "%(drive)s%(dirname)s%(sep)s%(basename)s" % kw)
+        return obj
+    
+    def _getbyspec(self, spec):
+        """ return a sequence of specified path parts.  'spec' is
+            a comma separated string containing path part names.
+            according to the following convention:
+            a:/some/path/to/a/file.ext
+            ||                            drive
+              |-------------|             dirname
+                              |------|    basename
+                              |--|        purebasename
+                                  |--|    ext
+        """
+        res = []
+        parts = self.strpath.split(self.sep)
+
+        args = filter(None, spec.split(',') )
+        append = res.append
+        for name in args:
+            if name == 'drive':
+                append(parts[0])
+            elif name == 'dirname':
+                append(self.sep.join(['']+parts[1:-1]))
+            else:
+                basename = parts[-1]
+                if name == 'basename':
+                    append(basename)
+                else:
+                    i = basename.rfind('.')
+                    if i == -1:
+                        purebasename, ext = basename, ''
+                    else:
+                        purebasename, ext = basename[:i], basename[i:]
+                    if name == 'purebasename':
+                        append(purebasename)
+                    elif name == 'ext':
+                        append(ext)
+                    else:
+                        raise ValueError("invalid part specification %r" % name)
+        return res
+
+    def join(self, *args, **kwargs):
+        """ return a new path by appending all 'args' as path
+        components.  if abs=1 is used restart from root if any
+        of the args is an absolute path.
+        """
+        if not args:
+            return self
+        strpath = self.strpath
+        sep = self.sep
+        strargs = [str(x) for x in args]
+        if kwargs.get('abs', 0):
+            for i in range(len(strargs)-1, -1, -1):
+                if os.path.isabs(strargs[i]):
+                    strpath = strargs[i]
+                    strargs = strargs[i+1:]
+                    break
+        for arg in strargs:
+            arg = arg.strip(sep)
+            if iswin32:
+                # allow unix style paths even on windows.
+                arg = arg.strip('/')
+                arg = arg.replace('/', sep)
+            if arg:
+                if not strpath.endswith(sep):
+                    strpath += sep
+                strpath += arg
+        obj = self.new()
+        obj.strpath = os.path.normpath(strpath)
+        return obj
+
+    def open(self, mode='r'):
+        """ return an opened file with the given mode. """
+        return py.error.checked_call(open, self.strpath, mode)
+
+    def listdir(self, fil=None, sort=None):
+        """ list directory contents, possibly filter by the given fil func
+            and possibly sorted.
+        """
+        if isinstance(fil, str):
+            fil = common.FNMatcher(fil)
+        res = []
+        for name in py.error.checked_call(os.listdir, self.strpath):
+            childurl = self.join(name)
+            if fil is None or fil(childurl):
+                res.append(childurl)
+        self._sortlist(res, sort)
+        return res 
+
+    def size(self):
+        """ return size of the underlying file object """
+        return self.stat().size
+
+    def mtime(self):
+        """ return last modification time of the path. """
+        return self.stat().mtime
+
+    def copy(self, target, archive=False):
+        """ copy path to target."""
+        assert not archive, "XXX archive-mode not supported"
+        if self.check(file=1):
+            if target.check(dir=1):
+                target = target.join(self.basename)
+            assert self!=target
+            copychunked(self, target)
+        else:
+            def rec(p):
+                return p.check(link=0)
+            for x in self.visit(rec=rec):
+                relpath = x.relto(self)
+                newx = target.join(relpath)
+                newx.dirpath().ensure(dir=1)
+                if x.check(link=1):
+                    newx.mksymlinkto(x.readlink())
+                elif x.check(file=1):
+                    copychunked(x, newx)
+                elif x.check(dir=1):
+                    newx.ensure(dir=1)
+
+    def rename(self, target):
+        """ rename this path to target. """
+        return py.error.checked_call(os.rename, str(self), str(target))
+
+    def dump(self, obj, bin=1):
+        """ pickle object into path location"""
+        f = self.open('wb')
+        try:
+            py.error.checked_call(py.std.pickle.dump, obj, f, bin)
+        finally:
+            f.close()
+
+    def mkdir(self, *args):
+        """ create & return the directory joined with args. """
+        p = self.join(*args)
+        py.error.checked_call(os.mkdir, str(p))
+        return p
+
+    def write(self, data, mode='w'):
+        """ write data into path. """
+        if 'b' in mode:
+            if not py.builtin._isbytes(data):
+                raise ValueError("can only process bytes")
+        else:
+            if not py.builtin._istext(data):
+                if not py.builtin._isbytes(data):
+                    data = str(data)
+                else:
+                    data = py.builtin._totext(data, sys.getdefaultencoding())
+        f = self.open(mode)
+        try:
+            f.write(data)
+        finally:
+            f.close()
+
+    def _ensuredirs(self):
+        parent = self.dirpath()
+        if parent == self:
+            return self
+        if parent.check(dir=0):
+            parent._ensuredirs()
+        if self.check(dir=0):
+            try:
+                self.mkdir()
+            except py.error.EEXIST:
+                # race condition: file/dir created by another thread/process.
+                # complain if it is not a dir
+                if self.check(dir=0):
+                    raise
+        return self
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). if you specify a keyword argument 'dir=True'
+            then the path is forced to be a directory path.
+        """
+        p = self.join(*args)
+        if kwargs.get('dir', 0):
+            return p._ensuredirs()
+        else:
+            p.dirpath()._ensuredirs()
+            if not p.check(file=1):
+                p.open('w').close()
+            return p
+
+    def stat(self):
+        """ Return an os.stat() tuple. """
+        return Stat(self, py.error.checked_call(os.stat, self.strpath))
+
+    def lstat(self):
+        """ Return an os.lstat() tuple. """
+        return Stat(self, py.error.checked_call(os.lstat, self.strpath))
+
+    def setmtime(self, mtime=None):
+        """ set modification time for the given path.  if 'mtime' is None
+        (the default) then the file's mtime is set to current time.
+
+        Note that the resolution for 'mtime' is platform dependent.
+        """
+        if mtime is None:
+            return py.error.checked_call(os.utime, self.strpath, mtime)
+        try:
+            return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
+        except py.error.EINVAL:
+            return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
+
+    def chdir(self):
+        """ change directory to self and return old current directory """
+        old = self.__class__()
+        py.error.checked_call(os.chdir, self.strpath)
+        return old
+
+    def realpath(self):
+        """ return a new path which contains no symbolic links."""
+        return self.__class__(os.path.realpath(self.strpath))
+
+    def atime(self):
+        """ return last access time of the path. """
+        return self.stat().atime
+
+    def __repr__(self):
+        return 'local(%r)' % self.strpath
+
+    def __str__(self):
+        """ return string representation of the Path. """
+        return self.strpath
+
+    def pypkgpath(self, pkgname=None):
+        """ return the path's package path by looking for the given
+            pkgname.  If pkgname is None then look for the last
+            directory upwards which still contains an __init__.py.
+            Return None if a pkgpath can not be determined.
+        """
+        pkgpath = None
+        for parent in self.parts(reverse=True):
+            if pkgname is None:
+                if parent.check(file=1):
+                    continue
+                if parent.join('__init__.py').check():
+                    pkgpath = parent
+                    continue
+                return pkgpath
+            else:
+                if parent.basename == pkgname:
+                    return parent
+        return pkgpath
+
+    def _prependsyspath(self, path):
+        s = str(path)
+        if s != sys.path[0]:
+            #print "prepending to sys.path", s
+            sys.path.insert(0, s)
+
+    def chmod(self, mode, rec=0):
+        """ change permissions to the given mode. If mode is an
+            integer it directly encodes the os-specific modes.
+            if rec is True perform recursively.
+        """
+        if not isinstance(mode, int):
+            raise TypeError("mode %r must be an integer" % (mode,))
+        if rec:
+            for x in self.visit(rec=rec):
+                py.error.checked_call(os.chmod, str(x), mode)
+        py.error.checked_call(os.chmod, str(self), mode)
+
+    def pyimport(self, modname=None, ensuresyspath=True):
+        """ return path as an imported python module.
+            if modname is None, look for the containing package
+            and construct an according module name.
+            The module will be put/looked up in sys.modules.
+        """
+        if not self.check():
+            raise py.error.ENOENT(self)
+        #print "trying to import", self
+        pkgpath = None
+        if modname is None:
+            pkgpath = self.pypkgpath()
+            if pkgpath is not None:
+                if ensuresyspath:
+                    self._prependsyspath(pkgpath.dirpath())
+                pkg = __import__(pkgpath.basename, None, None, [])
+                names = self.new(ext='').relto(pkgpath.dirpath())
+                names = names.split(self.sep)
+                modname = ".".join(names)
+            else:
+                # no package scope, still make it possible
+                if ensuresyspath:
+                    self._prependsyspath(self.dirpath())
+                modname = self.purebasename
+            mod = __import__(modname, None, None, ['__doc__'])
+            #self._module = mod
+            return mod
+        else:
+            try:
+                return sys.modules[modname]
+            except KeyError:
+                # we have a custom modname, do a pseudo-import
+                mod = py.std.types.ModuleType(modname)
+                mod.__file__ = str(self)
+                sys.modules[modname] = mod
+                try:
+                    py.builtin.execfile(str(self), mod.__dict__)
+                except:
+                    del sys.modules[modname]
+                    raise
+                return mod
+
+    def sysexec(self, *argv):
+        """ return stdout text from executing a system child process,
+            where the 'self' path points to executable. 
+            The process is directly invoked and not through a system shell. 
+        """
+        from subprocess import Popen, PIPE
+        argv = map(str, argv)
+        proc = Popen([str(self)] + list(argv), stdout=PIPE, stderr=PIPE)
+        stdout, stderr = proc.communicate()
+        ret = proc.wait()
+        if py.builtin._isbytes(stdout):
+            stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
+        if ret != 0:
+            if py.builtin._isbytes(stderr):
+                stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
+            raise py.process.cmdexec.Error(ret, ret, str(self),
+                                           stdout, stderr,)
+        return stdout
+
+    def sysfind(cls, name, checker=None):
+        """ return a path object found by looking at the systems
+            underlying PATH specification. If the checker is not None
+            it will be invoked to filter matching paths.  If a binary
+            cannot be found, None is returned
+            Note: This is probably not working on plain win32 systems
+            but may work on cygwin.
+        """
+        if os.path.isabs(name):
+            p = py.path.local(name)
+            if p.check(file=1):
+                return p
+        else:
+            if iswin32:
+                paths = py.std.os.environ['Path'].split(';')
+                if '' not in paths and '.' not in paths:
+                    paths.append('.')
+                try:
+                    systemroot = os.environ['SYSTEMROOT']
+                except KeyError:
+                    pass
+                else:
+                    paths = [re.sub('%SystemRoot%', systemroot, path)
+                             for path in paths]
+                tryadd = '', '.exe', '.com', '.bat' # XXX add more?
+            else:
+                paths = py.std.os.environ['PATH'].split(':')
+                tryadd = ('',)
+
+            for x in paths:
+                for addext in tryadd:
+                    p = py.path.local(x).join(name, abs=True) + addext
+                    try:
+                        if p.check(file=1):
+                            if checker:
+                                if not checker(p):
+                                    continue
+                            return p
+                    except py.error.EACCES:
+                        pass
+        return None
+    sysfind = classmethod(sysfind)
+
+    def _gethomedir(cls):
+        try:
+            x = os.environ['HOME']
+        except KeyError:
+            x = os.environ['HOMEPATH']
+        return cls(x)
+    _gethomedir = classmethod(_gethomedir)
+
+    #"""
+    #special class constructors for local filesystem paths
+    #"""
+    def get_temproot(cls):
+        """ return the system's temporary directory
+            (where tempfiles are usually created in)
+        """
+        return py.path.local(py.std.tempfile.gettempdir())
+    get_temproot = classmethod(get_temproot)
+
+    def mkdtemp(cls):
+        """ return a Path object pointing to a fresh new temporary directory
+            (which we created ourself).
+        """
+        import tempfile
+        tries = 10
+        for i in range(tries):
+            dname = tempfile.mktemp()
+            dpath = cls(tempfile.mktemp())
+            try:
+                dpath.mkdir()
+            except (py.error.EEXIST, py.error.EPERM, py.error.EACCES):
+                continue
+            return dpath
+        raise py.error.ENOENT(dpath, "could not create tempdir, %d tries" % tries)
+    mkdtemp = classmethod(mkdtemp)
+
+    def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
+                          lock_timeout = 172800):   # two days
+        """ return unique directory with a number greater than the current
+            maximum one.  The number is assumed to start directly after prefix.
+            if keep is true directories with a number less than (maxnum-keep)
+            will be removed.
+        """
+        if rootdir is None:
+            rootdir = cls.get_temproot()
+
+        def parse_num(path):
+            """ parse the number out of a path (if it matches the prefix) """
+            bn = path.basename
+            if bn.startswith(prefix):
+                try:
+                    return int(bn[len(prefix):])
+                except ValueError:
+                    pass
+
+        # compute the maximum number currently in use with the
+        # prefix
+        lastmax = None
+        while True:
+            maxnum = -1
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None:
+                    maxnum = max(maxnum, num)
+
+            # make the new directory
+            try:
+                udir = rootdir.mkdir(prefix + str(maxnum+1))
+            except py.error.EEXIST:
+                # race condition: another thread/process created the dir
+                # in the meantime.  Try counting again
+                if lastmax == maxnum:
+                    raise
+                lastmax = maxnum
+                continue
+            break
+
+        # put a .lock file in the new directory that will be removed at
+        # process exit
+        if lock_timeout:
+            lockfile = udir.join('.lock')
+            mypid = os.getpid()
+            if hasattr(lockfile, 'mksymlinkto'):
+                lockfile.mksymlinkto(str(mypid))
+            else:
+                lockfile.write(str(mypid))
+            def try_remove_lockfile():
+                # in a fork() situation, only the last process should
+                # remove the .lock, otherwise the other processes run the
+                # risk of seeing their temporary dir disappear.  For now
+                # we remove the .lock in the parent only (i.e. we assume
+                # that the children finish before the parent).
+                if os.getpid() != mypid:
+                    return
+                try:
+                    lockfile.remove()
+                except py.error.Error:
+                    pass
+            atexit.register(try_remove_lockfile)
+
+        # prune old directories
+        if keep:
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None and num <= (maxnum - keep):
+                    lf = path.join('.lock')
+                    try:
+                        t1 = lf.lstat().mtime
+                        t2 = lockfile.lstat().mtime
+                        if not lock_timeout or abs(t2-t1) < lock_timeout:
+                            continue   # skip directories still locked
+                    except py.error.Error:
+                        pass   # assume that it means that there is no 'lf'
+                    try:
+                        path.remove(rec=1)
+                    except KeyboardInterrupt:
+                        raise
+                    except: # this might be py.error.Error, WindowsError ...
+                        pass
+        
+        # make link...
+        try:
+            username = os.environ['USER']           #linux, et al
+        except KeyError:
+            try:
+                username = os.environ['USERNAME']   #windows
+            except KeyError:
+                username = 'current'
+
+        src  = str(udir)
+        dest = src[:src.rfind('-')] + '-' + username
+        try:
+            os.unlink(dest)
+        except OSError:
+            pass
+        try:
+            os.symlink(src, dest)
+        except (OSError, AttributeError): # AttributeError on win32
+            pass
+
+        return udir
+    make_numbered_dir = classmethod(make_numbered_dir)
+
+def copychunked(src, dest):
+    chunksize = 524288 # half a meg of bytes
+    fsrc = src.open('rb')
+    try:
+        fdest = dest.open('wb')
+        try:
+            while 1:
+                buf = fsrc.read(chunksize)
+                if not buf:
+                    break
+                fdest.write(buf)
+        finally:
+            fdest.close()
+    finally:
+        fsrc.close()
+
+def autopath(globs=None):
+    """ (deprecated) return the (local) path of the "current" file pointed to by globals or - if it is none - alternatively the callers frame globals.
+
+        the path will always point to a .py file  or to None.
+        the path will have the following payload:
+        pkgdir   is the last parent directory path containing __init__.py 
+    """
+    py.log._apiwarn("1.1", "py.magic.autopath deprecated, "
+        "use py.path.local(__file__) and maybe pypkgpath/pyimport().")
+    if globs is None:
+        globs = sys._getframe(1).f_globals
+    try:
+        __file__ = globs['__file__']
+    except KeyError:
+        if not sys.argv[0]:
+            raise ValueError("cannot compute autopath in interactive mode")
+        __file__ = os.path.abspath(sys.argv[0])
+
+    ret = py.path.local(__file__)
+    if ret.ext in ('.pyc', '.pyo'):
+        ret = ret.new(ext='.py')
+    current = pkgdir = ret.dirpath()
+    while 1:
+        if current.join('__init__.py').check():
+            pkgdir = current
+            current = current.dirpath()
+            if pkgdir != current:
+                continue
+        elif str(current) not in sys.path:
+            sys.path.insert(0, str(current))
+        break
+    ret.pkgdir = pkgdir
+    return ret
+

--- /dev/null
+++ b/_py/compat/__init__.py
@@ -0,0 +1,2 @@
+""" compatibility modules (taken from 2.4.4) """
+

--- /dev/null
+++ b/_py/cmdline/__init__.py
@@ -0,0 +1,1 @@
+#

--- /dev/null
+++ b/_py/cmdline/pysvnwcrevert.py
@@ -0,0 +1,55 @@
+#! /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)

--- /dev/null
+++ b/_py/compat/dep_doctest.py
@@ -0,0 +1,4 @@
+import py
+
+py.log._apiwarn("1.1", "py.compat.doctest deprecated, use standard library version.", stacklevel="initpkg")
+doctest = py.std.doctest

--- /dev/null
+++ b/_py/path/gateway/channeltest2.py
@@ -0,0 +1,21 @@
+import py
+from remotepath import RemotePath
+
+
+SRC = open('channeltest.py', 'r').read()
+
+SRC += '''
+import py
+srv = PathServer(channel.receive())
+channel.send(srv.p2c(py.path.local("/tmp")))
+'''
+
+
+#gw = execnet.SshGateway('codespeak.net')
+gw = execnet.PopenGateway()
+gw.remote_init_threads(5)
+c = gw.remote_exec(SRC, stdout=py.std.sys.stdout, stderr=py.std.sys.stderr)
+subchannel = gw._channelfactory.new()
+c.send(subchannel)
+
+p = RemotePath(subchannel, c.receive())

--- /dev/null
+++ b/_py/path/gateway/channeltest.py
@@ -0,0 +1,65 @@
+import threading
+
+
+class PathServer:
+
+    def __init__(self, channel):
+        self.channel = channel
+        self.C2P = {}
+        self.next_id = 0
+        threading.Thread(target=self.serve).start()
+
+    def p2c(self, path):
+        id = self.next_id
+        self.next_id += 1
+        self.C2P[id] = path
+        return id
+
+    def command_LIST(self, id, *args):
+        path = self.C2P[id]
+        answer = [(self.p2c(p), p.basename) for p in path.listdir(*args)]
+        self.channel.send(answer)
+
+    def command_DEL(self, id):
+        del self.C2P[id]
+
+    def command_GET(self, id, spec):
+        path = self.C2P[id]
+        self.channel.send(path._getbyspec(spec))
+
+    def command_READ(self, id):
+        path = self.C2P[id]
+        self.channel.send(path.read())
+
+    def command_JOIN(self, id, resultid, *args):
+        path = self.C2P[id]
+        assert resultid not in self.C2P
+        self.C2P[resultid] = path.join(*args)
+
+    def command_DIRPATH(self, id, resultid):
+        path = self.C2P[id]
+        assert resultid not in self.C2P
+        self.C2P[resultid] = path.dirpath()
+
+    def serve(self):
+        try:
+            while 1:
+                msg = self.channel.receive()
+                meth = getattr(self, 'command_' + msg[0])
+                meth(*msg[1:])
+        except EOFError:
+            pass
+
+if __name__ == '__main__':
+    import py
+    gw = execnet.PopenGateway()
+    channel = gw._channelfactory.new()
+    srv = PathServer(channel)
+    c = gw.remote_exec("""
+        import remotepath
+        p = remotepath.RemotePath(channel.receive(), channel.receive())
+        channel.send(len(p.listdir()))
+    """)
+    c.send(channel)
+    c.send(srv.p2c(py.path.local('/tmp')))
+    print(c.receive())

--- /dev/null
+++ b/_py/io/capture.py
@@ -0,0 +1,344 @@
+import os
+import sys
+import py
+import tempfile
+
+try: 
+    from io import StringIO
+except ImportError: 
+    from StringIO import StringIO
+
+if sys.version_info < (3,0):
+    class TextIO(StringIO):
+        def write(self, data):
+            if not isinstance(data, unicode):
+                data = unicode(data, getattr(self, '_encoding', 'UTF-8'))
+            StringIO.write(self, data)
+else:
+    TextIO = StringIO
+
+try:
+    from io import BytesIO
+except ImportError:
+    class BytesIO(StringIO):
+        def write(self, data):
+            if isinstance(data, unicode):
+                raise TypeError("not a byte value: %r" %(data,))
+            StringIO.write(self, data)
+
+class FDCapture: 
+    """ Capture IO to/from a given os-level filedescriptor. """
+    
+    def __init__(self, targetfd, tmpfile=None): 
+        """ save targetfd descriptor, and open a new 
+            temporary file there.  If no tmpfile is 
+            specified a tempfile.Tempfile() will be opened
+            in text mode. 
+        """
+        self.targetfd = targetfd
+        if tmpfile is None: 
+            f = tempfile.TemporaryFile('wb+')
+            tmpfile = dupfile(f, encoding="UTF-8") 
+            f.close()
+        self.tmpfile = tmpfile 
+        self._savefd = os.dup(targetfd)
+        os.dup2(self.tmpfile.fileno(), targetfd) 
+        self._patched = []
+
+    def setasfile(self, name, module=sys): 
+        """ patch <module>.<name> to self.tmpfile
+        """
+        key = (module, name)
+        self._patched.append((key, getattr(module, name)))
+        setattr(module, name, self.tmpfile) 
+
+    def unsetfiles(self): 
+        """ unpatch all patched items
+        """
+        while self._patched: 
+            (module, name), value = self._patched.pop()
+            setattr(module, name, value) 
+
+    def done(self): 
+        """ unpatch and clean up, returns the self.tmpfile (file object)
+        """
+        os.dup2(self._savefd, self.targetfd) 
+        self.unsetfiles() 
+        os.close(self._savefd) 
+        self.tmpfile.seek(0)
+        return self.tmpfile 
+
+    def writeorg(self, data):
+        """ write a string to the original file descriptor
+        """
+        tempfp = tempfile.TemporaryFile()
+        try:
+            os.dup2(self._savefd, tempfp.fileno())
+            tempfp.write(data)
+        finally:
+            tempfp.close()
+
+
+def dupfile(f, mode=None, buffering=0, raising=False, encoding=None): 
+    """ return a new open file object that's a duplicate of f
+
+        mode is duplicated if not given, 'buffering' controls 
+        buffer size (defaulting to no buffering) and 'raising'
+        defines whether an exception is raised when an incompatible
+        file object is passed in (if raising is False, the file
+        object itself will be returned)
+    """
+    try: 
+        fd = f.fileno() 
+    except AttributeError: 
+        if raising: 
+            raise 
+        return f
+    newfd = os.dup(fd) 
+    mode = mode and mode or f.mode
+    if sys.version_info >= (3,0):
+        if encoding is not None:
+            mode = mode.replace("b", "")
+            buffering = True
+        return os.fdopen(newfd, mode, buffering, encoding, closefd=False)
+    else:
+        f = os.fdopen(newfd, mode, buffering) 
+        if encoding is not None:
+            return EncodedFile(f, encoding)
+        return f
+
+class EncodedFile(object):
+    def __init__(self, _stream, encoding):
+        self._stream = _stream
+        self.encoding = encoding
+
+    def write(self, obj):
+        if isinstance(obj, unicode):
+            obj = obj.encode(self.encoding)
+        elif isinstance(obj, str):
+            pass
+        else:
+            obj = str(obj)
+        self._stream.write(obj)
+
+    def writelines(self, linelist):
+        data = ''.join(linelist)
+        self.write(data)
+
+    def __getattr__(self, name):
+        return getattr(self._stream, name)
+
+class Capture(object):
+    def call(cls, func, *args, **kwargs): 
+        """ return a (res, out, err) tuple where
+            out and err represent the output/error output
+            during function execution. 
+            call the given function with args/kwargs
+            and capture output/error during its execution. 
+        """ 
+        so = cls()
+        try: 
+            res = func(*args, **kwargs)
+        finally: 
+            out, err = so.reset()
+        return res, out, err 
+    call = classmethod(call) 
+
+    def reset(self):
+        """ reset sys.stdout/stderr and return captured output as strings. """
+        if hasattr(self, '_suspended'):
+            outfile = self._kwargs['out']
+            errfile = self._kwargs['err']
+            del self._kwargs
+        else:
+            outfile, errfile = self.done() 
+        out, err = "", ""
+        if outfile:
+            out = outfile.read()
+            outfile.close()
+        if errfile and errfile != outfile:
+            err = errfile.read()
+            errfile.close()
+        return out, err
+
+    def suspend(self):
+        """ return current snapshot captures, memorize tempfiles. """
+        assert not hasattr(self, '_suspended')
+        self._suspended = True
+        outerr = self.readouterr()
+        outfile, errfile = self.done()
+        self._kwargs['out'] = outfile
+        self._kwargs['err'] = errfile
+        return outerr
+
+    def resume(self):
+        """ resume capturing with original temp files. """
+        assert self._suspended
+        self._initialize(**self._kwargs)
+        del self._suspended
+
+
+class StdCaptureFD(Capture): 
+    """ This class allows to capture writes to FD1 and FD2 
+        and may connect a NULL file to FD0 (and prevent
+        reads from sys.stdin)
+    """
+    def __init__(self, out=True, err=True, 
+                 mixed=False, in_=True, patchsys=True): 
+        self._kwargs = locals().copy()
+        del self._kwargs['self']
+        self._initialize(**self._kwargs)
+
+    def _initialize(self, out=True, err=True, 
+                    mixed=False, in_=True, patchsys=True): 
+        if in_:
+            self._oldin = (sys.stdin, os.dup(0))
+            sys.stdin  = DontReadFromInput()
+            fd = os.open(devnullpath, os.O_RDONLY)
+            os.dup2(fd, 0)
+            os.close(fd)
+        if out: 
+            tmpfile = None
+            if hasattr(out, 'write'):
+                tmpfile = out
+            self.out = py.io.FDCapture(1, tmpfile=tmpfile)
+            if patchsys: 
+                self.out.setasfile('stdout')
+        if err: 
+            if mixed and out:
+                tmpfile = self.out.tmpfile 
+            elif hasattr(err, 'write'):
+                tmpfile = err
+            else:
+                tmpfile = None
+            self.err = py.io.FDCapture(2, tmpfile=tmpfile) 
+            if patchsys: 
+                self.err.setasfile('stderr')
+
+    def done(self):
+        """ return (outfile, errfile) and stop capturing. """
+        if hasattr(self, 'out'): 
+            outfile = self.out.done() 
+        else:
+            outfile = None
+        if hasattr(self, 'err'): 
+            errfile = self.err.done() 
+        else:
+            errfile = None 
+        if hasattr(self, '_oldin'):
+            oldsys, oldfd = self._oldin 
+            os.dup2(oldfd, 0)
+            os.close(oldfd)
+            sys.stdin = oldsys 
+        return outfile, errfile 
+
+    def readouterr(self):
+        """ return snapshot value of stdout/stderr capturings. """
+        l = []
+        for name in ('out', 'err'):
+            res = ""
+            if hasattr(self, name):
+                f = getattr(self, name).tmpfile
+                f.seek(0)
+                res = f.read()
+                f.truncate(0)
+                f.seek(0)
+            l.append(res)
+        return l 
+
+class StdCapture(Capture):
+    """ This class allows to capture writes to sys.stdout|stderr "in-memory"
+        and will raise errors on tries to read from sys.stdin. It only
+        modifies sys.stdout|stderr|stdin attributes and does not 
+        touch underlying File Descriptors (use StdCaptureFD for that). 
+    """
+    def __init__(self, out=True, err=True, in_=True, mixed=False):
+        self._kwargs = locals().copy()
+        del self._kwargs['self']
+        self._initialize(**self._kwargs)
+
+    def _initialize(self, out, err, in_, mixed):
+        self._out = out
+        self._err = err 
+        self._in = in_
+        if out: 
+            self._oldout = sys.stdout
+            if not hasattr(out, 'write'):
+                out = TextIO()
+            sys.stdout = self.out = out
+        if err: 
+            self._olderr = sys.stderr
+            if out and mixed: 
+                err = self.out 
+            elif not hasattr(err, 'write'):
+                err = TextIO()
+            sys.stderr = self.err = err
+        if in_:
+            self._oldin  = sys.stdin
+            sys.stdin  = self.newin  = DontReadFromInput()
+
+    def done(self): 
+        """ return (outfile, errfile) and stop capturing. """
+        o,e = sys.stdout, sys.stderr
+        if self._out: 
+            try:
+                sys.stdout = self._oldout 
+            except AttributeError:
+                raise IOError("stdout capturing already reset")
+            del self._oldout
+            outfile = self.out
+            outfile.seek(0)
+        else:
+            outfile = None
+        if self._err: 
+            try:
+                sys.stderr = self._olderr 
+            except AttributeError:
+                raise IOError("stderr capturing already reset")
+            del self._olderr 
+            errfile = self.err 
+            errfile.seek(0)
+        else:
+            errfile = None
+        if self._in:
+            sys.stdin = self._oldin 
+        return outfile, errfile
+
+    def readouterr(self):
+        """ return snapshot value of stdout/stderr capturings. """
+        out = err = ""
+        if self._out:
+            out = sys.stdout.getvalue()
+            sys.stdout.truncate(0)
+        if self._err:
+            err = sys.stderr.getvalue()
+            sys.stderr.truncate(0)
+        return out, err 
+
+class DontReadFromInput:
+    """Temporary stub class.  Ideally when stdin is accessed, the
+    capturing should be turned off, with possibly all data captured
+    so far sent to the screen.  This should be configurable, though,
+    because in automated test runs it is better to crash than
+    hang indefinitely.
+    """
+    def read(self, *args):
+        raise IOError("reading from stdin while output is captured")
+    readline = read
+    readlines = read
+    __iter__ = read
+   
+    def fileno(self):
+        raise ValueError("redirected Stdin is pseudofile, has no fileno()") 
+    def isatty(self):
+        return False
+
+try:
+    devnullpath = os.devnull
+except AttributeError:
+    if os.name == 'nt':
+        devnullpath = 'NUL'
+    else:
+        devnullpath = '/dev/null'
+
+

--- /dev/null
+++ b/_py/code/oldmagic2.py
@@ -0,0 +1,6 @@
+
+import py
+
+py.log._apiwarn("1.1", "py.magic.AssertionError is deprecated, use py.code._AssertionError", stacklevel=2)
+
+from py.code import _AssertionError as AssertionError

--- /dev/null
+++ b/_py/path/cacheutil.py
@@ -0,0 +1,111 @@
+"""
+This module contains multithread-safe cache implementations.
+
+All Caches have 
+
+    getorbuild(key, builder) 
+    delentry(key) 
+
+methods and allow configuration when instantiating the cache class. 
+"""
+from time import time as gettime
+
+class BasicCache(object):
+    def __init__(self, maxentries=128):
+        self.maxentries = maxentries
+        self.prunenum = int(maxentries - maxentries/8)
+        self._dict = {}
+
+    def _getentry(self, key):
+        return self._dict[key]
+
+    def _putentry(self, key, entry):
+        self._prunelowestweight()
+        self._dict[key] = entry 
+
+    def delentry(self, key, raising=False):
+        try:
+            del self._dict[key]
+        except KeyError:
+            if raising:
+                raise
+
+    def getorbuild(self, key, builder):
+        try:
+            entry = self._getentry(key)
+        except KeyError:
+            entry = self._build(key, builder)
+            self._putentry(key, entry)
+        return entry.value
+
+    def _prunelowestweight(self):
+        """ prune out entries with lowest weight. """
+        numentries = len(self._dict)
+        if numentries >= self.maxentries:
+            # evict according to entry's weight
+            items = [(entry.weight, key) 
+                        for key, entry in self._dict.items()]
+            items.sort()
+            index = numentries - self.prunenum
+            if index > 0:
+                for weight, key in items[:index]:
+                    # in MT situations the element might be gone
+                    self.delentry(key, raising=False) 
+
+class BuildcostAccessCache(BasicCache):
+    """ A BuildTime/Access-counting cache implementation.
+        the weight of a value is computed as the product of
+
+            num-accesses-of-a-value * time-to-build-the-value
+
+        The values with the least such weights are evicted
+        if the cache maxentries threshold is superceded.
+        For implementation flexibility more than one object
+        might be evicted at a time.
+    """
+    # time function to use for measuring build-times
+
+    def _build(self, key, builder):
+        start = gettime()
+        val = builder()
+        end = gettime()
+        return WeightedCountingEntry(val, end-start)
+
+
+class WeightedCountingEntry(object):
+    def __init__(self, value, oneweight):
+        self._value = value
+        self.weight = self._oneweight = oneweight 
+
+    def value(self):
+        self.weight += self._oneweight
+        return self._value
+    value = property(value)
+
+class AgingCache(BasicCache):
+    """ This cache prunes out cache entries that are too old.
+    """
+    def __init__(self, maxentries=128, maxseconds=10.0):
+        super(AgingCache, self).__init__(maxentries)
+        self.maxseconds = maxseconds
+
+    def _getentry(self, key):
+        entry = self._dict[key]
+        if entry.isexpired():
+            self.delentry(key) 
+            raise KeyError(key) 
+        return entry
+
+    def _build(self, key, builder):
+        val = builder()
+        entry = AgingEntry(val, gettime() + self.maxseconds)
+        return entry
+
+class AgingEntry(object):
+    def __init__(self, value, expirationtime):
+        self.value = value
+        self.weight = expirationtime
+
+    def isexpired(self):
+        t = gettime()
+        return t >= self.weight 

--- /dev/null
+++ b/_py/path/svnwc.py
@@ -0,0 +1,1236 @@
+"""
+svn-Command based Implementation of a Subversion WorkingCopy Path.
+
+  SvnWCCommandPath  is the main class.
+
+"""
+
+import os, sys, time, re, calendar
+import py
+import subprocess
+from _py.path import common
+
+#-----------------------------------------------------------
+# Caching latest repository revision and repo-paths
+# (getting them is slow with the current implementations)
+#
+# XXX make mt-safe
+#-----------------------------------------------------------
+
+class cache:
+    proplist = {}
+    info = {}
+    entries = {}
+    prop = {}
+
+class RepoEntry:
+    def __init__(self, url, rev, timestamp):
+        self.url = url
+        self.rev = rev
+        self.timestamp = timestamp
+
+    def __str__(self):
+        return "repo: %s;%s  %s" %(self.url, self.rev, self.timestamp)
+
+class RepoCache:
+    """ The Repocache manages discovered repository paths
+    and their revisions.  If inside a timeout the cache
+    will even return the revision of the root.
+    """
+    timeout = 20 # seconds after which we forget that we know the last revision
+
+    def __init__(self):
+        self.repos = []
+
+    def clear(self):
+        self.repos = []
+
+    def put(self, url, rev, timestamp=None):
+        if rev is None:
+            return
+        if timestamp is None:
+            timestamp = time.time()
+
+        for entry in self.repos:
+            if url == entry.url:
+                entry.timestamp = timestamp
+                entry.rev = rev
+                #print "set repo", entry
+                break
+        else:
+            entry = RepoEntry(url, rev, timestamp)
+            self.repos.append(entry)
+            #print "appended repo", entry
+
+    def get(self, url):
+        now = time.time()
+        for entry in self.repos:
+            if url.startswith(entry.url):
+                if now < entry.timestamp + self.timeout:
+                    #print "returning immediate Etrny", entry
+                    return entry.url, entry.rev
+                return entry.url, -1
+        return url, -1
+
+repositories = RepoCache()
+
+
+# svn support code 
+
+ALLOWED_CHARS = "_ -/\\=$.~+" #add characters as necessary when tested
+if sys.platform == "win32":
+    ALLOWED_CHARS += ":"
+ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
+    
+def _getsvnversion(ver=[]):
+    try:
+        return ver[0]
+    except IndexError:
+        v = py.process.cmdexec("svn -q --version")
+        v.strip()
+        v = '.'.join(v.split('.')[:2])
+        ver.append(v)
+        return v
+
+def _escape_helper(text):
+    text = str(text)
+    if py.std.sys.platform != 'win32':
+        text = str(text).replace('$', '\\$')
+    return text
+
+def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS):
+    for c in str(text):
+        if c.isalnum():
+            continue
+        if c in allowed_chars:
+            continue
+        return True
+    return False
+
+def checkbadchars(url):
+    # (hpk) not quite sure about the exact purpose, guido w.? 
+    proto, uri = url.split("://", 1)
+    if proto != "file":
+        host, uripath = uri.split('/', 1)
+        # only check for bad chars in the non-protocol parts
+        if (_check_for_bad_chars(host, ALLOWED_CHARS_HOST) \
+            or _check_for_bad_chars(uripath, ALLOWED_CHARS)):
+            raise ValueError("bad char in %r" % (url, ))
+            
+
+#_______________________________________________________________
+
+class SvnPathBase(common.PathBase):
+    """ Base implementation for SvnPath implementations. """
+    sep = '/'
+
+    def _geturl(self):
+        return self.strpath
+    url = property(_geturl, None, None, "url of this svn-path.")
+
+    def __str__(self):
+        """ return a string representation (including rev-number) """
+        return self.strpath
+
+    def __hash__(self):
+        return hash(self.strpath)
+
+    def new(self, **kw):
+        """ create a modified version of this path. A 'rev' argument
+            indicates a new revision.
+            the following keyword arguments modify various path parts:
+
+              http://host.com/repo/path/file.ext
+              |-----------------------|          dirname
+                                        |------| basename
+                                        |--|     purebasename
+                                            |--| ext
+        """
+        obj = object.__new__(self.__class__)
+        obj.rev = kw.get('rev', self.rev)
+        obj.auth = kw.get('auth', self.auth)
+        dirname, basename, purebasename, ext = self._getbyspec(
+             "dirname,basename,purebasename,ext")
+        if 'basename' in kw:
+            if 'purebasename' in kw or 'ext' in kw:
+                raise ValueError("invalid specification %r" % kw)
+        else:
+            pb = kw.setdefault('purebasename', purebasename)
+            ext = kw.setdefault('ext', ext)
+            if ext and not ext.startswith('.'):
+                ext = '.' + ext
+            kw['basename'] = pb + ext
+
+        kw.setdefault('dirname', dirname)
+        kw.setdefault('sep', self.sep)
+        if kw['basename']:
+            obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw
+        else:
+            obj.strpath = "%(dirname)s" % kw
+        return obj
+
+    def _getbyspec(self, spec):
+        """ get specified parts of the path.  'arg' is a string
+            with comma separated path parts. The parts are returned
+            in exactly the order of the specification.
+
+            you may specify the following parts:
+
+            http://host.com/repo/path/file.ext
+            |-----------------------|          dirname
+                                      |------| basename
+                                      |--|     purebasename
+                                          |--| ext
+        """
+        res = []
+        parts = self.strpath.split(self.sep)
+        for name in spec.split(','):
+            name = name.strip()
+            if name == 'dirname':
+                res.append(self.sep.join(parts[:-1]))
+            elif name == 'basename':
+                res.append(parts[-1])
+            else:
+                basename = parts[-1]
+                i = basename.rfind('.')
+                if i == -1:
+                    purebasename, ext = basename, ''
+                else:
+                    purebasename, ext = basename[:i], basename[i:]
+                if name == 'purebasename':
+                    res.append(purebasename)
+                elif name == 'ext':
+                    res.append(ext)
+                else:
+                    raise NameError("Don't know part %r" % name)
+        return res
+
+    def __eq__(self, other):
+        """ return true if path and rev attributes each match """
+        return (str(self) == str(other) and
+               (self.rev == other.rev or self.rev == other.rev))
+
+    def __ne__(self, other):
+        return not self == other
+
+    def join(self, *args):
+        """ return a new Path (with the same revision) which is composed
+            of the self Path followed by 'args' path components.
+        """
+        if not args:
+            return self
+
+        args = tuple([arg.strip(self.sep) for arg in args])
+        parts = (self.strpath, ) + args
+        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
+        return newpath
+
+    def propget(self, name):
+        """ return the content of the given property. """
+        value = self._propget(name)
+        return value
+
+    def proplist(self):
+        """ list all property names. """
+        content = self._proplist()
+        return content
+
+    def info(self):
+        """ return an Info structure with svn-provided information. """
+        parent = self.dirpath()
+        nameinfo_seq = parent._listdir_nameinfo()
+        bn = self.basename
+        for name, info in nameinfo_seq:
+            if name == bn:
+                return info
+        raise py.error.ENOENT(self)
+
+    def size(self):
+        """ Return the size of the file content of the Path. """
+        return self.info().size
+
+    def mtime(self):
+        """ Return the last modification time of the file. """
+        return self.info().mtime
+
+    # shared help methods
+
+    def _escape(self, cmd):
+        return _escape_helper(cmd)
+
+
+    #def _childmaxrev(self):
+    #    """ return maximum revision number of childs (or self.rev if no childs) """
+    #    rev = self.rev
+    #    for name, info in self._listdir_nameinfo():
+    #        rev = max(rev, info.created_rev)
+    #    return rev
+
+    #def _getlatestrevision(self):
+    #    """ return latest repo-revision for this path. """
+    #    url = self.strpath
+    #    path = self.__class__(url, None)
+    #
+    #    # we need a long walk to find the root-repo and revision
+    #    while 1:
+    #        try:
+    #            rev = max(rev, path._childmaxrev())
+    #            previous = path
+    #            path = path.dirpath()
+    #        except (IOError, process.cmdexec.Error):
+    #            break
+    #    if rev is None:
+    #        raise IOError, "could not determine newest repo revision for %s" % self
+    #    return rev
+
+    class Checkers(common.Checkers):
+        def dir(self):
+            try:
+                return self.path.info().kind == 'dir'
+            except py.error.Error:
+                return self._listdirworks()
+
+        def _listdirworks(self):
+            try:
+                self.path.listdir()
+            except py.error.ENOENT:
+                return False
+            else:
+                return True
+
+        def file(self):
+            try:
+                return self.path.info().kind == 'file'
+            except py.error.ENOENT:
+                return False
+
+        def exists(self):
+            try:
+                return self.path.info()
+            except py.error.ENOENT:
+                return self._listdirworks()
+
+def parse_apr_time(timestr):
+    i = timestr.rfind('.')
+    if i == -1:
+        raise ValueError("could not parse %s" % timestr)
+    timestr = timestr[:i]
+    parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
+    return time.mktime(parsedtime)
+
+class PropListDict(dict):
+    """ a Dictionary which fetches values (InfoSvnCommand instances) lazily"""
+    def __init__(self, path, keynames):
+        dict.__init__(self, [(x, None) for x in keynames])
+        self.path = path
+
+    def __getitem__(self, key):
+        value = dict.__getitem__(self, key)
+        if value is None:
+            value = self.path.propget(key)
+            dict.__setitem__(self, key, value)
+        return value
+
+def fixlocale():
+    if sys.platform != 'win32':
+        return 'LC_ALL=C '
+    return ''
+
+# some nasty chunk of code to solve path and url conversion and quoting issues
+ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ')
+if os.sep in ILLEGAL_CHARS:
+    ILLEGAL_CHARS.remove(os.sep)
+ISWINDOWS = sys.platform == 'win32'
+_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I)
+def _check_path(path):
+    illegal = ILLEGAL_CHARS[:]
+    sp = path.strpath
+    if ISWINDOWS:
+        illegal.remove(':')
+        if not _reg_allow_disk.match(sp):
+            raise ValueError('path may not contain a colon (:)')
+    for char in sp:
+        if char not in string.printable or char in illegal:
+            raise ValueError('illegal character %r in path' % (char,))
+
+def path_to_fspath(path, addat=True):
+    _check_path(path)
+    sp = path.strpath
+    if addat and path.rev != -1:
+        sp = '%s@%s' % (sp, path.rev)
+    elif addat:
+        sp = '%s at HEAD' % (sp,)
+    return sp
+    
+def url_from_path(path):
+    fspath = path_to_fspath(path, False)
+    quote = py.std.urllib.quote
+    if ISWINDOWS:
+        match = _reg_allow_disk.match(fspath)
+        fspath = fspath.replace('\\', '/')
+        if match.group(1):
+            fspath = '/%s%s' % (match.group(1).replace('\\', '/'),
+                                quote(fspath[len(match.group(1)):]))
+        else:
+            fspath = quote(fspath)
+    else:
+        fspath = quote(fspath)
+    if path.rev != -1:
+        fspath = '%s@%s' % (fspath, path.rev)
+    else:
+        fspath = '%s at HEAD' % (fspath,)
+    return 'file://%s' % (fspath,)
+
+class SvnAuth(object):
+    """ container for auth information for Subversion """
+    def __init__(self, username, password, cache_auth=True, interactive=True):
+        self.username = username
+        self.password = password
+        self.cache_auth = cache_auth
+        self.interactive = interactive
+
+    def makecmdoptions(self):
+        uname = self.username.replace('"', '\\"')
+        passwd = self.password.replace('"', '\\"')
+        ret = []
+        if uname:
+            ret.append('--username="%s"' % (uname,))
+        if passwd:
+            ret.append('--password="%s"' % (passwd,))
+        if not self.cache_auth:
+            ret.append('--no-auth-cache')
+        if not self.interactive:
+            ret.append('--non-interactive')
+        return ' '.join(ret)
+
+    def __str__(self):
+        return "<SvnAuth username=%s ...>" %(self.username,)
+
+rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)')
+
+class SvnWCCommandPath(common.PathBase):
+    """ path implementation offering access/modification to svn working copies.
+        It has methods similar to the functions in os.path and similar to the
+        commands of the svn client.
+    """
+    sep = os.sep
+
+    def __new__(cls, wcpath=None, auth=None):
+        self = object.__new__(cls)
+        if isinstance(wcpath, cls):
+            if wcpath.__class__ == cls:
+                return wcpath
+            wcpath = wcpath.localpath
+        if _check_for_bad_chars(str(wcpath),
+                                          ALLOWED_CHARS):
+            raise ValueError("bad char in wcpath %s" % (wcpath, ))
+        self.localpath = py.path.local(wcpath)
+        self.auth = auth
+        return self
+
+    strpath = property(lambda x: str(x.localpath), None, None, "string path")
+
+    def __eq__(self, other):
+        return self.localpath == getattr(other, 'localpath', None)
+
+    def _geturl(self):
+        if getattr(self, '_url', None) is None:
+            info = self.info()
+            self._url = info.url #SvnPath(info.url, info.rev)
+        assert isinstance(self._url, py.builtin._basestring)
+        return self._url
+
+    url = property(_geturl, None, None, "url of this WC item")
+
+    def _escape(self, cmd):
+        return _escape_helper(cmd)
+
+    def dump(self, obj):
+        """ pickle object into path location"""
+        return self.localpath.dump(obj)
+
+    def svnurl(self):
+        """ return current SvnPath for this WC-item. """
+        info = self.info()
+        return py.path.svnurl(info.url)
+
+    def __repr__(self):
+        return "svnwc(%r)" % (self.strpath) # , self._url)
+
+    def __str__(self):
+        return str(self.localpath)
+
+    def _makeauthoptions(self):
+        if self.auth is None:
+            return ''
+        return self.auth.makecmdoptions()
+
+    def _authsvn(self, cmd, args=None):
+        args = args and list(args) or []
+        args.append(self._makeauthoptions())
+        return self._svn(cmd, *args)
+        
+    def _svn(self, cmd, *args):
+        l = ['svn %s' % cmd]
+        args = [self._escape(item) for item in args]
+        l.extend(args)
+        l.append('"%s"' % self._escape(self.strpath))
+        # try fixing the locale because we can't otherwise parse
+        string = fixlocale() + " ".join(l)
+        try:
+            try:
+                key = 'LC_MESSAGES'
+                hold = os.environ.get(key)
+                os.environ[key] = 'C'
+                out = py.process.cmdexec(string)
+            finally:
+                if hold:
+                    os.environ[key] = hold
+                else:
+                    del os.environ[key]
+        except py.process.cmdexec.Error:
+            e = sys.exc_info()[1]
+            strerr = e.err.lower()
+            if strerr.find('file not found') != -1: 
+                raise py.error.ENOENT(self) 
+            if (strerr.find('file exists') != -1 or 
+                strerr.find('file already exists') != -1 or
+                strerr.find("can't create directory") != -1):
+                raise py.error.EEXIST(self)
+            raise
+        return out
+
+    def switch(self, url):
+        """ switch to given URL. """
+        self._authsvn('switch', [url])
+
+    def checkout(self, url=None, rev=None):
+        """ checkout from url to local wcpath. """
+        args = []
+        if url is None:
+            url = self.url
+        if rev is None or rev == -1:
+            if (py.std.sys.platform != 'win32' and
+                    _getsvnversion() == '1.3'):
+                url += "@HEAD" 
+        else:
+            if _getsvnversion() == '1.3':
+                url += "@%d" % rev
+            else:
+                args.append('-r' + str(rev))
+        args.append(url)
+        self._authsvn('co', args)
+
+    def update(self, rev='HEAD'):
+        """ update working copy item to given revision. (None -> HEAD). """
+        self._authsvn('up', ['-r', rev, "--non-interactive"],)
+
+    def write(self, content, mode='w'):
+        """ write content into local filesystem wc. """
+        self.localpath.write(content, mode)
+
+    def dirpath(self, *args):
+        """ return the directory Path of the current Path. """
+        return self.__class__(self.localpath.dirpath(*args), auth=self.auth)
+
+    def _ensuredirs(self):
+        parent = self.dirpath()
+        if parent.check(dir=0):
+            parent._ensuredirs()
+        if self.check(dir=0):
+            self.mkdir()
+        return self
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). if you specify a keyword argument 'directory=True'
+            then the path is forced  to be a directory path.
+        """
+        p = self.join(*args)
+        if p.check():
+            if p.check(versioned=False):
+                p.add()
+            return p 
+        if kwargs.get('dir', 0):
+            return p._ensuredirs()
+        parent = p.dirpath()
+        parent._ensuredirs()
+        p.write("")
+        p.add()
+        return p
+
+    def mkdir(self, *args):
+        """ create & return the directory joined with args. """
+        if args:
+            return self.join(*args).mkdir()
+        else:
+            self._svn('mkdir')
+            return self
+
+    def add(self):
+        """ add ourself to svn """
+        self._svn('add')
+
+    def remove(self, rec=1, force=1):
+        """ remove a file or a directory tree. 'rec'ursive is
+            ignored and considered always true (because of
+            underlying svn semantics.
+        """
+        assert rec, "svn cannot remove non-recursively"
+        if not self.check(versioned=True):
+            # not added to svn (anymore?), just remove
+            py.path.local(self).remove()
+            return
+        flags = []
+        if force:
+            flags.append('--force')
+        self._svn('remove', *flags)
+
+    def copy(self, target):
+        """ copy path to target."""
+        py.process.cmdexec("svn copy %s %s" %(str(self), str(target)))
+
+    def rename(self, target):
+        """ rename this path to target. """
+        py.process.cmdexec("svn move --force %s %s" %(str(self), str(target)))
+
+    def lock(self):
+        """ set a lock (exclusive) on the resource """
+        out = self._authsvn('lock').strip()
+        if not out:
+            # warning or error, raise exception
+            raise Exception(out[4:])
+    
+    def unlock(self):
+        """ unset a previously set lock """
+        out = self._authsvn('unlock').strip()
+        if out.startswith('svn:'):
+            # warning or error, raise exception
+            raise Exception(out[4:])
+
+    def cleanup(self):
+        """ remove any locks from the resource """
+        # XXX should be fixed properly!!!
+        try:
+            self.unlock()
+        except:
+            pass
+
+    def status(self, updates=0, rec=0, externals=0):
+        """ return (collective) Status object for this file. """
+        # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1
+        #             2201     2192        jum   test
+        # XXX
+        if externals:
+            raise ValueError("XXX cannot perform status() "
+                             "on external items yet")
+        else:
+            #1.2 supports: externals = '--ignore-externals'
+            externals = ''
+        if rec:
+            rec= ''
+        else:
+            rec = '--non-recursive'
+
+        # XXX does not work on all subversion versions
+        #if not externals: 
+        #    externals = '--ignore-externals' 
+
+        if updates:
+            updates = '-u'
+        else:
+            updates = ''
+
+        try:
+            cmd = 'status -v --xml --no-ignore %s %s %s' % (
+                    updates, rec, externals)
+            out = self._authsvn(cmd)
+        except py.process.cmdexec.Error:
+            cmd = 'status -v --no-ignore %s %s %s' % (
+                    updates, rec, externals)
+            out = self._authsvn(cmd)
+            rootstatus = WCStatus(self).fromstring(out, self)
+        else:
+            rootstatus = XMLWCStatus(self).fromstring(out, self)
+        return rootstatus
+
+    def diff(self, rev=None):
+        """ return a diff of the current path against revision rev (defaulting
+            to the last one).
+        """
+        args = []
+        if rev is not None:
+            args.append("-r %d" % rev)
+        out = self._authsvn('diff', args)
+        return out
+
+    def blame(self):
+        """ return a list of tuples of three elements:
+            (revision, commiter, line)
+        """
+        out = self._svn('blame')
+        result = []
+        blamelines = out.splitlines()
+        reallines = py.path.svnurl(self.url).readlines()
+        for i, (blameline, line) in enumerate(
+                zip(blamelines, reallines)):
+            m = rex_blame.match(blameline)
+            if not m:
+                raise ValueError("output line %r of svn blame does not match "
+                                 "expected format" % (line, ))
+            rev, name, _ = m.groups()
+            result.append((int(rev), name, line))
+        return result
+
+    _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
+    def commit(self, msg='', rec=1):
+        """ commit with support for non-recursive commits """
+        # XXX i guess escaping should be done better here?!?
+        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
+        if not rec:
+            cmd += ' -N'
+        out = self._authsvn(cmd)
+        try:
+            del cache.info[self]
+        except KeyError:
+            pass
+        if out: 
+            m = self._rex_commit.match(out)
+            return int(m.group(1))
+
+    def propset(self, name, value, *args):
+        """ set property name to value on this path. """
+        d = py.path.local.mkdtemp() 
+        try: 
+            p = d.join('value') 
+            p.write(value) 
+            self._svn('propset', name, '--file', str(p), *args)
+        finally: 
+            d.remove() 
+
+    def propget(self, name):
+        """ get property name on this path. """
+        res = self._svn('propget', name)
+        return res[:-1] # strip trailing newline
+
+    def propdel(self, name):
+        """ delete property name on this path. """
+        res = self._svn('propdel', name)
+        return res[:-1] # strip trailing newline
+
+    def proplist(self, rec=0):
+        """ return a mapping of property names to property values.
+If rec is True, then return a dictionary mapping sub-paths to such mappings.
+"""
+        if rec:
+            res = self._svn('proplist -R')
+            return make_recursive_propdict(self, res)
+        else:
+            res = self._svn('proplist')
+            lines = res.split('\n')
+            lines = [x.strip() for x in lines[1:]]
+            return PropListDict(self, lines)
+
+    def revert(self, rec=0):
+        """ revert the local changes of this path. if rec is True, do so
+recursively. """
+        if rec:
+            result = self._svn('revert -R')
+        else:
+            result = self._svn('revert')
+        return result
+
+    def new(self, **kw):
+        """ create a modified version of this path. A 'rev' argument
+            indicates a new revision.
+            the following keyword arguments modify various path parts:
+
+              http://host.com/repo/path/file.ext
+              |-----------------------|          dirname
+                                        |------| basename
+                                        |--|     purebasename
+                                            |--| ext
+        """
+        if kw:
+            localpath = self.localpath.new(**kw)
+        else:
+            localpath = self.localpath
+        return self.__class__(localpath, auth=self.auth)
+
+    def join(self, *args, **kwargs):
+        """ return a new Path (with the same revision) which is composed
+            of the self Path followed by 'args' path components.
+        """
+        if not args:
+            return self
+        localpath = self.localpath.join(*args, **kwargs)
+        return self.__class__(localpath, auth=self.auth)
+
+    def info(self, usecache=1):
+        """ return an Info structure with svn-provided information. """
+        info = usecache and cache.info.get(self)
+        if not info:
+            try:
+                output = self._svn('info')
+            except py.process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('Path is not a working copy directory') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find("is not under version control") != -1:
+                    raise py.error.ENOENT(self, e.err)
+                raise
+            # XXX SVN 1.3 has output on stderr instead of stdout (while it does
+            # return 0!), so a bit nasty, but we assume no output is output
+            # to stderr...
+            if (output.strip() == '' or 
+                    output.lower().find('not a versioned resource') != -1):
+                raise py.error.ENOENT(self, output)
+            info = InfoSvnWCCommand(output)
+
+            # Can't reliably compare on Windows without access to win32api
+            if py.std.sys.platform != 'win32': 
+                if info.path != self.localpath: 
+                    raise py.error.ENOENT(self, "not a versioned resource:" + 
+                            " %s != %s" % (info.path, self.localpath)) 
+            cache.info[self] = info
+        self.rev = info.rev
+        return info
+
+    def listdir(self, fil=None, sort=None):
+        """ return a sequence of Paths.
+
+        listdir will return either a tuple or a list of paths
+        depending on implementation choices.
+        """
+        if isinstance(fil, str):
+            fil = common.FNMatcher(fil)
+        # XXX unify argument naming with LocalPath.listdir
+        def notsvn(path):
+            return path.basename != '.svn' 
+
+        paths = [self.__class__(p, auth=self.auth) 
+                    for p in self.localpath.listdir()
+                        if notsvn(p) and (not fil or fil(p))]
+        self._sortlist(paths, sort)
+        return paths
+
+    def open(self, mode='r'):
+        """ return an opened file with the given mode. """
+        return open(self.strpath, mode)
+
+    def _getbyspec(self, spec):
+        return self.localpath._getbyspec(spec)
+
+    class Checkers(py.path.local.Checkers):
+        def __init__(self, path):
+            self.svnwcpath = path
+            self.path = path.localpath
+        def versioned(self):
+            try:
+                s = self.svnwcpath.info()
+            except (py.error.ENOENT, py.error.EEXIST): 
+                return False 
+            except py.process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('is not a working copy')!=-1:
+                    return False
+                if e.err.lower().find('not a versioned resource') != -1:
+                    return False
+                raise
+            else:
+                return True 
+
+    def log(self, rev_start=None, rev_end=1, verbose=False):
+        """ return a list of LogEntry instances for this path.
+rev_start is the starting revision (defaulting to the first one).
+rev_end is the last revision (defaulting to HEAD).
+if verbose is True, then the LogEntry instances also know which files changed.
+"""
+        assert self.check()   # make it simpler for the pipe
+        rev_start = rev_start is None and "HEAD" or rev_start
+        rev_end = rev_end is None and "HEAD" or rev_end
+        if rev_start == "HEAD" and rev_end == 1:
+                rev_opt = ""
+        else:
+            rev_opt = "-r %s:%s" % (rev_start, rev_end)
+        verbose_opt = verbose and "-v" or ""
+        locale_env = fixlocale()
+        # some blather on stderr
+        auth_opt = self._makeauthoptions()
+        #stdin, stdout, stderr  = os.popen3(locale_env +
+        #                                   'svn log --xml %s %s %s "%s"' % (
+        #                                    rev_opt, verbose_opt, auth_opt,
+        #                                    self.strpath))
+        cmd = locale_env + 'svn log --xml %s %s %s "%s"' % (
+            rev_opt, verbose_opt, auth_opt, self.strpath)
+
+        popen = subprocess.Popen(cmd, 
+                    stdout=subprocess.PIPE, 
+                    stderr=subprocess.PIPE,  
+                    shell=True,
+        )
+        stdout, stderr = popen.communicate()
+        stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
+        minidom,ExpatError = importxml()
+        try:
+            tree = minidom.parseString(stdout)
+        except ExpatError:
+            raise ValueError('no such revision')
+        result = []
+        for logentry in filter(None, tree.firstChild.childNodes):
+            if logentry.nodeType == logentry.ELEMENT_NODE:
+                result.append(LogEntry(logentry))
+        return result
+
+    def size(self):
+        """ Return the size of the file content of the Path. """
+        return self.info().size
+
+    def mtime(self):
+        """ Return the last modification time of the file. """
+        return self.info().mtime
+
+    def __hash__(self):
+        return hash((self.strpath, self.__class__, self.auth))
+
+
+class WCStatus:
+    attrnames = ('modified','added', 'conflict', 'unchanged', 'external',
+                'deleted', 'prop_modified', 'unknown', 'update_available',
+                'incomplete', 'kindmismatch', 'ignored', 'locked', 'replaced'
+                )
+
+    def __init__(self, wcpath, rev=None, modrev=None, author=None):
+        self.wcpath = wcpath
+        self.rev = rev
+        self.modrev = modrev
+        self.author = author
+
+        for name in self.attrnames:
+            setattr(self, name, [])
+
+    def allpath(self, sort=True, **kw):
+        d = {}
+        for name in self.attrnames:
+            if name not in kw or kw[name]:
+                for path in getattr(self, name):
+                    d[path] = 1
+        l = d.keys()
+        if sort:
+            l.sort()
+        return l
+
+    # XXX a bit scary to assume there's always 2 spaces between username and
+    # path, however with win32 allowing spaces in user names there doesn't
+    # seem to be a more solid approach :(
+    _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
+
+    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
+        """ return a new WCStatus object from data 's'
+        """
+        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
+        update_rev = None
+        for line in data.split('\n'):
+            if not line.strip():
+                continue
+            #print "processing %r" % line
+            flags, rest = line[:8], line[8:]
+            # first column
+            c0,c1,c2,c3,c4,c5,x6,c7 = flags
+            #if '*' in line:
+            #    print "flags", repr(flags), "rest", repr(rest)
+
+            if c0 in '?XI':
+                fn = line.split(None, 1)[1]
+                if c0 == '?':
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.unknown.append(wcpath)
+                elif c0 == 'X':
+                    wcpath = rootwcpath.__class__(
+                        rootwcpath.localpath.join(fn, abs=1),
+                        auth=rootwcpath.auth)
+                    rootstatus.external.append(wcpath)
+                elif c0 == 'I':
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.ignored.append(wcpath)
+
+                continue
+
+            #elif c0 in '~!' or c4 == 'S':
+            #    raise NotImplementedError("received flag %r" % c0)
+
+            m = WCStatus._rex_status.match(rest)
+            if not m:
+                if c7 == '*':
+                    fn = rest.strip()
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.update_available.append(wcpath)
+                    continue
+                if line.lower().find('against revision:')!=-1:
+                    update_rev = int(rest.split(':')[1].strip())
+                    continue
+                if line.lower().find('status on external') > -1:
+                    # XXX not sure what to do here... perhaps we want to
+                    # store some state instead of just continuing, as right
+                    # now it makes the top-level external get added twice
+                    # (once as external, once as 'normal' unchanged item)
+                    # because of the way SVN presents external items
+                    continue
+                # keep trying
+                raise ValueError("could not parse line %r" % line)
+            else:
+                rev, modrev, author, fn = m.groups()
+            wcpath = rootwcpath.join(fn, abs=1)
+            #assert wcpath.check()
+            if c0 == 'M':
+                assert wcpath.check(file=1), "didn't expect a directory with changed content here"
+                rootstatus.modified.append(wcpath)
+            elif c0 == 'A' or c3 == '+' :
+                rootstatus.added.append(wcpath)
+            elif c0 == 'D':
+                rootstatus.deleted.append(wcpath)
+            elif c0 == 'C':
+                rootstatus.conflict.append(wcpath)
+            elif c0 == '~':
+                rootstatus.kindmismatch.append(wcpath)
+            elif c0 == '!':
+                rootstatus.incomplete.append(wcpath)
+            elif c0 == 'R':
+                rootstatus.replaced.append(wcpath)
+            elif not c0.strip():
+                rootstatus.unchanged.append(wcpath)
+            else:
+                raise NotImplementedError("received flag %r" % c0)
+
+            if c1 == 'M':
+                rootstatus.prop_modified.append(wcpath)
+            # XXX do we cover all client versions here?
+            if c2 == 'L' or c5 == 'K':
+                rootstatus.locked.append(wcpath)
+            if c7 == '*':
+                rootstatus.update_available.append(wcpath)
+
+            if wcpath == rootwcpath:
+                rootstatus.rev = rev
+                rootstatus.modrev = modrev
+                rootstatus.author = author
+                if update_rev:
+                    rootstatus.update_rev = update_rev
+                continue
+        return rootstatus
+    fromstring = staticmethod(fromstring)
+
+class XMLWCStatus(WCStatus):
+    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
+        """ parse 'data' (XML string as outputted by svn st) into a status obj
+        """
+        # XXX for externals, the path is shown twice: once
+        # with external information, and once with full info as if
+        # the item was a normal non-external... the current way of
+        # dealing with this issue is by ignoring it - this does make
+        # externals appear as external items as well as 'normal',
+        # unchanged ones in the status object so this is far from ideal
+        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
+        update_rev = None
+        minidom, ExpatError = importxml()
+        try:
+            doc = minidom.parseString(data)
+        except ExpatError:
+            e = sys.exc_info()[1]
+            raise ValueError(str(e))
+        urevels = doc.getElementsByTagName('against')
+        if urevels:
+            rootstatus.update_rev = urevels[-1].getAttribute('revision')
+        for entryel in doc.getElementsByTagName('entry'):
+            path = entryel.getAttribute('path')
+            statusel = entryel.getElementsByTagName('wc-status')[0]
+            itemstatus = statusel.getAttribute('item')
+
+            if itemstatus == 'unversioned':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.unknown.append(wcpath)
+                continue
+            elif itemstatus == 'external':
+                wcpath = rootwcpath.__class__(
+                    rootwcpath.localpath.join(path, abs=1),
+                    auth=rootwcpath.auth)
+                rootstatus.external.append(wcpath)
+                continue
+            elif itemstatus == 'ignored':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.ignored.append(wcpath)
+                continue
+            elif itemstatus == 'incomplete':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.incomplete.append(wcpath)
+                continue
+
+            rev = statusel.getAttribute('revision')
+            if itemstatus == 'added' or itemstatus == 'none':
+                rev = '0'
+                modrev = '?'
+                author = '?'
+                date = ''
+            else:
+                #print entryel.toxml()
+                commitel = entryel.getElementsByTagName('commit')[0]
+                if commitel:
+                    modrev = commitel.getAttribute('revision')
+                    author = ''
+                    author_els = commitel.getElementsByTagName('author')
+                    if author_els:
+                        for c in author_els[0].childNodes:
+                            author += c.nodeValue
+                    date = ''
+                    for c in commitel.getElementsByTagName('date')[0]\
+                            .childNodes:
+                        date += c.nodeValue
+
+            wcpath = rootwcpath.join(path, abs=1)
+
+            assert itemstatus != 'modified' or wcpath.check(file=1), (
+                'did\'t expect a directory with changed content here')
+
+            itemattrname = {
+                'normal': 'unchanged',
+                'unversioned': 'unknown',
+                'conflicted': 'conflict',
+                'none': 'added',
+            }.get(itemstatus, itemstatus)
+
+            attr = getattr(rootstatus, itemattrname)
+            attr.append(wcpath)
+
+            propsstatus = statusel.getAttribute('props')
+            if propsstatus not in ('none', 'normal'):
+                rootstatus.prop_modified.append(wcpath)
+
+            if wcpath == rootwcpath:
+                rootstatus.rev = rev
+                rootstatus.modrev = modrev
+                rootstatus.author = author
+                rootstatus.date = date
+
+            # handle repos-status element (remote info)
+            rstatusels = entryel.getElementsByTagName('repos-status')
+            if rstatusels:
+                rstatusel = rstatusels[0]
+                ritemstatus = rstatusel.getAttribute('item')
+                if ritemstatus in ('added', 'modified'):
+                    rootstatus.update_available.append(wcpath)
+
+            lockels = entryel.getElementsByTagName('lock')
+            if len(lockels):
+                rootstatus.locked.append(wcpath)
+
+        return rootstatus
+    fromstring = staticmethod(fromstring)
+
+class InfoSvnWCCommand:
+    def __init__(self, output):
+        # Path: test
+        # URL: http://codespeak.net/svn/std.path/trunk/dist/std.path/test
+        # Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
+        # Revision: 2151
+        # Node Kind: directory
+        # Schedule: normal
+        # Last Changed Author: hpk
+        # Last Changed Rev: 2100
+        # Last Changed Date: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
+        # Properties Last Updated: 2003-11-03 14:47:48 +0100 (Mon, 03 Nov 2003)
+
+        d = {}
+        for line in output.split('\n'):
+            if not line.strip():
+                continue
+            key, value = line.split(':', 1)
+            key = key.lower().replace(' ', '')
+            value = value.strip()
+            d[key] = value
+        try:
+            self.url = d['url']
+        except KeyError:
+            raise  ValueError("Not a versioned resource")
+            #raise ValueError, "Not a versioned resource %r" % path
+        self.kind = d['nodekind'] == 'directory' and 'dir' or d['nodekind']
+        self.rev = int(d['revision'])
+        self.path = py.path.local(d['path'])
+        self.size = self.path.size()
+        if 'lastchangedrev' in d:
+            self.created_rev = int(d['lastchangedrev'])
+        if 'lastchangedauthor' in d:
+            self.last_author = d['lastchangedauthor']
+        if 'lastchangeddate' in d:
+            self.mtime = parse_wcinfotime(d['lastchangeddate'])
+            self.time = self.mtime * 1000000
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+def parse_wcinfotime(timestr):
+    """ Returns seconds since epoch, UTC. """
+    # example: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
+    m = re.match(r'(\d+-\d+-\d+ \d+:\d+:\d+) ([+-]\d+) .*', timestr)
+    if not m:
+        raise ValueError("timestring %r does not match" % timestr)
+    timestr, timezone = m.groups()
+    # do not handle timezone specially, return value should be UTC
+    parsedtime = time.strptime(timestr, "%Y-%m-%d %H:%M:%S")
+    return calendar.timegm(parsedtime)
+
+def make_recursive_propdict(wcroot,
+                            output,
+                            rex = re.compile("Properties on '(.*)':")):
+    """ Return a dictionary of path->PropListDict mappings. """
+    lines = [x for x in output.split('\n') if x]
+    pdict = {}
+    while lines:
+        line = lines.pop(0)
+        m = rex.match(line)
+        if not m:
+            raise ValueError("could not parse propget-line: %r" % line)
+        path = m.groups()[0]
+        wcpath = wcroot.join(path, abs=1)
+        propnames = []
+        while lines and lines[0].startswith('  '):
+            propname = lines.pop(0).strip()
+            propnames.append(propname)
+        assert propnames, "must have found properties!"
+        pdict[wcpath] = PropListDict(wcpath, propnames)
+    return pdict
+
+
+def importxml(cache=[]):
+    if cache:
+        return cache
+    from xml.dom import minidom
+    from xml.parsers.expat import ExpatError
+    cache.extend([minidom, ExpatError])
+    return cache
+
+class LogEntry:
+    def __init__(self, logentry):
+        self.rev = int(logentry.getAttribute('revision'))
+        for lpart in filter(None, logentry.childNodes):
+            if lpart.nodeType == lpart.ELEMENT_NODE:
+                if lpart.nodeName == 'author':
+                    self.author = lpart.firstChild.nodeValue
+                elif lpart.nodeName == 'msg':
+                    if lpart.firstChild:
+                        self.msg = lpart.firstChild.nodeValue
+                    else:
+                        self.msg = ''
+                elif lpart.nodeName == 'date':
+                    #2003-07-29T20:05:11.598637Z
+                    timestr = lpart.firstChild.nodeValue
+                    self.date = parse_apr_time(timestr)
+                elif lpart.nodeName == 'paths':
+                    self.strpaths = []
+                    for ppart in filter(None, lpart.childNodes):
+                        if ppart.nodeType == ppart.ELEMENT_NODE:
+                            self.strpaths.append(PathEntry(ppart))
+    def __repr__(self):
+        return '<Logentry rev=%d author=%s date=%s>' % (
+            self.rev, self.author, self.date)
+
+

--- /dev/null
+++ b/_py/code/assertion.py
@@ -0,0 +1,75 @@
+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)
+
+
+if sys.version_info >= (2, 6):
+    from _py.code._assertionnew import interpret
+else:
+    from _py.code._assertionold import interpret
+
+
+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 = interpret(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"

--- /dev/null
+++ b/_py/log/warning.py
@@ -0,0 +1,70 @@
+import py, sys
+
+class Warning(DeprecationWarning):
+    def __init__(self, msg, path, lineno):
+        self.msg = msg
+        self.path = path
+        self.lineno = lineno 
+    def __repr__(self):
+        return "%s:%d: %s" %(self.path, self.lineno+1, self.msg)
+    def __str__(self):
+        return self.msg 
+
+def _apiwarn(startversion, msg, stacklevel=2, function=None):
+    # below is mostly COPIED from python2.4/warnings.py's def warn()
+    # Get context information
+    if stacklevel == "initpkg":
+        frame = sys._getframe(stacklevel == "initpkg" and 1 or stacklevel)
+        level = 2
+        while frame:
+            co = frame.f_code
+            if co.co_name == "__getattr__" and co.co_filename.find("initpkg") !=-1:
+                stacklevel = level 
+                break
+            level += 1
+            frame = frame.f_back
+        else:
+            stacklevel = 1
+    msg = "%s (since version %s)" %(msg, startversion)
+    warn(msg, stacklevel=stacklevel+1, function=function)
+
+def warn(msg, stacklevel=1, function=None):
+    if function is not None:
+        filename = py.std.inspect.getfile(function)
+        lineno = py.code.getrawcode(function).co_firstlineno
+    else:
+        try:
+            caller = sys._getframe(stacklevel)
+        except ValueError:
+            globals = sys.__dict__
+            lineno = 1
+        else:
+            globals = caller.f_globals
+            lineno = caller.f_lineno
+        if '__name__' in globals:
+            module = globals['__name__']
+        else:
+            module = "<string>"
+        filename = globals.get('__file__')
+    if filename:
+        fnl = filename.lower()
+        if fnl.endswith(".pyc") or fnl.endswith(".pyo"):
+            filename = filename[:-1]
+    else:
+        if module == "__main__":
+            try:
+                filename = sys.argv[0]
+            except AttributeError:
+                # embedded interpreters don't have sys.argv, see bug #839151
+                filename = '__main__'
+        if not filename:
+            filename = module
+    path = py.path.local(filename)
+    warning = Warning(msg, path, lineno)
+    py.std.warnings.warn_explicit(warning, category=Warning, 
+        filename=str(warning.path), 
+        lineno=warning.lineno,
+        registry=py.std.warnings.__dict__.setdefault(
+            "__warningsregistry__", {})
+    )
+

--- /dev/null
+++ b/_py/path/gateway/__init__.py
@@ -0,0 +1,1 @@
+#

--- /dev/null
+++ b/_py/io/__init__.py
@@ -0,0 +1,1 @@
+""" input/output helping """

--- /dev/null
+++ b/_py/path/common.py
@@ -0,0 +1,329 @@
+"""
+"""
+import os, sys
+import py
+
+class Checkers:
+    _depend_on_existence = 'exists', 'link', 'dir', 'file'
+
+    def __init__(self, path):
+        self.path = path
+
+    def dir(self):
+        raise NotImplementedError
+
+    def file(self):
+        raise NotImplementedError
+
+    def dotfile(self):
+        return self.path.basename.startswith('.')
+
+    def ext(self, arg):
+        if not arg.startswith('.'):
+            arg = '.' + arg
+        return self.path.ext == arg
+
+    def exists(self):
+        raise NotImplementedError
+
+    def basename(self, arg):
+        return self.path.basename == arg
+
+    def basestarts(self, arg):
+        return self.path.basename.startswith(arg)
+
+    def relto(self, arg):
+        return self.path.relto(arg)
+
+    def fnmatch(self, arg):
+        return FNMatcher(arg)(self.path)
+
+    def endswith(self, arg):
+        return str(self.path).endswith(arg)
+
+    def _evaluate(self, kw):
+        for name, value in kw.items():
+            invert = False
+            meth = None
+            try:
+                meth = getattr(self, name)
+            except AttributeError:
+                if name[:3] == 'not':
+                    invert = True
+                    try:
+                        meth = getattr(self, name[3:])
+                    except AttributeError:
+                        pass
+            if meth is None:
+                raise TypeError(
+                    "no %r checker available for %r" % (name, self.path))
+            try:
+                if py.code.getrawcode(meth).co_argcount > 1:
+                    if (not meth(value)) ^ invert:
+                        return False
+                else:
+                    if bool(value) ^ bool(meth()) ^ invert:
+                        return False
+            except (py.error.ENOENT, py.error.ENOTDIR):
+                for name in self._depend_on_existence:
+                    if name in kw:
+                        if kw.get(name):
+                            return False
+                    name = 'not' + name
+                    if name in kw:
+                        if not kw.get(name):
+                            return False
+        return True
+
+class NeverRaised(Exception): 
+    pass
+
+class PathBase(object):
+    """ shared implementation for filesystem path objects."""
+    Checkers = Checkers
+
+    def __div__(self, other):
+        return self.join(str(other))
+    __truediv__ = __div__ # py3k
+
+    def basename(self):
+        """ basename part of path. """
+        return self._getbyspec('basename')[0]
+    basename = property(basename, None, None, basename.__doc__)
+
+    def purebasename(self):
+        """ pure base name of the path."""
+        return self._getbyspec('purebasename')[0]
+    purebasename = property(purebasename, None, None, purebasename.__doc__)
+
+    def ext(self):
+        """ extension of the path (including the '.')."""
+        return self._getbyspec('ext')[0]
+    ext = property(ext, None, None, ext.__doc__)
+
+    def dirpath(self, *args, **kwargs):
+        """ return the directory Path of the current Path joined
+            with any given path arguments.
+        """
+        return self.new(basename='').join(*args, **kwargs)
+
+    def read(self, mode='r'):
+        """ read and return a bytestring from reading the path. """
+        if sys.version_info < (2,3):
+            for x in 'u', 'U':
+                if x in mode:
+                    mode = mode.replace(x, '')
+        f = self.open(mode)
+        try:
+            return f.read()
+        finally:
+            f.close()
+
+    def readlines(self, cr=1):
+        """ read and return a list of lines from the path. if cr is False, the
+newline will be removed from the end of each line. """
+        if not cr:
+            content = self.read('rU')
+            return content.split('\n')
+        else:
+            f = self.open('rU')
+            try:
+                return f.readlines()
+            finally:
+                f.close()
+
+    def load(self):
+        """ (deprecated) return object unpickled from self.read() """
+        f = self.open('rb')
+        try:
+            return py.error.checked_call(py.std.pickle.load, f)
+        finally:
+            f.close()
+
+    def move(self, target):
+        """ move this path to target. """
+        if target.relto(self):
+            raise py.error.EINVAL(target, 
+                "cannot move path into a subdirectory of itself")
+        try:
+            self.rename(target)
+        except py.error.EXDEV:  # invalid cross-device link
+            self.copy(target)
+            self.remove()
+
+    def __repr__(self):
+        """ return a string representation of this path. """
+        return repr(str(self))
+
+    def check(self, **kw):
+        """ check a path for existence, or query its properties
+
+            without arguments, this returns True if the path exists (on the
+            filesystem), False if not
+
+            with (keyword only) arguments, the object compares the value
+            of the argument with the value of a property with the same name
+            (if it has one, else it raises a TypeError)
+
+            when for example the keyword argument 'ext' is '.py', this will
+            return True if self.ext == '.py', False otherwise
+        """
+        if not kw:
+            kw = {'exists' : 1}
+        return self.Checkers(self)._evaluate(kw)
+
+    def relto(self, relpath):
+        """ return a string which is the relative part of the path
+        to the given 'relpath'. 
+        """
+        if not isinstance(relpath, (str, PathBase)): 
+            raise TypeError("%r: not a string or path object" %(relpath,))
+        strrelpath = str(relpath)
+        if strrelpath and strrelpath[-1] != self.sep:
+            strrelpath += self.sep
+        #assert strrelpath[-1] == self.sep
+        #assert strrelpath[-2] != self.sep
+        strself = str(self)
+        if sys.platform == "win32":
+            if os.path.normcase(strself).startswith(
+               os.path.normcase(strrelpath)):
+                return strself[len(strrelpath):]        
+        elif strself.startswith(strrelpath):
+            return strself[len(strrelpath):]
+        return ""
+
+    def bestrelpath(self, dest): 
+        """ return a string which is a relative path from self 
+            to dest such that self.join(bestrelpath) == dest and 
+            if not such path can be determined return dest. 
+        """ 
+        try:
+            base = self.common(dest)
+            if not base:  # can be the case on windows
+                return str(dest)
+            self2base = self.relto(base)
+            reldest = dest.relto(base)
+            if self2base:
+                n = self2base.count(self.sep) + 1
+            else:
+                n = 0
+            l = ['..'] * n
+            if reldest:
+                l.append(reldest)     
+            target = dest.sep.join(l)
+            return target 
+        except AttributeError:
+            return str(dest)
+
+
+    def parts(self, reverse=False):
+        """ return a root-first list of all ancestor directories
+            plus the path itself.
+        """
+        current = self
+        l = [self]
+        while 1:
+            last = current
+            current = current.dirpath()
+            if last == current:
+                break
+            l.insert(0, current)
+        if reverse:
+            l.reverse()
+        return l
+
+    def common(self, other):
+        """ return the common part shared with the other path
+            or None if there is no common part.
+        """
+        last = None
+        for x, y in zip(self.parts(), other.parts()):
+            if x != y:
+                return last
+            last = x
+        return last
+
+    def __add__(self, other):
+        """ return new path object with 'other' added to the basename"""
+        return self.new(basename=self.basename+str(other))
+
+    def __cmp__(self, other):
+        """ return sort value (-1, 0, +1). """
+        try:
+            return cmp(self.strpath, other.strpath)
+        except AttributeError:
+            return cmp(str(self), str(other)) # self.path, other.path)
+
+    def __lt__(self, other):
+        try:
+            return self.strpath < other.strpath 
+        except AttributeError:
+            return str(self) < str(other)
+
+    def visit(self, fil=None, rec=None, ignore=NeverRaised):
+        """ yields all paths below the current one
+
+            fil is a filter (glob pattern or callable), if not matching the
+            path will not be yielded, defaulting to None (everything is
+            returned)
+
+            rec is a filter (glob pattern or callable) that controls whether
+            a node is descended, defaulting to None
+
+            ignore is an Exception class that is ignoredwhen calling dirlist()
+            on any of the paths (by default, all exceptions are reported)
+        """
+        if isinstance(fil, str):
+            fil = FNMatcher(fil)
+        if rec: 
+            if isinstance(rec, str):
+                rec = fnmatch(fil)
+            elif not hasattr(rec, '__call__'):
+                rec = None
+        try:
+            entries = self.listdir()
+        except ignore:
+            return
+        dirs = [p for p in entries 
+                    if p.check(dir=1) and (rec is None or rec(p))]
+        for subdir in dirs:
+            for p in subdir.visit(fil=fil, rec=rec, ignore=ignore):
+                yield p
+        for p in entries:
+            if fil is None or fil(p):
+                yield p
+
+    def _sortlist(self, res, sort):
+        if sort:
+            if hasattr(sort, '__call__'):
+                res.sort(sort)
+            else:
+                res.sort()
+
+class FNMatcher:
+    def __init__(self, pattern):
+        self.pattern = pattern
+    def __call__(self, path):
+        """return true if the basename/fullname matches the glob-'pattern'.
+
+        *       matches everything
+        ?       matches any single character
+        [seq]   matches any character in seq
+        [!seq]  matches any char not in seq
+
+        if the pattern contains a path-separator then the full path
+        is used for pattern matching and a '*' is prepended to the
+        pattern.
+
+        if the pattern doesn't contain a path-separator the pattern
+        is only matched against the basename.
+        """
+        pattern = self.pattern
+        if pattern.find(path.sep) == -1:
+            name = path.basename
+        else:
+            name = str(path) # path.strpath # XXX svn?
+            pattern = '*' + path.sep + pattern
+        from fnmatch import fnmatch
+        return fnmatch(name, pattern)
+

--- /dev/null
+++ b/_py/builtin/__init__.py
@@ -0,0 +1,2 @@
+""" backports and additions of builtins """
+


--- /dev/null
+++ b/_py/compat/dep_optparse.py
@@ -0,0 +1,4 @@
+import py
+py.log._apiwarn("1.1", "py.compat.optparse deprecated, use standard library version.", stacklevel="initpkg")
+
+optparse = py.std.optparse 

--- /dev/null
+++ b/_py/cmdline/pycountloc.py
@@ -0,0 +1,94 @@
+#!/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))
+

--- /dev/null
+++ b/_py/code/oldmagic.py
@@ -0,0 +1,62 @@
+""" deprecated module for turning on/off some features. """ 
+
+import py 
+
+from py.builtin import builtins as cpy_builtin
+
+def invoke(assertion=False, compile=False):
+    """ (deprecated) invoke magic, currently you can specify:
+
+        assertion  patches the builtin AssertionError to try to give
+                   more meaningful AssertionErrors, which by means
+                   of deploying a mini-interpreter constructs
+                   a useful error message.
+    """
+    py.log._apiwarn("1.1", 
+        "py.magic.invoke() is deprecated, use py.code.patch_builtins()",
+        stacklevel=2, 
+    )
+    py.code.patch_builtins(assertion=assertion, compile=compile)
+
+def revoke(assertion=False, compile=False):
+    """ (deprecated) revoke previously invoked magic (see invoke())."""
+    py.log._apiwarn("1.1", 
+        "py.magic.revoke() is deprecated, use py.code.unpatch_builtins()",
+        stacklevel=2, 
+    )
+    py.code.unpatch_builtins(assertion=assertion, compile=compile)
+
+patched = {}
+
+def patch(namespace, name, value):
+    """ (deprecated) rebind the 'name' on the 'namespace'  to the 'value',
+        possibly and remember the original value. Multiple
+        invocations to the same namespace/name pair will
+        remember a list of old values.
+    """
+    py.log._apiwarn("1.1", 
+        "py.magic.patch() is deprecated, in tests use monkeypatch funcarg.", 
+        stacklevel=2, 
+    )
+    nref = (namespace, name)
+    orig = getattr(namespace, name)
+    patched.setdefault(nref, []).append(orig)
+    setattr(namespace, name, value)
+    return orig
+
+def revert(namespace, name):
+    """ (deprecated) revert to the orginal value the last patch modified.
+        Raise ValueError if no such original value exists.
+    """
+    py.log._apiwarn("1.1", 
+        "py.magic.revert() is deprecated, in tests use monkeypatch funcarg.",
+        stacklevel=2, 
+    )
+    nref = (namespace, name)
+    if nref not in patched or not patched[nref]:
+        raise ValueError("No original value stored for %s.%s" % nref)
+    current = getattr(namespace, name)
+    orig = patched[nref].pop()
+    setattr(namespace, name, orig)
+    return current
+

--- /dev/null
+++ b/_py/code/__init__.py
@@ -0,0 +1,1 @@
+""" python inspection/code generation API """

--- /dev/null
+++ b/_py/cmdline/pyconvert_unittest.py
@@ -0,0 +1,249 @@
+import re
+import sys
+import parser
+
+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()

--- /dev/null
+++ b/_py/code/_assertionnew.py
@@ -0,0 +1,314 @@
+"""
+Like _assertion.py but using builtin AST.  It should replace _assertion.py
+eventually.
+"""
+
+import sys
+import ast
+
+import py
+from _py.code.assertion import _format_explanation, BuiltinAssertionError
+
+
+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 --nomagic)")
+
+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] += " << {0}".format(value)
+        explanation = "\n".join(lines)
+    text = "{0}: {1}".format(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.Is : "is",
+    ast.IsNot : "is not",
+    ast.In : "in",
+    ast.NotIn : "not in"
+}
+
+unary_map = {
+    ast.Not : "not {0}",
+    ast.Invert : "~{0}",
+    ast.USub : "-{0}",
+    ast.UAdd : "+{0}"
+}
+
+
+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 isinstance(node, ast.expr):
+            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 isinstance(node, ast.stmt):
+            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 {0}".format(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 = "{0!r} in locals() is not globals()".format(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 = "{0} {1} {2}".format(left_explanation, op_symbol,
+                                               next_explanation)
+            source = "__exprinfo_left {0} __exprinfo_right".format(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.format(operand_explanation)
+        co = self._compile(pattern.format("__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 = "({0} {1} {2})".format(left_explanation, symbol,
+                                             right_explanation)
+        source = "__exprinfo_left {0} __exprinfo_right".format(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_{0}".format(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_{0}".format(len(ns))
+            ns[arg_name] = arg_result
+            keyword_source = "{0}={{0}}".format(keyword.id)
+            arguments.append(keyword_source.format(arg_name))
+            arg_explanations.append(keyword_source.format(arg_explanation))
+        if call.starargs:
+            arg_explanation, arg_result = self.visit(call.starargs)
+            arg_name = "__exprinfo_star"
+            ns[arg_name] = arg_result
+            arguments.append("*{0}".format(arg_name))
+            arg_explanations.append("*{0}".format(arg_explanation))
+        if call.kwargs:
+            arg_explanation, arg_result = self.visit(call.kwargs)
+            arg_name = "__exprinfo_kwds"
+            ns[arg_name] = arg_result
+            arguments.append("**{0}".format(arg_name))
+            arg_explanations.append("**{0}".format(arg_explanation))
+        args_explained = ", ".join(arg_explanations)
+        explanation = "{0}({1})".format(func_explanation, args_explained)
+        args = ", ".join(arguments)
+        source = "__exprinfo_func({0})".format(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 = "{0}\n{{{0} = {1}\n}}"
+                rep = self.frame.repr(result)
+                explanation = pattern.format(rep, explanation)
+        return explanation, result
+
+    def _is_builtin_name(self, name):
+        pattern = "{0!r} not in globals() and {0!r} not in locals()"
+        source = pattern.format(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 = "{0}.{1}".format(source_explanation, attr.attr)
+        source = "__exprinfo_expr.{0}".format(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 = "{0!r} in getattr(__exprinfo_expr, '__dict__', {{}})"
+        source = source.format(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 = "{0}\n{{{0} = {1}\n}}"
+            explanation = pattern.format(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 {0}".format(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 = "... = {0}".format(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

--- /dev/null
+++ b/_py/compat/dep_textwrap.py
@@ -0,0 +1,4 @@
+import py
+
+py.log._apiwarn("1.1", "py.compat.textwrap deprecated, use standard library version.", stacklevel="initpkg")
+textwrap = py.std.textwrap

--- /dev/null
+++ b/_py/builtin/builtin24.py
@@ -0,0 +1,71 @@
+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 

--- /dev/null
+++ b/_py/process/killproc.py
@@ -0,0 +1,23 @@
+import py
+import os, sys
+
+if sys.platform == "win32":
+    try:
+        import ctypes
+    except ImportError:
+        def dokill(pid):
+            py.process.cmdexec("taskkill /F /PID %d" %(pid,))
+    else: 
+        def dokill(pid):
+            PROCESS_TERMINATE = 1
+            handle = ctypes.windll.kernel32.OpenProcess(
+                        PROCESS_TERMINATE, False, pid)
+            ctypes.windll.kernel32.TerminateProcess(handle, -1)
+            ctypes.windll.kernel32.CloseHandle(handle)
+else:
+    def dokill(pid):
+        os.kill(pid, 15)     
+
+def kill(pid):
+    """ kill process by id. """
+    dokill(pid)

--- /dev/null
+++ b/_py/process/cmdexec.py
@@ -0,0 +1,177 @@
+"""
+
+module defining basic hook for executing commands
+in a - as much as possible - platform independent way.
+
+Current list:
+
+    exec_cmd(cmd)       executes the given command and returns output
+                        or ExecutionFailed exception (if exit status!=0)
+
+"""
+
+import os, sys
+import py
+from subprocess import Popen, PIPE
+
+#-----------------------------------------------------------
+# posix external command execution
+#-----------------------------------------------------------
+def posix_exec_cmd(cmd):
+    """ return output of executing 'cmd'.
+
+    raise ExecutionFailed exeception if the command failed.
+    the exception will provide an 'err' attribute containing
+    the error-output from the command.
+    """
+    #__tracebackhide__ = True
+
+    import errno
+
+    child = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True)
+    stdin, stdout, stderr = child.stdin, child.stdout, child.stderr
+
+    # XXX sometimes we get a blocked r.read() call (see below)
+    #     although select told us there is something to read.
+    #     only the next three lines appear to prevent
+    #     the read call from blocking infinitely.
+    import fcntl
+    def set_non_block(fd):
+        flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+        flags = flags | os.O_NONBLOCK
+        fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+    set_non_block(stdout.fileno())
+    set_non_block(stderr.fileno())
+    #fcntl.fcntl(stdout, fcntl.F_SETFL, os.O_NONBLOCK)
+    #fcntl.fcntl(stderr, fcntl.F_SETFL, os.O_NONBLOCK)
+
+    import select
+    out, err = [], []
+    while 1:
+        r_list = [x for x in [stdout, stderr] if x and not x.closed]
+        if not r_list:
+            break
+        try:
+            r_list = select.select(r_list, [], [])[0]
+        except (select.error, IOError):
+            se = sys.exc_info()[1]
+            if se.args[0] == errno.EINTR:
+                continue
+            else:
+                raise
+        for r  in r_list:
+            try:
+                data = r.read()   # XXX see XXX above
+            except IOError:
+                io = sys.exc_info()[1]
+                if io.args[0] == errno.EAGAIN:
+                    continue
+                # Connection Lost
+                raise
+            except OSError:
+                ose = sys.exc_info()[1]
+                if ose.errno == errno.EPIPE:
+                    # Connection Lost
+                    raise
+                if ose.errno == errno.EAGAIN: # MacOS-X does this
+                    continue
+                raise
+
+            if not data:
+                r.close()
+                continue
+            if r is stdout:
+                out.append(data)
+            else:
+                err.append(data)
+    pid, systemstatus = os.waitpid(child.pid, 0)
+    if pid != child.pid:
+        raise ExecutionFailed("child process disappeared during: "+ cmd)
+    if systemstatus:
+        if os.WIFSIGNALED(systemstatus):
+            status = os.WTERMSIG(systemstatus) + 128
+        else:
+            status = os.WEXITSTATUS(systemstatus)
+        raise ExecutionFailed(status, systemstatus, cmd,
+                              joiner(out), joiner(err))
+    return joiner(out)
+
+def joiner(out):
+    encoding = sys.getdefaultencoding()
+    return "".join([py.builtin._totext(x, encoding) for x in out])
+
+#-----------------------------------------------------------
+# simple win32 external command execution
+#-----------------------------------------------------------
+def win32_exec_cmd(cmd):
+    """ return output of executing 'cmd'.
+
+    raise ExecutionFailed exeception if the command failed.
+    the exception will provide an 'err' attribute containing
+    the error-output from the command.
+
+    Note that this method can currently deadlock because
+    we don't have WaitForMultipleObjects in the std-python api.
+
+    Further note that the rules for quoting are very special
+    under Windows. Do a HELP CMD in a shell, and tell me if
+    you understand this. For now, I try to do a fix.
+    """
+    #print "*****", cmd
+
+    # the following quoting is only valid for CMD.EXE, not COMMAND.COM
+    cmd_quoting = True
+    try:
+        if os.environ['COMSPEC'].upper().endswith('COMMAND.COM'):
+            cmd_quoting = False
+    except KeyError:
+        pass
+    if cmd_quoting:
+        if '"' in cmd and not cmd.startswith('""'):
+            cmd = '"%s"' % cmd
+
+    return popen3_exec_cmd(cmd)
+
+def popen3_exec_cmd(cmd):
+    stdin, stdout, stderr = os.popen3(cmd)
+    out = stdout.read()
+    err = stderr.read()
+    stdout.close()
+    stderr.close()
+    status = stdin.close()
+    if status:
+        raise ExecutionFailed(status, status, cmd, out, err)
+    return out
+
+def pypy_exec_cmd(cmd):
+    return popen3_exec_cmd(cmd)
+
+class ExecutionFailed(py.error.Error):
+    def __init__(self, status, systemstatus, cmd, out, err):
+        Exception.__init__(self)
+        self.status = status
+        self.systemstatus = systemstatus
+        self.cmd = cmd
+        self.err = err
+        self.out = out
+
+    def __str__(self):
+        return "ExecutionFailed: %d  %s\n%s" %(self.status, self.cmd, self.err)
+#
+# choose correct platform-version
+#
+
+if sys.platform == 'win32':
+    cmdexec = win32_exec_cmd
+elif hasattr(sys, 'pypy') or hasattr(sys, 'pypy_objspaceclass'):
+    cmdexec = popen3_exec_cmd
+else:
+    cmdexec = posix_exec_cmd
+
+# export the exception under the name 'py.process.cmdexec.Error'
+cmdexec.Error = ExecutionFailed
+try:
+    ExecutionFailed.__module__ = 'py.process.cmdexec'
+    ExecutionFailed.__name__ = 'Error'
+except (AttributeError, TypeError):
+    pass

--- /dev/null
+++ b/_py/code/code.py
@@ -0,0 +1,764 @@
+import py
+import sys
+
+builtin_repr = repr
+
+repr = 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 new(self, rec=False, **kwargs): 
+        """ return new code object with modified attributes. 
+            if rec-cursive is true then dive into code 
+            objects contained in co_consts. 
+        """ 
+        names = [x for x in dir(self.raw) if x[:3] == 'co_']
+        for name in kwargs: 
+            if name not in names: 
+                raise TypeError("unknown code attribute: %r" %(name, ))
+        if rec and hasattr(self.raw, 'co_consts'):  # jython 
+            newconstlist = []
+            co = self.raw
+            cotype = type(co)
+            for c in co.co_consts:
+                if isinstance(c, cotype):
+                    c = self.__class__(c).new(rec=True, **kwargs) 
+                newconstlist.append(c)
+            return self.new(rec=False, co_consts=tuple(newconstlist), **kwargs) 
+        for name in names:
+            if name not in kwargs:
+                kwargs[name] = getattr(self.raw, name)
+        arglist = [
+                 kwargs['co_argcount'],
+                 kwargs['co_nlocals'],
+                 kwargs.get('co_stacksize', 0), # jython
+                 kwargs.get('co_flags', 0), # jython
+                 kwargs.get('co_code', ''), # jython
+                 kwargs.get('co_consts', ()), # jython
+                 kwargs.get('co_names', []), # 
+                 kwargs['co_varnames'],
+                 kwargs['co_filename'],
+                 kwargs['co_name'],
+                 kwargs['co_firstlineno'],
+                 kwargs.get('co_lnotab', ''), #jython
+                 kwargs.get('co_freevars', None), #jython
+                 kwargs.get('co_cellvars', None), # jython
+        ]
+        if sys.version_info >= (3,0):
+            arglist.insert(1, kwargs['co_kwonlyargcount'])
+            return self.raw.__class__(*arglist)
+        else:
+            return py.std.new.code(*arglist)
+
+    def path(self):
+        """ return a py.path.local object pointing to the source code """
+        fn = self.raw.co_filename 
+        try:
+            return fn.__path__
+        except AttributeError:
+            p = py.path.local(self.raw.co_filename)
+            if not p.check(file=1):
+                # 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
+
+    def statement(self):
+        if self.code.fullsource is None:
+            return py.code.Source("")
+        return self.code.fullsource.getstatement(self.lineno)
+    statement = property(statement, None, None,
+                         "statement this frame is at")
+
+    def eval(self, code, **vars):
+        """ evaluate 'code' in the frame
+
+            'vars' are optional additional local variables
+
+            returns the result of the evaluation
+        """
+        f_locals = self.f_locals.copy() 
+        f_locals.update(vars)
+        return eval(code, self.f_globals, f_locals)
+
+    def exec_(self, code, **vars):
+        """ exec 'code' in the frame
+
+            'vars' are optiona; additional local variables
+        """
+        f_locals = self.f_locals.copy() 
+        f_locals.update(vars)
+        py.builtin.exec_(code, self.f_globals, f_locals )
+
+    def repr(self, object):
+        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
+        """
+        return safe_repr(object)
+
+    def is_true(self, object):
+        return object
+
+    def getargs(self):
+        """ return a list of tuples (name, value) for all arguments
+        """
+        retval = []
+        for arg in self.code.getargs():
+            try:
+                retval.append((arg, self.f_locals[arg]))
+            except KeyError:
+                pass     # this can occur when using Psyco
+        return retval
+
+class TracebackEntry(object):
+    """ a single entry in a traceback """
+    
+    exprinfo = None 
+
+    def __init__(self, rawentry):
+        self._rawentry = rawentry
+        self.frame = py.code.Frame(rawentry.tb_frame)
+        # Ugh. 2.4 and 2.5 differs here when encountering
+        # multi-line statements. Not sure about the solution, but
+        # should be portable
+        self.lineno = rawentry.tb_lineno - 1
+        self.relline = self.lineno - self.frame.code.firstlineno
+
+    def __repr__(self):
+        return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
+
+    def statement(self):
+        """ return a py.code.Source object for the current statement """
+        source = self.frame.code.fullsource
+        return source.getstatement(self.lineno)
+    statement = property(statement, None, None,
+                         "statement of this traceback entry.")
+
+    def path(self):
+        return self.frame.code.path
+    path = property(path, None, None, "path to the full source code")
+
+    def getlocals(self):
+        return self.frame.f_locals
+    locals = property(getlocals, None, None, "locals of underlaying frame")
+
+    def reinterpret(self):
+        """Reinterpret the failing statement and returns a detailed information
+           about what operations are performed."""
+        if self.exprinfo is None:
+            from _py.code import assertion 
+            source = str(self.statement).strip()
+            x = assertion.interpret(source, self.frame, should_fail=True)
+            if not isinstance(x, str):
+                raise TypeError("interpret returned non-string %r" % (x,))
+            self.exprinfo = x 
+        return self.exprinfo
+
+    def getfirstlinesource(self):
+        return self.frame.code.firstlineno
+
+    def getsource(self): 
+        """ return failing source code. """
+        source = self.frame.code.fullsource
+        if source is None:
+            return None
+        start = self.getfirstlinesource()
+        end = self.lineno
+        try:
+            _, end = source.getstatementrange(end) 
+        except IndexError: 
+            end = self.lineno + 1 
+        # heuristic to stop displaying source on e.g. 
+        #   if something:  # assume this causes a NameError
+        #      # _this_ lines and the one 
+               #        below we don't want from entry.getsource() 
+        for i in range(self.lineno, end): 
+            if source[i].rstrip().endswith(':'): 
+                end = i + 1
+                break 
+        return source[start:end]
+    source = property(getsource)
+
+    def ishidden(self):
+        """ return True if the current frame has a var __tracebackhide__ 
+            resolving to True
+            
+            mostly for internal use
+        """
+        try: 
+            return self.frame.eval("__tracebackhide__") 
+        except (SystemExit, KeyboardInterrupt): 
+            raise
+        except:
+            return False 
+
+    def __str__(self): 
+        try: 
+            fn = str(self.path) 
+        except py.error.Error: 
+            fn = '???'
+        name = self.frame.code.name 
+        try: 
+            line = str(self.statement).lstrip()
+        except KeyboardInterrupt:
+            raise
+        except:
+            line = "???"
+        return "  File %r:%d in %s\n  %s\n" %(fn, self.lineno+1, name, line) 
+
+    def name(self):
+        return self.frame.code.raw.co_name
+    name = property(name, None, None, "co_name of underlaying code")
+
+class Traceback(list):
+    """ Traceback objects encapsulate and offer higher level 
+        access to Traceback entries.  
+    """
+    Entry = TracebackEntry 
+    def __init__(self, tb):
+        """ initialize from given python traceback object. """
+        if hasattr(tb, 'tb_next'):
+            def f(cur): 
+                while cur is not None: 
+                    yield self.Entry(cur)
+                    cur = cur.tb_next 
+            list.__init__(self, f(tb)) 
+        else:
+            list.__init__(self, tb)
+
+    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
+        """ return a Traceback instance wrapping part of this Traceback
+
+            by provding any combination of path, lineno and firstlineno, the
+            first frame to start the to-be-returned traceback is determined
+
+            this allows cutting the first part of a Traceback instance e.g.
+            for formatting reasons (removing some uninteresting bits that deal
+            with handling of the exception/traceback)
+        """
+        for x in self:
+            code = x.frame.code
+            codepath = code.path
+            if ((path is None or codepath == path) and
+                (excludepath is None or (hasattr(codepath, 'relto') and
+                 not codepath.relto(excludepath))) and 
+                (lineno is None or x.lineno == lineno) and
+                (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
+                return Traceback(x._rawentry)
+        return self
+
+    def __getitem__(self, key):
+        val = super(Traceback, self).__getitem__(key)
+        if isinstance(key, type(slice(0))):
+            val = self.__class__(val)
+        return val
+
+    def filter(self, fn=lambda x: not x.ishidden()):
+        """ return a Traceback instance with certain items removed
+
+            fn is a function that gets a single argument, a TracebackItem
+            instance, and should return True when the item should be added
+            to the Traceback, False when not
+
+            by default this removes all the TracebackItems which are hidden
+            (see ishidden() above)
+        """
+        return Traceback(filter(fn, self))
+
+    def getcrashentry(self):
+        """ return last non-hidden traceback entry that lead
+        to the exception of a traceback. 
+        """
+        tb = self.filter()
+        if not tb:
+            tb = self
+        return tb[-1]
+
+    def recursionindex(self):
+        """ return the index of the frame/TracebackItem where recursion
+            originates if appropriate, None if no recursion occurred
+        """
+        cache = {}
+        for i, entry in enumerate(self):
+            key = entry.frame.code.path, entry.lineno 
+            #print "checking for recursion at", key
+            l = cache.setdefault(key, [])
+            if l: 
+                f = entry.frame
+                loc = f.f_locals
+                for otherloc in l: 
+                    if f.is_true(f.eval(co_equal, 
+                        __recursioncache_locals_1=loc,
+                        __recursioncache_locals_2=otherloc)):
+                        return i 
+            l.append(entry.frame.f_locals)
+        return None
+
+co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
+                   '?', 'eval')
+
+class ExceptionInfo(object):
+    """ wraps sys.exc_info() objects and offers
+        help for navigating the traceback.
+    """
+    _striptext = '' 
+    def __init__(self, tup=None, exprinfo=None):
+        # NB. all attributes are private!  Subclasses or other
+        #     ExceptionInfo-like classes may have different attributes.
+        if tup is None:
+            tup = sys.exc_info()
+            if exprinfo is None and isinstance(tup[1], py.code._AssertionError):
+                exprinfo = getattr(tup[1], 'msg', None)
+                if exprinfo is None:
+                    exprinfo = str(tup[1])
+                if exprinfo and exprinfo.startswith('assert '):
+                    self._striptext = 'AssertionError: '
+        self._excinfo = tup
+        self.type, self.value, tb = self._excinfo
+        self.typename = self.type.__name__
+        self.traceback = py.code.Traceback(tb) 
+
+    def __repr__(self):
+        return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
+
+    def exconly(self, tryshort=False): 
+        """ return the exception as a string
+        
+            when 'tryshort' resolves to True, and the exception is a
+            py.code._AssertionError, only the actual exception part of
+            the exception representation is returned (so 'AssertionError: ' is
+            removed from the beginning)
+        """
+        lines = py.std.traceback.format_exception_only(self.type, self.value)
+        text = ''.join(lines)
+        text = text.rstrip()
+        if tryshort: 
+            if text.startswith(self._striptext): 
+                text = text[len(self._striptext):]
+        return text
+
+    def errisinstance(self, exc): 
+        """ return True if the exception is an instance of exc """
+        return isinstance(self.value, exc) 
+
+    def _getreprcrash(self):
+        exconly = self.exconly(tryshort=True)
+        entry = self.traceback.getcrashentry()
+        path, lineno = entry.path, entry.lineno
+        reprcrash = ReprFileLocation(path, lineno+1, exconly)
+        return reprcrash
+
+    def getrepr(self, showlocals=False, style="long", 
+            abspath=False, tbfilter=True, funcargs=False):
+        """ return str()able representation of this exception info.
+            showlocals: show locals per traceback entry 
+            style: long|short|no traceback style 
+            tbfilter: hide entries (where __tracebackhide__ is true)
+        """
+        fmt = FormattedExcinfo(showlocals=showlocals, style=style, 
+            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
+        return fmt.repr_excinfo(self)
+
+    def __str__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return str(loc)
+
+class FormattedExcinfo(object):
+    """ presenting information about failing Functions and Generators. """ 
+    # for traceback entries 
+    flow_marker = ">"    
+    fail_marker = "E"
+    
+    def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
+        self.showlocals = showlocals
+        self.style = style
+        self.tbfilter = tbfilter
+        self.funcargs = funcargs
+        self.abspath = abspath 
+
+    def _getindent(self, source):
+        # figure out indent for given source 
+        try:
+            s = str(source.getstatement(len(source)-1))
+        except KeyboardInterrupt: 
+            raise 
+        except:
+            try:
+                s = str(source[-1])
+            except KeyboardInterrupt:
+                raise
+            except:
+                return 0
+        return 4 + (len(s) - len(s.lstrip()))
+
+    def _getentrysource(self, entry):
+        source = entry.getsource()
+        if source is not None:
+            source = source.deindent()
+        return source
+    
+    def _saferepr(self, obj):
+        return safe_repr(obj)
+
+    def repr_args(self, entry):
+        if self.funcargs:
+            args = []
+            for argname, argvalue in entry.frame.getargs():
+                args.append((argname, self._saferepr(argvalue)))
+            return ReprFuncArgs(args)
+
+    def get_source(self, source, line_index=-1, excinfo=None):
+        """ return formatted and marked up source lines. """
+        lines = []
+        if source is None:
+            source = py.code.Source("???")
+            line_index = 0 
+        if line_index < 0:
+            line_index += len(source)
+        for i in range(len(source)):
+            if i == line_index:
+                prefix = self.flow_marker + "   "
+            else:
+                prefix = "    "
+            line = prefix + source[i]
+            lines.append(line)
+        if excinfo is not None:
+            indent = self._getindent(source)
+            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
+        return lines
+
+    def get_exconly(self, excinfo, indent=4, markall=False):
+        lines = []
+        indent = " " * indent 
+        # get the real exception information out 
+        exlines = excinfo.exconly(tryshort=True).split('\n')
+        failindent = self.fail_marker + indent[1:]
+        for line in exlines:
+            lines.append(failindent + line)
+            if not markall:
+                failindent = indent 
+        return lines
+
+    def repr_locals(self, locals):
+        if self.showlocals: 
+            lines = []
+            keys = list(locals)
+            keys.sort()
+            for name in keys:
+                value = locals[name]
+                if name == '__builtins__': 
+                    lines.append("__builtins__ = <builtins>")
+                else:
+                    # This formatting could all be handled by the
+                    # _repr() function, which is only repr.Repr in
+                    # disguise, so is very configurable.
+                    str_repr = self._saferepr(value)
+                    #if len(str_repr) < 70 or not isinstance(value,
+                    #                            (list, tuple, dict)):
+                    lines.append("%-10s = %s" %(name, str_repr))
+                    #else:
+                    #    self._line("%-10s =\\" % (name,))
+                    #    # XXX
+                    #    py.std.pprint.pprint(value, stream=self.excinfowriter)
+            return ReprLocals(lines)
+
+    def repr_traceback_entry(self, entry, excinfo=None):
+        # excinfo is not None if this is the last tb entry 
+        source = self._getentrysource(entry)
+        if source is None:
+            source = py.code.Source("???")
+            line_index = 0
+        else:
+            line_index = entry.lineno - entry.getfirstlinesource()
+
+        lines = []
+        if self.style == "long":
+            reprargs = self.repr_args(entry) 
+            lines.extend(self.get_source(source, line_index, excinfo))
+            message = excinfo and excinfo.typename or ""
+            path = self._makepath(entry.path)
+            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
+            localsrepr =  self.repr_locals(entry.locals)
+            return ReprEntry(lines, reprargs, localsrepr, filelocrepr)
+        else: 
+            if self.style == "short":
+                line = source[line_index].lstrip()
+                lines.append('  File "%s", line %d, in %s' % (
+                    entry.path.basename, entry.lineno+1, entry.name))
+                lines.append("    " + line) 
+            if excinfo: 
+                lines.extend(self.get_exconly(excinfo, indent=4))
+            return ReprEntry(lines, None, None, None)
+
+    def _makepath(self, path):
+        if not self.abspath:
+            np = py.path.local().bestrelpath(path)
+            if len(np) < len(str(path)):
+                path = np
+        return path
+
+    def repr_traceback(self, excinfo): 
+        traceback = excinfo.traceback 
+        if self.tbfilter:
+            traceback = traceback.filter()
+        recursionindex = None
+        if excinfo.errisinstance(RuntimeError):
+            recursionindex = traceback.recursionindex()
+        last = traceback[-1]
+        entries = []
+        extraline = None
+        for index, entry in enumerate(traceback): 
+            einfo = (last == entry) and excinfo or None
+            reprentry = self.repr_traceback_entry(entry, einfo)
+            entries.append(reprentry)
+            if index == recursionindex:
+                extraline = "!!! Recursion detected (same locals & position)"
+                break
+        return ReprTraceback(entries, extraline, style=self.style)
+
+    def repr_excinfo(self, excinfo):
+        reprtraceback = self.repr_traceback(excinfo)
+        reprcrash = excinfo._getreprcrash()
+        return ReprExceptionInfo(reprtraceback, reprcrash)
+
+class TerminalRepr:
+    def __str__(self):
+        tw = py.io.TerminalWriter(stringio=True)
+        self.toterminal(tw)
+        return tw.stringio.getvalue().strip()
+
+    def __repr__(self):
+        return "<%s instance at %0x>" %(self.__class__, id(self))
+
+class ReprExceptionInfo(TerminalRepr):
+    def __init__(self, reprtraceback, reprcrash):
+        self.reprtraceback = reprtraceback
+        self.reprcrash = reprcrash 
+        self.sections = []
+
+    def addsection(self, name, content, sep="-"):
+        self.sections.append((name, content, sep))
+
+    def toterminal(self, tw):
+        self.reprtraceback.toterminal(tw)
+        for name, content, sep in self.sections:
+            tw.sep(sep, name)
+            tw.line(content)
+    
+class ReprTraceback(TerminalRepr):
+    entrysep = "_ "
+
+    def __init__(self, reprentries, extraline, style):
+        self.reprentries = reprentries
+        self.extraline = extraline
+        self.style = style
+
+    def toterminal(self, tw):
+        sepok = False 
+        for entry in self.reprentries:
+            if self.style == "long":
+                if sepok:
+                    tw.sep(self.entrysep)
+                tw.line("")
+            sepok = True
+            entry.toterminal(tw)
+        if self.extraline:
+            tw.line(self.extraline)
+
+class ReprEntry(TerminalRepr):
+    localssep = "_ "
+
+    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr):
+        self.lines = lines
+        self.reprfuncargs = reprfuncargs
+        self.reprlocals = reprlocals 
+        self.reprfileloc = filelocrepr
+
+    def toterminal(self, tw):
+        if self.reprfuncargs:
+            self.reprfuncargs.toterminal(tw)
+        for line in self.lines:
+            red = line.startswith("E   ") 
+            tw.line(line, bold=True, red=red)
+        if self.reprlocals:
+            #tw.sep(self.localssep, "Locals")
+            tw.line("")
+            self.reprlocals.toterminal(tw)
+        if self.reprfileloc:
+            tw.line("")
+            self.reprfileloc.toterminal(tw)
+
+    def __str__(self):
+        return "%s\n%s\n%s" % ("\n".join(self.lines), 
+                               self.reprlocals, 
+                               self.reprfileloc)
+
+class ReprFileLocation(TerminalRepr):
+    def __init__(self, path, lineno, message):
+        self.path = str(path)
+        self.lineno = lineno
+        self.message = message
+
+    def toterminal(self, tw):
+        # filename and lineno output for each entry,
+        # using an output format that most editors unterstand
+        msg = self.message 
+        i = msg.find("\n")
+        if i != -1:
+            msg = msg[:i] 
+        tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
+
+class ReprLocals(TerminalRepr):
+    def __init__(self, lines):
+        self.lines = lines 
+
+    def toterminal(self, tw):
+        for line in self.lines:
+            tw.line(line)
+
+class ReprFuncArgs(TerminalRepr):
+    def __init__(self, args):
+        self.args = args
+
+    def toterminal(self, tw):
+        if self.args:
+            linesofar = ""
+            for name, value in self.args:
+                ns = "%s = %s" %(name, value)
+                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
+                    if linesofar:
+                        tw.line(linesofar)
+                    linesofar =  ns 
+                else:
+                    if linesofar:
+                        linesofar += ", " + ns
+                    else:
+                        linesofar = ns
+            if linesofar:
+                tw.line(linesofar)
+            tw.line("")
+
+
+
+class SafeRepr(repr.Repr):
+    """ subclass of repr.Repr that limits the resulting size of repr() 
+        and includes information on exceptions raised during the call. 
+    """ 
+    def __init__(self, *args, **kwargs):
+        repr.Repr.__init__(self, *args, **kwargs)
+        self.maxstring = 240   # 3 * 80 chars
+        self.maxother = 160    # 2 * 80 chars
+
+    def repr(self, x):
+        return self._callhelper(repr.Repr.repr, self, x)
+
+    def repr_instance(self, x, level):
+        return self._callhelper(builtin_repr, x)
+        
+    def _callhelper(self, call, x, *args):
+        try:
+            # Try the vanilla repr and make sure that the result is a string
+            s = call(x, *args)
+        except (KeyboardInterrupt, MemoryError, SystemExit):
+            raise
+        except:
+            cls, e, tb = sys.exc_info()
+            try:
+                exc_name = cls.__name__
+            except:
+                exc_name = 'unknown'
+            try:
+                exc_info = str(e)
+            except:
+                exc_info = 'unknown'
+            return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
+                exc_name, exc_info, x.__class__.__name__, id(x))
+        else:
+            if len(s) > self.maxstring:
+                i = max(0, (self.maxstring-3)//2)
+                j = max(0, self.maxstring-3-i)
+                s = s[:i] + '...' + s[len(s)-j:]
+            return s
+
+safe_repr = SafeRepr().repr
+
+oldbuiltins = {}
+
+def patch_builtins(assertion=True, compile=True):
+    """ put compile and AssertionError builtins to Python's builtins. """
+    if assertion:
+        from _py.code import assertion
+        l = oldbuiltins.setdefault('AssertionError', [])
+        l.append(py.builtin.builtins.AssertionError)
+        py.builtin.builtins.AssertionError = assertion.AssertionError
+    if compile: 
+        l = oldbuiltins.setdefault('compile', [])
+        l.append(py.builtin.builtins.compile)
+        py.builtin.builtins.compile = py.code.compile
+
+def unpatch_builtins(assertion=True, compile=True):
+    """ remove compile and AssertionError builtins from Python builtins. """
+    if assertion:
+        py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
+    if compile: 
+        py.builtin.builtins.compile = oldbuiltins['compile'].pop()
+
+def getrawcode(obj):
+    """ return code object for given function. """ 
+    obj = getattr(obj, 'im_func', obj)
+    obj = getattr(obj, 'func_code', obj)
+    obj = getattr(obj, 'f_code', obj)
+    obj = getattr(obj, '__code__', obj)
+    return obj
+    

--- /dev/null
+++ b/_py/path/__init__.py
@@ -0,0 +1,1 @@
+""" unified file system api """

--- /dev/null
+++ b/_py/builtin/builtin31.py
@@ -0,0 +1,117 @@
+import py
+import sys
+
+if sys.version_info >= (3, 0):
+    exec ("print_ = print ; exec_=exec")
+    import builtins
+
+    # some backward compatibility helpers 
+    _basestring = str 
+    def _totext(obj, encoding):
+        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 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 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. """ 
+        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):
+    assert hasattr(val, '__traceback__')
+    raise val
+""")
+else:
+    exec ("""
+def _reraise(cls, val, tb):
+    raise cls, val, tb
+def exec2(obj, globals, locals):
+    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()
+    py.builtin._reraise(*excinfo)

--- /dev/null
+++ b/_py/cmdline/pytest.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python 
+import py
+
+def main():
+    py.test.cmdline.main() 

--- /dev/null
+++ b/_py/log/__init__.py
@@ -0,0 +1,2 @@
+""" logging API ('producers' and 'consumers' connected via keywords) """
+

--- /dev/null
+++ b/_py/process/__init__.py
@@ -0,0 +1,1 @@
+""" high-level sub-process handling """


--- /dev/null
+++ b/_py/code/_assertionold.py
@@ -0,0 +1,558 @@
+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):
+        co = compile('%r in locals() is not globals()' % self.name, '?', 'eval')
+        try:
+            return frame.is_true(frame.eval(co))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_global(self, frame):
+        co = compile('%r in globals()' % self.name, '?', 'eval')
+        try:
+            return frame.is_true(frame.eval(co))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_builtin(self, frame):
+        co = compile('%r not in locals() and %r not in globals()' % (
+            self.name, self.name), '?', 'eval')
+        try:
+            return frame.is_true(frame.eval(co))
+        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)
+            co = compile("__exprinfo_left %s __exprinfo_right" % operation,
+                         '?', 'eval')
+            try:
+                self.result = frame.eval(co, __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,
+                              co=compile(astpattern, '?', 'eval')):
+            expr = Interpretable(self.expr)
+            expr.eval(frame)
+            self.explanation = astpattern.replace('__exprinfo_expr',
+                                                  expr.explanation)
+            try:
+                self.result = frame.eval(co, __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,
+                              co=compile(astpattern, '?', 'eval')):
+            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(co, __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):
+        co = compile('isinstance(__exprinfo_value, bool)', '?', 'eval')
+        try:
+            return frame.is_true(frame.eval(co, __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 += ')'
+        co = compile(source, '?', 'eval')
+        try:
+            self.result = frame.eval(co, **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)
+        co = compile('__exprinfo_expr.%s' % self.attrname, '?', 'eval')
+        try:
+            self.result = frame.eval(co, __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
+        co = compile('hasattr(__exprinfo_expr, "__dict__") and '
+                     '%r in __exprinfo_expr.__dict__' % self.attrname,
+                     '?', 'eval')
+        try:
+            from_instance = frame.is_true(
+                frame.eval(co, __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:
+        import sys
+        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:
+        import sys
+        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())")

--- /dev/null
+++ b/_py/cmdline/pylookup.py
@@ -0,0 +1,83 @@
+#!/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.terminalwriter import ansi_print, 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")
+
+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)

--- /dev/null
+++ b/_py/_com.py
@@ -0,0 +1,125 @@
+"""
+py lib plugins and plugin call management
+"""
+
+import py
+import inspect
+
+__all__ = ['Registry', 'MultiCall', 'comregistry', 'HookRelay']
+
+class MultiCall:
+    """ execute a call into multiple python functions/methods.  """
+
+    def __init__(self, methods, kwargs, firstresult=False):
+        self.methods = methods[:]
+        self.kwargs = kwargs.copy()
+        self.kwargs['__multicall__'] = self
+        self.results = []
+        self.firstresult = firstresult
+
+    def __repr__(self):
+        status = "%d results, %d meths" % (len(self.results), len(self.methods))
+        return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
+
+    def execute(self):
+        while self.methods:
+            method = self.methods.pop()
+            kwargs = self.getkwargs(method)
+            res = method(**kwargs)
+            if res is not None:
+                self.results.append(res) 
+                if self.firstresult:
+                    return res
+        if not self.firstresult:
+            return self.results 
+
+    def getkwargs(self, method):
+        kwargs = {}
+        for argname in varnames(method):
+            try:
+                kwargs[argname] = self.kwargs[argname]
+            except KeyError:
+                pass # might be optional param
+        return kwargs 
+
+def varnames(func):
+    ismethod = inspect.ismethod(func)
+    rawcode = py.code.getrawcode(func)
+    try:
+        return rawcode.co_varnames[ismethod:]
+    except AttributeError:
+        return ()
+
+class Registry:
+    """
+        Manage Plugins: register/unregister call calls to plugins. 
+    """
+    def __init__(self, plugins=None):
+        if plugins is None:
+            plugins = []
+        self._plugins = plugins
+
+    def register(self, plugin):
+        assert not isinstance(plugin, str)
+        assert not plugin in self._plugins
+        self._plugins.append(plugin)
+
+    def unregister(self, plugin):
+        self._plugins.remove(plugin)
+
+    def isregistered(self, plugin):
+        return plugin in self._plugins 
+
+    def __iter__(self):
+        return iter(self._plugins)
+
+    def listattr(self, attrname, plugins=None, extra=(), reverse=False):
+        l = []
+        if plugins is None:
+            plugins = self._plugins
+        candidates = list(plugins) + list(extra)
+        for plugin in candidates:
+            try:
+                l.append(getattr(plugin, attrname))
+            except AttributeError:
+                continue 
+        if reverse:
+            l.reverse()
+        return l
+
+class HookRelay: 
+    def __init__(self, hookspecs, registry):
+        self._hookspecs = hookspecs
+        self._registry = registry
+        for name, method in vars(hookspecs).items():
+            if name[:1] != "_":
+                setattr(self, name, self._makecall(name))
+
+    def _makecall(self, name, extralookup=None):
+        hookspecmethod = getattr(self._hookspecs, name)
+        firstresult = getattr(hookspecmethod, 'firstresult', False)
+        return HookCaller(self, name, firstresult=firstresult,
+            extralookup=extralookup)
+
+    def _getmethods(self, name, extralookup=()):
+        return self._registry.listattr(name, extra=extralookup)
+
+    def _performcall(self, name, multicall):
+        return multicall.execute()
+        
+class HookCaller:
+    def __init__(self, hookrelay, name, firstresult, extralookup=None):
+        self.hookrelay = hookrelay 
+        self.name = name 
+        self.firstresult = firstresult 
+        self.extralookup = extralookup and [extralookup] or ()
+
+    def __repr__(self):
+        return "<HookCaller %r>" %(self.name,)
+
+    def __call__(self, **kwargs):
+        methods = self.hookrelay._getmethods(self.name, self.extralookup)
+        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
+        return self.hookrelay._performcall(self.name, mc)
+   
+comregistry = Registry([])



More information about the pytest-commit mailing list