#!/usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""
Generate method skeletins for intefaces.

Usage: python pyskel.py dotted_name

Example:

    cd lib/python
    python Interface/pyskel.py Zope.App.Security.IRoleService.IRoleService

The dotted name is the module name and interface object name connected
with a dot.

Revision information: $Id: pyskel.py,v 1.6 2002/08/14 21:35:32 mj Exp $
"""

import sys, os, re

sys.path.insert(0, os.getcwd())

from _object import isInstance

from types import ModuleType
from Interface.Method import Method
from Interface.Attribute import Attribute

class_re = re.compile(r'\s*class\s+([a-zA-Z_][a-zA-Z0-9_]*)')
def_re = re.compile(r'\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)')
attr_re = re.compile(r'\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*Attribute')


def rskel(iface, print_iface=1):
    name = "%s.%s" % (iface.__module__, iface.__name__)

    file = resolve(iface.__module__).__file__
    if file.endswith('pyc'):
        file = file[:-1]
    order = guessOrder(open(file))
    namesAndDescriptions =  getAttributesInOrder(iface, order)

    if namesAndDescriptions and print_iface:
        print
        print "    ######################################"
        print "    # from:", name

    for aname, ades in namesAndDescriptions:
        if isInstance(ades, Method):
            sig = ades.getSignatureString()[1:-1]
            if sig: sig = "self, %s" % sig
            else:   sig = "self"
            print
            print "    def %s(%s):" % (aname, sig)
            print "        'See %s'" % name

        elif isInstance(ades, Attribute):
            print
            print "    # See %s" % name
            print "    %s = None" %aname

    for base in iface.__bases__:
        if base.__name__ not in ('Interface',):
            rskel(base)

def skel(name):
    iface = resolve(name)
    class_name = iface.__name__
    if class_name.startswith('I'):
        class_name = class_name[1:]
    print "from %s import %s" % (iface.__module__, iface.__name__)
    print
    print "class %s:" %class_name
    print
    print "    __implements__ = ", iface.__name__
    print
    print "    ############################################################"
    print "    # Implementation methods for interface"
    print "    #", name

    rskel(iface, 0)

    print
    print "    #"
    print "    ############################################################"


def resolve(name, _silly=('__doc__',), _globals={}):
    # Support for file path syntax; this way I can use TAB to search for
    # the module.
    if '/' in name or name.endswith('.py'):
        # We got a relative path. Let's try to get the full one and then
        # make a package path out of it.
        if not name.startswith('/'):
            cwd = os.getcwd()
            for path in sys.path[1:]: # Yeah, we need to exclude the cwd itself
                if path != '' and cwd.startswith(path):
                    name = os.path.join(cwd[len(path)+1:], name)
                    name = os.path.normpath(name)
                    break

        # get rid of the file ending :)
        if name.endswith('.py'):
            name = name[:-3]
        name = name.replace('/', '.')

    # Now to the regular lookup
    if name[:1]=='.':
        name='ZopeProducts'+name

    if name[-1:] == '.':
        name = name[:-1]
        repeat = 1
    else:
        repeat = 0

    names=name.split('.')
    last=names[-1]
    mod='.'.join(names[:-1])

    while 1:
        m=__import__(mod, _globals, _globals, _silly)
        try:
            a=getattr(m, last)
        except AttributeError:
            pass
        else:
            if not repeat or (type(a) is not ModuleType):
                return a
        mod += '.' + last


def guessOrder(source_file):
    order = {}  # { class name -> list of methods }
    lines = source_file.readlines()
    class_name = None
    for line in lines:
        m = class_re.match(line)
        if m and m.groups():
            class_name = m.groups()[0]
        else:
            for m in (def_re.match(line),
                      attr_re.match(line)):
                if m and m.groups():
                    def_name = m.groups()[0]
                    name_order = order.get(class_name)
                    if name_order is None:
                        name_order = []
                        order[class_name] = name_order
                    name_order.append(def_name)

    return order


def getAttributesInOrder(interface, order):
    # order is the dictionary returned from guessOrder().
    # interface is a metaclass-based interface object.
    name_order = order.get(interface.__name__)

    if name_order is None:
        # Something's wrong.  Oh well.
        return interface.__dict__.items()
    else:
        items = []
        for key, value in interface.namesAndDescriptions():
            if key in name_order:
                items.append((name_order.index(key), key, value))
            else:
                items.append((99999, key, value))  # Go to end.
        items.sort()
        return map(lambda item: item[1:], items)



if __name__ == '__main__':
    for a in sys.argv[1:]:
        skel(a)
