##############################################################################
#
# Copyright (c) 2004 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Module representation for code browser
 
$Id: module.py 113308 2010-06-10 01:09:36Z srichter $
"""
__docformat__ = 'restructuredtext'
import os
import types
 
import zope
from zope.interface import implements
from zope.interface import providedBy
from zope.interface.interface import InterfaceClass
from zope.location.interfaces import ILocation
from zope.location import LocationProxy
from zope.hookable import hookable
 
from zope.app.apidoc.classregistry import safe_import
from zope.app.apidoc.utilities import ReadContainerBase
from interfaces import IModuleDocumentation
 
from zope.app.apidoc.codemodule.class_ import Class
from zope.app.apidoc.codemodule.function import Function
from zope.app.apidoc.codemodule.text import TextFile
from zope.app.apidoc.codemodule.zcml import ZCMLFile
 
# Ignore these files, since they are not necessary or cannot be imported
# correctly.
IGNORE_FILES = ('tests', 'tests.py', 'ftests', 'ftests.py', 'CVS', 'gadfly',
                'setup.py', 'introspection.py', 'Mount.py')
 
class Module(ReadContainerBase):
    """This class represents a Python module."""
    implements(ILocation, IModuleDocumentation)
 
    def __init__(self, parent, name, module, setup=True):
        """Initialize object."""
        self.__parent__ = parent
        self.__name__ = name
        self._module = module
        self._children = {}
        self._package = False
        if setup:
            self.__setup()
 
    def __setup(self):
        """Setup the module sub-tree."""
        # Detect packages
        if hasattr(self._module, '__file__') and \
               (self._module.__file__.endswith('__init__.py') or
                self._module.__file__.endswith('__init__.pyc')or
                self._module.__file__.endswith('__init__.pyo')):
            self._package = True
            for dir in self._module.__path__:
                # TODO: If we are dealing with eggs, we will not have a
                # directory right away. For now we just ignore zipped eggs;
                # later we want to unzip it.
                if not os.path.isdir(dir):
                    continue
                for file in os.listdir(dir):
                    if file in IGNORE_FILES or file in self._children:
                        continue
                    path = os.path.join(dir, file)
 
                    if (os.path.isdir(path) and
                        '__init__.py' in os.listdir(path)):
                        # subpackage
                        fullname = self._module.__name__ + '.' + file
                        module = safe_import(fullname)
                        if module is not None:
                            self._children[file] = Module(self, file, module)
 
                    elif os.path.isfile(path) and file.endswith('.py') and \
                             not file.startswith('__init__'):
                        # module
                        name = file[:-3]
                        fullname = self._module.__name__ + '.' + name
                        module = safe_import(fullname)
                        if module is not None:
                            self._children[name] = Module(self, name, module)
 
                    elif os.path.isfile(path) and file.endswith('.zcml'):
                        self._children[file] = ZCMLFile(path, self._module,
                                                        self, file)
 
                    elif os.path.isfile(path) and file.endswith('.txt'):
                        self._children[file] = TextFile(path, file, self)
 
        # List the classes and functions in module, if any are available.
        zope.deprecation.__show__.off()
        module_decl = self.getDeclaration()
        ifaces = list(module_decl)
        if ifaces:
            # The module has an interface declaration.  Yay!
            names = set()
            for iface in ifaces:
                names.update(iface.names())
        else:
            names = getattr(self._module, '__all__', None)
            if names is None:
                # The module doesn't declare its interface.  Boo!
                # Guess what names to document, avoiding aliases and names
                # imported from other modules.
                names = []
                for name in self._module.__dict__.keys():
                    attr = getattr(self._module, name, None)
                    attr_module = getattr(attr, '__module__', None)
                    if attr_module != self._module.__name__:
                        continue
                    if getattr(attr, '__name__', None) != name:
                        continue
                    names.append(name)
 
        for name in names:
            # If there is something the same name beneath, then module should
            # have priority.
            if name in self._children:
                continue
 
            attr = getattr(self._module, name, None)
            if attr is None:
                continue
 
	    if isinstance(attr, hookable):
		attr = attr.implementation
 
            if isinstance(attr, (types.ClassType, types.TypeType)):
                self._children[name] = Class(self, name, attr)
 
            elif isinstance(attr, InterfaceClass):
                self._children[name] = LocationProxy(attr, self, name)
 
            elif isinstance(attr, types.FunctionType):
                doc = attr.__doc__
                if not doc:
                    f = module_decl.get(name)
                    if f is not None:
                        doc = f.__doc__
                self._children[name] = Function(self, name, attr, doc=doc)
 
        zope.deprecation.__show__.on()
 
 
    def getDocString(self):
        """See IModuleDocumentation."""
        return self._module.__doc__
 
    def getFileName(self):
        """See IModuleDocumentation."""
        return self._module.__file__
 
    def getPath(self):
        """See IModuleDocumentation."""
        return self._module.__name__
 
    def isPackage(self):
        """See IModuleDocumentation."""
        return self._package
 
    def getDeclaration(self):
        """See IModuleDocumentation."""
        return providedBy(self._module)
 
    def get(self, key, default=None):
        """See zope.container.interfaces.IReadContainer."""
        obj = self._children.get(key, default)
        if obj is not default:
            return obj
 
        # We are actually able to find much more than we promise
        if self.getPath():
            path = self.getPath() + '.' + key
        else:
            path = key
        obj = safe_import(path)
 
        if obj is not None:
            return Module(self, key, obj)
 
        # Maybe it is a simple attribute of the module
        if obj is None:
            obj = getattr(self._module, key, default)
            if obj is not default:
                obj = LocationProxy(obj, self, key)
 
        return obj
 
    def items(self):
        """See zope.container.interfaces.IReadContainer."""
        # Only publicize public objects, even though we do keep track of
        # private ones
        return [(name, value)
                for name, value in self._children.items()
                if not name.startswith('_')]