# Written by Ziga Seilnacht in 2009, no copyright is claimed.
# This code is in public domain; do with it what you wish.
"""
This module allows you to specify class declarations in the bases of
a class definition. A simple example::

    >>> def fooSetter(cls):
    ...     cls.foo = 1
    ...     return cls
    ...
    >>> class Foo(declare.decorators(fooSetter)):
    ...     pass
    ...
    >>> Foo.foo
    1

Because the syntax for bases is reused for class declarations, you have
to specify class bases in a different way::

    >>> class Bar(declare.bases(Foo)):
    ...     pass
    ...
    >>> issubclass(Bar, Foo)
    True

Note also that classes that use `declare` inherit from `object` by default,
i.e. they are new style classes::

    >>> isinstance(Foo, type)
    True

However, if all the bases are old style classes and there is no metaclass
specified, the class will be old style::

    >>> class OldClass:
    ...     pass
    ...
    >>> class OldSubclass(declare.bases(OldClass)):
    ...     pass
    ...
    >>> isinstance(OldSubclass, type(OldClass))
    True

The metaclass syntax is also changed::

    >>> class Meta(type):
    ...     pass
    ...
    >>> class Baz(declare.metaclass(Meta)):
    ...     pass
    ...
    >>> isinstance(Baz, Meta)
    True

The good news is that this syntax also works in Python 3.0. When metaclass is
not explicitely specified, it is determined from the first base, as is normal
in Python::

    >>> class Qux(declare.bases(Baz, Foo)):
    ...     pass
    ...
    >>> isinstance(Qux, Meta)
    True

As shown in the first example, this module adds decorator support to classes.
The decorators are called from left to right as specified in the `decorators`'s
parameters, and their return value is used as the result of the class
statement::

    >>> def deco1(cls):
    ...     print ("deco1")
    ...     return cls
    ...
    >>> def deco2(cls):
    ...     print ("deco2")
    ...     return "spam"
    ...
    >>> class Quux(declare.decorators(deco1, deco2)):
    ...     pass
    ...
    deco1
    deco2
    >>> print (Quux)
    spam

You can specify decorators in multiple calls::

    >>> class Quux(declare.decorators(deco1).decorators(deco2)):
    ...     pass
    ...
    deco1
    deco2

but you can't call `bases` or `metaclass` more than once::

    >>> class Quux(declare.bases(Bar).bases(Foo)):
    ...     pass
    ...
    Traceback (most recent call last):
      ...
    TypeError: bases declared more than once

    >>> class Quux(declare.metaclass(type).metaclass(Meta)):
    ...     pass
    ...
    Traceback (most recent call last):
      ...
    TypeError: metaclass declared more than once

You can create your own declaration handlers by subclassing `Declaration`,
exposing the desired methods with the `beforeClassCreation` and
`afterClassCreation` decorators, and finally wrapping the class with a
`DeclarationCreator`::

    >>> class MyDeclaration(Declaration):
    ...     @beforeClassCreation
    ...     def setBefore(self, **kwargs):
    ...         assert self._class_ is None
    ...         self._metaclass_ = Meta
    ...         self._name_ = "Spam"
    ...         self._attributes_.update(kwargs)
    ...     @afterClassCreation
    ...     def setAfter(self, **kwargs):
    ...         for n, v in kwargs.items():
    ...            setattr(self._class_, n, v)
    ...
    >>> mydeclare = DeclarationCreator(MyDeclaration)
    >>> class Quux(mydeclare.setAfter(bar=2).setBefore(foo=1, bar=1)):
    ...     pass
    ...
    >>> print (Quux.__name__)
    Spam
    >>> isinstance(Quux, Meta)
    True
    >>> Quux.foo
    1
    >>> Quux.bar
    2

This example shows that `before_class_creation` handlers are roughly equivalent
to the `__new__` method of metaclasses, and `after_class_creation` handlers are
equivalent to the `__init__` method of metaclasses. They all have access to
four attributes that are equivalent to the arguments passed to the metaclass at
class construction:

- `self._metaclass_` is the metaclass declared or computed from bases,
- `self._name_` is the name of the class,
- `self._bases_` is a tuple of base classes,
- `self._attributes_` is a dictionary containing the class attributes.

Modifying these attributes will only have effect in the `before_class_creation`
handlers, but `after_class_creation` handlers can modify the `self._class_`
attribute, which is the class being created itself. E.g., the `decorators`
handler looks like this::

    @afterClassCreation
    def decorators(self, *decorators):
        for decorator in decorators:
            self._class_ = decorator(self._class_)

The handlers get called after the class body has already been evaluated. The
handlers for a specific state (before or after class creation) are called in
the same order that the user specified in the class definition.

The `Declaration` class doesn't contain any magic, all of the magic is provided
by the `DeclarationCreator` class. You are not required to subclass it, as long
as its constructor accepts the same arguments as a metaclass and it is a new
style class. It also must have a `_create_` method, which will be called after
all the before handlers have been called, and a `_result_` method, which will
be called after all the after handlers have been called. The `_result_`
method's return value will then be the final result of the class definition.

The handler decorators are not magical either, they just set an attribute on
the specified method to True. The only limitation is that handler's name can't
start with an underscore, which shouldn't be problematic, since they are only
meant for public interfaces.

Note that the `bases` and `metaclass` handlers are special, they are provided
by the `DeclarationCreator` itself. This is because they have to be computed
before the `Declaration` gets constructed. This also means that they can't be
easily overriden.
"""

