"""Later: Go-like execution deferral for python

This module provides Go-like execution deferral for Python. A call may be
deferred, meaning that it is executed once the context of the deferral
closes. Several calls may be deferred, in which case they are all executed
in LIFO order as the context closes.

A deferral needs only to happen within a deferral context. The context
stores the deferred calls and invokes them as the context is closed. Contexts
may be shared and nested, but Later instances and their contexts are not
thread safe.

The purpose of the module is to avoid common headaches when untangling
resources after an exception, error or finishing business as usual, and to
avoid the pyramidic indentation that nested "with" contexts may result in.

Example:

    from later import Later

    # Using the decorator

    l = Later()

    @l.context
    def copy(filename):
        f = open(filename)
        l.defer(f.close)

        f2 = open(filename + '.copy', 'wb')
        l.defer(f2.close)

        f2.write(f.read())

    # Using the context manager

    with Later() as l:
        f = open('a')
        l.defer(f.close)

        f2 = open('b', 'wb')
        l.defer(f2.close)

        f2.write(f.read())

"""

class Later(object):
    """Later represents a stack of deferral contexts."""

    def __init__(self):
        """Create a Later instance"""
        self.contexts = []


    def __enter__(self):
        """Opens a new deferral context."""
        self.contexts.append([])
        return self


    def __exit__(self, exc_type, exc_value, traceback):
        """Closes a deferral context, executing any deferred calls in LIFO
        order."""
        ctx = self.contexts.pop()
        while ctx:
            call = ctx.pop()
            call[0](*call[1], **call[2])


    def context(self, func):
        """context is used as a decorator to wrap functions that may use the
        defer method. It wraps the function in a deferral context manager."""

        def ret(*args, **kwargs):
            """function wrapper for managing the context of the wrapped
            function"""
            with self:
                return func(*args, **kwargs)

        return ret


    def defer(self, func, *args, **kwargs):
        """defer stores a function and an optional set of arguments in the
        active context, to be invoked when the active context is finished."""
        self.contexts[-1].append((func, args, kwargs))
