"""Support for Zope interfaces.""" from pydoctor import model, ast_pp, astbuilder from compiler import ast import re class ZopeInterfaceClass(model.Class): isinterface = False isschemafield = False isinterfaceclass = False implementsOnly = False implementedby_directly = None # [objects], when isinterface == True def setup(self): super(ZopeInterfaceClass, self).setup() self.implements_directly = [] # [name of interface] @property def allImplementedInterfaces(self): """Return all the interfaces implemented by this class. This returns them in something like the classic class MRO. """ r = list(self.implements_directly) if self.implementsOnly: return r for b in self.baseobjects: if b is None: continue for interface in b.allImplementedInterfaces: if interface not in r: r.append(interface) return r @property def allImplementations(self): r = list(self.implementedby_directly) stack = [self.system.objForFullName(n) for n in r] while stack: c = stack.pop(0) for sc in c.subclasses: if sc.implementsOnly: continue stack.append(sc) if sc.fullName() not in r: r.append(sc.fullName()) return r class Attribute(model.Documentable): kind = "Attribute" css_class = "attribute" document_in_parent_page = True class ZopeInterfaceFunction(model.Function): def docsources(self): for source in super(ZopeInterfaceFunction, self).docsources(): yield source if not isinstance(self.parent, model.Class): return for interface in self.parent.allImplementedInterfaces: io = self.system.objForFullName(interface) if io is not None: if self.name in io.contents: yield io.contents[self.name] def addInterfaceInfoToClass(cls, interfaceargs, implementsOnly): cls.implementsOnly = implementsOnly if implementsOnly: cls.implements_directly = [] for arg in interfaceargs: fullName = cls.dottedNameToFullName(ast_pp.pp(arg)) if fullName not in cls.system.allobjects: fullName = cls.system.resolveAlias(fullName) cls.implements_directly.append(fullName) obj = cls.system.objForFullName(fullName) if obj is not None: if not obj.isinterface: obj.system.msg( 'zopeinterface', 'probable interface %r not marked as such'%obj, thresh=1) obj.isinterface = True obj.kind = "Interface" obj.implementedby_directly = [] obj.implementedby_directly.append(cls.fullName()) schema_prog = re.compile('zope\.schema\.([a-zA-Z_][a-zA-Z0-9_]*)') interface_prog = re.compile( 'zope\.schema\.interfaces\.([a-zA-Z_][a-zA-Z0-9_]*)' '|zope\.interface\.Interface') def namesInterface(system, name): if interface_prog.match(name): return True obj = system.objForFullName(name) if not obj or not isinstance(obj, model.Class): return False return obj.isinterface def extractAttributeDescription(node): pass def extractSchemaDescription(node): pass class ZopeInterfaceModuleVisitor(astbuilder.ModuleVistor): schema_like_patterns = [ ('zope\.interface\.Attribute', extractAttributeDescription), ] def funcNameFromCall(self, node): str_base = ast_pp.pp(node.node) return self.builder.current.dottedNameToFullName(str_base) def visitAssign(self, node): # i would like pattern matching in python please # if match(Assign([AssName(?name, _)], CallFunc(?funcName, [Const(?docstring)])), node): # ... sup = lambda : super(ZopeInterfaceModuleVisitor, self).visitAssign(node) if isinstance(self.builder.current, model.Module) and \ ast_pp.pp(node) == 'Interface = interface.Interface\n': # warner!!! n2fn = self.builder.current._name2fullname n2fn['Interface'] = 'zope.interface.Interface' return sup() if len(node.nodes) != 1 or \ not isinstance(node.nodes[0], ast.AssName) or \ not isinstance(node.expr, ast.CallFunc): return sup() funcName = self.funcNameFromCall(node.expr) if isinstance(self.builder.current, model.Module): name = node.nodes[0].name args = node.expr.args ob = self.system.objForFullName(funcName) if ob is not None and isinstance(ob, model.Class) and ob.isinterfaceclass: interface = self.builder.pushClass(name, "...") self.builder.system.msg('parsing', 'new interface') interface.isinterface = True interface.implementedby_directly = [] interface.linenumber = node.lineno self.builder.popClass() return sup() elif not isinstance(self.builder.current, model.Class): return sup() def pushAttribute(docstring, kind): attr = self.builder._push(Attribute, node.nodes[0].name, docstring) attr.linenumber = node.lineno attr.kind = kind if attr.parentMod.sourceHref: attr.sourceHref = attr.parentMod.sourceHref + '#L' + \ str(attr.linenumber) self.builder._pop(Attribute) def extractStringLiteral(node): if isinstance(node, ast.Const) and isinstance(node.value, str): return node.value elif isinstance(node, ast.CallFunc) \ and isinstance(node.node, ast.Name) \ and node.node.name == '_' \ and len(node.args) == 1 \ and isinstance(node.args[0], ast.Const) \ and isinstance(node.args[0].value, str): return node.args[0].value def handleSchemaField(kind): #print node.expr descriptions = [arg for arg in node.expr.args if isinstance(arg, ast.Keyword) and arg.name == 'description'] docstring = None if len(descriptions) > 1: self.builder.system.msg('parsing', 'xxx') elif len(descriptions) == 1: docstring = extractStringLiteral(descriptions[0].expr) pushAttribute(docstring, kind) if funcName == 'zope.interface.Attribute': args = node.expr.args if len(args) != 1: return sup() pushAttribute(extractStringLiteral(args[0]), "Attribute") return sup() if schema_prog.match(funcName): kind = schema_prog.match(funcName).group(1) handleSchemaField(kind) return sup() cls = self.builder.system.objForFullName(funcName) if cls and isinstance(cls, ZopeInterfaceClass) and cls.isschemafield: handleSchemaField(cls.name) return sup() def visitCallFunc(self, node): base = self.funcNameFromCall(node) meth = getattr(self, "visitCallFunc_" + base.replace('.', '_'), None) if meth is not None: meth(base, node) def visitCallFunc_zope_interface_implements(self, funcName, node): if not isinstance(self.builder.current, model.Class): self.default(node) return addInterfaceInfoToClass(self.builder.current, node.args, funcName == 'zope.interface.implementsOnly') visitCallFunc_zope_interface_implementsOnly = visitCallFunc_zope_interface_implements def visitCallFunc_zope_interface_classImplements(self, funcName, node): clsname = self.builder.current.dottedNameToFullName( ast_pp.pp(node.args[0])) if clsname not in self.system.allobjects: self.builder.system.msg( "parsing", "classImplements on unknown class %r"%clsname) return cls = self.system.allobjects[clsname] addInterfaceInfoToClass(cls, node.args[1:], funcName == 'zope.interface.classImplementsOnly') visitCallFunc_zope_interface_classImplementsOnly = visitCallFunc_zope_interface_classImplements def visitClass(self, node): super(ZopeInterfaceModuleVisitor, self).visitClass(node) cls = self.builder.current.contents[node.name] if 'zope.interface.interface.InterfaceClass' in cls.bases: cls.isinterfaceclass = True if len([b for b in cls.bases if namesInterface(self.system, b)]) > 0: cls.isinterface = True cls.kind = "Interface" cls.implementedby_directly = [] for n, o in zip(cls.bases, cls.baseobjects): if schema_prog.match(n) or (o and o.isschemafield): cls.isschemafield = True class ZopeInterfaceASTBuilder(astbuilder.ASTBuilder): ModuleVistor = ZopeInterfaceModuleVisitor class ZopeInterfaceSystem(model.System): Class = ZopeInterfaceClass Function = ZopeInterfaceFunction defaultBuilder = ZopeInterfaceASTBuilder