_interface_doc = """
Finally, some `zope.interface` examples::

    >>> from zope.interface import Interface
    >>>
    >>> class IFoo(Interface):
    ...     pass
    ...
    >>> class IFooFactory(Interface):
    ...     pass
    ...
    >>> class Foo(declare.implements(IFoo).classProvides(IFooFactory)):
    ...     pass
    ...
    >>> IFoo.implementedBy(Foo)
    True
    >>> IFooFactory.providedBy(Foo)
    True
    >>> class IBar(Interface):
    ...     pass
    ...
    >>> class Bar(declare.bases(Foo).implementsOnly(IBar)):
    ...     pass
    ...
    >>> IFoo.implementedBy(Bar)
    False
    >>> IBar.implementedBy(Bar)
    True

"""

__all__ = ["declare", "Declaration", "DeclarationCreator",
           "beforeClassCreation", "afterClassCreation"]


def beforeClassCreation(func):
    func.beforeClassCreationHandler = True
    return func


def afterClassCreation(func):
    func.afterClassCreationHandler = True
    return func


class Declaration(object):

    def __init__(self, metaclass, name, bases, attributes):
        self._metaclass_ = metaclass
        self._name_ = name
        self._bases_ = bases
        self._attributes_ = attributes
        self._class_ = None

    def _create_(self):
        self._class_ = self._metaclass_(self._name_, self._bases_,
                                        self._attributes_)

    def _result_(self):
        return self._class_

    def decorators(self, *decorators):
        for decorator in decorators:
            self._class_ = decorator(self._class_)

    decorators = afterClassCreation(decorators)

    def implements(self, *interfaces):
        zope.interface.classImplements(self._class_, *interfaces)

    implements = afterClassCreation(implements)

    def implementsOnly(self, *interfaces):
        zope.interface.classImplementsOnly(self._class_, *interfaces)

    implementsOnly = afterClassCreation(implementsOnly)

    def classProvides(self, *interfaces):
        zope.interface.directlyProvides(self._class_, *interfaces)

    classProvides = afterClassCreation(classProvides)


class DeclarationCreator(object):

    def __init__(self, declarationFactory):
        handlers = {}
        dicts = [c.__dict__ for c in declarationFactory.__mro__]
        dicts.reverse()
        for d in dicts:
            for n, v in d.items():
                if getattr(v, "beforeClassCreationHandler", False):
                    if getattr(v, "afterClassCreationHandler", False):
                        handlers[n] = ("before", "after")
                    else:
                        handlers[n] = ("before",)
                elif getattr(v, "afterClassCreationHandler", False):
                    handlers[n] = ("after",)
                else:
                    handlers[n] = ()
        self.data = (declarationFactory, handlers)

    def __getattribute__(self, name):
        declarationFactory, handlers = _getattribute(self, "data")
        if _special(name):
            return getattr(declarationFactory, name)
        collector = _Collector.new(declarationFactory, handlers,
                                   None, None, [], [])
        return getattr(collector, name)


_getattribute = object.__getattribute__


def _special(name):
    if name.startswith("_"):
        if name.startswith("__") and name.endswith("__"):
            return True
        raise AttributeError(name)
    return False


class _Collector(object):

    def new(cls, factory, handlers, meta, bases, before, after):
        self = object.__new__(cls)
        self.data = (factory, handlers, meta, bases, before, after)
        return self

    new = classmethod(new)

    def __new__(cls, name, bases, attributes):
        try:
            self, = bases
        except ValueError:
            raise TypeError("only one base allowed, got: %s" % (bases,))
        factory, handlers, meta, bases, before, after = _getattribute(self,
                                                                      "data")
        if bases is None:
            bases = (object,)
        if meta is None:
            meta = type(bases[0])
        declaration = factory(meta, name, bases, attributes)
        for n, args, kwargs in before:
            getattr(declaration, n)(*args, **kwargs)
        declaration._create_()
        for n, args, kwargs in after:
            getattr(declaration, n)(*args, **kwargs)
        return declaration._result_()

    def __getattribute__(self, name):
        factory, handlers, meta, bss, before, after = _getattribute(self,
                                                                     "data")
        # workaround for the Don Beaudry hook
        if name == "__class__":
            return type(self)
        if _special(name):
            return getattr(factory, name)
        if name == "metaclass":
            def func(metaclass):
                if meta is not None:
                    raise TypeError("metaclass declared more than once")
                return _Collector.new(factory, handlers, metaclass,
                                       bss, before[:], after[:])
        elif name == "bases":
            def func(*bases):
                if not bases:
                    raise TypeError("at least one base must be specified")
                if bss is not None:
                    raise TypeError("bases declared more than once")
                return _Collector.new(factory, handlers, meta,
                                       bases, before[:], after[:])
        elif handlers.get(name):
            def func(*args, **kwargs):
                states = handlers[name]
                b = before[:]
                a = after[:]
                for state, L in (("before", b), ("after", a)):
                    if state in states:
                        L.append((name, args, kwargs))
                return _Collector.new(factory, handlers, meta, bss, b, a)
        else:
            raise AttributeError(name)
        return func


declare = DeclarationCreator(Declaration)

try:
    import zope.interface
except (ImportError, SyntaxError):
    pass
else:
    __doc__ += _interface_doc

if __name__ == "__main__":
    import sys
    import doctest
    doctest.testmod(sys.modules[__name__])
